├── test ├── cluster │ ├── cluster.test.ts │ └── distance.test.ts ├── .jshintrc ├── testcase │ └── visrank.json ├── gen │ ├── scales.test.ts │ ├── projections.test.ts │ ├── aggregates.test.ts │ ├── specs.test.ts │ ├── marktypes.test.ts │ └── encodings.test.ts ├── rank │ └── rankEncodings.test.ts └── fixture.ts ├── img ├── glyphicons-halflings.png └── glyphicons-halflings-white.png ├── src ├── rank │ ├── rank.ts │ └── rankEncodings.ts ├── schema.ts ├── cp.ts ├── cluster │ ├── clusterconsts.ts │ ├── cluster.ts │ └── distance.ts ├── gen │ ├── scales.ts │ ├── gen.ts │ ├── specs.ts │ ├── projections.ts │ ├── marks.ts │ ├── aggregates.ts │ └── encodings.ts ├── util.ts └── consts.ts ├── typings ├── clusterfck.d.ts ├── mocha.d.ts └── chai.d.ts ├── nodemon.json ├── old-scripts ├── test │ ├── .jshintrc │ ├── chartRaterTest.js │ ├── ratingTestCase.tsv │ ├── dataTypesTest.js │ └── chartTemplateTest.js ├── app.build.js ├── encodings.js ├── dataTypes.js ├── demo-birdstrike.js ├── chartTemplates.js └── chartTemplate.js ├── .gitignore ├── .jshintrc ├── .vscode └── settings.json ├── gulpfile.js ├── .travis.yml ├── bower.json ├── css └── main.less ├── scripts └── deploy.sh ├── LICENSE ├── README.md ├── tsconfig.json ├── data └── birdstrikes │ └── birdstrikes-schema.json ├── tslint.json ├── package.json ├── index.html ├── lib ├── topojson.js └── require.js └── demo └── vrdemo.js /test/cluster/cluster.test.ts: -------------------------------------------------------------------------------- 1 | // FIXME: write this 2 | -------------------------------------------------------------------------------- /img/glyphicons-halflings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vega/compass/HEAD/img/glyphicons-halflings.png -------------------------------------------------------------------------------- /src/rank/rank.ts: -------------------------------------------------------------------------------- 1 | import rankEncodings from './rankEncodings'; 2 | 3 | export const encoding = rankEncodings; 4 | -------------------------------------------------------------------------------- /typings/clusterfck.d.ts: -------------------------------------------------------------------------------- 1 | declare module "clusterfck" { 2 | export function hcluster(a, b, c, d): any; 3 | } 4 | -------------------------------------------------------------------------------- /img/glyphicons-halflings-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vega/compass/HEAD/img/glyphicons-halflings-white.png -------------------------------------------------------------------------------- /nodemon.json: -------------------------------------------------------------------------------- 1 | { 2 | "ext": "ts", 3 | "ignore": [ 4 | "node_modules/*", 5 | "bower_components/*" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /old-scripts/test/.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "node": "true", 3 | "sub": "false", 4 | "predef": ["require", "describe", "it"], 5 | "expr": "true" 6 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | npm-debug.log 2 | node_modules 3 | compass.min.js 4 | compass.min.js.map 5 | compass.js 6 | bower_components 7 | coverage 8 | lib/vega-lite.js 9 | src/**/*.js 10 | test/**/*.js 11 | **/*.js.map 12 | -------------------------------------------------------------------------------- /test/.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "undef": true, 3 | "unused": true, 4 | "eqnull": true, 5 | "freeze": true, 6 | "noarg": true, 7 | "node": true, 8 | "browser": true, 9 | "mocha": true, 10 | "expr": true 11 | } -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "undef": true, 3 | "unused": true, 4 | "eqnull": true, 5 | "freeze": true, 6 | "noarg": true, 7 | "node": true, 8 | "browser": true, 9 | "mocha": true, 10 | "globalstrict": true 11 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | // Place your settings in this file to overwrite default and user settings. 2 | { 3 | "editor.tabSize": 2, 4 | "files.exclude": { 5 | "**/*.js": {"when": "$(basename).ts"}, 6 | "**/*.js.map": true 7 | } 8 | } -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var gulp = require('gulp'); 4 | 5 | gulp.paths = { 6 | src: 'src', 7 | test: 'test' 8 | }; 9 | 10 | require('require-dir')('./gulp'); 11 | 12 | gulp.task('default', ['bundle', 'watch']); 13 | -------------------------------------------------------------------------------- /src/schema.ts: -------------------------------------------------------------------------------- 1 | import {FieldDef} from 'vega-lite/src/fielddef'; 2 | 3 | export interface SchemaField extends FieldDef { 4 | selected?: boolean; 5 | _aggregate?: string; 6 | _raw?: boolean; 7 | _bin?: boolean; 8 | _timeUnit?: boolean; 9 | } 10 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js:: 3 | - "0.12" 4 | - "4.1" 5 | install: npm install 6 | notifications: 7 | email: 8 | on_success: never 9 | on_failure: change 10 | slack: 11 | rooms: 12 | - 'uwdub:Ry6mwlUX1aZevqiqmYLiA3N1' 13 | on_success: never 14 | on_failure: change 15 | -------------------------------------------------------------------------------- /old-scripts/app.build.js: -------------------------------------------------------------------------------- 1 | ({ 2 | appDir : "../", 3 | baseUrl : "scripts/", 4 | dir : "../../projectName-build", 5 | paths : { 6 | jquery : 'lib/jquery-1.7.1.min', 7 | underscore : 'lib/underscore-min' 8 | }, 9 | modules: [ 10 | { 11 | name: "main" 12 | } 13 | ] 14 | }) 15 | -------------------------------------------------------------------------------- /test/testcase/visrank.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "hi": "bar.y-Effect__Amount_of_damage-O.x-mean_Speed_IAS_in_knots-Q", 4 | "lo": "bar.x-Effect__Amount_of_damage-O.y-mean_Speed_IAS_in_knots-Q", 5 | "note": "prefer horizontal bar over vertical bar" 6 | },{ 7 | "hi": "bar.y-Effect__Amount_of_damage-O.x-mean_Speed_IAS_in_knots-Q", 8 | "lo": "point.y-Effect__Amount_of_damage-O.x-mean_Speed_IAS_in_knots-Q" 9 | } 10 | ] -------------------------------------------------------------------------------- /old-scripts/test/chartRaterTest.js: -------------------------------------------------------------------------------- 1 | /* jshint expr:true */ 2 | 3 | var chai = require('chai'), 4 | assert = chai.assert; 5 | 6 | chai.should(); 7 | chai.config.includeStack = true; 8 | 9 | var path = "../"; 10 | 11 | var _ = require('lodash'); 12 | var dt = require(path+'dataTypes'); 13 | var templates = require(path+'/chartTemplates'); 14 | var template = require(path+'/chartTemplate'); 15 | 16 | describe('chartRater', function () { 17 | describe('chartRater', function () { 18 | 19 | }); 20 | }); -------------------------------------------------------------------------------- /src/cp.ts: -------------------------------------------------------------------------------- 1 | import * as cpConsts from './consts'; 2 | import cpCluster from './cluster/cluster'; // FIXME 3 | import * as cpGen from './gen/gen'; 4 | import * as cpRank from './rank/rank'; 5 | import * as cpUtil from './util'; 6 | 7 | export const consts = cpConsts; 8 | export const cluster = cpCluster; 9 | export const gen = cpGen; 10 | export const rank = cpRank; 11 | export const util = cpUtil; 12 | 13 | // FIXME move 14 | export const auto = '-, sum'; 15 | 16 | export const version = '__VERSION__'; 17 | -------------------------------------------------------------------------------- /old-scripts/test/ratingTestCase.tsv: -------------------------------------------------------------------------------- 1 | # 2D Strong cases 2 | "BAR--x:#,y:C", "BAR--x:Q,y:C" 3 | "LINE--y:#,x:D ", "LINE--y:Q,x:D" 4 | "MAP--geo:G,size/color:# ", "MAP--geo:G,size/Color:Q" 5 | "LINE--y:Q*,x:D ", "BAR--x:Q*,y:D" 6 | "MAP--geo:G,size:#", "BAR--x:Q*,y:C" 7 | "MAP--geo:G,size:Q*", "MAP--geo:G,color:Q*" 8 | 9 | # everything 2D > table 10 | "BAR--x:*1,y:*2 ", "TABLE--x:*1,y:*2" 11 | "LINE--x:*1,y:*2 ", "TABLE--x:*1,y:*2" 12 | "PLOT--x:*1,y:*2 ", "TABLE--x:*1,y:*2" 13 | "MAP--geo:G,size/color:Q*", "TABLE--x:G,size/color:Q*" 14 | 15 | # BAR 16 | 17 | 18 | 19 | 20 | 21 | # WEAK 22 | # small multiple is better 23 | "PLOT--x:@#1,@Q1;y:G1", "PLOT--x:@#1,@Q1;y:G1" 24 | "PLOT--x:@#1,@Q1;y:G1", "PLOT--x:@#1;y:G1;size:@Q1" 25 | 26 | 27 | -------------------------------------------------------------------------------- /src/cluster/clusterconsts.ts: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | export const SWAPPABLE = 0.05; 4 | export const DIST_MISSING = 1; 5 | export const CLUSTER_THRESHOLD = 1; 6 | 7 | function reduceTupleToTable(r, x) { 8 | var a = x[0], b = x[1], d = x[2]; 9 | r[a] = r[a] || {}; 10 | r[b] = r[b] || {}; 11 | r[a][b] = r[b][a] = d; 12 | return r; 13 | } 14 | 15 | export const DIST_BY_CHANNEL = [ 16 | // positional 17 | ['x', 'y', SWAPPABLE], 18 | ['row', 'column', SWAPPABLE], 19 | 20 | // ordinal mark properties 21 | ['color', 'shape', SWAPPABLE], 22 | ['color', 'detail', SWAPPABLE], 23 | ['detail', 'shape', SWAPPABLE], 24 | 25 | // quantitative mark properties 26 | ['size', 'color', SWAPPABLE] 27 | ].reduce(reduceTupleToTable, {}); 28 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "viscompass", 3 | "main": "compass.js", 4 | "homepage": "https://github.com/vega/compass", 5 | "authors": [ 6 | "Kanit \"Ham\" Wongsuphasawat ", 7 | "Interactive Data Lab (http://idl.cs.washington.edu)" 8 | ], 9 | "description": "Visualization Recommendation Engine", 10 | "moduleType": [ 11 | "amd", 12 | "globals", 13 | "node" 14 | ], 15 | "keywords": [ 16 | "Visualization", 17 | "Vegalite", 18 | "Recommendation", 19 | "Chart", 20 | "Analysis", 21 | "Exploration" 22 | ], 23 | "license": "BSD-3-Clause", 24 | "ignore": [ 25 | "**/.*", 26 | "node_modules", 27 | "bower_components", 28 | "test", 29 | "tests" 30 | ], 31 | "dependencies": { 32 | "vega-lite": "~1.0.10" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /old-scripts/test/dataTypesTest.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | var chai = require('chai'), 4 | expect=chai.expect, 5 | assert=chai.assert; 6 | var path = "../"; 7 | var dt = require(path+'dataTypes'); 8 | 9 | chai.should(); 10 | 11 | describe('DataType', function(){ 12 | describe('#isType()', function () { 13 | it("know if types are similar", function(done){ 14 | dt.categorical.isType("categorical").should.be.true; 15 | [dt.categorical, dt.ordinal, dt.interval].forEach(function(t){ 16 | assert.isTrue(t.isType("categorical")); 17 | }); 18 | 19 | [dt.ordinal, dt.interval].forEach(function(t){ 20 | assert.isFalse(dt.categorical.isType(t.name)); 21 | }); 22 | 23 | done(); 24 | }); 25 | }); 26 | 27 | describe('#allSubtypes', function(){ 28 | it("should return correct allSubTypes", function(done){ 29 | assert.sameMembers(dt.categorical.allSubtypes(), [dt.categorical, dt.ordinal, dt.interval, dt.datetime, dt.geographic]); 30 | 31 | assert.sameMembers(dt.aggregate.allSubtypes(), [dt.aggregate, dt.quantitative, dt.count]); 32 | done(); 33 | }); 34 | }); 35 | }); -------------------------------------------------------------------------------- /old-scripts/encodings.js: -------------------------------------------------------------------------------- 1 | // Universal Module Pattern from https://github.com/umdjs/umd/blob/master/returnExports.js 2 | // Uses Node, AMD or browser globals to create a module. 3 | 4 | (function (root, factory) { 5 | if (typeof define === 'function' && define.amd) { 6 | // AMD. Register as an anonymous module. 7 | define([], factory); 8 | } else if (typeof exports === 'object') { 9 | // Node. Does not work with strict CommonJS, but 10 | // only CommonJS-like environments that support module.exports, 11 | // like Node. 12 | module.exports = factory(); 13 | } else { 14 | // Browser globals (root is window) 15 | root.encodings = factory(); 16 | } 17 | }(this, function () { 18 | var encodings = { 19 | summary: 'summary', 20 | hist: 'hist', 21 | x: 'x', 22 | y: 'y', 23 | row: 'row', 24 | col: 'col', 25 | text: 'text', 26 | size: 'size', 27 | shape: 'shape', 28 | color: 'color', 29 | geo: 'geo' 30 | }; 31 | 32 | encodings.nonPositional = ['summary', 'hist', 'text', 'size', 'shape', 'color', 'geo']; 33 | 34 | return encodings; 35 | })); 36 | 37 | -------------------------------------------------------------------------------- /test/cluster/distance.test.ts: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | import {expect} from 'chai'; 3 | import * as distance from '../../src/cluster/distance'; 4 | 5 | describe('cp.cluster.distance.get()', function () { 6 | it('should return correct distance', function() { 7 | var table1 = { 8 | 'mark': 'text', 9 | 'encoding': { 10 | 'row': {'field': 'Effect__Amount_of_damage','type': 'ordinal'}, 11 | 'column': {'field': 'Aircraft__Airline_Operator','type': 'ordinal'}, 12 | 'text': {'field': '*','aggregate': 'count','type': 'quantitative','displayName': 'Number of Records'} 13 | } 14 | }; 15 | 16 | var table2 = { 17 | 'mark': 'text', 18 | 'encoding': { 19 | 'column': {'field': 'Effect__Amount_of_damage','type': 'ordinal'}, 20 | 'row': {'field': 'Aircraft__Airline_Operator','type': 'ordinal'}, 21 | 'text': {'field': '*','aggregate': 'count','type': 'quantitative','displayName': 'Number of Records'} 22 | } 23 | }; 24 | 25 | var colenc1 = distance.extendSpecWithChannelByColumnName(table1), 26 | colenc2 = distance.extendSpecWithChannelByColumnName(table2); 27 | 28 | expect(distance.get(colenc1, colenc2)).to.lt(1); 29 | }); 30 | }); 31 | -------------------------------------------------------------------------------- /src/cluster/cluster.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | import * as vlShorthand from 'vega-lite/src/shorthand'; 4 | import * as clusterfck from 'clusterfck'; 5 | import * as consts from './clusterconsts'; 6 | import * as util from '../util'; 7 | import * as clDistance from './distance'; 8 | 9 | export const distance = clDistance; 10 | 11 | export default function cluster(specs, opt) { 12 | // jshint unused:false 13 | var dist = distance.table(specs); 14 | 15 | var clusterTrees = clusterfck.hcluster(specs, function(e1, e2) { 16 | var s1 = vlShorthand.shorten(e1), 17 | s2 = vlShorthand.shorten(e2); 18 | return dist[s1][s2]; 19 | }, 'average', consts.CLUSTER_THRESHOLD); 20 | 21 | var clusters = clusterTrees.map(function(tree) { 22 | return util.traverse(tree, []); 23 | }) 24 | .map(function(cluster) { 25 | return cluster.sort(function(spec1, spec2) { 26 | // sort each cluster -- have the highest score as 1st item 27 | return spec2._info.score - spec1._info.score; 28 | }); 29 | }).filter(function(cluster) { // filter empty cluster 30 | return cluster.length >0; 31 | }).sort(function(cluster1, cluster2) { 32 | // sort by highest scoring item in each cluster 33 | return cluster2[0]._info.score - cluster1[0]._info.score; 34 | }); 35 | 36 | clusters.dist = dist; // append dist in the array for debugging 37 | 38 | return clusters; 39 | } 40 | -------------------------------------------------------------------------------- /test/gen/scales.test.ts: -------------------------------------------------------------------------------- 1 | import {Type} from 'vega-lite/src/type'; 2 | import genScales from '../../src/gen/scales'; 3 | import {expect} from 'chai'; 4 | 5 | describe('cp.gen.scales()', function () { 6 | it('should correctly generate fieldSets with scale variation', function() { 7 | const fields = [0,1,2,3].map(function(i) { 8 | return { 9 | field: 'f' + i, 10 | type: i < 2 ? Type.QUANTITATIVE : Type.ORDINAL // 2xQ, 2xO 11 | }; 12 | }); 13 | const output = genScales([], fields, {rescaleQuantitative: [undefined, 'log']}); 14 | expect(output.length).to.equal(4); // only 0 and 1 get rescaled 2x2 = 4 15 | 16 | // output[0]'s scale: default, default 17 | // output[1]'s scale: default, log 18 | expect(output[1][1].scale.type).to.equal('log'); 19 | // output[2]'s scale: log, default 20 | expect(output[2][0].scale.type).to.equal('log'); 21 | // output[3]'s scale: log, log 22 | expect(output[3][0].scale.type).to.equal('log'); 23 | expect(output[3][1].scale.type).to.equal('log'); 24 | }); 25 | 26 | it('should not generate scale variation for bin', function() { 27 | const fields = [{ 28 | field: 'f', 29 | type: Type.QUANTITATIVE, 30 | bin: true 31 | }]; 32 | 33 | const output = genScales([], fields, {rescaleQuantitative: [undefined, 'log']}); 34 | expect(output.length).to.equal(1); // can't be applied log scale. 35 | 36 | 37 | }); 38 | 39 | }); 40 | -------------------------------------------------------------------------------- /css/main.less: -------------------------------------------------------------------------------- 1 | * { 2 | font-family: Helvetica Neue; 3 | } 4 | 5 | body{ 6 | font-size: 14px; 7 | } 8 | 9 | h2 { 10 | margin: 0; 11 | 12 | } 13 | 14 | .hide { 15 | display: none; 16 | } 17 | 18 | .desc{ 19 | font-size: 12px; 20 | min-height: 30px; 21 | } 22 | 23 | #ctrl { 24 | position: absolute; 25 | left: 10px; 26 | top: 10px; 27 | width: 220px; 28 | } 29 | 30 | #ctrl .config { 31 | font-size: 12px; 32 | } 33 | 34 | .pull-right{ 35 | float: right; 36 | } 37 | 38 | .toggle{ 39 | font-size: 10px; 40 | } 41 | 42 | .ctrlgrp{ 43 | width: 100%; 44 | background-color: #eee; 45 | padding: 0.5em; 46 | margin-bottom: 1em; 47 | } 48 | 49 | #aggr-container { 50 | position: absolute; 51 | left: 260px; 52 | top: 10px; 53 | width: 400px; 54 | 55 | .fieldset{ 56 | margin-bottom: 15px; 57 | } 58 | 59 | .select { 60 | font-size: 10px; 61 | text-align: right; 62 | } 63 | 64 | .topvis { 65 | width:100%; 66 | max-height: 350px; 67 | overflow: scroll; 68 | } 69 | } 70 | 71 | #vis-container { 72 | position: absolute; 73 | left: 680px; 74 | top: 10px; 75 | 76 | #vis { 77 | padding: 3px; 78 | } 79 | } 80 | 81 | .datafields { 82 | background-color: #E0F0F5; 83 | padding: 3px; 84 | } 85 | 86 | .shorthand { 87 | width: 300px; 88 | } 89 | 90 | 91 | span.name{ 92 | font-weight: 500; 93 | font-size: 14px; 94 | } 95 | 96 | span.fn{ 97 | display: inline-block; 98 | width:40px; 99 | text-align: right; 100 | padding-right: 3px; 101 | } -------------------------------------------------------------------------------- /scripts/deploy.sh: -------------------------------------------------------------------------------- 1 | set -e 2 | 3 | # define color 4 | RED='\033[0;31m' 5 | NC='\033[0m' # No Color 6 | 7 | 8 | # 0.1 Check if jq has been installed 9 | type jq >/dev/null 2>&1 || { echo >&2 "I require jq but it's not installed. Aborting."; exit 1; } 10 | 11 | # 0.2 check if on master 12 | if [ "$(git rev-parse --abbrev-ref HEAD)" != "master" ]; then 13 | echo "${RED}Not on master, please checkout master branch before running this script${NC}" 14 | exit 1 15 | fi 16 | 17 | # 0.3 Check if all files are committed 18 | if [ -z "$(git status --porcelain)" ]; then 19 | echo "All tracked files are committed. Publishing on npm and bower. \n" 20 | else 21 | echo "${RED}There are uncommitted files. Please commit or stash first!${NC} \n\n" 22 | git status 23 | exit 1 24 | fi 25 | 26 | # 1. BOWER PUBLISH 27 | 28 | # read version 29 | gitsha=$(git rev-parse HEAD) 30 | version=$(cat package.json | jq .version | sed -e 's/^"//' -e 's/"$//') 31 | 32 | # swap to head so we don't commit compiled file to master along with tags 33 | git checkout head 34 | gulp build 35 | 36 | # add the compiled files, commit and tag! 37 | git add compass* -f 38 | git commit -m "release $version $gitsha" 39 | git tag -am "Release v$version." "v$version" 40 | 41 | # now swap back to the clean master and push the new tag 42 | git checkout master 43 | git push --tags 44 | gulp build # rebuild -- so compiled files are back for linked bower 45 | 46 | # 2. NPM PUBLISH 47 | 48 | npm publish 49 | # exit if npm publish failed 50 | rc=$? 51 | if [[ $rc != 0 ]]; then 52 | echo "${RED} npm publish failed. Publishing cancelled. ${NC} \n\n" 53 | exit $rc; 54 | fi -------------------------------------------------------------------------------- /src/gen/scales.ts: -------------------------------------------------------------------------------- 1 | import {SchemaField} from '../schema'; 2 | import {ScaleOption, DEFAULT_SCALE_OPTION} from '../consts'; 3 | import {duplicate, extend} from '../util'; 4 | import {Type} from 'vega-lite/src/type'; 5 | 6 | export default function genScales(output, fieldDefs: SchemaField[], opt: ScaleOption = {}) { 7 | opt = extend({}, DEFAULT_SCALE_OPTION, opt); 8 | 9 | function genScaleField(i: number, tmpFieldDefs: SchemaField[]) { 10 | if (i === fieldDefs.length) { 11 | // Done, emit result 12 | output.push(duplicate(tmpFieldDefs)); 13 | return; 14 | } 15 | 16 | if (fieldDefs[i].type === Type.QUANTITATIVE && opt.rescaleQuantitative && !(fieldDefs[i].bin)) { 17 | // if quantitative and we have rescaleQuantitative, generate different scales 18 | opt.rescaleQuantitative.forEach(function(scaleType) { 19 | // clone to prevent side effect on the original data 20 | if (scaleType) { 21 | let fieldDef = duplicate(fieldDefs[i]); 22 | fieldDef.scale = fieldDef.scale || {}; 23 | fieldDef.scale.type = scaleType; 24 | tmpFieldDefs.push(fieldDef); 25 | genScaleField(i + 1, tmpFieldDefs); 26 | } else { 27 | tmpFieldDefs.push(fieldDefs[i]); 28 | genScaleField(i + 1, tmpFieldDefs); 29 | } 30 | tmpFieldDefs.pop(); 31 | }); 32 | 33 | } else { 34 | // Otherwise, just deal with next field 35 | tmpFieldDefs.push(fieldDefs[i]); 36 | genScaleField(i + 1, tmpFieldDefs); 37 | tmpFieldDefs.pop(); 38 | } 39 | } 40 | 41 | genScaleField(0, []); 42 | 43 | return output; 44 | } 45 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015, University of Washington Interactive Data Lab. 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | * Neither the name of the University of Washington Interactive Data Lab 15 | nor the names of its contributors may be used to endorse or promote products 16 | derived from this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE 22 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # (Vis)Compass: Visualization Recommender. 2 | 3 | [![Build Status](https://travis-ci.org/vega/compass.svg)](https://travis-ci.org/vega/compass) 4 | [![npm dependencies](https://david-dm.org/vega/compass.svg)](https://www.npmjs.com/package/viscompass) 5 | [![npm version](https://img.shields.io/npm/v/viscompass.svg)](https://www.npmjs.com/package/viscompass) 6 | 7 | (Vis)Compass is a module for generating and ranking visualizations. Given user 8 | query, Compass produces ranked group of visualization described using 9 | [Vega-Lite](http://github.com/vega/vega-lite). 10 | 11 | Compass is NO LONGER in an active development. Please use https://github.com/vega/compassql 12 | 13 | ## Development Guide 14 | 15 | ### Dependencies 16 | 17 | This project depends on [Vega-Lite](https://github.com/vega/vega-lite) as a formal model for visualization. 18 | 19 | If you plan to make changes to these dependencies and observe the changes without publishing / copying compiled libraries all the time, use [`bower link`](https://oncletom.io/2013/live-development-bower-component/) and 'npm link'. 20 | 21 | ``` 22 | cd path/to/vega-lite 23 | bower link 24 | npm link 25 | ``` 26 | 27 | In the directory for compass, run 28 | 29 | ``` 30 | # optional: npm link datalib 31 | npm link vega-lite 32 | bower link vega-lite 33 | ``` 34 | 35 | ### Compiling 36 | 37 | You can run `npm run build` to compile Vega-Lite. 38 | 39 | You can `npm run watch` to start a watcher task that automatically re-compiles and tests Compass when any `.js` file in `test/` or `src/` changes. 40 | 41 | Note: These commands use [Gulp](http://gulpjs.com) internally; Therefore, you need to install gulp globally with 42 | ```sh 43 | npm install -g gulp 44 | ``` 45 | to make them work. 46 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "module": "commonjs", 5 | "moduleResolution": "node", 6 | "isolatedModules": false, 7 | "jsx": "react", 8 | "experimentalDecorators": true, 9 | "emitDecoratorMetadata": true, 10 | "declaration": false, 11 | "noImplicitAny": false, 12 | "removeComments": true, 13 | "noLib": false, 14 | "preserveConstEnums": true, 15 | "suppressImplicitAnyIndexErrors": true, 16 | "sourceMap": true 17 | }, 18 | "filesGlob": [ 19 | "**/*.ts", 20 | "**/*.tsx" 21 | ], 22 | "exclude": [ 23 | "node_modules", 24 | "bower_components", 25 | "data", 26 | "_site", 27 | "lib" 28 | ], 29 | "files": [ 30 | "src/cluster/cluster.ts", 31 | "src/cluster/clusterconsts.ts", 32 | "src/cluster/distance.ts", 33 | "src/consts.ts", 34 | "src/cp.ts", 35 | "src/gen/aggregates.ts", 36 | "src/gen/encodings.ts", 37 | "src/gen/gen.ts", 38 | "src/gen/marks.ts", 39 | "src/gen/projections.ts", 40 | "src/gen/scales.ts", 41 | "src/gen/specs.ts", 42 | "src/rank/rank.ts", 43 | "src/rank/rankEncodings.ts", 44 | "src/schema.ts", 45 | "src/util.ts", 46 | "test/cluster/cluster.test.ts", 47 | "test/cluster/distance.test.ts", 48 | "test/fixture.ts", 49 | "test/gen/aggregates.test.ts", 50 | "test/gen/encodings.test.ts", 51 | "test/gen/marktypes.test.ts", 52 | "test/gen/projections.test.ts", 53 | "test/gen/scales.test.ts", 54 | "test/gen/specs.test.ts", 55 | "test/rank/rankEncodings.test.ts", 56 | "typings/chai.d.ts", 57 | "typings/clusterfck.d.ts", 58 | "typings/mocha.d.ts" 59 | ], 60 | "atom": { 61 | "rewriteTsconfig": true 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/gen/gen.ts: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import genAggregates from './aggregates'; 4 | import genProjections from './projections'; 5 | import {key} from './projections'; 6 | import genSpecs from './specs'; 7 | import genEncodings from './encodings'; 8 | import genMarks from './marks'; 9 | 10 | /** 11 | * Module for generating visualizations 12 | */ 13 | 14 | // data variations 15 | export const aggregates = genAggregates; 16 | export let projections: any = genProjections; 17 | // FIXME 18 | projections.key = key; 19 | 20 | // encodings / visual variations 21 | export const specs = genSpecs; 22 | export const encodings = genEncodings; 23 | export const marks = genMarks; 24 | 25 | // // TODO(kanitw): revise if this is still working 26 | // export function charts(fieldDefs, opt, config, flat) { 27 | // opt = util.gen.getOpt(opt); 28 | // flat = flat === undefined ? {encodings: 1} : flat; 29 | // 30 | // // TODO generate 31 | // 32 | // // generate permutation of encoding mappings 33 | // var fieldSets = opt.genAggr ? genAggregates([], fieldDefs, opt) : [fieldDefs], 34 | // encodings, charts, level = 0; 35 | // 36 | // if (flat === true || (flat && flat.aggregate)) { 37 | // encodings = fieldSets.reduce(function(output, fieldDefs) { 38 | // return genEncodings(output, fieldDefs, opt); 39 | // }, []); 40 | // } else { 41 | // encodings = fieldSets.map(function(fieldDefs) { 42 | // return genEncodings([], fieldDefs, opt); 43 | // }, true); 44 | // level += 1; 45 | // } 46 | // 47 | // if (flat === true || (flat && flat.encodings)) { 48 | // charts = util.nestedReduce(encodings, function(output, encoding) { 49 | // return gen.marks(output, encoding, opt, config); 50 | // }, level, true); 51 | // } else { 52 | // charts = util.nestedMap(encodings, function(encoding) { 53 | // return gen.marks([], encoding, opt, config); 54 | // }, level, true); 55 | // level += 1; 56 | // } 57 | // return charts; 58 | // }; 59 | -------------------------------------------------------------------------------- /data/birdstrikes/birdstrikes-schema.json: -------------------------------------------------------------------------------- 1 | [{ 2 | "key": "OPERATOR", 3 | "field_name": "Aircraft: Airline/Operator", 4 | "enabled": true, 5 | "data_type": "categorical" 6 | }, { 7 | "key": "ATYPE", 8 | "field_name": "Aircraft: Make/Model", 9 | "data_type": "categorical" 10 | }, { 11 | "key": "AIRPORT", 12 | "field_name": "Airport: Name", 13 | "data_type": "categorical" 14 | }, { 15 | "key": "Effect_Amount of damage", 16 | "field_name": "Effect: Amount of damage", 17 | "data_type": "categorical" 18 | }, { 19 | "key": "INCIDENT_DATE", 20 | "field_name": "Flight Date", 21 | "data_type": "datetime" 22 | }, { 23 | "key": "FAAREGION", 24 | "field_name": "Location: FAA Region", 25 | "data_type": "categorical", 26 | "disabled": true 27 | }, { 28 | "key": "Origin_State_Code_(copy)", 29 | "field_name": "Origin State", 30 | "data_type": "geographic" 31 | }, { 32 | "key": "PHASE_OF_FLT", 33 | "field_name": "When: Phase of flight", 34 | "data_type": "categorical" 35 | }, { 36 | "key": "TIME_OF_DAY", 37 | "field_name": "When: Time of day", 38 | "data_type": "categorical" 39 | }, { 40 | "key": "SIZE", 41 | "field_name": "Wildlife: Size", 42 | "data_type": "categorical" 43 | }, { 44 | "key": "SPECIES", 45 | "field_name": "Wildlife: Species", 46 | "data_type": "categorical" 47 | }, { 48 | "key": "AOS", 49 | "field_name": "Cost: Aircraft time out of Service", 50 | "disabled": true, 51 | "data_type": "quantitative" 52 | }, { 53 | "key": "Cost_Other_inflation adj", 54 | "field_name": "Cost: Other", 55 | "data_type": "quantitative" 56 | }, { 57 | "key": "Cost__Repairs_(adj)", 58 | "field_name": "Cost: Repair", 59 | "data_type": "quantitative" 60 | }, { 61 | "key": "Cost__Total_$", 62 | "field_name": "Cost: Total $", 63 | "data_type": "quantitative" 64 | }, { 65 | "key": "SPEED", 66 | "field_name": "Speed (IAS) in knots", 67 | "data_type": "quantitative" 68 | }, { 69 | "key": "Number_of_Records", 70 | "field_name": "Number of Strikes", 71 | "data_type": "count" 72 | }] -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "ban": true, 4 | "class-name": true, 5 | "eofline": true, 6 | "indent": [true, "spaces"], 7 | "comment-format": [true, "check-space"], 8 | "_curly": true, 9 | "_forin": true, 10 | "label-position": true, 11 | "label-undefined": true, 12 | "no-arg": true, 13 | "no-any": false, 14 | "jsdoc-format": true, 15 | "member-access": true, 16 | "semicolon": true, 17 | "no-duplicate-key": true, 18 | "no-duplicate-variable": true, 19 | "no-unreachable": true, 20 | "no-conditional-assignment": true, 21 | "no-bitwise": true, 22 | "no-consecutive-blank-lines": false, 23 | "no-console": [ 24 | true, 25 | "debug", 26 | "info", 27 | "time", 28 | "timeEnd", 29 | "trace" 30 | ], 31 | "no-construct": true, 32 | "no-constructor-vars": true, 33 | "no-debugger": true, 34 | "no-duplicate-key": true, 35 | "no-duplicate-variable": true, 36 | "no-empty": true, 37 | "no-eval": true, 38 | "no-inferrable-types": false, 39 | "no-internal-module": true, 40 | "no-shadowed-variable": true, 41 | "no-string-literal": false, 42 | "// no-string-literal": "We need this for fixture", 43 | "no-trailing-whitespace": true, 44 | "no-unreachable": true, 45 | "no-unused-expression": true, 46 | "no-unused-variable": true, 47 | "no-use-before-declare": true, 48 | "one-line": [ 49 | true, 50 | "check-open-brace", 51 | "check-catch", 52 | "check-else", 53 | "check-whitespace" 54 | ], 55 | "typedef-whitespace": [ 56 | true, 57 | { 58 | "call-signature": "nospace", 59 | "index-signature": "nospace", 60 | "parameter": "nospace", 61 | "property-declaration": "nospace", 62 | "variable-declaration": "nospace" 63 | } 64 | ], 65 | "quotemark": [true, "single"], 66 | "triple-equals": [true, "allow-null-check"], 67 | "variable-name": [true, "check-format", "allow-leading-underscore", "ban-keywords"] 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "viscompass", 3 | "version": "0.7.1", 4 | "description": "VisCompass: Visualization Recommender", 5 | "author": { 6 | "name": "UW Interactive Data Lab", 7 | "url": "http://idl.cs.washington.edu" 8 | }, 9 | "collaborators": [ 10 | "Kanit Wongsuphasawat (http://kanitw.yellowpigz.com)", 11 | "Dominik Moritz (http://domoritz.de)", 12 | "Jeffrey Heer (http://jheer.org)" 13 | ], 14 | "main": "compass.js", 15 | "directories": { 16 | "test": "test" 17 | }, 18 | "scripts": { 19 | "build": "tsc && browserify src/cp.js -d -s cp | exorcist compass.js.map > compass.js", 20 | "postbuild": "uglifyjs compass.js -cm --source-map compass.min.js.map > compass.min.js", 21 | "clean": "rm -f compass.* & find src -name '*.js*' -type f -delete & find test -name '*.js*' -type f -delete", 22 | "cover": "tsc && istanbul cover node_modules/.bin/_mocha -- --recursive", 23 | "deploy": "npm run clean && npm run lint && npm run test && scripts/deploy.sh", 24 | "lint": "tslint -c tslint.json src/*.ts test/*.ts src/**/*.ts test/**/*.ts", 25 | "watch": "nodemon -x 'npm run build && npm run test:only && npm run lint'", 26 | "tsc": "tsc", 27 | "test": "tsc && npm run test:only", 28 | "test:only": "mocha --recursive --require source-map-support/register" 29 | }, 30 | "browserify-shim": {}, 31 | "repository": { 32 | "type": "git", 33 | "url": "https://github.com/vega/compass.git" 34 | }, 35 | "license": "BSD-3-Clause", 36 | "bugs": { 37 | "url": "https://github.com/vega/compass/issues" 38 | }, 39 | "homepage": "https://github.com/vega/visrec", 40 | "devDependencies": { 41 | "browser-sync": "^2.11.1", 42 | "browserify": "^13.0.0", 43 | "browserify-versionify": "^1.0.6", 44 | "chai": "^3.4.1", 45 | "exorcist": "^0.4.0", 46 | "istanbul": "^0.4.2", 47 | "mocha": "~2.4.4", 48 | "nodemon": "^1.8.1", 49 | "source-map-support": "^0.4.0", 50 | "tsify": "^0.13.2", 51 | "tslint": "^3.3.0", 52 | "typescript": "^1.7.5", 53 | "uglify-js": "^2.6.1", 54 | "vega-datasets": "^1.5.0" 55 | }, 56 | "dependencies": { 57 | "clusterfck": "~0.6.0", 58 | "vega-lite": "~1.0.10" 59 | }, 60 | "browserify": { 61 | "transform": [ 62 | "browserify-versionify" 63 | ] 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/cluster/distance.ts: -------------------------------------------------------------------------------- 1 | import * as vlSpec from 'vega-lite/src/spec'; 2 | import * as vlShorthand from 'vega-lite/src/shorthand'; 3 | import * as consts from './clusterconsts'; 4 | import * as util from '../util'; 5 | 6 | 7 | export function table(specs) { 8 | var len = specs.length, 9 | extendedSpecs = specs.map(function(e) { return extendSpecWithChannelByColumnName(e); }), 10 | shorthands = specs.map(vlShorthand.shorten), 11 | diff = {}, i, j; 12 | 13 | for (i = 0; i < len; i++) { 14 | diff[shorthands[i]] = {}; 15 | } 16 | 17 | for (i = 0; i < len; i++) { 18 | for (j = i + 1; j < len; j++) { 19 | var sj = shorthands[j], si = shorthands[i]; 20 | 21 | diff[sj][si] = diff[si][sj] = get(extendedSpecs[i], extendedSpecs[j]); 22 | } 23 | } 24 | return diff; 25 | } 26 | 27 | export function get(extendedSpec1, extendedSpec2) { 28 | var cols = util.union(util.keys(extendedSpec1.channelByField), util.keys(extendedSpec2.channelByField)), 29 | dist = 0; 30 | 31 | cols.forEach(function(column) { 32 | var e1 = extendedSpec1.channelByField[column], e2 = extendedSpec2.channelByField[column]; 33 | 34 | if (e1 && e2) { 35 | if (e1.channel !== e2.channel) { 36 | dist += (consts.DIST_BY_CHANNEL[e1.channel] || {})[e2.channel] || 1; 37 | } 38 | } else { 39 | dist += consts.DIST_MISSING; 40 | } 41 | }); 42 | 43 | // do not group stacked chart with similar non-stacked chart! 44 | var isStack1 = vlSpec.isStack(extendedSpec1), 45 | isStack2 = vlSpec.isStack(extendedSpec2); 46 | 47 | if (isStack1 || isStack2) { 48 | if (isStack1 && isStack2) { 49 | if ((extendedSpec1.encoding.color && extendedSpec2.encoding.color && 50 | extendedSpec1.encoding.color.field !== extendedSpec2.encoding.color.field) || 51 | (extendedSpec1.encoding.detail && extendedSpec2.encoding.detail && 52 | extendedSpec1.encoding.detail.field !== extendedSpec2.encoding.detail.field) 53 | ) { 54 | dist+=1; 55 | } 56 | } else { 57 | dist+=1; // surely different 58 | } 59 | } 60 | return dist; 61 | } 62 | 63 | // get encoding type by fieldname 64 | export function extendSpecWithChannelByColumnName(spec) { 65 | var _channelByField = {}, 66 | encoding = spec.encoding; 67 | 68 | util.keys(encoding).forEach(function(channel) { 69 | var e = util.duplicate(encoding[channel]); 70 | e.channel = channel; 71 | _channelByField[e.field || ''] = e; 72 | delete e.field; 73 | }); 74 | 75 | return { 76 | mark: spec.mark, 77 | channelByField: _channelByField, 78 | encoding: spec.encoding 79 | }; 80 | } 81 | -------------------------------------------------------------------------------- /src/gen/specs.ts: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import * as vlFieldDef from 'vega-lite/src/fielddef'; 4 | import * as util from '../util'; 5 | import {SpecOption, DEFAULT_SPEC_OPTION} from '../consts'; 6 | import genEncodings from './encodings'; 7 | import getMarks from './marks'; 8 | import * as rank from '../rank/rank'; 9 | import {shortenEncoding} from 'vega-lite/src/shorthand'; 10 | 11 | /** Design Encodings for a set of field definition */ 12 | 13 | export default function genSpecsFromFieldDefs(output, fieldDefs, stats, opt: SpecOption = {}, nested?): any { 14 | // opt must be augmented before being passed to genEncodings or getMarks 15 | opt = util.extend({}, DEFAULT_SPEC_OPTION, opt); 16 | var encodings = genEncodings([], fieldDefs, stats, opt); 17 | 18 | if (nested) { 19 | return encodings.reduce(function(dict, encoding) { 20 | var encodingShorthand = shortenEncoding(encoding); 21 | dict[encodingShorthand] = genSpecsFromEncodings([], encoding, stats, opt); 22 | return dict; 23 | }, {}); 24 | } else { 25 | return encodings.reduce(function(list, encoding) { 26 | return genSpecsFromEncodings(list, encoding, stats, opt); 27 | }, output); 28 | } 29 | } 30 | 31 | function genSpecsFromEncodings(output, encoding, stats, opt) { 32 | getMarks(encoding, stats, opt) 33 | .forEach(function(mark) { 34 | var spec = util.duplicate({ 35 | // Clone config & encoding to unique objects 36 | encoding: encoding, 37 | config: opt.config 38 | }); 39 | 40 | spec.mark = mark; 41 | // Data object is the same across charts: pass by reference 42 | spec.data = opt.data; 43 | 44 | spec = finalTouch(spec, stats, opt); 45 | var score = rank.encoding(spec, stats, opt); 46 | 47 | spec._info = score; 48 | output.push(spec); 49 | }); 50 | return output; 51 | } 52 | 53 | // FIXME this should be refactors 54 | function finalTouch(spec, stats, opt) { 55 | if (spec.mark === 'text' && opt.alwaysGenerateTableAsHeatmap) { 56 | spec.encoding.color = spec.encoding.text; 57 | } 58 | 59 | // don't include zero if stdev/mean < 0.01 60 | // https://github.com/uwdata/visrec/issues/69 61 | var encoding = spec.encoding; 62 | ['x', 'y'].forEach(function(channel) { 63 | var fieldDef = encoding[channel]; 64 | 65 | // TODO add a parameter for this case 66 | if (fieldDef && vlFieldDef.isMeasure(fieldDef) && !vlFieldDef.isCount(fieldDef)) { 67 | var stat = stats[fieldDef.field]; 68 | if (stat && stat.stdev / stat.mean < 0.01) { 69 | fieldDef.scale = {zero: false}; 70 | } 71 | } 72 | }); 73 | return spec; 74 | } 75 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Visrec Demo 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 |
31 |
32 |

Data Fields

33 |
34 | Please select data fields. 35 |
36 |
37 |
38 | 39 | 46 |
47 |
48 | Show/Hide 49 |
50 |

Configs

51 |
52 |
53 |
54 | 55 | 62 | 63 |
64 |

Transformations Variations

65 |
66 | Displaying the best representation for each data variation of the selected projections. 67 |
68 | 69 |
70 |
71 | 72 |
73 |

Encodings Variations

74 |
75 | Displaying clusters of representations for the selected data variation. 76 | Each row shows a similar cluster. 77 |
78 |
79 |
80 |
81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | -------------------------------------------------------------------------------- /test/gen/projections.test.ts: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import {expect} from 'chai'; 4 | import {fixture} from '../fixture'; 5 | import genProjections from '../../src/gen/projections'; 6 | 7 | describe('cp.gen.projections()', function () { 8 | describe('with empty set of fields', function () { 9 | var fields = []; 10 | var projections = genProjections(fields); 11 | 12 | it('should return nothing', function () { 13 | expect(projections.length).to.equal(0); 14 | }); 15 | }); 16 | 17 | describe('with a set of fields without any field selected', function () { 18 | var f = fixture.birdstrikes; 19 | 20 | var projections = genProjections(f.fields, f.stats); 21 | 22 | it('should preserve name order', function() { 23 | for (var i=0; i { return {'field': 'f' + i, selected: undefined};} ); 58 | const projections = genProjections(fields, {}, {maxAdditionalVariables: 2}); 59 | 60 | it('should generate projections with 2 fields', function() { 61 | expect(projections.length).to.equal(6); // P(3,2) = 3 62 | const lengthCount = projections.reduce(function(count, p, i) { 63 | count[p.length] = (count[p.length] || 0) + 1; 64 | return count; 65 | }, {}); 66 | expect(lengthCount).to.eql({ 67 | 1:3, // P(3,1) = 3 68 | 2:3 // P(3,2) = 3 69 | }) 70 | }); 71 | }); 72 | 73 | describe('with a set of fields with count', function () { 74 | var f = fixture['OxOxQxQx#']; 75 | 76 | it('should generate correct # of projections', function () { 77 | var projections = genProjections(f.fields, f.stats); 78 | expect(projections.length).to.equal(4); 79 | expect(projections.filter(function(p){ return p.length === 2;}).length).to.equal(0); 80 | }); 81 | 82 | it('should generate more # of projections if not omitting dot plots', function () { 83 | var projections = genProjections(f.fields, f.stats, { 84 | omitDotPlot: false 85 | }); 86 | 87 | expect(projections.length).to.equal(4); 88 | expect(projections.filter(function(p){ return p.length === 2;}).length).to.equal(0); 89 | }); 90 | }); 91 | }); 92 | -------------------------------------------------------------------------------- /old-scripts/dataTypes.js: -------------------------------------------------------------------------------- 1 | // Universal Module Pattern -- modified from https://github.com/umdjs/umd/blob/master/returnExports.js 2 | // Uses Node, AMD or browser globals to create a module. 3 | 4 | (function (root, deps, factory) { 5 | if (typeof define === 'function' && define.amd) { 6 | // AMD. Register as an anonymous module. 7 | define(deps, factory); 8 | } else if (typeof exports === 'object') { 9 | // Node. Does not work with strict CommonJS, but 10 | // only CommonJS-like environments that support module.exports, 11 | // like Node. 12 | 13 | module.exports = factory.apply(this, deps.map(function(dep){ return require(dep);})); 14 | } else { 15 | // Browser globals (root is window) 16 | root.returnExports = factory.apply(this, deps.map(function(dep){ return root[dep];})); 17 | } 18 | }(this, ['lodash', './aggTypes'], function (_, at) { 19 | 20 | 21 | var dt = {}, DataType; 22 | DataType = (function(){ 23 | var d = function(name, short, parent, aggTypes){ 24 | this.name = name; 25 | this.short = short; 26 | this.parent = parent || null; 27 | if(parent) parent.addChild(this); 28 | this.children = []; 29 | this.specificity = (parent && parent.specificity || 0) + 1; //for checking chart requirement 30 | 31 | this.aggTypes = aggTypes || [at.RAW]; 32 | if(aggTypes){ 33 | //FIXME will i need this later? 34 | } 35 | }; 36 | var prototype = d.prototype; 37 | 38 | prototype.addChild = function(child){ 39 | this.children.push(child); 40 | }; 41 | 42 | 43 | prototype.isType = function(type){ 44 | var typeName = typeof type === "string" ? type : type.name; 45 | if(typeName == this.name) return true; 46 | return this.parent ? this.parent.isType(typeName) : false; 47 | }; 48 | 49 | prototype.allSubtypes = function(){ 50 | if(this._allSubTypes) return this._allSubTypes; //cache 51 | 52 | var q = [this], i; 53 | for(i=0; i opt.maxCardinalityForAutoAddOrdinal 52 | ) { 53 | return; 54 | } 55 | fieldsToAdd.push(fieldDef); 56 | } 57 | }); 58 | 59 | fieldsToAdd.sort(compareFieldsToAdd(hasSelectedDimension, hasSelectedMeasure, indices)); 60 | 61 | var setsToAdd = util.chooseKorLess(fieldsToAdd, opt.maxAdditionalVariables); 62 | 63 | setsToAdd.forEach(function(setToAdd) { 64 | var fieldSet = selected.concat(setToAdd); 65 | if (fieldSet.length > 0) { 66 | if (opt.omitDotPlot && fieldSet.length === 1) { 67 | return; 68 | } 69 | fieldSets.push(fieldSet); 70 | } 71 | }); 72 | 73 | // FIXME - this d3 style should be refactored 74 | fieldSets.forEach(function(fieldSet) { 75 | // always append projection's key to each projection returned, d3 style. 76 | fieldSet.key = key(fieldSet); 77 | }); 78 | 79 | return fieldSets; 80 | } 81 | 82 | var typeIsMeasureScore = { 83 | nominal: 0, 84 | ordinal: 0, 85 | temporal: 2, 86 | quantitative: 3 87 | }; 88 | 89 | function compareFieldsToAdd(hasSelectedDimension, hasSelectedMeasure, indices) { 90 | return function(a, b){ 91 | // sort by type of the data 92 | if (a.type !== b.type) { 93 | if (!hasSelectedDimension) { 94 | return typeIsMeasureScore[a.type] - typeIsMeasureScore[b.type]; 95 | } else { // if (!hasSelectedMeasure) { 96 | return typeIsMeasureScore[b.type] - typeIsMeasureScore[a.type]; 97 | } 98 | } 99 | // make the sort stable 100 | return indices[a.field] - indices[b.field]; 101 | }; 102 | } 103 | 104 | export function key(projection) { 105 | return projection.map(function(fieldDef) { 106 | return vlFieldDef.isCount(fieldDef) ? 'count' : fieldDef.field; 107 | }).join(','); 108 | }; 109 | -------------------------------------------------------------------------------- /test/gen/aggregates.test.ts: -------------------------------------------------------------------------------- 1 | import {expect} from 'chai'; 2 | import genAggregates from '../../src/gen/aggregates'; 3 | import {fixture} from '../fixture'; 4 | 5 | describe('cp.gen.aggregates()', function () { 6 | 7 | describe('Ox#', function () { 8 | var f = fixture['Ox#']; 9 | var tables = genAggregates([], f.fields, f.stats); 10 | it('should output 1 data table', function () { 11 | expect(tables.length).to.equal(1); // O 12 | }); 13 | }); 14 | 15 | describe('Q', function () { 16 | var f = fixture['Q']; 17 | 18 | describe('(No option)', function() { 19 | var tables = genAggregates([], f.fields, f.stats); 20 | 21 | it('should output 1 data table that has length 2', function () { 22 | expect(tables.filter(function(t) { 23 | return t.length === 1; 24 | }).length).to.equal(2); 25 | }); 26 | 27 | it('should output 3 data tables: Q, A(Q), BIN(Q)x#', function () { 28 | expect(tables.length).to.equal(3); 29 | }); 30 | 31 | it('should append key to each fieldSet', function() { 32 | expect(tables[0].key).to.be.ok; 33 | }); 34 | }); 35 | 36 | it('should output Q, mean(Q), bin(Q)x# if omitMeasureOnly', function () { 37 | var tables = genAggregates([], f.fields, f.stats, { 38 | omitMeasureOnly: false 39 | }); 40 | 41 | // each field can be Q, mean(Q) 42 | expect(tables.length).to.equal(3); 43 | 44 | // Q 45 | expect(tables.filter(function(table){ 46 | var t = table[0]; 47 | return !t.aggregate && !t.bin && t.type==='quantitative'; 48 | }).length).to.equal(1); 49 | 50 | // mean(Q) 51 | expect(tables.filter(function(table){ 52 | var t = table[0]; 53 | return t.aggregate && !t.bin && t.type==='quantitative'; 54 | }).length).to.equal(1); 55 | 56 | // bin(Q) x # 57 | expect(tables.filter(function(table){ 58 | return table.length === 2; 59 | }).length).to.equal(1); 60 | }); 61 | }); 62 | 63 | describe('Q_10', function() { 64 | it('should not be binned', function() { 65 | // FIXME write test 66 | }); 67 | }); 68 | 69 | 70 | describe('Qx#', function () { 71 | var f = fixture['Qx#']; 72 | var tables = genAggregates([], f.fields, f.stats); 73 | it('should output 2 data table', function () { 74 | expect(tables.length).to.equal(2); // bin 75 | expect(tables[1][0].bin).to.be.true; // bin 76 | }); 77 | }); 78 | 79 | 80 | describe('QxQ', function () { 81 | var f = fixture['QxQ']; 82 | 83 | describe('(No option)', function() { 84 | var tables = genAggregates([], f.fields, f.stats, {}); 85 | 86 | it('should output 3 data table', function () { 87 | expect(tables.length).to.equal(3); 88 | }); 89 | 90 | it('should append key to each fieldSet', function() { 91 | expect(tables[0].key).to.be.ok; 92 | }); 93 | 94 | it('not generate mean with bin', function () { 95 | var filtered = tables.filter(function(table) { 96 | return table[0].aggregate && table[1].bin; 97 | }); 98 | expect(filtered.length).to.equal(0); 99 | }); 100 | 101 | it('not generate raw with bin', function () { 102 | var filtered = tables.filter(function(t) { 103 | return !t[0].aggregate && !t[0].bin && t[1].bin; 104 | }); 105 | expect(filtered.length).to.equal(0); 106 | }); 107 | }); 108 | 109 | it('should output 3 data table if not omit', function () { 110 | var tables = genAggregates([], f.fields, f.stats, { 111 | omitMeasureOnly: false 112 | }); 113 | expect(tables.length).to.equal(3); 114 | }); 115 | }); 116 | 117 | 118 | describe('1 count', function () { 119 | var f = fixture['#']; 120 | var tables = genAggregates([], f.fields, f.stats); 121 | it('should output one data table', function () { 122 | expect(tables.length).to.equal(1); // O, bin 123 | }); 124 | }); 125 | }); 126 | -------------------------------------------------------------------------------- /src/util.ts: -------------------------------------------------------------------------------- 1 | export let isArray = Array.isArray || function (obj) { 2 | return {}.toString.call(obj) === '[object Array]'; 3 | }; 4 | 5 | export function isin(item, array) { 6 | return array.indexOf(item) !== -1; 7 | }; 8 | 9 | export function json(s, sp) { 10 | return JSON.stringify(s, null, sp); 11 | }; 12 | 13 | export function keys(obj) { 14 | var k = [], x; 15 | for (x in obj) { 16 | k.push(x); 17 | } 18 | return k; 19 | }; 20 | 21 | export function duplicate(obj) { 22 | return JSON.parse(JSON.stringify(obj)); 23 | }; 24 | 25 | export function forEach(obj, f, thisArg?) { 26 | if (obj.forEach) { 27 | obj.forEach.call(thisArg, f); 28 | } else { 29 | for (var k in obj) { 30 | f.call(thisArg, obj[k], k, obj); 31 | } 32 | } 33 | }; 34 | 35 | export function any(arr, f) { 36 | var i = 0, k; 37 | for (k in arr) { 38 | if (f(arr[k], k, i++)) { 39 | return true; 40 | } 41 | } 42 | return false; 43 | }; 44 | 45 | export function nestedMap(collection, f, level, filter?) { 46 | return level === 0 ? 47 | collection.map(f) : 48 | collection.map(function(v) { 49 | var r = nestedMap(v, f, level - 1); 50 | return filter ? r.filter(nonEmpty) : r; 51 | }); 52 | }; 53 | 54 | export function nestedReduce(collection, f, level, filter?) { 55 | return level === 0 ? 56 | collection.reduce(f, []) : 57 | collection.map(function(v) { 58 | var r = nestedReduce(v, f, level - 1); 59 | return filter ? r.filter(nonEmpty) : r; 60 | }); 61 | }; 62 | 63 | export function nonEmpty(grp) { 64 | return !isArray(grp) || grp.length > 0; 65 | }; 66 | 67 | 68 | export function traverse(node, arr) { 69 | if (node.value !== undefined) { 70 | arr.push(node.value); 71 | } else { 72 | if (node.left) { 73 | traverse(node.left, arr); 74 | } 75 | if (node.right) { 76 | traverse(node.right, arr); 77 | } 78 | 79 | } 80 | return arr; 81 | }; 82 | 83 | export function extend(obj, b, ...rest) { 84 | for (var x, name, i=1, len=arguments.length; i 0; 53 | expect(hasTextTable).to.be.false; 54 | }); 55 | 56 | it('should not contain bar', function() { 57 | var filtered = specs.filter(function(spec) { 58 | return spec.mark === 'bar'; 59 | }); 60 | expect(filtered.length).to.equal(0); 61 | }); 62 | }); 63 | 64 | describe('OxA(Q)', function() { 65 | var f = fixture['OxA(Q)']; 66 | var specs = genSpecs([], f.fields, f.stats); 67 | 68 | it('should contain text table', function() { 69 | var hasTextTable = specs.filter(function(spec) { 70 | return spec.mark === 'text'; 71 | }).length > 0; 72 | expect(hasTextTable).to.be.true; 73 | }); 74 | }); 75 | 76 | describe('O_30x#', function(){ 77 | var f = fixture['O_30x#']; 78 | var specs = genSpecs([], f.fields, f.stats); 79 | 80 | it('should contain text table', function() { 81 | var hasTextTable = specs.filter(function(spec) { 82 | return spec.mark === 'text'; 83 | }).length > 0; 84 | expect(hasTextTable).to.be.true; 85 | }); 86 | 87 | it('should contain bar', function() { 88 | var hasBar = specs.filter(function(spec) { 89 | return spec.mark === 'bar'; 90 | }).length > 0; 91 | expect(hasBar).to.be.true; 92 | }); 93 | }); 94 | 95 | describe('OxQxQ', function() { 96 | // var f = fixture['OxQxQ']; 97 | // var encodings = genEncodings([], f.fields, f.stats); 98 | 99 | it('should contain colored scatter plot', function() { 100 | // var filtered = encodings.filter(function(encoding) { 101 | // var encoding = encoding.encoding; 102 | // return encoding.mark==='point' && encoding.x && encoding.y && encoding.color; 103 | // }); 104 | // FIXME(kanitw): Jul 19, 2015 - write test! 105 | }); 106 | }); 107 | 108 | describe('QxT', function(){ 109 | var f = fixture['QxT']; 110 | var specs = genSpecs([], f.fields, f.stats); 111 | 112 | it('should not contain line', function() { 113 | var hasLine = specs.filter(function(spec) { 114 | return spec.mark === 'line'; 115 | }).length > 0; 116 | expect(hasLine).to.be.false; 117 | }); 118 | }); 119 | 120 | describe('QxYEAR(T)', function(){ 121 | var f = fixture['QxYEAR(T)']; 122 | 123 | var specs = genSpecs([], f.fields, f.stats); 124 | 125 | it('should not contain line', function() { 126 | var hasLine = specs.filter(function(spec) { 127 | return spec.mark === 'line'; 128 | }).length > 0; 129 | expect(hasLine).to.be.false; 130 | }); 131 | }); 132 | 133 | describe('A(Q)xYEAR(T)', function(){ 134 | var f = fixture['A(Q)xYEAR(T)']; 135 | var specs = genSpecs([], f.fields, f.stats); 136 | 137 | it('should contain line', function() { 138 | var hasLine = specs.filter(function(spec) { 139 | return spec.mark === 'line'; 140 | }).length > 0; 141 | expect(hasLine).to.be.true; 142 | }); 143 | }); 144 | 145 | describe('#xB(Q)', function() { 146 | var f = fixture['#xB(Q)']; 147 | var specs = genSpecs([], f.fields, f.stats); 148 | 149 | it('should contain text table', function() { 150 | var hasTextTable = specs.filter(function(spec) { 151 | return spec.mark === 'text'; 152 | }).length > 0; 153 | expect(hasTextTable).to.be.true; 154 | }); 155 | }); 156 | }); 157 | -------------------------------------------------------------------------------- /test/gen/marktypes.test.ts: -------------------------------------------------------------------------------- 1 | import {expect} from 'chai'; 2 | import {fixture} from '../fixture'; 3 | import getMarks from '../../src/gen/marks'; 4 | import {DEFAULT_SPEC_OPTION} from '../../src/consts'; 5 | import * as vlShorthand from 'vega-lite/src/shorthand'; 6 | import {BAR, POINT, TEXT, AREA} from 'vega-lite/src/mark'; 7 | import {QUANTITATIVE, ORDINAL, TEMPORAL} from 'vega-lite/src/type'; 8 | import {Encoding} from 'vega-lite/src/encoding'; 9 | import {ScaleType} from 'vega-lite/src/scale'; 10 | import {AggregateOp} from 'vega-lite/src/aggregate'; 11 | 12 | describe('cp.gen.marks()', function(){ 13 | var opt; 14 | 15 | beforeEach(function() { 16 | opt = DEFAULT_SPEC_OPTION; 17 | }); 18 | 19 | describe('#', function () { 20 | var f; 21 | 22 | beforeEach(function() { 23 | f = fixture['#']; 24 | }); 25 | 26 | it('should generate point and bar', function() { 27 | var encoding = {x: f.fields[0]}, 28 | marks = getMarks(encoding, f.stats, opt); 29 | 30 | expect(marks.length).to.eql(2); 31 | expect(marks).to.eql([POINT, BAR]); 32 | }); 33 | }); 34 | 35 | describe('1Q', function () { 36 | var encoding, marks; 37 | beforeEach(function() { 38 | encoding = {'x': {'field': 'Cost__Total_$','type': QUANTITATIVE}}; 39 | marks = getMarks(encoding, {}, opt); 40 | }); 41 | it('should contain tick', function () { 42 | expect(marks.indexOf('tick')).to.gt(-1); 43 | }); 44 | it('should contain point', function () { 45 | expect(marks.indexOf('point')).to.gt(-1); 46 | }); 47 | }); 48 | 49 | it('should require at least one basic encoding', function (){ 50 | // var basicEncodings = ['x','y','geo','text','arc']; 51 | // FIXME(kanitw): Jul 19, 2015 - write test 52 | 53 | }); 54 | 55 | describe('point', function(){ 56 | describe('scatter and bubble plots', function(){ 57 | // TODO 58 | }); 59 | 60 | describe('dot plot', function(){ 61 | // TODO 62 | }); 63 | }); 64 | 65 | describe('bar', function () { 66 | describe('with stacked average', function () { 67 | it('should not be generated', function () { 68 | var encoding = { 69 | 'color': {'field': 'When__Phase_of_flight','type': ORDINAL}, 70 | 'x': {'field': 'Cost__Total_$','type': QUANTITATIVE,'aggregate': AggregateOp.MEAN}, 71 | 'y': {'selected': undefined,'field': 'Aircraft__Airline_Operator','type': ORDINAL} 72 | }; 73 | 74 | var marks = getMarks(encoding, {}, opt); 75 | expect(marks.indexOf(BAR)).to.equal(-1); 76 | }); 77 | }); 78 | 79 | describe('with stacked sum', function () { 80 | it('should be generated', function () { 81 | var encoding = { 82 | 'color': {'field': 'When__Phase_of_flight','type': ORDINAL}, 83 | 'x': {'field': 'Cost__Total_$','type': QUANTITATIVE,'aggregate': AggregateOp.SUM}, 84 | 'y': {'field': 'Aircraft__Airline_Operator','type': ORDINAL} 85 | }; 86 | var marks = getMarks(encoding, {}, opt); 87 | expect(marks.indexOf(BAR)).to.gt(-1); 88 | }); 89 | }); 90 | describe('with log scale', function () { 91 | it('should not be generated', function () { 92 | var encoding = { 93 | 'x': { 'fiel': 'Cost__Total_$', 'type': QUANTITATIVE, 'scale': { 'type' : ScaleType.LOG } }, 94 | 'y': { 'field': 'Aircraft__Airline_Operator', 'type': ORDINAL } 95 | }; 96 | var marks = getMarks(encoding, {}, opt); 97 | expect(marks.indexOf(BAR)).to.equal(-1); 98 | }); 99 | }); 100 | }); 101 | describe('line/area', function () { 102 | describe('with log scale', function () { 103 | it('should not be generated', function () { 104 | var encoding = { 105 | 'x': { 'field': 'Year', 'type': TEMPORAL }, 106 | 'y': { 'field': 'Weight_in_lbs', 'type': QUANTITATIVE, 'scale': { 'type': ScaleType.LOG }, 'aggregate': AggregateOp.SUM } 107 | }; 108 | var marks = getMarks(encoding, {}, opt); 109 | expect(marks.indexOf(AREA)).to.equal(-1); 110 | }); 111 | }); 112 | // TODO 113 | }); 114 | 115 | describe('text', function() { 116 | it('should be generated', function () { 117 | var shorthand = 'row=1,O|text=mean_2,Q', 118 | encoding = vlShorthand.parseEncoding(shorthand), 119 | marks = getMarks(encoding, {}, opt); 120 | expect(marks.indexOf(TEXT)).to.gt(-1); 121 | }); 122 | 123 | it('should not contain size', function() { 124 | var encoding = { 125 | 'column': { 126 | 'field': 'Effect__Amount_of_damage', 127 | 'type': ORDINAL, 128 | }, 129 | 'size': { 130 | 'field': 'Cost__Repair','type': QUANTITATIVE,'aggregate': AggregateOp.MEAN 131 | }, 132 | 'text': { 133 | 'field': 'Cost__Total_$','type': QUANTITATIVE,'aggregate': AggregateOp.MEAN 134 | } 135 | }; 136 | 137 | var marks = getMarks(encoding, {}, opt); 138 | 139 | expect(marks.indexOf(TEXT)).to.equal(-1); 140 | }); 141 | }); 142 | }); 143 | -------------------------------------------------------------------------------- /test/gen/encodings.test.ts: -------------------------------------------------------------------------------- 1 | import {expect} from 'chai'; 2 | import {fixture} from '../fixture'; 3 | import * as vlShorthand from 'vega-lite/src/shorthand'; 4 | import {ORDINAL} from 'vega-lite/src/type'; 5 | import genEncodings from '../../src/gen/encodings'; 6 | import {DEFAULT_SPEC_OPTION} from '../../src/consts'; 7 | import {extend} from '../../src/util'; 8 | 9 | describe('cp.gen.encodings()', function () { 10 | describe('#', function () { 11 | var f; 12 | 13 | beforeEach(function() { 14 | f = fixture['#']; 15 | }); 16 | 17 | it('should generate one encodings', function() { 18 | var encodings = genEncodings([], f.fields, f.stats); 19 | expect(encodings.length).to.eql(1); 20 | expect(encodings[0].x).to.be.ok; 21 | }); 22 | }); 23 | 24 | describe('1Q,', function() { 25 | // FIXME write tests 26 | }); 27 | 28 | describe('#xB(Q)', function() { 29 | var f, encodings, encShorthands; 30 | beforeEach(function() { 31 | f = fixture['#xB(Q)']; 32 | encodings = genEncodings([], f.fields, f.stats); 33 | encShorthands = encodings.map(vlShorthand.shortenEncoding); 34 | }); 35 | 36 | it('should show only vertical bar/plots', function() { 37 | expect(encShorthands.indexOf('x=count_*,Q|y=bin_2,Q')).to.equal(-1); 38 | expect(encShorthands.indexOf('x=bin_2,Q|y=count_*,Q')).to.gt(-1); 39 | }); 40 | 41 | }); 42 | 43 | describe('#xT', function() { 44 | var f, encodings, encShorthands; 45 | beforeEach(function() { 46 | f = fixture['#xT']; 47 | encodings = genEncodings([], f.fields, f.stats); 48 | encShorthands = encodings.map(vlShorthand.shortenEncoding); 49 | }); 50 | 51 | it('should show only vertical bar/plots', function() { 52 | expect(encShorthands.indexOf('x=count_*,Q|y=2,T')).to.equal(-1); 53 | expect(encShorthands.indexOf('x=2,T|y=count_*,Q')).to.gt(-1); 54 | }); 55 | }); 56 | 57 | describe('#xYR(T)', function() { 58 | var f, encodings, encShorthands; 59 | beforeEach(function() { 60 | f = fixture['#xYR(T)']; 61 | encodings = genEncodings([], f.fields, f.stats); 62 | encShorthands = encodings.map(vlShorthand.shortenEncoding); 63 | }); 64 | 65 | it('should show only vertical bar/plots', function() { 66 | expect(encShorthands.indexOf('x=count_*,Q|y=year_2,T')).to.equal(-1); 67 | expect(encShorthands.indexOf('x=year_2,T|y=count_*,Q')).to.gt(-1); 68 | }); 69 | }); 70 | 71 | describe('QxT', function () { 72 | var f, encodings, encShorthands; 73 | beforeEach(function() { 74 | f = fixture['QxT']; 75 | encodings = genEncodings([], f.fields, f.stats); 76 | encShorthands = encodings.map(vlShorthand.shortenEncoding); 77 | }); 78 | it('should show only vertical bar/plots', function() { 79 | expect(encShorthands.indexOf('x=1,Q|y=2,T')).to.equal(-1); 80 | expect(encShorthands.indexOf('x=2,T|y=1,Q')).to.gt(-1); 81 | }); 82 | }); 83 | 84 | // describe('QxO,', function() { 85 | // 86 | // }); 87 | 88 | // describe('QxA(Q),', function() { 89 | // 90 | // }); 91 | 92 | describe('OxOxQ', function () { 93 | var f; 94 | beforeEach(function() { 95 | f = fixture.OxOxQ; 96 | }); 97 | 98 | it('without stats about occlusion, it should not include charts with both O\'s on axes', function() { 99 | var encodings = genEncodings([], f.fields, f.stats); 100 | 101 | var filtered = encodings.filter(function(encoding){ 102 | return encoding.x.type === ORDINAL && encoding.y.type === ORDINAL; 103 | }); 104 | 105 | expect(filtered.length).to.equal(0); 106 | }); 107 | }); 108 | 109 | describe('OxOxA(Q)', function () { 110 | var f; 111 | beforeEach(function() { 112 | f = fixture['OxOxA(Q)']; 113 | }); 114 | 115 | it('without stats about occlusion, it should include charts with both O\'s on axes', function() { 116 | var encodings = genEncodings([], f.fields, f.stats); 117 | 118 | var filtered = encodings.filter(function(encoding){ 119 | return encoding.x && encoding.x.type === ORDINAL && encoding.y && encoding.y.type === ORDINAL; 120 | }); 121 | 122 | expect(filtered.length).to.gt(0); 123 | }); 124 | }); 125 | 126 | describe('OxA(Q)xA(Q)', function () { 127 | var f, fields, stats; 128 | beforeEach(function() { 129 | f = fixture['OxA(Q)xA(Q)']; 130 | fields = f.fields; 131 | stats = f.stats; 132 | }); 133 | 134 | it('should not include charts with O on row/column except with text', function() { 135 | var encodings = genEncodings([], fields, stats); 136 | 137 | expect(encodings.filter(function(encoding) { 138 | var rowIsO = encoding.row && encoding.row.type === ORDINAL, 139 | colIsO = encoding.column && encoding.column.type === ORDINAL; 140 | return !encoding.text && (rowIsO || colIsO); 141 | }).length).to.equal(0); 142 | }); 143 | 144 | it('should include charts with O on row/column when omit flag is disabled', function() { 145 | const opt = extend({}, DEFAULT_SPEC_OPTION, {omitNonTextAggrWithAllDimsOnFacets: false}); 146 | var encodings = genEncodings([], fields, stats, opt); 147 | expect(encodings.filter(function(encoding) { 148 | return (encoding.row && encoding.row.type === ORDINAL) || 149 | (encoding.column && encoding.column.type === ORDINAL); 150 | }).length).to.gt(0); 151 | }); 152 | }); 153 | }); 154 | -------------------------------------------------------------------------------- /src/gen/marks.ts: -------------------------------------------------------------------------------- 1 | import {isAggregate} from 'vega-lite/src/encoding'; 2 | import {isDimension, cardinality} from 'vega-lite/src/fielddef'; 3 | import {getEncodingMappingError} from 'vega-lite/src/validate'; 4 | import {SpecOption} from '../consts'; 5 | 6 | import {Type} from 'vega-lite/src/type'; 7 | import {Encoding} from 'vega-lite/src/encoding'; 8 | import {ScaleType} from 'vega-lite/src/scale'; 9 | import {AggregateOp} from 'vega-lite/src/aggregate'; 10 | 11 | import * as util from '../util'; 12 | 13 | export default function genMarks(encoding: Encoding, stats, opt: SpecOption) { 14 | return opt.markList.filter(function(mark){ 15 | const noVlError = getEncodingMappingError({ 16 | mark: mark, 17 | encoding: encoding 18 | }) === null; 19 | return noVlError && rule[mark](encoding, stats, opt); 20 | }); 21 | } 22 | 23 | export namespace rule { 24 | function facetRule(fieldDef, stats, opt: SpecOption) { 25 | return cardinality(fieldDef, stats) <= opt.maxCardinalityForFacets; 26 | } 27 | 28 | function facetsRule(encoding: Encoding, stats, opt: SpecOption) { 29 | if(encoding.row && !facetRule(encoding.row, stats, opt)) return false; 30 | if(encoding.column && !facetRule(encoding.column, stats, opt)) return false; 31 | return true; 32 | } 33 | 34 | export function point(encoding: Encoding, stats, opt: SpecOption) { 35 | if(!facetsRule(encoding, stats, opt)) return false; 36 | if (encoding.x && encoding.y) { 37 | // have both x & y ==> scatter plot / bubble plot 38 | 39 | var xIsDim = isDimension(encoding.x), 40 | yIsDim = isDimension(encoding.y); 41 | 42 | // For OxO 43 | if (xIsDim && yIsDim) { 44 | // TODO: revise if we need his 45 | // shape doesn't work with both x, y as ordinal 46 | if (encoding.shape) { 47 | return false; 48 | } 49 | 50 | // TODO(kanitw): check that there is quant at least ... 51 | if (encoding.color && isDimension(encoding.color)) { 52 | return false; 53 | } 54 | } 55 | 56 | } else { // plot with one axis = dot plot 57 | if (opt.omitDotPlot) return false; 58 | 59 | // Dot plot should always be horizontal 60 | if (opt.omitTranspose && encoding.y) return false; 61 | 62 | // dot plot shouldn't have other encoding 63 | if (opt.omitDotPlotWithExtraEncoding && util.keys(encoding).length > 1) return false; 64 | 65 | } 66 | return true; 67 | } 68 | 69 | export function tick(encoding: Encoding, stats, opt: SpecOption) { 70 | // jshint unused:false 71 | if (encoding.x || encoding.y) { 72 | if(isAggregate(encoding)) return false; 73 | 74 | var xIsDim = isDimension(encoding.x), 75 | yIsDim = isDimension(encoding.y); 76 | 77 | return (!xIsDim && (!encoding.y || yIsDim)) || 78 | (!yIsDim && (!encoding.x || xIsDim)); 79 | } 80 | return false; 81 | } 82 | 83 | export function bar(encoding: Encoding, stats, opt: SpecOption) { 84 | if(!facetsRule(encoding, stats, opt)) return false; 85 | 86 | // bar requires at least x or y 87 | if (!encoding.x && !encoding.y) return false; 88 | 89 | if (opt.omitSizeOnBar && encoding.size !== undefined) return false; 90 | 91 | 92 | if (opt.omitLengthForLogScale) { 93 | if (encoding.x && encoding.x.scale && encoding.x.scale.type === ScaleType.LOG ) return false; 94 | if (encoding.y && encoding.y.scale && encoding.y.scale.type === ScaleType.LOG ) return false; 95 | } 96 | 97 | 98 | 99 | // FIXME actually check if there would be occlusion #90 100 | // need to aggregate on either x or y 101 | 102 | var aggEitherXorY = 103 | (!encoding.x || encoding.x.aggregate === undefined) !== // xor 104 | (!encoding.y || encoding.y.aggregate === undefined); 105 | 106 | 107 | if (aggEitherXorY) { 108 | var eitherXorYisDimOrNull = 109 | (!encoding.x || isDimension(encoding.x)) !== // xor 110 | (!encoding.y || isDimension(encoding.y)); 111 | 112 | if (eitherXorYisDimOrNull) { 113 | var aggregate = encoding.x.aggregate || encoding.y.aggregate; 114 | 115 | // TODO: revise 116 | return !(opt.omitStackedAverage && aggregate === AggregateOp.MEAN && encoding.color); 117 | } 118 | } 119 | 120 | return false; 121 | } 122 | 123 | export function line(encoding: Encoding, stats, opt: SpecOption) { 124 | if (!facetsRule(encoding, stats, opt)) return false; 125 | 126 | // TODO(kanitw): add omitVerticalLine as config 127 | 128 | // FIXME truly ordinal data is fine here too. 129 | // Line chart should be only horizontal 130 | // and use only temporal data 131 | return encoding.x.type === Type.TEMPORAL && !!encoding.x.timeUnit && 132 | encoding.y.type === Type.QUANTITATIVE && !!encoding.y.aggregate; 133 | } 134 | 135 | export function area(encoding: Encoding, stats, opt: SpecOption) { 136 | if (!facetsRule(encoding, stats, opt)) return false; 137 | 138 | if (!line(encoding, stats, opt)) return false; 139 | if (opt.omitLengthForLogScale) { 140 | if (encoding.x && encoding.x.scale && encoding.x.scale.type === ScaleType.LOG ) return false; 141 | if (encoding.y && encoding.y.scale && encoding.y.scale.type === ScaleType.LOG ) return false; 142 | } 143 | 144 | return !(opt.omitStackedAverage && encoding.y.aggregate === AggregateOp.MEAN && encoding.color); 145 | } 146 | 147 | export function text(encoding: Encoding, stats, opt: SpecOption) { 148 | // at least must have row or column and aggregated text values 149 | return (encoding.row || encoding.column) && encoding.text && encoding.text.aggregate && !encoding.x && !encoding.y && !encoding.size && 150 | (!opt.alwaysGenerateTableAsHeatmap || !encoding.color); 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /src/consts.ts: -------------------------------------------------------------------------------- 1 | import {Channel, X, Y, ROW, COLUMN, SIZE, COLOR, TEXT, DETAIL} from 'vega-lite/src/channel'; 2 | import {Mark} from 'vega-lite/src/mark'; 3 | 4 | export interface ProjectionOption { 5 | /** If true, exclude all dot plots. */ 6 | omitDotPlot?: boolean; 7 | /** Max cardinality for an ordinal variable to be considered for auto adding */ 8 | maxCardinalityForAutoAddOrdinal?: number; 9 | // TODO: explain 10 | alwaysAddHistogram?: boolean; 11 | maxAdditionalVariables?: number; 12 | } 13 | 14 | export const DEFAULT_PROJECTION_OPT:ProjectionOption = { 15 | omitDotPlot: false, 16 | maxCardinalityForAutoAddOrdinal: 50, 17 | alwaysAddHistogram: true, 18 | maxAdditionalVariables: 1 19 | }; 20 | 21 | export enum TableType { 22 | BOTH = 'both' as any, 23 | AGGREGATED = 'aggregated' as any, 24 | // TODO rename to raw? 25 | DISAGGREGATED = 'disaggregated' as any 26 | } 27 | 28 | export enum QuantitativeDimensionType { 29 | AUTO = 'auto' as any, 30 | BIN = 'bin' as any, 31 | CAST = 'cast' as any, 32 | NONE = 'none' as any 33 | } 34 | 35 | 36 | export interface AggregationOption { 37 | tableTypes?: TableType; 38 | /** Use Q as Dimension either by binning or casting */ 39 | genDimQ?: QuantitativeDimensionType; 40 | /** Minimum cardinality of an ordinal variable if we were to bin. */ 41 | minCardinalityForBin?: number; 42 | /** Remove all dot plots. */ 43 | omitDotPlot?: boolean; 44 | /** Omit aggregation with measure(s) only. */ 45 | omitMeasureOnly?: boolean; 46 | /** Omit aggregation with dimension(s) only. */ 47 | omitDimensionOnly?: boolean; 48 | /** Add count when there are dimension(s) only. */ 49 | addCountForDimensionOnly?: boolean; 50 | aggrList?: string[]; // FIXME 51 | timeUnitList?: string[]; // FIXME 52 | /** generate similar auto transform for quant */ 53 | consistentAutoQ?: boolean; 54 | } 55 | 56 | export const DEFAULT_AGGREGATION_OPTIONS: AggregationOption = { 57 | tableTypes: TableType.BOTH, 58 | genDimQ: QuantitativeDimensionType.AUTO, 59 | minCardinalityForBin: 20, 60 | omitDotPlot: false, 61 | omitMeasureOnly: false, 62 | omitDimensionOnly: true, 63 | addCountForDimensionOnly: true, 64 | aggrList: [undefined, 'mean'], // TODO: update this when we have box plots 65 | timeUnitList: ['year'], // 66 | consistentAutoQ: true 67 | }; 68 | 69 | export interface ScaleOption { 70 | /** List of scale types for rescaling quantitative field */ 71 | rescaleQuantitative?: string[]; 72 | } 73 | 74 | export const DEFAULT_SCALE_OPTION = { 75 | rescaleQuantitative: [undefined] 76 | }; 77 | 78 | export interface SpecOption { 79 | /** Allowed marks. */ 80 | markList?: Mark[]; 81 | 82 | /** Allowed channels. */ 83 | channelList?: Channel[]; 84 | 85 | // FIXME(kanitw): revise this when we revise text's encoding in Vega-Lite 86 | alwaysGenerateTableAsHeatmap?: boolean; 87 | 88 | maxGoodCardinalityForFacets?: number; 89 | /** Maximum cardinality of an ordinal variable to be put on facet (row/column). */ 90 | maxCardinalityForFacets?: number; 91 | /** Maximum cardinality of an ordinal variable to be put on color effectively */ 92 | maxGoodCardinalityForColor?: number; 93 | /** Maximum cardinality of an ordinal variable to be put on color */ 94 | maxCardinalityForColor?: number; 95 | /** Maximum cardinality of an ordinal variable to be put on shape */ 96 | maxCardinalityForShape?: number; 97 | 98 | /** Remove all dot plots */ 99 | omitDotPlot?: boolean; 100 | /** Remove all dot plots with shape or color or size since it's better to use both x and y first! */ 101 | omitDotPlotWithExtraEncoding?: boolean; 102 | /** Omit trellis plots that do not use X or Y since it's better to use both x and y first! */ 103 | omitDotPlotWithFacet?: boolean; 104 | /** Omit one dimension count */ 105 | omitDotPlotWithOnlyCount?: boolean; // FIXME remove 106 | /** Omit using multiple non-positional channels (size, color, shape) */ 107 | omitMultipleNonPositionalChannels?: boolean; // FIXME NonPositional 108 | /** 109 | * Remove all aggregated charts (except text tables) with all dims on facets (row, column) 110 | * because this would lead to only one mark per facet. 111 | */ 112 | omitNonTextAggrWithAllDimsOnFacets?: boolean; // FIXME revise this should become omitNonTextAggrWithAllDimsOnFacets 113 | /** Omit plot with both x and y as dimension, which most of the time have occlusion. */ 114 | omitRawWithXYBothDimension?: boolean; 115 | /** Omit binned fields on shape */ 116 | omitShapeWithBin?: boolean; 117 | /** Omit temporal dimension (time with time unit) on shape */ 118 | omitShapeWithTimeDimension?: boolean; 119 | /** Do not use bar\'s size. */ 120 | omitSizeOnBar?: boolean; // FIXME: remove 121 | /** Do not use bar with log scale. */ 122 | omitLengthForLogScale?: boolean; 123 | /** Do not stack bar chart with average. */ 124 | omitStackedAverage?: boolean; // FIXME: change to omit non-sum stacked 125 | /** 126 | * Eliminate all transpose by 127 | * (1) keeping horizontal dot plot only 128 | * (2) for OxQ charts, always put O on Y but put time and binned Q on X. 129 | * (3) show only one DxD, MxM (currently sorted by name) 130 | */ 131 | omitTranspose?: boolean; // FIXME revise 132 | }; 133 | 134 | export const DEFAULT_SPEC_OPTION: SpecOption = { 135 | markList: [Mark.POINT, Mark.BAR, Mark.LINE, Mark.AREA, Mark.TEXT, Mark.TICK], 136 | channelList: [X, Y, ROW, COLUMN, SIZE, COLOR, TEXT, DETAIL], 137 | 138 | alwaysGenerateTableAsHeatmap: true, 139 | maxGoodCardinalityForFacets: 5, 140 | maxCardinalityForFacets: 20, 141 | maxGoodCardinalityForColor: 7, 142 | maxCardinalityForColor: 20, 143 | maxCardinalityForShape: 6, 144 | omitDotPlot: false, 145 | omitDotPlotWithExtraEncoding: true, 146 | omitDotPlotWithFacet: true, 147 | omitDotPlotWithOnlyCount: false, // TODO: revise if this should be true 148 | omitMultipleNonPositionalChannels: true, // TODO: revise if we penalize this in ranking 149 | omitNonTextAggrWithAllDimsOnFacets: true, 150 | omitRawWithXYBothDimension: true, 151 | omitShapeWithBin: true, 152 | omitShapeWithTimeDimension: true, 153 | omitSizeOnBar: true, 154 | omitLengthForLogScale: true, 155 | omitStackedAverage: true, 156 | omitTranspose: true, 157 | }; 158 | -------------------------------------------------------------------------------- /test/rank/rankEncodings.test.ts: -------------------------------------------------------------------------------- 1 | import {expect} from 'chai'; 2 | import {fixture} from '../fixture'; 3 | import * as consts from '../../src/consts'; 4 | 5 | // FIXME 6 | import rankEncodings from '../../src/rank/rankEncodings'; 7 | import {dimensionScore, measureScore} from '../../src/rank/rankEncodings'; 8 | 9 | var setter = function(x, p, val, noaugment?) { 10 | for (var i=0; i= *', function () { 50 | expect(score.Q.point.x).to.gt(score.Q.point.row); 51 | expect(score.Q.point.x).to.gt(score.Q.point.size); 52 | expect(score.Q.point.x).to.gt(score.Q.point.color); 53 | expect(score.Q.point.x).to.gt(score.Q.point.text); 54 | expect(score.Q.point.x).to.gt(score.Q.point.detail); 55 | }); 56 | }); 57 | 58 | describe('size', function () { 59 | it('>= color, text', function () { 60 | expect(score.Q.point.size).to.gt(score.Q.point.color); 61 | expect(score.Q.point.size).to.gt(score.Q.point.text); 62 | expect(score.Q.point.size).to.gt(score.Q.point.detail); 63 | }); 64 | }); 65 | 66 | describe('color', function () { 67 | it('>= text', function () { 68 | expect(score.Q.point.color).to.gt(score.Q.point.text); 69 | expect(score.Q.point.color).to.gt(score.Q.point.detail); 70 | }); 71 | }); 72 | 73 | // FIXME write test for bar cases 74 | }); 75 | 76 | describe('dimensionScore()', function () { 77 | describe('x', function () { 78 | it('>= *', function () { 79 | expect(score.O.point.x).to.gt(score.O.point.row); 80 | expect(score.O.point.x).to.gt(score.O.point.size); 81 | expect(score.O.point.x).to.gt(score.O.point.color); 82 | expect(score.O.point.x).to.gt(score.O.point.text); 83 | expect(score.O.point.x).to.gt(score.O.point.detail); 84 | }); 85 | }); 86 | 87 | describe('color', function () { 88 | it('>= shape, detail', function () { 89 | expect(score.O.point.color).to.gt(score.O.point.shape); 90 | expect(score.O.point.color).to.gt(score.O.point.detail); 91 | }); 92 | 93 | it('< detail if cardinality above 20', function () { 94 | expect(score.O_30.point.color).to.lt(score.O_30.point.detail); 95 | }); 96 | 97 | it('for bar/area < for point', function() { 98 | expect(score.O.bar.color).to.lt(score.O.point.color); 99 | expect(score.O.area.color).to.lt(score.O.point.color); 100 | }); 101 | 102 | it('for BIN(Q) < for O', function() { 103 | expect(score['BIN(Q)'].area.color).to.lt(score.O.point.color); 104 | expect(score['BIN(Q)'].point.color).to.lt(score.O.point.color); 105 | }); 106 | 107 | it('BIN(Q) is bad', function (){ 108 | expect(score['BIN(Q)'].point.color).to.equal(D.color_bad); 109 | }); 110 | }); 111 | 112 | describe('shape', function () { 113 | it('>= detail, text', function () { 114 | expect(score.O.point.shape).to.gt(score.O.point.detail); 115 | expect(score.O.point.shape).to.gt(score.O.point.text); 116 | }); 117 | 118 | it('< detail if cardinality above 15', function () { 119 | expect(score.O_15.point.shape).to.lt(score.O_15.point.detail); 120 | }); 121 | }); 122 | 123 | }); 124 | 125 | describe('score()', function () { 126 | it('color(O) > size(Q)', function (){ 127 | expect(score.O.point.color).to.gt(score.Q.point.size); 128 | }); 129 | 130 | it('D.facet_ok < M.size', function() { 131 | expect(D.facet_ok).to.lt(M.size); 132 | }); 133 | }); 134 | 135 | describe('rankEncodings()', function() { 136 | 137 | describe('B(Q)xB(Q)x#', function () { 138 | var f = fixture['B(Q)xB(Q)x#']; 139 | var bp = { 140 | mark: 'point', 141 | encoding: { 142 | x: f.fields[0], 143 | y: f.fields[1], 144 | color: f.fields[2] // count 145 | } 146 | }; 147 | 148 | var sb = { 149 | mark: 'bar', 150 | encoding: { 151 | x: f.fields[0], 152 | y: f.fields[2], // count 153 | color: f.fields[1] 154 | } 155 | }; 156 | 157 | var bpScore = rankEncodings(bp, f.stats), 158 | sbScore = rankEncodings(sb, f.stats); 159 | 160 | it('bubble plot > stacked bar', function () { 161 | expect(bpScore.score).to.gt(sbScore.score); 162 | }); 163 | }); 164 | 165 | describe('text tables', function() { 166 | it('\'s text and color score should be merged', function () { 167 | var spec = { 168 | 'mark': 'text', 169 | 'encoding': { 170 | 'column': {'field': 'Aircraft__Airline_Operator','type': 'ordinal'}, 171 | 'text': {'field': '*','aggregate': 'count','type': 'quantitative'}, 172 | 'color': {'field': '*','aggregate': 'count','type': 'quantitative'} 173 | } 174 | }; 175 | var textScore = rankEncodings(spec, { 176 | Aircraft__Airline_Operator: {cardinality: 10}, 177 | count: 15 178 | }); 179 | expect(textScore.features.length).to.equal(3); 180 | }); 181 | }); 182 | }); 183 | }); 184 | -------------------------------------------------------------------------------- /src/rank/rankEncodings.ts: -------------------------------------------------------------------------------- 1 | // FIXME: rename to rankSpecs 2 | 3 | 'use strict'; 4 | 5 | import * as vlEncoding from 'vega-lite/src/encoding'; 6 | import * as vlFieldDef from 'vega-lite/src/fielddef'; 7 | import * as vlChannel from 'vega-lite/src/channel'; 8 | var isDimension = vlFieldDef.isDimension; 9 | import * as util from '../util'; 10 | import * as vlShorthand from 'vega-lite/src/shorthand'; 11 | 12 | import {Type} from 'vega-lite/src/type'; 13 | import {Mark} from 'vega-lite/src/mark'; 14 | 15 | // bad score not specified in the table above 16 | var UNUSED_POSITION = 0.5; 17 | 18 | var MARK_SCORE = { 19 | line: 0.99, 20 | area: 0.98, 21 | bar: 0.97, 22 | tick: 0.96, 23 | point: 0.95, 24 | circle: 0.94, 25 | square: 0.94, 26 | text: 0.8 27 | }; 28 | 29 | var D:any = {}, M:any = {}, BAD = 0.1, TERRIBLE = 0.01; 30 | 31 | D.minor = 0.01; 32 | D.pos = 1; 33 | D.Y_T = 0.8; 34 | D.facet_text = 1; 35 | D.facet_good = 0.675; // < color_ok, > color_bad 36 | D.facet_ok = 0.55; 37 | D.facet_bad = 0.4; 38 | D.color_good = 0.7; 39 | D.color_ok = 0.65; // > M.Size 40 | D.color_bad = 0.3; 41 | D.color_stack = 0.6; 42 | D.shape = 0.6; 43 | D.detail = 0.5; 44 | D.bad = BAD; 45 | D.terrible = TERRIBLE; 46 | 47 | M.pos = 1; 48 | M.size = 0.6; 49 | M.color = 0.5; 50 | M.text = 0.4; 51 | M.bad = BAD; 52 | M.terrible = TERRIBLE; 53 | 54 | // FIXME 55 | export let dimensionScore:any = function(fieldDef, channel, mark, stats, opt){ 56 | var cardinality = vlFieldDef.cardinality(fieldDef, stats); 57 | switch (channel) { 58 | case vlChannel.X: 59 | if (fieldDef.type === Type.NOMINAL || fieldDef.type === Type.ORDINAL) { 60 | return D.pos - D.minor; 61 | } 62 | return D.pos; 63 | 64 | case vlChannel.Y: 65 | if (fieldDef.type === Type.NOMINAL || fieldDef.type === Type.ORDINAL) { 66 | return D.pos - D.minor; // prefer ordinal on y 67 | } 68 | if (fieldDef.type === Type.TEMPORAL) { 69 | return D.Y_T; // time should not be on Y 70 | } 71 | return D.pos - D.minor; 72 | 73 | case vlChannel.COLUMN: 74 | if (mark === 'text') return D.facet_text; 75 | // prefer column over row due to scrolling issues 76 | return cardinality <= opt.maxGoodCardinalityForFacets ? D.facet_good : 77 | cardinality <= opt.maxCardinalityForFacets ? D.facet_ok : D.facet_bad; 78 | 79 | case vlChannel.ROW: 80 | if (mark === 'text') return D.facet_text; 81 | return (cardinality <= opt.maxGoodCardinalityForFacets ? D.facet_good : 82 | cardinality <= opt.maxCardinalityForFacets ? D.facet_ok : D.facet_bad) - D.minor; 83 | 84 | case vlChannel.COLOR: 85 | var hasOrder = (fieldDef.bin && fieldDef.type=== Type.QUANTITATIVE) || (fieldDef.timeUnit && fieldDef.type=== Type.TEMPORAL); 86 | 87 | // FIXME add stacking option once we have control .. 88 | var isStacked = mark === Mark.BAR || mark === Mark.AREA; 89 | 90 | // true ordinal on color is currently BAD (until we have good ordinal color scale support) 91 | if (hasOrder) return D.color_bad; 92 | 93 | // stacking gets lower score 94 | if (isStacked) return D.color_stack; 95 | 96 | return cardinality <= opt.maxGoodCardinalityForColor ? D.color_good: cardinality <= opt.maxCardinalityForColor ? D.color_ok : D.color_bad; 97 | case vlChannel.SHAPE: 98 | return cardinality <= opt.maxCardinalityForShape ? D.shape : TERRIBLE; 99 | case vlChannel.DETAIL: 100 | return D.detail; 101 | } 102 | return TERRIBLE; 103 | }; 104 | 105 | dimensionScore.consts = D; 106 | 107 | // FIXME 108 | export let measureScore:any = function(fieldDef, channel, mark, stats, opt) { 109 | // jshint unused:false 110 | switch (channel) { 111 | case vlChannel.X: return M.pos; 112 | case vlChannel.Y: return M.pos; 113 | case vlChannel.SIZE: 114 | if (mark === Mark.BAR || mark === Mark.TEXT || mark === Mark.LINE) { 115 | return BAD; // size of bar is very bad 116 | } 117 | return M.size; 118 | case vlChannel.COLOR: return M.color; 119 | case vlChannel.TEXT: return M.text; 120 | } 121 | return BAD; 122 | }; 123 | 124 | measureScore.consts = M; 125 | 126 | export default function rankEncodings(spec, stats, opt?, selected?) { 127 | var features = [], 128 | channels = util.keys(spec.encoding), 129 | mark = spec.mark, 130 | encoding = spec.encoding; 131 | 132 | var encodingMappingByField = vlEncoding.reduce(spec.encoding, function(o, fieldDef, channel) { 133 | var key = vlShorthand.shortenFieldDef(fieldDef); 134 | var mappings = o[key] = o[key] || []; 135 | mappings.push({channel: channel, fieldDef: fieldDef}); 136 | return o; 137 | }, {}); 138 | 139 | // data - encoding mapping score 140 | util.forEach(encodingMappingByField, function(mappings) { 141 | var reasons = mappings.map(function(m) { 142 | return m.channel + vlShorthand.ASSIGN + vlShorthand.shortenFieldDef(m.fieldDef) + 143 | ' ' + (selected && selected[m.fieldDef.field] ? '[x]' : '[ ]'); 144 | }), 145 | scores = mappings.map(function(m) { 146 | var roleScore = vlFieldDef.isDimension(m.fieldDef) ? 147 | dimensionScore : measureScore; 148 | 149 | var score = roleScore(m.fieldDef, m.channel, spec.mark, stats, opt); 150 | 151 | return !selected || selected[m.fieldDef.field] ? score : Math.pow(score, 0.125); 152 | }); 153 | 154 | features.push({ 155 | reason: reasons.join(' | '), 156 | score: Math.max.apply(null, scores) 157 | }); 158 | }); 159 | 160 | // plot type 161 | if (mark === 'text') { 162 | // TODO 163 | } else { 164 | if (encoding.x && encoding.y) { 165 | if (isDimension(encoding.x) !== isDimension(encoding.y)) { 166 | features.push({ 167 | reason: 'OxQ plot', 168 | score: 0.8 169 | }); 170 | } 171 | } 172 | } 173 | 174 | // penalize not using positional only penalize for non-text 175 | if (channels.length > 1 && mark !== Mark.TEXT) { 176 | if ((!encoding.x || !encoding.y) && !encoding.geo && !encoding.text) { 177 | features.push({ 178 | reason: 'unused position', 179 | score: UNUSED_POSITION 180 | }); 181 | } 182 | } 183 | 184 | // mark type score 185 | features.push({ 186 | reason: 'mark='+mark, 187 | score: MARK_SCORE[mark] 188 | }); 189 | 190 | return { 191 | score: features.reduce(function(p, f) { 192 | return p * f.score; 193 | }, 1), 194 | features: features 195 | }; 196 | } 197 | -------------------------------------------------------------------------------- /src/gen/aggregates.ts: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import * as vlFieldDef from 'vega-lite/src/fielddef'; 4 | import * as vlShorthand from 'vega-lite/src/shorthand'; 5 | import {Type} from 'vega-lite/src/type'; 6 | import {FieldDef} from 'vega-lite/src/fielddef'; 7 | import {AggregateOp} from 'vega-lite/src/aggregate'; 8 | import {SchemaField} from '../schema'; 9 | 10 | import * as util from '../util'; 11 | import {TableType, QuantitativeDimensionType, AggregationOption, DEFAULT_AGGREGATION_OPTIONS} from '../consts'; 12 | 13 | var AUTO = '*'; 14 | 15 | export default function genAggregates(output, fieldDefs: SchemaField[], stats, opt: AggregationOption = {}) { 16 | opt = util.extend({}, DEFAULT_AGGREGATION_OPTIONS, opt); 17 | 18 | var tf: FieldDef[] = new Array(fieldDefs.length); 19 | var hasNorO = util.any(fieldDefs, function(f) { 20 | return f.type === Type.NOMINAL || f.type === Type.ORDINAL; 21 | }); 22 | 23 | function emit(fieldSet) { 24 | fieldSet = util.duplicate(fieldSet); 25 | fieldSet.key = fieldSet.map(function(fieldDef) { 26 | return vlShorthand.shortenFieldDef(fieldDef); 27 | }).join(vlShorthand.DELIM); 28 | output.push(fieldSet); 29 | } 30 | 31 | function checkAndPush() { 32 | if (opt.omitMeasureOnly || opt.omitDimensionOnly) { 33 | var hasMeasure = false, hasDimension = false, hasRaw = false; 34 | tf.forEach(function(f) { 35 | if (vlFieldDef.isDimension(f)) { 36 | hasDimension = true; 37 | } else { 38 | hasMeasure = true; 39 | if (!f.aggregate) { hasRaw = true; } 40 | } 41 | }); 42 | // Constraint: Omit Aggregate Plot with measure only 43 | if (!hasDimension && !hasRaw && opt.omitMeasureOnly) { 44 | return; 45 | } 46 | if (!hasMeasure) { 47 | // Special Option: Add count for dimension only 48 | if (opt.addCountForDimensionOnly) { 49 | tf.push(vlFieldDef.count()); 50 | emit(tf); 51 | tf.pop(); 52 | } 53 | if (opt.omitDimensionOnly) { return; } 54 | } 55 | } 56 | if (opt.omitDotPlot && tf.length === 1) { return; } 57 | emit(tf); 58 | } 59 | 60 | function assignAggrQ(i, hasAggr, autoMode, a) { 61 | var canHaveAggr = hasAggr === true || hasAggr === null, 62 | cantHaveAggr = hasAggr === false || hasAggr === null; 63 | if (a) { 64 | if (canHaveAggr) { 65 | tf[i].aggregate = a; 66 | assignField(i + 1, true, autoMode); 67 | delete tf[i].aggregate; 68 | } 69 | } else { // if(a === undefined) 70 | if (cantHaveAggr) { 71 | assignField(i + 1, false, autoMode); 72 | } 73 | } 74 | } 75 | 76 | function assignBinQ(i, hasAggr, autoMode) { 77 | tf[i].bin = true; 78 | assignField(i + 1, hasAggr, autoMode); 79 | delete tf[i].bin; 80 | } 81 | 82 | function assignQ(i, hasAggr, autoMode) { 83 | var f = fieldDefs[i], 84 | canHaveAggr = hasAggr === true || hasAggr === null; 85 | 86 | tf[i] = {field: f.field, type: f.type}; 87 | 88 | if (f.aggregate === AggregateOp.COUNT) { // if count is included in the selected fields 89 | if (canHaveAggr) { 90 | tf[i].aggregate = f.aggregate; 91 | assignField(i + 1, true, autoMode); 92 | } 93 | } else if (f._aggregate) { 94 | // TODO support array of f._aggrs too 95 | assignAggrQ(i, hasAggr, autoMode, f._aggregate); 96 | } else if (f._raw) { 97 | assignAggrQ(i, hasAggr, autoMode, undefined); 98 | } else if (f._bin) { 99 | assignBinQ(i, hasAggr, autoMode); 100 | } else { 101 | opt.aggrList.forEach(function(a) { 102 | if (!opt.consistentAutoQ || autoMode === AUTO || autoMode === a) { 103 | assignAggrQ(i, hasAggr, a /*assign autoMode*/, a); 104 | } 105 | }); 106 | 107 | if ((!opt.consistentAutoQ || util.isin(autoMode, [AUTO, 'bin', 'cast', 'autocast'])) && !hasNorO) { 108 | // Constraint: Bin a field only if its cardinality is above certain threshold 109 | var highCardinality = vlFieldDef.cardinality(f, stats) > opt.minCardinalityForBin; 110 | 111 | var isAuto = opt.genDimQ === QuantitativeDimensionType.AUTO, 112 | genBin = opt.genDimQ === QuantitativeDimensionType.BIN || (isAuto && highCardinality), 113 | genCast = opt.genDimQ === QuantitativeDimensionType.CAST || (isAuto && !highCardinality); 114 | 115 | if (genBin && util.isin(autoMode, [AUTO, 'bin', 'autocast'])) { 116 | assignBinQ(i, hasAggr, isAuto ? 'autocast' : 'bin'); 117 | } 118 | if (genCast && util.isin(autoMode, [AUTO, 'cast', 'autocast'])) { 119 | tf[i].type = Type.ORDINAL; 120 | assignField(i + 1, hasAggr, isAuto ? 'autocast' : 'cast'); 121 | tf[i].type = Type.QUANTITATIVE; 122 | } 123 | } 124 | } 125 | } 126 | 127 | function assignTimeUnitT(i, hasAggr, autoMode, timeUnit) { 128 | tf[i].timeUnit = timeUnit; 129 | assignField(i+1, hasAggr, autoMode); 130 | delete tf[i].timeUnit; 131 | } 132 | 133 | function assignT(i, hasAggr, autoMode) { 134 | var f = fieldDefs[i]; 135 | tf[i] = {field: f.field, type: f.type}; 136 | 137 | // TODO support array of f._timeUnits 138 | if (f._timeUnit) { 139 | assignTimeUnitT(i, hasAggr, autoMode, f._timeUnit); 140 | } else { 141 | opt.timeUnitList.forEach(function(timeUnit) { 142 | // TODO: add option 143 | if (timeUnit === undefined) { 144 | // Constraint: we should not aggregate over raw time 145 | if (!hasAggr) { 146 | assignField(i+1, false, autoMode); 147 | } 148 | } else { 149 | assignTimeUnitT(i, hasAggr, autoMode, timeUnit); 150 | } 151 | }); 152 | } 153 | 154 | // FIXME what if you aggregate time? 155 | } 156 | 157 | function assignField(i, hasAggr, autoMode) { 158 | if (i === fieldDefs.length) { // If all fields are assigned 159 | checkAndPush(); 160 | return; 161 | } 162 | 163 | var f = fieldDefs[i]; 164 | // Otherwise, assign i-th field 165 | switch (f.type) { 166 | // TODO: "D", "G" 167 | case Type.QUANTITATIVE: 168 | assignQ(i, hasAggr, autoMode); 169 | break; 170 | 171 | case Type.TEMPORAL: 172 | assignT(i, hasAggr, autoMode); 173 | break; 174 | case Type.ORDINAL: 175 | /* falls through */ 176 | case Type.NOMINAL: 177 | /* falls through */ 178 | default: 179 | tf[i] = f; 180 | assignField(i + 1, hasAggr, autoMode); 181 | break; 182 | } 183 | } 184 | 185 | var hasAggr = opt.tableTypes === TableType.AGGREGATED ? true : 186 | opt.tableTypes === TableType.DISAGGREGATED ? false : null; 187 | 188 | assignField(0, hasAggr, AUTO); 189 | 190 | return output; 191 | } 192 | -------------------------------------------------------------------------------- /old-scripts/test/chartTemplateTest.js: -------------------------------------------------------------------------------- 1 | /* jshint expr:true */ 2 | 3 | var chai = require('chai'), 4 | assert = chai.assert, 5 | util = require('util'); 6 | 7 | chai.should(); 8 | chai.config.includeStack = true; 9 | 10 | var path = "../"; 11 | 12 | var _ = require('lodash'); 13 | var dt = require(path+'dataTypes'); 14 | var templates = require(path+'/chartTemplates'); 15 | var template = require(path+'/chartTemplate'); 16 | var field = require(path+"/field"); 17 | 18 | 19 | var C_Q = {categorical:1, quantitative:1}; 20 | var CGQ = {"categorical":1,"geographic":1,"quantitative":1}; 21 | var Count3 = {"count":3}; 22 | 23 | describe('ChartTemplates', function(){ 24 | it('findByDataTypes', function () { 25 | _.pluck(templates.findByDataTypes(C_Q), "id").should.have.same.members( 26 | ['histogram', 'bar-chart', 'text-table', 'plot-raw--y:C-color:_'], 27 | "test"); 28 | }); 29 | 30 | }); 31 | 32 | describe('ChartTemplate', function(){ 33 | describe('satisfyFieldTypeCount', function(){ 34 | it("summary-table should satisfy correct dataTypes", function(){ 35 | var summaryTable = templates["summary-table"]; 36 | 37 | summaryTable.satisfyFieldTypeCount({quantitative:1}).should.be.true; 38 | summaryTable.satisfyFieldTypeCount({count:1}).should.be.false; 39 | 40 | //testing disabled small multiples 41 | summaryTable.satisfyFieldTypeCount({quantitative:2}).should.be.false; 42 | summaryTable.satisfyFieldTypeCount({quantitative:1,categorical:1}).should.be.false; 43 | 44 | }); 45 | 46 | it("bar-chart should satisfy correct dataTypes", function(){ 47 | var barChart = templates["bar-chart"]; 48 | 49 | barChart.satisfyFieldTypeCount({ 50 | quantitative: 1, 51 | categorical: 1 52 | }).should.be.true; 53 | barChart.satisfyFieldTypeCount({ 54 | quantitative: 2 55 | }).should.be.false; 56 | barChart.satisfyFieldTypeCount({ 57 | quantitative: 1, 58 | categorical: 3 59 | }).should.be.true; 60 | barChart.satisfyFieldTypeCount({ 61 | quantitative: 3, //multiple quants 62 | categorical: 1 63 | }).should.be.true; 64 | 65 | barChart.satisfyFieldTypeCount({ 66 | quantitative: 1, 67 | datetime: 1 68 | }).should.be.true; 69 | }); 70 | 71 | it('text-table should satisfy correct dataTypes', function () { 72 | var textTable = templates['text-table']; 73 | textTable.satisfyFieldTypeCount({quantitative:1}).should.be.false; 74 | textTable.satisfyFieldTypeCount({quantitative:2}).should.be.false; 75 | textTable.satisfyFieldTypeCount({quantitative:1,categorical:1}).should.be.true; 76 | textTable.satisfyFieldTypeCount({count:1,categorical:1}).should.be.true; 77 | }); 78 | 79 | it("histogram should satisfy correct dataTypes", function(){ 80 | var histogram = templates['histogram']; 81 | histogram.satisfyFieldTypeCount({quantitative:1}).should.be.true; 82 | histogram.satisfyFieldTypeCount({quantitative:2}).should.be.false; 83 | histogram.satisfyFieldTypeCount({count:1}).should.be.false; 84 | histogram.satisfyFieldTypeCount({aggregate:1}).should.be.false; 85 | }); 86 | 87 | it("plot-raw should satisfy correct dataTypes", function(){ 88 | var plotRaw = templates.GROUPS['plot-raw']; 89 | plotRaw.satisfyFieldTypeCount({ 90 | quantitative: 1 91 | }).should.be.false; 92 | plotRaw.satisfyFieldTypeCount({ 93 | quantitative: 2 94 | }).should.be.true; 95 | plotRaw.satisfyFieldTypeCount({ 96 | quantitative: 3 97 | }).should.be.true; 98 | plotRaw.satisfyFieldTypeCount({ 99 | quantitative: 4 100 | }).should.be.true; 101 | 102 | 103 | plotRaw.satisfyFieldTypeCount({ 104 | quantitative: 2, 105 | categorical: 1 106 | }).should.be.true; 107 | 108 | }); 109 | 110 | it("plot-agg should satisfy correct dataTypes", function(){ 111 | var plotAgg = templates.GROUPS['plot-agg']; 112 | plotAgg.satisfyFieldTypeCount({ 113 | quantitative: 1 114 | }).should.be.false; 115 | plotAgg.satisfyFieldTypeCount({ 116 | quantitative: 2 117 | }).should.be.false; 118 | plotAgg.satisfyFieldTypeCount({ 119 | quantitative: 3 120 | }).should.be.true; 121 | plotAgg.satisfyFieldTypeCount({ 122 | quantitative: 4 123 | }).should.be.true; 124 | 125 | 126 | plotAgg.satisfyFieldTypeCount({ 127 | quantitative: 2, 128 | categorical: 1 129 | }).should.be.true; 130 | }); 131 | 132 | it("line should satisfy correct dataTypes", function(){ 133 | var lineChart = templates['line-chart']; 134 | lineChart.satisfyFieldTypeCount({ 135 | quantitative: 1, 136 | datetime: 1 137 | }).should.be.true; 138 | 139 | lineChart.satisfyFieldTypeCount({ 140 | quantitative: 5, 141 | datetime: 1 142 | }).should.be.true; 143 | 144 | lineChart.satisfyFieldTypeCount({ 145 | quantitative: 1, 146 | categorical: 1 147 | }).should.be.false; 148 | }); 149 | 150 | //TODO(kanitw) test two type of colored line/area 151 | //TODO(kanitw): test "sameUnit" once implemented 152 | }); 153 | 154 | describe('generateCharts', function () { 155 | var c_q = field.fromTypeCountMap(C_Q); 156 | var cgq = field.fromTypeCountMap(CGQ); 157 | 158 | function rf(fieldTypes){ 159 | return _.reduce(fieldTypes, function(arr, count, type){ 160 | var i=0; 161 | for(i=0; i' + selectedColNames.join(",") + ''); 173 | $('#content').append('
'); 174 | var group = $('#group-'+groupId); 175 | 176 | // var block = 177 | 178 | _.each(charts, function(chart){ 179 | // console.log('chart', chart, chart.toShorthand()); 180 | var id = 'vis-' + (visIdCounter++), 181 | data = chart.isAggregated ? aggregatedData : rawData; 182 | 183 | var spec = vl.parse(chart, schema, data, '#'+id); 184 | 185 | group.append('
' + chart.toShorthand() + '
'+ JSON.stringify(spec, null, 2) +'
'+ JSON.stringify(aggregatedData) +'
'); 186 | 187 | if(spec){ 188 | vg.parse.spec(spec, function(vgChart){ 189 | vgChart({el: '#'+id}).data({ 190 | all: data, 191 | selected: [], 192 | filtered: [] 193 | }).update(); 194 | }); 195 | } 196 | 197 | 198 | }) 199 | }); 200 | 201 | }); 202 | }) -------------------------------------------------------------------------------- /src/gen/encodings.ts: -------------------------------------------------------------------------------- 1 | import {isDimension, isMeasure, cardinality} from 'vega-lite/src/fielddef'; 2 | import {isAggregate} from 'vega-lite/src/encoding'; 3 | import {keys, duplicate} from '../util'; 4 | import {rule as marksRule} from './marks'; 5 | import {SpecOption, DEFAULT_SPEC_OPTION} from '../consts'; 6 | import {AggregateOp} from 'vega-lite/src/aggregate'; 7 | import {ROW, COLUMN, getSupportedRole, Channel} from 'vega-lite/src/channel'; 8 | import {Type} from 'vega-lite/src/type'; 9 | import {Encoding} from 'vega-lite/src/encoding'; 10 | import {FieldDef} from 'vega-lite/src/fielddef'; 11 | 12 | export default function genEncodings(encodings: Encoding[], fieldDefs: FieldDef[], stats, opt: SpecOption = DEFAULT_SPEC_OPTION) { 13 | // generate a collection vega-lite's encoding 14 | var tmpEncoding: Encoding = {}; 15 | 16 | function assignField(i) { 17 | // If all fields are assigned, save 18 | if (i === fieldDefs.length) { 19 | // at the minimal all chart should have x, y, geo, text or arc 20 | if (rule.encoding(tmpEncoding, stats, opt)) { 21 | encodings.push(duplicate(tmpEncoding)); 22 | } 23 | return; 24 | } 25 | 26 | // Otherwise, assign i-th field 27 | var fieldDef = fieldDefs[i]; 28 | for (var j in opt.channelList) { 29 | var channel = opt.channelList[j], 30 | isDim = isDimension(fieldDef); 31 | 32 | const supportedRole = getSupportedRole(channel); 33 | 34 | // TODO: support "multiple" assignment 35 | if ( 36 | // encoding not used 37 | !(channel in tmpEncoding) && 38 | // channel support the assigned role 39 | ((isDim && supportedRole.dimension) || (!isDim && supportedRole.measure)) && 40 | // the field satisfies the channel's rule 41 | rule.channel[channel](tmpEncoding, fieldDef, stats, opt) 42 | ) { 43 | tmpEncoding[channel] = fieldDef; 44 | assignField(i + 1); 45 | delete tmpEncoding[channel]; 46 | } 47 | } 48 | } 49 | 50 | assignField(0); 51 | 52 | return encodings; 53 | } 54 | 55 | namespace rule { 56 | export namespace channel { 57 | export const x = noRule; 58 | export const y = noRule; 59 | export const text = noRule; 60 | export const detail = noRule; 61 | export const size = retinalEncRules; 62 | 63 | // facet rules has interaction with mark -- so they are in marks.ts 64 | // TODO: revise this after we revise text encoding in Vega-lite 65 | export const row = noRule; 66 | export const column = noRule; 67 | 68 | export function color(encoding: Encoding, fieldDef: FieldDef, stats, opt: SpecOption) { 69 | // Don't use color if omitMultipleRetinalEncodings is true and we already have other retinal encoding 70 | if (!retinalEncRules(encoding, fieldDef, stats, opt)) { 71 | return false; 72 | } 73 | 74 | // Color must be either measure or dimension with cardinality lower than the max cardinality 75 | return isMeasure(fieldDef) || 76 | cardinality(fieldDef, stats) <= opt.maxCardinalityForColor; 77 | } 78 | 79 | export function shape(encoding: Encoding, fieldDef: FieldDef, stats, opt: SpecOption) { 80 | if (!retinalEncRules(encoding, fieldDef, stats, opt)) { 81 | return false; 82 | } 83 | 84 | // TODO: remove this because we can only bin quantitative, and we don't support quantitative for shape anyway 85 | if (opt.omitShapeWithBin && fieldDef.bin && fieldDef.type === Type.QUANTITATIVE) { 86 | return false; 87 | } 88 | if (opt.omitShapeWithTimeDimension && fieldDef.timeUnit && fieldDef.type === Type.TEMPORAL) { 89 | return false; 90 | } 91 | 92 | // TODO: omit shape with ordinal 93 | 94 | return cardinality(fieldDef, stats) <= opt.maxCardinalityForShape; 95 | } 96 | 97 | function noRule() { return true; } 98 | function retinalEncRules(encoding: Encoding, fieldDef: FieldDef, stats, opt: SpecOption) { 99 | if (opt.omitMultipleNonPositionalChannels) { 100 | if (encoding.color || encoding.size || encoding.shape) { 101 | return false; 102 | } 103 | } 104 | return true; 105 | } 106 | } 107 | 108 | function dotPlotRules(encoding: Encoding, stats, opt: SpecOption) { 109 | if (opt.omitDotPlot) { return false;} 110 | 111 | // Dot plot should always be horizontal 112 | if (opt.omitTranspose && encoding.y) { return false;} 113 | 114 | // Omit Dot plot with facet 115 | if (opt.omitDotPlotWithFacet && (encoding.row || encoding.column)) { 116 | return false; 117 | } 118 | 119 | // dot plot shouldn't have other encoding 120 | if (opt.omitDotPlotWithExtraEncoding && keys(encoding).length > 1) { 121 | return false; 122 | } 123 | 124 | if (opt.omitDotPlotWithOnlyCount) { 125 | // one dimension "count" 126 | if (encoding.x && encoding.x.aggregate === AggregateOp.COUNT && !encoding.y) { 127 | return false; 128 | } 129 | if (encoding.y && encoding.y.aggregate === AggregateOp.COUNT && !encoding.x) { 130 | return false; 131 | } 132 | } 133 | return true; 134 | } 135 | 136 | function isAggrWithAllDimOnFacets(encoding) { 137 | var hasAggr = false, hasOtherO = false; 138 | for (var c in encoding) { 139 | const channel: Channel = c as any; 140 | var fieldDef = encoding[channel]; 141 | if (fieldDef.aggregate) { 142 | hasAggr = true; 143 | } 144 | if (isDimension(fieldDef) && (channel !== ROW && channel !== COLUMN)) { 145 | hasOtherO = true; 146 | } 147 | if (hasAggr && hasOtherO) { break; } 148 | } 149 | 150 | return hasAggr && !hasOtherO; 151 | }; 152 | 153 | function xyPlotRules(encoding: Encoding, stats, opt: SpecOption) { 154 | if (encoding.row || encoding.column) { // have facet(s) 155 | if (opt.omitNonTextAggrWithAllDimsOnFacets) { 156 | // remove all aggregated charts with all dims on facets (row, column) 157 | if (isAggrWithAllDimOnFacets(encoding)) { return false; } 158 | } 159 | } 160 | 161 | var isDimX = isDimension(encoding.x), 162 | isDimY = isDimension(encoding.y); 163 | 164 | // If both x and y are dimension, and the plot is not aggregated, 165 | // there might be occlusion. 166 | if (opt.omitRawWithXYBothDimension && isDimX && isDimY && !isAggregate(encoding)) { 167 | // FIXME actually check if there would be occlusion #90 168 | return false; 169 | } 170 | 171 | if (opt.omitTranspose) { 172 | if (isDimX !== isDimY) { // dim x mea 173 | // create horizontal histogram for ordinal 174 | if ((encoding.y.type === Type.NOMINAL || encoding.y.type === Type.ORDINAL) && isMeasure(encoding.x)) { 175 | return true; 176 | } 177 | 178 | // vertical histogram for binned Q and T 179 | if (!isDimY && isDimX && !(encoding.x.type === Type.NOMINAL || encoding.x.type === Type.ORDINAL)) { 180 | return true; 181 | } 182 | 183 | return false; 184 | } else if (encoding.y.type=== Type.TEMPORAL || encoding.x.type === Type.TEMPORAL) { 185 | // FIXME revise this 186 | if (encoding.y.type=== Type.TEMPORAL && encoding.x.type !== Type.TEMPORAL) { 187 | return false; 188 | } 189 | } else { 190 | // FIXME: test if we can remove this rule 191 | // show only one OxO, QxQ 192 | if (encoding.x.field > encoding.y.field) { 193 | return false; 194 | } 195 | } 196 | } 197 | return true; 198 | } 199 | 200 | /** List of rules that are only considered at the end of the generation process */ 201 | export function encoding(encoding: Encoding, stats, opt: SpecOption) { 202 | // TODO call Vega-Lite validate instead once it is implemented 203 | // encoding.text is only used for TEXT TABLE 204 | if (encoding.text) { 205 | return marksRule.text(encoding, stats, opt); 206 | } 207 | 208 | const hasX = !!encoding.x, hasY = !!encoding.y; 209 | 210 | if (hasX !== hasY) { // DOT PLOT (plot with one axis) 211 | return dotPlotRules(encoding, stats, opt); 212 | } else if (hasX && hasY) { // CARTESIAN PLOT with X and Y 213 | return xyPlotRules(encoding, stats, opt); 214 | } 215 | // TODO: consider other type of visualization (e.g., geo, arc) when we have them. 216 | return false; 217 | } 218 | } 219 | -------------------------------------------------------------------------------- /typings/mocha.d.ts: -------------------------------------------------------------------------------- 1 | // Type definitions for mocha 2.2.5 2 | // Project: http://mochajs.org/ 3 | // Definitions by: Kazi Manzur Rashid , otiai10 , jt000 , Vadim Macagon 4 | // Definitions: https://github.com/borisyankov/DefinitelyTyped 5 | 6 | interface MochaSetupOptions { 7 | //milliseconds to wait before considering a test slow 8 | slow?: number; 9 | 10 | // timeout in milliseconds 11 | timeout?: number; 12 | 13 | // ui name "bdd", "tdd", "exports" etc 14 | ui?: string; 15 | 16 | //array of accepted globals 17 | globals?: any[]; 18 | 19 | // reporter instance (function or string), defaults to `mocha.reporters.Spec` 20 | reporter?: any; 21 | 22 | // bail on the first test failure 23 | bail?: boolean; 24 | 25 | // ignore global leaks 26 | ignoreLeaks?: boolean; 27 | 28 | // grep string or regexp to filter tests with 29 | grep?: any; 30 | } 31 | 32 | interface MochaDone { 33 | (error?: Error): void; 34 | } 35 | 36 | declare var mocha: Mocha; 37 | declare var describe: Mocha.IContextDefinition; 38 | declare var xdescribe: Mocha.IContextDefinition; 39 | // alias for `describe` 40 | declare var context: Mocha.IContextDefinition; 41 | // alias for `describe` 42 | declare var suite: Mocha.IContextDefinition; 43 | declare var it: Mocha.ITestDefinition; 44 | declare var xit: Mocha.ITestDefinition; 45 | // alias for `it` 46 | declare var test: Mocha.ITestDefinition; 47 | 48 | declare function before(action: () => void): void; 49 | 50 | declare function before(action: (done: MochaDone) => void): void; 51 | 52 | declare function before(description: string, action: () => void): void; 53 | 54 | declare function before(description: string, action: (done: MochaDone) => void): void; 55 | 56 | declare function setup(action: () => void): void; 57 | 58 | declare function setup(action: (done: MochaDone) => void): void; 59 | 60 | declare function after(action: () => void): void; 61 | 62 | declare function after(action: (done: MochaDone) => void): void; 63 | 64 | declare function after(description: string, action: () => void): void; 65 | 66 | declare function after(description: string, action: (done: MochaDone) => void): void; 67 | 68 | declare function teardown(action: () => void): void; 69 | 70 | declare function teardown(action: (done: MochaDone) => void): void; 71 | 72 | declare function beforeEach(action: () => void): void; 73 | 74 | declare function beforeEach(action: (done: MochaDone) => void): void; 75 | 76 | declare function beforeEach(description: string, action: () => void): void; 77 | 78 | declare function beforeEach(description: string, action: (done: MochaDone) => void): void; 79 | 80 | declare function suiteSetup(action: () => void): void; 81 | 82 | declare function suiteSetup(action: (done: MochaDone) => void): void; 83 | 84 | declare function afterEach(action: () => void): void; 85 | 86 | declare function afterEach(action: (done: MochaDone) => void): void; 87 | 88 | declare function afterEach(description: string, action: () => void): void; 89 | 90 | declare function afterEach(description: string, action: (done: MochaDone) => void): void; 91 | 92 | declare function suiteTeardown(action: () => void): void; 93 | 94 | declare function suiteTeardown(action: (done: MochaDone) => void): void; 95 | 96 | declare class Mocha { 97 | constructor(options?: { 98 | grep?: RegExp; 99 | ui?: string; 100 | reporter?: string; 101 | timeout?: number; 102 | bail?: boolean; 103 | }); 104 | 105 | /** Setup mocha with the given options. */ 106 | setup(options: MochaSetupOptions): Mocha; 107 | bail(value?: boolean): Mocha; 108 | addFile(file: string): Mocha; 109 | /** Sets reporter by name, defaults to "spec". */ 110 | reporter(name: string): Mocha; 111 | /** Sets reporter constructor, defaults to mocha.reporters.Spec. */ 112 | reporter(reporter: (runner: Mocha.IRunner, options: any) => any): Mocha; 113 | ui(value: string): Mocha; 114 | grep(value: string): Mocha; 115 | grep(value: RegExp): Mocha; 116 | invert(): Mocha; 117 | ignoreLeaks(value: boolean): Mocha; 118 | checkLeaks(): Mocha; 119 | /** 120 | * Function to allow assertion libraries to throw errors directly into mocha. 121 | * This is useful when running tests in a browser because window.onerror will 122 | * only receive the 'message' attribute of the Error. 123 | */ 124 | throwError(error: Error): void; 125 | /** Enables growl support. */ 126 | growl(): Mocha; 127 | globals(value: string): Mocha; 128 | globals(values: string[]): Mocha; 129 | useColors(value: boolean): Mocha; 130 | useInlineDiffs(value: boolean): Mocha; 131 | timeout(value: number): Mocha; 132 | slow(value: number): Mocha; 133 | enableTimeouts(value: boolean): Mocha; 134 | asyncOnly(value: boolean): Mocha; 135 | noHighlighting(value: boolean): Mocha; 136 | /** Runs tests and invokes `onComplete()` when finished. */ 137 | run(onComplete?: (failures: number) => void): Mocha.IRunner; 138 | } 139 | 140 | // merge the Mocha class declaration with a module 141 | declare module Mocha { 142 | /** Partial interface for Mocha's `Runnable` class. */ 143 | interface IRunnable { 144 | title: string; 145 | fn: Function; 146 | async: boolean; 147 | sync: boolean; 148 | timedOut: boolean; 149 | } 150 | 151 | /** Partial interface for Mocha's `Suite` class. */ 152 | interface ISuite { 153 | parent: ISuite; 154 | title: string; 155 | 156 | fullTitle(): string; 157 | } 158 | 159 | /** Partial interface for Mocha's `Test` class. */ 160 | interface ITest extends IRunnable { 161 | parent: ISuite; 162 | pending: boolean; 163 | 164 | fullTitle(): string; 165 | } 166 | 167 | /** Partial interface for Mocha's `Runner` class. */ 168 | interface IRunner {} 169 | 170 | interface IContextDefinition { 171 | (description: string, spec: () => void): ISuite; 172 | only(description: string, spec: () => void): ISuite; 173 | skip(description: string, spec: () => void): void; 174 | timeout(ms: number): void; 175 | } 176 | 177 | interface ITestDefinition { 178 | (expectation: string, assertion?: () => void): ITest; 179 | (expectation: string, assertion?: (done: MochaDone) => void): ITest; 180 | only(expectation: string, assertion?: () => void): ITest; 181 | only(expectation: string, assertion?: (done: MochaDone) => void): ITest; 182 | skip(expectation: string, assertion?: () => void): void; 183 | skip(expectation: string, assertion?: (done: MochaDone) => void): void; 184 | timeout(ms: number): void; 185 | } 186 | 187 | export module reporters { 188 | export class Base { 189 | stats: { 190 | suites: number; 191 | tests: number; 192 | passes: number; 193 | pending: number; 194 | failures: number; 195 | }; 196 | 197 | constructor(runner: IRunner); 198 | } 199 | 200 | export class Doc extends Base {} 201 | export class Dot extends Base {} 202 | export class HTML extends Base {} 203 | export class HTMLCov extends Base {} 204 | export class JSON extends Base {} 205 | export class JSONCov extends Base {} 206 | export class JSONStream extends Base {} 207 | export class Landing extends Base {} 208 | export class List extends Base {} 209 | export class Markdown extends Base {} 210 | export class Min extends Base {} 211 | export class Nyan extends Base {} 212 | export class Progress extends Base { 213 | /** 214 | * @param options.open String used to indicate the start of the progress bar. 215 | * @param options.complete String used to indicate a complete test on the progress bar. 216 | * @param options.incomplete String used to indicate an incomplete test on the progress bar. 217 | * @param options.close String used to indicate the end of the progress bar. 218 | */ 219 | constructor(runner: IRunner, options?: { 220 | open?: string; 221 | complete?: string; 222 | incomplete?: string; 223 | close?: string; 224 | }); 225 | } 226 | export class Spec extends Base {} 227 | export class TAP extends Base {} 228 | export class XUnit extends Base { 229 | constructor(runner: IRunner, options?: any); 230 | } 231 | } 232 | } 233 | 234 | declare module "mocha" { 235 | export = Mocha; 236 | } 237 | -------------------------------------------------------------------------------- /lib/topojson.js: -------------------------------------------------------------------------------- 1 | topojson = (function() { 2 | 3 | function merge(topology, arcs) { 4 | var arcsByEnd = {}, 5 | fragmentByStart = {}, 6 | fragmentByEnd = {}; 7 | 8 | arcs.forEach(function(i) { 9 | var e = ends(i); 10 | (arcsByEnd[e[0]] || (arcsByEnd[e[0]] = [])).push(i); 11 | (arcsByEnd[e[1]] || (arcsByEnd[e[1]] = [])).push(~i); 12 | }); 13 | 14 | arcs.forEach(function(i) { 15 | var e = ends(i), 16 | start = e[0], 17 | end = e[1], 18 | f, g; 19 | 20 | if (f = fragmentByEnd[start]) { 21 | delete fragmentByEnd[f.end]; 22 | f.push(i); 23 | f.end = end; 24 | if (g = fragmentByStart[end]) { 25 | delete fragmentByStart[g.start]; 26 | var fg = g === f ? f : f.concat(g); 27 | fragmentByStart[fg.start = f.start] = fragmentByEnd[fg.end = g.end] = fg; 28 | } else if (g = fragmentByEnd[end]) { 29 | delete fragmentByStart[g.start]; 30 | delete fragmentByEnd[g.end]; 31 | var fg = f.concat(g.map(function(i) { return ~i; }).reverse()); 32 | fragmentByStart[fg.start = f.start] = fragmentByEnd[fg.end = g.start] = fg; 33 | } else { 34 | fragmentByStart[f.start] = fragmentByEnd[f.end] = f; 35 | } 36 | } else if (f = fragmentByStart[end]) { 37 | delete fragmentByStart[f.start]; 38 | f.unshift(i); 39 | f.start = start; 40 | if (g = fragmentByEnd[start]) { 41 | delete fragmentByEnd[g.end]; 42 | var gf = g === f ? f : g.concat(f); 43 | fragmentByStart[gf.start = g.start] = fragmentByEnd[gf.end = f.end] = gf; 44 | } else if (g = fragmentByStart[start]) { 45 | delete fragmentByStart[g.start]; 46 | delete fragmentByEnd[g.end]; 47 | var gf = g.map(function(i) { return ~i; }).reverse().concat(f); 48 | fragmentByStart[gf.start = g.end] = fragmentByEnd[gf.end = f.end] = gf; 49 | } else { 50 | fragmentByStart[f.start] = fragmentByEnd[f.end] = f; 51 | } 52 | } else if (f = fragmentByStart[start]) { 53 | delete fragmentByStart[f.start]; 54 | f.unshift(~i); 55 | f.start = end; 56 | if (g = fragmentByEnd[end]) { 57 | delete fragmentByEnd[g.end]; 58 | var gf = g === f ? f : g.concat(f); 59 | fragmentByStart[gf.start = g.start] = fragmentByEnd[gf.end = f.end] = gf; 60 | } else if (g = fragmentByStart[end]) { 61 | delete fragmentByStart[g.start]; 62 | delete fragmentByEnd[g.end]; 63 | var gf = g.map(function(i) { return ~i; }).reverse().concat(f); 64 | fragmentByStart[gf.start = g.end] = fragmentByEnd[gf.end = f.end] = gf; 65 | } else { 66 | fragmentByStart[f.start] = fragmentByEnd[f.end] = f; 67 | } 68 | } else if (f = fragmentByEnd[end]) { 69 | delete fragmentByEnd[f.end]; 70 | f.push(~i); 71 | f.end = start; 72 | if (g = fragmentByEnd[start]) { 73 | delete fragmentByStart[g.start]; 74 | var fg = g === f ? f : f.concat(g); 75 | fragmentByStart[fg.start = f.start] = fragmentByEnd[fg.end = g.end] = fg; 76 | } else if (g = fragmentByStart[start]) { 77 | delete fragmentByStart[g.start]; 78 | delete fragmentByEnd[g.end]; 79 | var fg = f.concat(g.map(function(i) { return ~i; }).reverse()); 80 | fragmentByStart[fg.start = f.start] = fragmentByEnd[fg.end = g.start] = fg; 81 | } else { 82 | fragmentByStart[f.start] = fragmentByEnd[f.end] = f; 83 | } 84 | } else { 85 | f = [i]; 86 | fragmentByStart[f.start = start] = fragmentByEnd[f.end = end] = f; 87 | } 88 | }); 89 | 90 | function ends(i) { 91 | var arc = topology.arcs[i], p0 = arc[0], p1 = [0, 0]; 92 | arc.forEach(function(dp) { p1[0] += dp[0], p1[1] += dp[1]; }); 93 | return [p0, p1]; 94 | } 95 | 96 | var fragments = []; 97 | for (var k in fragmentByEnd) fragments.push(fragmentByEnd[k]); 98 | return fragments; 99 | } 100 | 101 | function mesh(topology, o, filter) { 102 | var arcs = []; 103 | 104 | if (arguments.length > 1) { 105 | var geomsByArc = [], 106 | geom; 107 | 108 | function arc(i) { 109 | if (i < 0) i = ~i; 110 | (geomsByArc[i] || (geomsByArc[i] = [])).push(geom); 111 | } 112 | 113 | function line(arcs) { 114 | arcs.forEach(arc); 115 | } 116 | 117 | function polygon(arcs) { 118 | arcs.forEach(line); 119 | } 120 | 121 | function geometry(o) { 122 | if (o.type === "GeometryCollection") o.geometries.forEach(geometry); 123 | else if (o.type in geometryType) { 124 | geom = o; 125 | geometryType[o.type](o.arcs); 126 | } 127 | } 128 | 129 | var geometryType = { 130 | LineString: line, 131 | MultiLineString: polygon, 132 | Polygon: polygon, 133 | MultiPolygon: function(arcs) { arcs.forEach(polygon); } 134 | }; 135 | 136 | geometry(o); 137 | 138 | geomsByArc.forEach(arguments.length < 3 139 | ? function(geoms, i) { arcs.push([i]); } 140 | : function(geoms, i) { if (filter(geoms[0], geoms[geoms.length - 1])) arcs.push([i]); }); 141 | } else { 142 | for (var i = 0, n = topology.arcs.length; i < n; ++i) arcs.push([i]); 143 | } 144 | 145 | return object(topology, {type: "MultiLineString", arcs: merge(topology, arcs)}); 146 | } 147 | 148 | function featureOrCollection(topology, o) { 149 | return o.type === "GeometryCollection" ? { 150 | type: "FeatureCollection", 151 | features: o.geometries.map(function(o) { return feature(topology, o); }) 152 | } : feature(topology, o); 153 | } 154 | 155 | function feature(topology, o) { 156 | var f = { 157 | type: "Feature", 158 | id: o.id, 159 | properties: o.properties || {}, 160 | geometry: object(topology, o) 161 | }; 162 | if (o.id == null) delete f.id; 163 | return f; 164 | } 165 | 166 | function object(topology, o) { 167 | var tf = topology.transform, 168 | kx = tf.scale[0], 169 | ky = tf.scale[1], 170 | dx = tf.translate[0], 171 | dy = tf.translate[1], 172 | arcs = topology.arcs; 173 | 174 | function arc(i, points) { 175 | if (points.length) points.pop(); 176 | for (var a = arcs[i < 0 ? ~i : i], k = 0, n = a.length, x = 0, y = 0, p; k < n; ++k) points.push([ 177 | (x += (p = a[k])[0]) * kx + dx, 178 | (y += p[1]) * ky + dy 179 | ]); 180 | if (i < 0) reverse(points, n); 181 | } 182 | 183 | function point(coordinates) { 184 | return [coordinates[0] * kx + dx, coordinates[1] * ky + dy]; 185 | } 186 | 187 | function line(arcs) { 188 | var points = []; 189 | for (var i = 0, n = arcs.length; i < n; ++i) arc(arcs[i], points); 190 | if (points.length < 2) points.push(points[0]); 191 | return points; 192 | } 193 | 194 | function ring(arcs) { 195 | var points = line(arcs); 196 | while (points.length < 4) points.push(points[0]); 197 | return points; 198 | } 199 | 200 | function polygon(arcs) { 201 | return arcs.map(ring); 202 | } 203 | 204 | function geometry(o) { 205 | var t = o.type; 206 | return t === "GeometryCollection" ? {type: t, geometries: o.geometries.map(geometry)} 207 | : t in geometryType ? {type: t, coordinates: geometryType[t](o)} 208 | : null; 209 | } 210 | 211 | var geometryType = { 212 | Point: function(o) { return point(o.coordinates); }, 213 | MultiPoint: function(o) { return o.coordinates.map(point); }, 214 | LineString: function(o) { return line(o.arcs); }, 215 | MultiLineString: function(o) { return o.arcs.map(line); }, 216 | Polygon: function(o) { return polygon(o.arcs); }, 217 | MultiPolygon: function(o) { return o.arcs.map(polygon); } 218 | }; 219 | 220 | return geometry(o); 221 | } 222 | 223 | function reverse(array, n) { 224 | var t, j = array.length, i = j - n; while (i < --j) t = array[i], array[i++] = array[j], array[j] = t; 225 | } 226 | 227 | function bisect(a, x) { 228 | var lo = 0, hi = a.length; 229 | while (lo < hi) { 230 | var mid = lo + hi >>> 1; 231 | if (a[mid] < x) lo = mid + 1; 232 | else hi = mid; 233 | } 234 | return lo; 235 | } 236 | 237 | function neighbors(objects) { 238 | var objectsByArc = [], 239 | neighbors = objects.map(function() { return []; }); 240 | 241 | function line(arcs, i) { 242 | arcs.forEach(function(a) { 243 | if (a < 0) a = ~a; 244 | var o = objectsByArc[a] || (objectsByArc[a] = []); 245 | if (!o[i]) o.forEach(function(j) { 246 | var n, k; 247 | k = bisect(n = neighbors[i], j); if (n[k] !== j) n.splice(k, 0, j); 248 | k = bisect(n = neighbors[j], i); if (n[k] !== i) n.splice(k, 0, i); 249 | }), o[i] = i; 250 | }); 251 | } 252 | 253 | function polygon(arcs, i) { 254 | arcs.forEach(function(arc) { line(arc, i); }); 255 | } 256 | 257 | function geometry(o, i) { 258 | if (o.type === "GeometryCollection") o.geometries.forEach(function(o) { geometry(o, i); }); 259 | else if (o.type in geometryType) geometryType[o.type](o.arcs, i); 260 | } 261 | 262 | var geometryType = { 263 | LineString: line, 264 | MultiLineString: polygon, 265 | Polygon: polygon, 266 | MultiPolygon: function(arcs, i) { arcs.forEach(function(arc) { polygon(arc, i); }); } 267 | }; 268 | 269 | objects.forEach(geometry); 270 | return neighbors; 271 | } 272 | 273 | return { 274 | version: "1.0.0", 275 | mesh: mesh, 276 | feature: featureOrCollection, 277 | neighbors: neighbors 278 | }; 279 | })(); 280 | -------------------------------------------------------------------------------- /old-scripts/chartTemplates.js: -------------------------------------------------------------------------------- 1 | /** list of chart templates */ 2 | // Universal Module Pattern -- modified from https://github.com/umdjs/umd/blob/master/returnExports.js 3 | // Uses Node, AMD or browser globals to create a module. 4 | 5 | (function (root, deps, factory) { 6 | if (typeof define === 'function' && define.amd) { 7 | // AMD. Register as an anonymous module. 8 | define(deps, factory); 9 | } else if (typeof exports === 'object') { 10 | // Node. Does not work with strict CommonJS, but 11 | // only CommonJS-like environments that support module.exports, 12 | // like Node. 13 | 14 | module.exports = factory.apply(this, deps.map(function(dep){ return require(dep);})); 15 | } else { 16 | // Browser globals (root is window) 17 | root.returnExports = factory.apply(this, deps.map(function(dep){ return root[dep];})); 18 | } 19 | }(this, ['lodash', './dataTypes', './chartTypes', './chartTemplate'], function(_,dt,ct, ChartTemplate){ 20 | 21 | 22 | // var _ = require('lodash'); 23 | // var dt = require("./dataTypes"); 24 | // var ct = require('./chartTypes'); 25 | // var ChartTemplate = require('./chartTemplate'); 26 | 27 | var templates = [], groups = {}; 28 | 29 | var specs = [ 30 | //1D ------------------------------------- 31 | { 32 | type: ct.TABLE, //imply marks = text 33 | name:'Summary Table', 34 | id:"summary-table", 35 | note: "Table showing min,max, average, sum, median", 36 | encodings: { 37 | summary: {dataType: dt.quantitative} 38 | }, 39 | transposable: false, 40 | smallMultiple: false 41 | },{ 42 | type: ct.HISTOGRAM, //imply marks = rect 43 | name: "Histogram", 44 | id: "histogram", 45 | 46 | encodings: { 47 | hist: {dataType: dt.quantitative} 48 | } 49 | }, 50 | //2D ------------------------------------- 51 | { 52 | type: ct.BAR, //imply marks = rect 53 | name:"Bar Chart", 54 | id: "bar-chart", 55 | encodings: { 56 | x: {dataType: dt.aggregate, multiple:true}, 57 | y: dt.categorical 58 | }, 59 | transposable: true 60 | },{ 61 | type: ct.TABLE, 62 | name:"Text Table", 63 | id:"text-table", 64 | encodings: { 65 | text: {dataType: dt.aggregate} 66 | }, 67 | minField: 2, 68 | transposable: true 69 | },{ 70 | type: ct.PLOT, 71 | name: "Scatter Plot (Raw)", 72 | id: "plot-raw", 73 | encodings: { 74 | x: {dataType: dt.quantitative, multiple:true}, 75 | y: [{dataType: dt.quantitative, multiple:true}, 76 | //{dataType:dt.categorical} 77 | ], 78 | //optional 79 | size: {dataType: dt.quantitative, optional: true}, 80 | color: [null, dt.categorical, dt.quantitative], 81 | shape: {dataType: dt.categorical, optional: true} 82 | }, 83 | transposable: true 84 | // scatter plot aggregated is possible too but is useless for this case 85 | },{ 86 | type: ct.LINE, 87 | name: "Line Chart", 88 | id: "line-chart", 89 | encodings: { 90 | x: {dataType: dt.interval, multiple: true}, 91 | y: {dataType: dt.aggregate, multiple:true} 92 | }, 93 | transposable: false //line chart isn't normally transposed 94 | },{ 95 | type: ct.MAP, 96 | name: "Symbol Map", 97 | id: "symbol-map", 98 | encodings: { 99 | geo: dt.geographic, 100 | size: {dataType: dt.aggregate} 101 | }, 102 | minField: 2, 103 | transposable: false 104 | },{ 105 | type: ct.MAP, 106 | name: "Colored Map", 107 | id: "colored-map", 108 | encodings: { 109 | geo: dt.geographic, 110 | color: {dataType: dt.aggregate} 111 | }, 112 | transposable: false 113 | }, 114 | //3D ------------------------------------- 115 | { 116 | type: ct.BAR, 117 | name: "Stack Bar Chart", 118 | id: "stack-bar", 119 | encodings: { 120 | x: {dataType: dt.aggregate, multiple:true}, 121 | y: dt.categorical, 122 | color: dt.categorical 123 | }, 124 | transposable: true 125 | },{ 126 | type: ct.PLOT, 127 | name: "Scatter Plot (Agg)", 128 | id: "plot-agg", 129 | encodings: { 130 | x: {dataType: dt.aggregate, multiple:true}, 131 | y: [{dataType: dt.aggregate, multiple:true}, 132 | //{dataType:dt.categorical} 133 | ], 134 | //optional 135 | size: {dataType: dt.aggregate, optional: true}, 136 | color: [null, dt.categorical, dt.quantitative], 137 | shape: {dataType: dt.categorical, optional: true} 138 | }, 139 | minField: 3, 140 | transposable: true 141 | // scatter plot aggregated is possible too but is useless for this case 142 | },{ 143 | // TODO(kanitw): support splom! 144 | // type: ct.PLOT, 145 | // name: "SPLOM", 146 | // id: "splom", 147 | // encodings: { 148 | // "x&y": {dataType: dt.quantitative, multiple:true} 149 | // }, 150 | // minField: 3 151 | // },{ 152 | type: ct.LINE, 153 | name: "Colored Lines", 154 | id: "colored-line-C", 155 | note: "One measure per multiple, grouped by categorical variable on Color", 156 | encodings: { 157 | x: {dataType: dt.interval, multiple: true}, 158 | y: {dataType: dt.aggregate, multiple:true}, 159 | color: dt.categorical 160 | } 161 | },{ 162 | type: ct.AREA, 163 | name: "Stacked Area", 164 | id: "stacked-area-C", 165 | note: "One measure per multiple, grouped by categorical variable on Color", 166 | encodings: { 167 | x: {dataType: dt.interval, multiple: true}, 168 | y: {dataType: dt.aggregate, multiple:true}, 169 | color: dt.categorical 170 | } 171 | },{ 172 | type: ct.LINE, 173 | name: "Colored Lines", 174 | id: "colored-line-nQ", 175 | note: "Multi-measures with same unit", 176 | encodings: { 177 | x: {dataType: dt.interval, multiple: true}, 178 | y: {dataType: dt.aggregate, multiple:true, "same-unit":true} 179 | } 180 | },{ 181 | type: ct.AREA, 182 | name: "Stacked Area", 183 | id: "stacked-area-nQ", 184 | note: "Multi-measures with same unit", 185 | encodings: { 186 | x: {dataType: dt.interval, multiple: true}, 187 | y: {dataType: dt.aggregate, multiple:true, "same-unit":true} 188 | } 189 | },{ 190 | type: ct.MAP, 191 | name: "Symbol Pie Map", 192 | id: "pie-map", 193 | encodings: { 194 | geo: dt.geographic, 195 | size: {dataType: dt.aggregate}, 196 | color: {dataType: dt.aggregate} 197 | } 198 | } 199 | ]; 200 | 201 | var addTemplate = (function(){ 202 | var key = 0, list=1; 203 | /** 204 | * recursive function for exhaustive search 205 | * @param {Object} spec ChartTemplate spec (see ChartTemplates above) 206 | * @param {Array} encodingPairs list of encoding pairs (data attribute-visual variables) 207 | * @param {Int} i i th pair 208 | * @param {[type]} output output list of templates 209 | */ 210 | function addTemplateVariation(spec, encodingPairs, i, output){ // 211 | var j, pair; 212 | 213 | // console.log(encodingPairs); 214 | 215 | if(i===encodingPairs.length){ // finish assigning all encoding 216 | var id = spec.id; 217 | 218 | if(i>0){ //append variation to id 219 | id += "--" + encodingPairs.map(function(p){ 220 | var k = p[key], e = spec.encodings[k]; 221 | return k+":"+ (e ? dt[e.dataType || e].short : "_"); 222 | }).join("-"); 223 | } 224 | // console.log(id,spec.id); 225 | output.push(new ChartTemplate(spec,id)); 226 | return; 227 | } 228 | 229 | pair = encodingPairs[i]; 230 | 231 | for(j=0 ; j defaultValue 29 | note: null, 30 | transposable: false, 31 | smallMultiple: true, 32 | minField: 1 33 | }; 34 | 35 | 36 | 37 | var ChartTemplate = function(spec, id, isAggregated){ 38 | var self =this, requirements, req; 39 | 40 | _.extend(this, _.defaults(spec, defaults)); 41 | this.encodings = _.cloneDeep(spec.encodings); 42 | 43 | this.id = id || this.id; 44 | this.isAggregated = isAggregated || _.any(spec.encodings, function(encodingInfo, encodingVar){ 45 | return encodingInfo.dataType === dt.aggregate; 46 | }); 47 | 48 | //just check if have all the required props 49 | _.each(requiredProps,function(p){ 50 | if(! (p in self)) console.warn("missing requiredProps"); 51 | }); 52 | 53 | /** requirements by DataType 54 | {dataTypeName: {required:[encoding,...], optional: [encoding,...], multiple: {row/col: true|false*} } */ 55 | requirements = this.requirements = {}; 56 | 57 | // this.encodings = {encoding: spec}, spec = {dataType:..., multiple:(false), "same-unit": false} 58 | _.each(this.encodings, function(spec, encoding, encodings){ 59 | var dataType, req; 60 | 61 | // expand shorthand format 62 | // e.g., y: vt.categorical => y: {dataType: vt.categorical} 63 | if(! ("dataType" in spec )){ 64 | encodings[encoding] = spec = {dataType: spec}; 65 | } 66 | dataType = spec.dataType; 67 | 68 | req = requirements[dataType.name] = requirements[dataType.name] || {}; 69 | 70 | var reqType = spec.optional ? "optional" : "required"; 71 | (req[reqType] = req[reqType]|| []).push(encoding); 72 | 73 | if(spec.multiple){ 74 | (req.multiple = req.multiple || {})[encoding] = true; 75 | } 76 | }); 77 | 78 | //small multiples allow extra categorical fields 79 | if(this.smallMultiple){ 80 | req = requirements.categorical = requirements.categorical || {}; 81 | req.multiple = {col:true, row:true}; 82 | } 83 | 84 | /** list of dataType.name ordered by the most specific type first*/ 85 | this.requiredDataTypes = _(requirements) 86 | .keys() 87 | .filter(function(dataType){ return requirements[dataType].required; }) 88 | .sortBy(function(typeName){ 89 | return -dt[typeName].specificity; 90 | }) 91 | .value(); 92 | //console.log(id, this.dataTypes); 93 | }; 94 | 95 | var prototype = ChartTemplate.prototype; 96 | 97 | /* 98 | Generate all possible assignments of fields to each encoding. 99 | This doesn't generate permutations within encoding. 100 | It also excludes transposes of the same chart. 101 | */ 102 | prototype.generateCharts = function(fields, dataTypeKey, checkSatisfy){ 103 | if(checkSatisfy && !this.satisfyFields(fields)) return []; 104 | dataTypeKey = dataTypeKey || "dataType"; 105 | 106 | var self=this, 107 | reqs = this.requirements, 108 | requiredDataTypes=this.requiredDataTypes, 109 | len = requiredDataTypes.length, 110 | fieldsByType = _.groupBy(fields, dataTypeKey), 111 | hasNonAggregated = _.any(fieldsByType, function(list, type){ 112 | return !dt[type].isType(dt.aggregate); 113 | }), 114 | charts = [], 115 | chartFields = {}, 116 | chart; 117 | 118 | function push(encoding, fieldKey){ 119 | this[encoding] = this[encoding] || []; 120 | this[encoding].push(fieldKey); 121 | } 122 | function pop(encoding){ 123 | this[encoding].pop(); 124 | if(this[encoding].length === 0) 125 | delete this[encoding]; 126 | } 127 | 128 | 129 | function populateOptionalDataTypes(unusedFields, i, optionalUsed){ 130 | unusedFields = unusedFields || _.filter(fields, function(f){return !f.used;}); 131 | optionalUsed = optionalUsed || {}; 132 | i = i || 0; 133 | 134 | function populateOptionalEncodings(field, encodings, used){ 135 | field.used = true; 136 | encodings.forEach(function(encoding){ 137 | if(used && used[encoding]) return; 138 | 139 | if(used) used[encoding] = true; 140 | // console.log("FIELD:", field.key, "=>", encoding); 141 | push.call(chartFields, encoding, field); 142 | populateOptionalDataTypes(unusedFields,i+1, optionalUsed); 143 | pop.call(chartFields, encoding); 144 | if(used) delete used[encoding]; 145 | }); 146 | field.used = false; 147 | } 148 | 149 | if(i===unusedFields.length){ 150 | if(self.isAggregated && !hasNonAggregated) return; 151 | 152 | chart = new Chart(self, chartFields); 153 | if(!_.any(charts, chart.isTranspose, chart)){ 154 | 155 | charts.push(chart); 156 | } //TODO replace the first one with one with the best score? 157 | return; 158 | } 159 | 160 | var field = unusedFields[i], 161 | type=dt[field[dataTypeKey]], 162 | req; 163 | 164 | while(type!==null){ 165 | req = reqs[type.name]; 166 | 167 | if(req && req.optional){ 168 | var used = optionalUsed[type.name] = optionalUsed[type.name] || {}; 169 | populateOptionalEncodings(field, req.optional, used); 170 | } 171 | if(req && req.multiple){ 172 | populateOptionalEncodings(field, _.keys(req.multiple).reverse(), null); 173 | } 174 | type = type.parent; 175 | } 176 | } 177 | 178 | function populateRequiredDataTypes(i, j){ 179 | var dataType, supportedTypes, encoding, requiredEncodings; 180 | 181 | if(i===len){ 182 | populateOptionalDataTypes(); 183 | return; 184 | } 185 | 186 | requiredEncodings = reqs[requiredDataTypes[i]].required; 187 | 188 | if(!requiredEncodings || j=== requiredEncodings.length){ 189 | populateRequiredDataTypes(i+1,0); 190 | return; 191 | } 192 | 193 | dataType = dt[requiredDataTypes[i]]; 194 | supportedTypes = dataType.allSubtypes(); 195 | encoding = requiredEncodings[j]; 196 | 197 | dataType.allSubtypes().forEach(function(sType){ 198 | (fieldsByType[sType] || []).forEach(function(field){ 199 | if(field.used) return; 200 | //console.log("FIELD:", field); 201 | field.used = true; 202 | push.call(chartFields, encoding, field); 203 | populateRequiredDataTypes(i,j+1); 204 | pop.call(chartFields, encoding); 205 | field.used = false; 206 | }); 207 | }); 208 | } 209 | 210 | populateRequiredDataTypes(0,0); 211 | 212 | return charts; 213 | // .map(function(chart){ 214 | // chart.fields = _.reduce(chart.fields, function(fields, fieldKey, encoding){ 215 | // fields.push({ 216 | // encoding: encoding, 217 | // key: fieldKey 218 | // }); 219 | // return fields; 220 | // }, []); 221 | // return chart; 222 | // }); 223 | }; 224 | 225 | /** 226 | * @param fieldTypeCount {typeName: count} 227 | * @returns {boolean} 228 | */ 229 | //FIXME missing info about units 230 | prototype.satisfyFieldTypeCount = function(fieldTypeCount){ 231 | var count = _.reduce(fieldTypeCount, function(sum,x){ return sum+x;},0), 232 | self=this, 233 | requirements = self.requirements, 234 | satisfyRequirement, 235 | assignment = {}; 236 | 237 | fieldTypeCount = _.clone(fieldTypeCount); // clone for safety 238 | 239 | // Have enough field 240 | if(count < this.minField) return false; 241 | 242 | // Check if the requirements for each data type are satisfied (from the most specific one first) 243 | // (dt.TYPES is ordered by specificity already.) 244 | satisfyRequirement = _.all(requirements,function(req, typeName){ 245 | 246 | var i,s, 247 | type = dt[typeName], 248 | subtypes = type.allSubtypes(), 249 | count = (req.required || []).length; 250 | 251 | for(i=0, s=subtypes[0]; i= count){ 253 | fieldTypeCount[s.name] -= count; 254 | count = 0; 255 | if(fieldTypeCount[s.name] === 0) delete fieldTypeCount[s.name]; 256 | break; 257 | }else if(fieldTypeCount[s.name] > 0){ 258 | count -= fieldTypeCount[s.name]; 259 | delete fieldTypeCount[s.name]; 260 | } 261 | } 262 | 263 | return count === 0; 264 | }); 265 | 266 | if(!satisfyRequirement) return false; 267 | 268 | // check all the unused dataTypes (used ones would be deleted by now) 269 | return _.all(fieldTypeCount, function(typeCount, typeName){ 270 | var type = dt[typeName], req; 271 | while(type!==null){ 272 | req = requirements[type.name]; 273 | if(req && req.optional){ 274 | if(typeCount <= req.optional.length) return true; //enough! 275 | typeCount -= req.optional.length; 276 | } 277 | if(req && req.multiple) return true; 278 | type = type.parent; 279 | } 280 | return false; 281 | }); 282 | }; 283 | return ChartTemplate; 284 | })); 285 | -------------------------------------------------------------------------------- /lib/require.js: -------------------------------------------------------------------------------- 1 | /* 2 | RequireJS 1.0.4 Copyright (c) 2010-2011, The Dojo Foundation All Rights Reserved. 3 | Available via the MIT or new BSD license. 4 | see: http://github.com/jrburke/requirejs for details 5 | */ 6 | var requirejs,require,define; 7 | (function(){function J(b){return M.call(b)==="[object Function]"}function E(b){return M.call(b)==="[object Array]"}function Z(b,c,i){for(var j in c)if(!(j in K)&&(!(j in b)||i))b[j]=c[j];return d}function N(b,c,d){b=Error(c+"\nhttp://requirejs.org/docs/errors.html#"+b);if(d)b.originalError=d;return b}function $(b,c,d){var j,k,q;for(j=0;q=c[j];j++){q=typeof q==="string"?{name:q}:q;k=q.location;if(d&&(!k||k.indexOf("/")!==0&&k.indexOf(":")===-1))k=d+"/"+(k||q.name);b[q.name]={name:q.name,location:k|| 8 | q.name,main:(q.main||"main").replace(ea,"").replace(aa,"")}}}function U(b,c){b.holdReady?b.holdReady(c):c?b.readyWait+=1:b.ready(!0)}function fa(b){function c(a,h){var e,s;if(a&&a.charAt(0)===".")if(h){o.pkgs[h]?h=[h]:(h=h.split("/"),h=h.slice(0,h.length-1));e=a=h.concat(a.split("/"));var b;for(s=0;b=e[s];s++)if(b===".")e.splice(s,1),s-=1;else if(b==="..")if(s===1&&(e[2]===".."||e[0]===".."))break;else s>0&&(e.splice(s-1,2),s-=2);s=o.pkgs[e=a[0]];a=a.join("/");s&&a===e+"/"+s.main&&(a=e)}else a.indexOf("./")=== 9 | 0&&(a=a.substring(2));return a}function i(a,h){var e=a?a.indexOf("!"):-1,b=null,d=h?h.name:null,p=a,g,i;e!==-1&&(b=a.substring(0,e),a=a.substring(e+1,a.length));b&&(b=c(b,d));a&&(b?g=(e=l[b])&&e.normalize?e.normalize(a,function(a){return c(a,d)}):c(a,d):(g=c(a,d),i=E[g],i||(i=f.nameToUrl(a,null,h),E[g]=i)));return{prefix:b,name:g,parentMap:h,url:i,originalName:p,fullName:b?b+"!"+(g||""):g}}function j(){var a=!0,h=o.priorityWait,e,b;if(h){for(b=0;e=h[b];b++)if(!t[e]){a=!1;break}a&&delete o.priorityWait}return a} 10 | function k(a,h,e){return function(){var b=ga.call(arguments,0),c;if(e&&J(c=b[b.length-1]))c.__requireJsBuild=!0;b.push(h);return a.apply(null,b)}}function q(a,h){var e=k(f.require,a,h);Z(e,{nameToUrl:k(f.nameToUrl,a),toUrl:k(f.toUrl,a),defined:k(f.requireDefined,a),specified:k(f.requireSpecified,a),isBrowser:d.isBrowser});return e}function n(a){var h,e,b,c=a.callback,p=a.map,g=p.fullName,ba=a.deps;b=a.listeners;if(c&&J(c)){if(o.catchError.define)try{e=d.execCb(g,a.callback,ba,l[g])}catch(j){h=j}else e= 11 | d.execCb(g,a.callback,ba,l[g]);if(g)(c=a.cjsModule)&&c.exports!==void 0&&c.exports!==l[g]?e=l[g]=a.cjsModule.exports:e===void 0&&a.usingExports?e=l[g]:(l[g]=e,F[g]&&(R[g]=!0))}else g&&(e=l[g]=c,F[g]&&(R[g]=!0));if(C[a.id])delete C[a.id],a.isDone=!0,f.waitCount-=1,f.waitCount===0&&(I=[]);delete S[g];if(d.onResourceLoad&&!a.placeholder)d.onResourceLoad(f,p,a.depArray);if(h)return e=(g?i(g).url:"")||h.fileName||h.sourceURL,b=h.moduleTree,h=N("defineerror",'Error evaluating module "'+g+'" at location "'+ 12 | e+'":\n'+h+"\nfileName:"+e+"\nlineNumber: "+(h.lineNumber||h.line),h),h.moduleName=g,h.moduleTree=b,d.onError(h);for(h=0;c=b[h];h++)c(e)}function r(a,h){return function(e){a.depDone[h]||(a.depDone[h]=!0,a.deps[h]=e,a.depCount-=1,a.depCount||n(a))}}function v(a,h){var e=h.map,b=e.fullName,c=e.name,p=L[a]||(L[a]=l[a]),g;if(!h.loading)h.loading=!0,g=function(a){h.callback=function(){return a};n(h);t[h.id]=!0;x()},g.fromText=function(a,h){var e=O;t[a]=!1;f.scriptCount+=1;f.fake[a]=!0;e&&(O=!1);d.exec(h); 13 | e&&(O=!0);f.completeLoad(a)},b in l?g(l[b]):p.load(c,q(e.parentMap,!0),g,o)}function w(a){C[a.id]||(C[a.id]=a,I.push(a),f.waitCount+=1)}function D(a){this.listeners.push(a)}function u(a,h){var e=a.fullName,b=a.prefix,c=b?L[b]||(L[b]=l[b]):null,d,g;e&&(d=S[e]);if(!d&&(g=!0,d={id:(b&&!c?M++ +"__p@:":"")+(e||"__r@"+M++),map:a,depCount:0,depDone:[],depCallbacks:[],deps:[],listeners:[],add:D},y[d.id]=!0,e&&(!b||L[b])))S[e]=d;b&&!c?(e=i(b),b in l&&!l[b]&&(delete l[b],delete P[e.url]),b=u(e,!0),b.add(function(){var b= 14 | i(a.originalName,a.parentMap),b=u(b,!0);d.placeholder=!0;b.add(function(a){d.callback=function(){return a};n(d)})})):g&&h&&(t[d.id]=!1,f.paused.push(d),w(d));return d}function B(a,b,e,c){var a=i(a,c),d=a.name,p=a.fullName,g=u(a),j=g.id,k=g.deps,m;if(p){if(p in l||t[j]===!0||p==="jquery"&&o.jQuery&&o.jQuery!==e().fn.jquery)return;y[j]=!0;t[j]=!0;p==="jquery"&&e&&V(e())}g.depArray=b;g.callback=e;for(e=0;e0)){if(o.priorityWait)if(j())x();else return;for(i in t)if(!(i in K)&&(e=!0,!t[i]))if(b)a+=i+" ";else{c=!0;break}if(e||f.waitCount){if(b&&a)return i=N("timeout","Load timeout for modules: "+a),i.requireType="timeout",i.requireModules=a,d.onError(i);if(c||f.scriptCount){if((G||ca)&&!W)W=setTimeout(function(){W= 17 | 0;A()},50)}else{if(f.waitCount){for(H=0;a=I[H];H++)z(a,{});f.paused.length&&x();X<5&&(X+=1,A())}X=0;d.checkReadyState()}}}}var f,x,o={waitSeconds:7,baseUrl:"./",paths:{},pkgs:{},catchError:{}},Q=[],y={require:!0,exports:!0,module:!0},E={},l={},t={},C={},I=[],P={},M=0,S={},L={},F={},R={},Y=0;V=function(a){if(!f.jQuery&&(a=a||(typeof jQuery!=="undefined"?jQuery:null))&&!(o.jQuery&&a.fn.jquery!==o.jQuery)&&("holdReady"in a||"readyWait"in a))if(f.jQuery=a,m(["jquery",[],function(){return jQuery}]),f.scriptCount)U(a, 18 | !0),f.jQueryIncremented=!0};x=function(){var a,b,e,c,i,k;Y+=1;if(f.scriptCount<=0)f.scriptCount=0;for(;Q.length;)if(a=Q.shift(),a[0]===null)return d.onError(N("mismatch","Mismatched anonymous define() module: "+a[a.length-1]));else m(a);if(!o.priorityWait||j())for(;f.paused.length;){i=f.paused;f.pausedCount+=i.length;f.paused=[];for(c=0;a=i[c];c++)b=a.map,e=b.url,k=b.fullName,b.prefix?v(b.prefix,a):!P[e]&&!t[k]&&(d.load(f,k,e),e.indexOf("empty:")!==0&&(P[e]=!0));f.startTime=(new Date).getTime();f.pausedCount-= 19 | i.length}Y===1&&A();Y-=1};f={contextName:b,config:o,defQueue:Q,waiting:C,waitCount:0,specified:y,loaded:t,urlMap:E,urlFetched:P,scriptCount:0,defined:l,paused:[],pausedCount:0,plugins:L,needFullExec:F,fake:{},fullExec:R,managerCallbacks:S,makeModuleMap:i,normalize:c,configure:function(a){var b,e,c;a.baseUrl&&a.baseUrl.charAt(a.baseUrl.length-1)!=="/"&&(a.baseUrl+="/");b=o.paths;c=o.pkgs;Z(o,a,!0);if(a.paths){for(e in a.paths)e in K||(b[e]=a.paths[e]);o.paths=b}if((b=a.packagePaths)||a.packages){if(b)for(e in b)e in 20 | K||$(c,b[e],e);a.packages&&$(c,a.packages);o.pkgs=c}if(a.priority)e=f.requireWait,f.requireWait=!1,f.takeGlobalQueue(),x(),f.require(a.priority),x(),f.requireWait=e,o.priorityWait=a.priority;if(a.deps||a.callback)f.require(a.deps||[],a.callback)},requireDefined:function(a,b){return i(a,b).fullName in l},requireSpecified:function(a,b){return i(a,b).fullName in y},require:function(a,c,e){if(typeof a==="string"){if(J(c))return d.onError(N("requireargs","Invalid require call"));if(d.get)return d.get(f, 21 | a,c);c=i(a,c);a=c.fullName;return!(a in l)?d.onError(N("notloaded","Module name '"+c.fullName+"' has not been loaded yet for context: "+b)):l[a]}(a&&a.length||c)&&B(null,a,c,e);if(!f.requireWait)for(;!f.scriptCount&&f.paused.length;)f.takeGlobalQueue(),x();return f.require},takeGlobalQueue:function(){T.length&&(ha.apply(f.defQueue,[f.defQueue.length-1,0].concat(T)),T=[])},completeLoad:function(a){var b;for(f.takeGlobalQueue();Q.length;)if(b=Q.shift(),b[0]===null){b[0]=a;break}else if(b[0]===a)break; 22 | else m(b),b=null;b?m(b):m([a,[],a==="jquery"&&typeof jQuery!=="undefined"?function(){return jQuery}:null]);d.isAsync&&(f.scriptCount-=1);x();d.isAsync||(f.scriptCount-=1)},toUrl:function(a,b){var c=a.lastIndexOf("."),d=null;c!==-1&&(d=a.substring(c,a.length),a=a.substring(0,c));return f.nameToUrl(a,d,b)},nameToUrl:function(a,b,e){var i,j,k,g,l=f.config,a=c(a,e&&e.fullName);if(d.jsExtRegExp.test(a))b=a+(b?b:"");else{i=l.paths;j=l.pkgs;e=a.split("/");for(g=e.length;g>0;g--)if(k=e.slice(0,g).join("/"), 23 | i[k]){e.splice(0,g,i[k]);break}else if(k=j[k]){a=a===k.name?k.location+"/"+k.main:k.location;e.splice(0,g,a);break}b=e.join("/")+(b||".js");b=(b.charAt(0)==="/"||b.match(/^\w+:/)?"":l.baseUrl)+b}return l.urlArgs?b+((b.indexOf("?")===-1?"?":"&")+l.urlArgs):b}};f.jQueryCheck=V;f.resume=x;return f}function ia(){var b,c,d;if(m&&m.readyState==="interactive")return m;b=document.getElementsByTagName("script");for(c=b.length-1;c>-1&&(d=b[c]);c--)if(d.readyState==="interactive")return m=d;return null}var ja= 24 | /(\/\*([\s\S]*?)\*\/|([^:]|^)\/\/(.*)$)/mg,ka=/require\(\s*["']([^'"\s]+)["']\s*\)/g,ea=/^\.\//,aa=/\.js$/,M=Object.prototype.toString,r=Array.prototype,ga=r.slice,ha=r.splice,G=!!(typeof window!=="undefined"&&navigator&&document),ca=!G&&typeof importScripts!=="undefined",la=G&&navigator.platform==="PLAYSTATION 3"?/^complete$/:/^(complete|loaded)$/,da=typeof opera!=="undefined"&&opera.toString()==="[object Opera]",K={},D={},T=[],m=null,X=0,O=!1,d,r={},I,w,u,y,v,z,A,H,B,V,W;if(typeof define==="undefined"){if(typeof requirejs!== 25 | "undefined")if(J(requirejs))return;else r=requirejs,requirejs=void 0;typeof require!=="undefined"&&!J(require)&&(r=require,require=void 0);d=requirejs=function(b,c,d){var j="_",k;!E(b)&&typeof b!=="string"&&(k=b,E(c)?(b=c,c=d):b=[]);if(k&&k.context)j=k.context;d=D[j]||(D[j]=fa(j));k&&d.configure(k);return d.require(b,c)};d.config=function(b){return d(b)};require||(require=d);d.toUrl=function(b){return D._.toUrl(b)};d.version="1.0.4";d.jsExtRegExp=/^\/|:|\?|\.js$/;w=d.s={contexts:D,skipAsync:{}};if(d.isAsync= 26 | d.isBrowser=G)if(u=w.head=document.getElementsByTagName("head")[0],y=document.getElementsByTagName("base")[0])u=w.head=y.parentNode;d.onError=function(b){throw b;};d.load=function(b,c,i){d.resourcesReady(!1);b.scriptCount+=1;d.attach(i,b,c);if(b.jQuery&&!b.jQueryIncremented)U(b.jQuery,!0),b.jQueryIncremented=!0};define=function(b,c,d){var j,k;typeof b!=="string"&&(d=c,c=b,b=null);E(c)||(d=c,c=[]);!c.length&&J(d)&&d.length&&(d.toString().replace(ja,"").replace(ka,function(b,d){c.push(d)}),c=(d.length=== 27 | 1?["require"]:["require","exports","module"]).concat(c));if(O&&(j=I||ia()))b||(b=j.getAttribute("data-requiremodule")),k=D[j.getAttribute("data-requirecontext")];(k?k.defQueue:T).push([b,c,d])};define.amd={multiversion:!0,plugins:!0,jQuery:!0};d.exec=function(b){return eval(b)};d.execCb=function(b,c,d,j){return c.apply(j,d)};d.addScriptToDom=function(b){I=b;y?u.insertBefore(b,y):u.appendChild(b);I=null};d.onScriptLoad=function(b){var c=b.currentTarget||b.srcElement,i;if(b.type==="load"||c&&la.test(c.readyState))m= 28 | null,b=c.getAttribute("data-requirecontext"),i=c.getAttribute("data-requiremodule"),D[b].completeLoad(i),c.detachEvent&&!da?c.detachEvent("onreadystatechange",d.onScriptLoad):c.removeEventListener("load",d.onScriptLoad,!1)};d.attach=function(b,c,i,j,k,m){var n;if(G)return j=j||d.onScriptLoad,n=c&&c.config&&c.config.xhtml?document.createElementNS("http://www.w3.org/1999/xhtml","html:script"):document.createElement("script"),n.type=k||c&&c.config.scriptType||"text/javascript",n.charset="utf-8",n.async= 29 | !w.skipAsync[b],c&&n.setAttribute("data-requirecontext",c.contextName),n.setAttribute("data-requiremodule",i),n.attachEvent&&!da?(O=!0,m?n.onreadystatechange=function(){if(n.readyState==="loaded")n.onreadystatechange=null,n.attachEvent("onreadystatechange",j),m(n)}:n.attachEvent("onreadystatechange",j)):n.addEventListener("load",j,!1),n.src=b,m||d.addScriptToDom(n),n;else ca&&(importScripts(b),c.completeLoad(i));return null};if(G){v=document.getElementsByTagName("script");for(H=v.length-1;H>-1&&(z= 30 | v[H]);H--){if(!u)u=z.parentNode;if(A=z.getAttribute("data-main")){if(!r.baseUrl)v=A.split("/"),z=v.pop(),v=v.length?v.join("/")+"/":"./",r.baseUrl=v,A=z.replace(aa,"");r.deps=r.deps?r.deps.concat(A):[A];break}}}d.checkReadyState=function(){var b=w.contexts,c;for(c in b)if(!(c in K)&&b[c].waitCount)return;d.resourcesReady(!0)};d.resourcesReady=function(b){var c,i;d.resourcesDone=b;if(d.resourcesDone)for(i in b=w.contexts,b)if(!(i in K)&&(c=b[i],c.jQueryIncremented))U(c.jQuery,!1),c.jQueryIncremented= 31 | !1};d.pageLoaded=function(){if(document.readyState!=="complete")document.readyState="complete"};if(G&&document.addEventListener&&!document.readyState)document.readyState="loading",window.addEventListener("load",d.pageLoaded,!1);d(r);if(d.isAsync&&typeof setTimeout!=="undefined")B=w.contexts[r.context||"_"],B.requireWait=!0,setTimeout(function(){B.requireWait=!1;B.takeGlobalQueue();B.scriptCount||B.resume();d.checkReadyState()},0)}})(); 32 | -------------------------------------------------------------------------------- /test/fixture.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable:variable-name */ 2 | 3 | // commonly used data set 4 | export let fixture: any = {}; 5 | 6 | var stats:any = fixture.stats = {}; 7 | 8 | var count = {field:'*', type:'quantitative', aggregate:'count'}, 9 | count_stat = {min:0, max:100}; 10 | 11 | var O_15 = {field: 'O_15', type: 'ordinal'}, 12 | O_15_stat = {distinct: 15}, 13 | O_30 = {field:'O_30', type:'ordinal'}, 14 | O_30_stat = {distinct: 30}; 15 | 16 | var q10 = {field: 'Q_10', type: 'quantitative'}, 17 | q10_stat = {distinct: 10, min:0, max:150}; 18 | 19 | var o_stat = {distinct: 5}, 20 | q_stat = {distinct: 100, min:0, max:150}, 21 | t_stat = o_stat; 22 | 23 | export var stat = { 24 | o_stat: o_stat, 25 | q_stat: q_stat, 26 | t_stat: t_stat, 27 | count_stat: count_stat 28 | }; 29 | 30 | stats['#'] = { 31 | '*': count_stat 32 | }; 33 | 34 | stats['OxQ'] = { 35 | 1: o_stat, 36 | 2: q_stat, 37 | '*': count_stat 38 | }; 39 | 40 | stats.OxO = { 41 | 1: o_stat, 42 | 2: o_stat, 43 | '*': count_stat 44 | }; 45 | 46 | stats['Ox#'] = { 47 | 1: o_stat, 48 | '*': count_stat 49 | }; 50 | 51 | stats['O_30x#'] = { 52 | '*': count_stat, 53 | O_30: O_30_stat 54 | }; 55 | 56 | stats.OxOxQ = { 57 | 1: o_stat, 58 | 2: o_stat, 59 | 3: q_stat, 60 | '*': count_stat 61 | }; 62 | 63 | 64 | stats.OxQxQ = { 65 | 1: o_stat, 66 | 2: q_stat, 67 | 3: q_stat, 68 | '*': count_stat 69 | }; 70 | 71 | stats['QxQ'] = { 72 | 1: q_stat, 73 | 2: q_stat, 74 | '*': count_stat 75 | }; 76 | 77 | stats['QxT'] = { 78 | 1: q_stat, 79 | 2: t_stat, 80 | '*': count_stat 81 | }; 82 | 83 | stats['Q'] = { 84 | 1: q_stat, 85 | '*': count_stat 86 | }; 87 | 88 | stats['#xQ'] = { 89 | '*': count_stat, 90 | 2: q_stat 91 | }; 92 | stats['#xT'] = { 93 | '*': count_stat, 94 | 2: t_stat 95 | }; 96 | 97 | // fixtures 98 | 99 | 100 | 101 | fixture['O'] = { 102 | fields: [{field:1, type:'ordinal'}], 103 | stats: {1: o_stat} 104 | }; 105 | 106 | fixture['O_15'] = { 107 | fields: [O_15], 108 | stats: {O_15: O_15_stat} 109 | }; 110 | 111 | fixture['O_30'] = { 112 | fields: [O_30], 113 | stats: {O_30: O_30_stat} 114 | }; 115 | 116 | fixture['OxQ'] = { 117 | fields: [ 118 | {field:1, type:'ordinal'}, 119 | {field:2, type:'quantitative'} 120 | ], 121 | stats: stats['OxQ'] 122 | }; 123 | 124 | fixture['OxA(Q)'] = { 125 | fields: [ 126 | {field:1, type:'ordinal'}, 127 | {field:2, type:'quantitative', aggregate: 'mean'} 128 | ], 129 | stats: stats['OxQ'] 130 | }; 131 | 132 | 133 | fixture['Ox#'] = { 134 | fields: [{field:1, type:'ordinal'}, count], 135 | stats: stats['Ox#'] 136 | }; 137 | 138 | fixture['O_30x#'] = { 139 | fields: [O_30, count], 140 | stats: stats['O_30x#'] 141 | }; 142 | 143 | fixture['OxOx#'] = { 144 | fields: [ 145 | {field:1, type:'ordinal'}, 146 | {field:2, type:'ordinal'}, 147 | count 148 | ], 149 | stats: stats.OxO 150 | }; 151 | 152 | fixture.OxOxQ = { 153 | fields: [ 154 | {field:1, type:'ordinal'}, 155 | {field:2, type:'ordinal'}, 156 | {field:3, type:'quantitative'} 157 | ], 158 | stats: stats.OxOxQ 159 | }; 160 | 161 | 162 | fixture['OxOxA(Q)'] = { 163 | fields: [ 164 | {field:1, type:'ordinal'}, 165 | {field:2, type:'ordinal'}, 166 | {field:3, type:'quantitative', aggregate:'sum'} 167 | ], 168 | stats: stats.OxOxQ 169 | }; 170 | 171 | fixture.OxQxQ = { 172 | fields: [ 173 | {field:1, type:'ordinal'}, 174 | {field:2, type:'quantitative'}, 175 | {field:3, type:'quantitative'} 176 | ], 177 | stats: stats.OxQxQ 178 | }; 179 | 180 | fixture['OxA(Q)xA(Q)'] = { 181 | fields: [ 182 | {field:1, type:'ordinal'}, 183 | {aggregate:'mean', field:2, type:'quantitative'}, 184 | {aggregate:'mean', field:3, type:'quantitative'} 185 | ], 186 | stats: stats.OxQxQ 187 | }; 188 | 189 | fixture['OxQxQxQ'] = { 190 | fields: [ 191 | {field:1, type: 'ordinal'}, 192 | {field:2, type: 'quantitative'}, 193 | {field:3, type: 'quantitative'}, 194 | {field:4, type: 'quantitative'} 195 | ], 196 | stats: { 197 | 1: o_stat, 198 | 2: q_stat, 199 | 3: q_stat, 200 | 4: q_stat 201 | } 202 | }; 203 | 204 | fixture['OxOxQxQx#'] = { 205 | fields: [ 206 | {field:'1', type:'quantitative', selected: undefined}, 207 | {field:'2', type:'quantitative', selected: undefined}, 208 | {field:'3', type:'ordinal', selected: undefined}, 209 | {field:'4', type:'ordinal', selected: undefined}, 210 | {field:'*', aggregate:'count', selected: undefined} 211 | ], 212 | stats: { 213 | 1: q_stat, 214 | 2: q_stat, 215 | 3: o_stat, 216 | 4: o_stat, 217 | 5: count_stat 218 | } 219 | }; 220 | 221 | 222 | fixture['Q'] = { 223 | fields: [{field:1, type:'quantitative'}], 224 | stats: {1: q_stat} 225 | }; 226 | 227 | fixture['Q_10'] = { 228 | fields: [q10], 229 | stats: {Q_10: q10_stat} 230 | }; 231 | 232 | fixture['BIN(Q)'] = { 233 | fields: [{field:1, type:'quantitative', bin: {maxbins: 15}}], 234 | stats: {1: q_stat} 235 | }; 236 | 237 | fixture['QxQ'] = { 238 | fields: [{field:1, type:'quantitative'}, {field:2, type:'quantitative'}], 239 | stats: stats['QxQ'] 240 | }; 241 | 242 | fixture['Qx#'] = { 243 | fields: [{field:1, type:'quantitative'}, count], 244 | stats: stats.Q 245 | }; 246 | 247 | fixture['QxT'] = { 248 | fields: [ 249 | {field:1, type:'quantitative'}, 250 | {field:2, type:'temporal'} 251 | ], 252 | stats: stats['QxT'] 253 | }; 254 | 255 | fixture['QxYEAR(T)'] = { 256 | fields: [ 257 | {field:1, type:'quantitative'}, 258 | {field:2, type:'temporal', timeUnit: 'year'} 259 | ], 260 | stats: stats['QxT'] 261 | }; 262 | 263 | fixture['A(Q)xYEAR(T)'] = { 264 | fields: [ 265 | {field:1, type:'quantitative', aggregate: 'mean'}, 266 | {field:2, type:'temporal', timeUnit: 'year'} 267 | ], 268 | stats: stats['QxT'] 269 | }; 270 | 271 | 272 | fixture['B(Q)xB(Q)x#'] = { 273 | fields: [ 274 | {field:1, type:'quantitative', bin: {maxbins: 15}}, 275 | {field:2, type:'quantitative', bin: {maxbins: 15}}, 276 | count 277 | ], 278 | stats: stats.QxQ 279 | }; 280 | 281 | 282 | fixture['#'] = { 283 | fields: [count], 284 | stats: {'*': count_stat} 285 | }; 286 | 287 | // FIXME: swap order for these 288 | 289 | fixture['#xB(Q)'] = { 290 | fields: [ 291 | count, 292 | {field:2, type:'quantitative', bin: {maxbins: 15}} 293 | ], 294 | stats: stats['#xQ'] 295 | }; 296 | 297 | fixture['#xYR(T)'] = { 298 | fields: [ 299 | {field:'*', type:'quantitative', aggregate:'count'}, 300 | {field:2, type:'temporal', timeUnit:'year'} 301 | ], 302 | stats: stats['#xT'] 303 | }; 304 | 305 | fixture['#xT'] = { 306 | fields: [ 307 | {field:'*', type:'quantitative', aggregate:'count'}, 308 | {field:2, type:'temporal'} 309 | ], 310 | stats: stats['#xT'] 311 | }; 312 | 313 | fixture.birdstrikes = {}; 314 | fixture.birdstrikes.fields = [{ 315 | 'field': 'Aircraft__Airline_Operator', 316 | 'type': 'ordinal', 317 | '$$hashKey': 'object:12', 318 | '_any': true 319 | }, { 320 | 'field': 'Aircraft__Make_Model', 321 | 'type': 'ordinal', 322 | '$$hashKey': 'object:13', 323 | '_any': true 324 | }, { 325 | 'field': 'Airport__Name', 326 | 'type': 'ordinal', 327 | '$$hashKey': 'object:14', 328 | '_any': true 329 | }, { 330 | 'field': 'Effect__Amount_of_damage', 331 | 'type': 'ordinal', 332 | '$$hashKey': 'object:15', 333 | '_any': true 334 | }, { 335 | 'field': 'Origin_State', 336 | 'type': 'ordinal', 337 | '$$hashKey': 'object:16', 338 | '_any': true 339 | }, { 340 | 'field': 'When__Phase_of_flight', 341 | 'type': 'ordinal', 342 | '$$hashKey': 'object:17', 343 | '_any': true 344 | }, { 345 | 'field': 'When__Time_of_day', 346 | 'type': 'ordinal', 347 | '$$hashKey': 'object:18', 348 | '_any': true 349 | }, { 350 | 'field': 'Wildlife__Size', 351 | 'type': 'ordinal', 352 | '$$hashKey': 'object:19', 353 | '_any': true 354 | }, { 355 | 'field': 'Wildlife__Species', 356 | 'type': 'ordinal', 357 | '$$hashKey': 'object:20', 358 | '_any': true 359 | }, { 360 | 'field': 'Flight_Date', 361 | 'type': 'temporal', 362 | '$$hashKey': 'object:21', 363 | '_any': true 364 | }, { 365 | 'field': 'Cost__Other', 366 | 'type': 'quantitative', 367 | '$$hashKey': 'object:22', 368 | '_any': true 369 | }, { 370 | 'field': 'Cost__Repair', 371 | 'type': 'quantitative', 372 | '$$hashKey': 'object:23', 373 | '_any': true 374 | }, { 375 | 'field': 'Cost__Total_$', 376 | 'type': 'quantitative', 377 | '$$hashKey': 'object:24', 378 | '_any': true 379 | }, { 380 | 'field': 'Speed_IAS_in_knots', 381 | 'type': 'quantitative', 382 | '$$hashKey': 'object:25', 383 | '_any': true 384 | }, { 385 | 'field': '*', 386 | 'aggregate': 'count', 387 | 'type': 'quantitative', 388 | 'displayName': 'Number of Records', 389 | '$$hashKey': 'object:26', 390 | '_any': true 391 | }]; 392 | fixture.birdstrikes.stats = { 393 | 'Airport__Name': { 394 | 'min': null, 395 | 'max': null, 396 | 'cardinality': 50, 397 | 'count': 10000, 398 | 'maxlength': 38, 399 | 'numNulls': 0, 400 | 'sample': ['SACRAMENTO INTL', 'DALLAS/FORT WORTH INTL ARPT', 'GREATER PITTSBURGH', 401 | 'KANSAS CITY INTL', 'LOUISVILLE INTL ARPT', 'CHICAGO MIDWAY INTL ARPT', 402 | 'CHARLOTTE/DOUGLAS INTL ARPT', 'MINETA SAN JOSE INTL', 'EPPLEY AIRFIELD', 403 | 'AUSTIN-BERGSTROM INTL' 404 | ] 405 | }, 406 | 'Aircraft__Make_Model': { 407 | 'min': null, 408 | 'max': null, 409 | 'cardinality': 225, 410 | 'count': 10000, 411 | 'maxlength': 18, 412 | 'numNulls': 0, 413 | 'sample': ['B-747-1/200', 'B-727-200', 'FOKKER F100', 'SAAB-340', 'EMB-120', 'B-737-400', 414 | 'B-737-200', 'A-320', 'DC-9-30', 'DC-9' 415 | ] 416 | }, 417 | 'Effect__Amount_of_damage': { 418 | 'min': null, 419 | 'max': null, 420 | 'cardinality': 6, 421 | 'count': 10000, 422 | 'maxlength': 11, 423 | 'numNulls': 0, 424 | 'sample': ['None', 'Minor', 'Substantial', 'Medium', 'C', 'B'] 425 | }, 426 | 'Flight_Date': { 427 | 'min': null, 428 | 'max': null, 429 | 'cardinality': 3625, 430 | 'count': 10000, 431 | 'maxlength': 13, 432 | 'numNulls': 0, 433 | 'sample': ['10/10/98 0:00', '10/5/99 0:00', '6/8/02 0:00', '5/14/91 0:00', '10/26/01 0:00', 434 | '8/13/94 0:00', '7/27/94 0:00', '12/12/95 0:00', '5/30/02 0:00', '10/21/93 0:00' 435 | ] 436 | }, 437 | 'Aircraft__Airline_Operator': { 438 | 'min': null, 439 | 'max': null, 440 | 'cardinality': 46, 441 | 'count': 10000, 442 | 'maxlength': 30, 443 | 'numNulls': 0, 444 | 'sample': ['UNITED AIRLINES', 'DELTA AIR LINES', 'CONTINENTAL AIRLINES', 'SOUTHWEST AIRLINES', 445 | 'AMERICAN AIRLINES', 'US AIRWAYS*', 'PINNACLE', 'BUSINESS', 'ABX AIR', 446 | 'NORTHWEST AIRLINES' 447 | ] 448 | }, 449 | 'Origin_State': { 450 | 'min': null, 451 | 'max': null, 452 | 'cardinality': 29, 453 | 'count': 10000, 454 | 'maxlength': 14, 455 | 'numNulls': 0, 456 | 'sample': ['California', 'New Jersey', 'New York', 'Washington', 'Louisiana', 'Hawaii', 457 | 'Texas', 'Utah', 'Tennessee', 'Georgia' 458 | ] 459 | }, 460 | 'When__Phase_of_flight': { 461 | 'min': null, 462 | 'max': null, 463 | 'cardinality': 7, 464 | 'count': 10000, 465 | 'maxlength': 12, 466 | 'numNulls': 0, 467 | 'sample': ['Landing Roll', 'Approach', 'Take-off run', 'Descent', 'Climb', 'Taxi', 'Parked'] 468 | }, 469 | 'Wildlife__Size': { 470 | 'min': null, 471 | 'max': null, 472 | 'cardinality': 3, 473 | 'count': 10000, 474 | 'maxlength': 6, 475 | 'numNulls': 0, 476 | 'sample': ['Medium', 'Small', 'Large'] 477 | }, 478 | 'Wildlife__Species': { 479 | 'min': null, 480 | 'max': null, 481 | 'cardinality': 37, 482 | 'count': 10000, 483 | 'maxlength': 21, 484 | 'numNulls': 0, 485 | 'sample': ['Unknown bird - small', 'Zebra dove', 'Unknown bird - large', 486 | 'Unknown bird - medium', 'Chimney swift', 'Unknown bird or bat', 'European starling', 487 | 'Killdeer', 'Horned lark', 'Barn owl' 488 | ] 489 | }, 490 | 'When__Time_of_day': { 491 | 'min': null, 492 | 'max': null, 493 | 'cardinality': 4, 494 | 'count': 10000, 495 | 'maxlength': 5, 496 | 'numNulls': 0, 497 | 'sample': ['Day', 'Night', 'Dawn', 'Dusk'] 498 | }, 499 | 'Cost__Other': { 500 | 'min': 0, 501 | 'max': 1565354, 502 | 'cardinality': 65, 503 | 'count': 10000, 504 | 'maxlength': 7, 505 | 'numNulls': 0, 506 | 'skew': 0.023185985633316083, 507 | 'stdev': 18297.3071194526, 508 | 'mean': 424.2411, 509 | 'median': 0, 510 | 'sample': ['0', '70', '130', '137', '260', '297', '401', '6860', '8250', '13364'] 511 | }, 512 | 'Cost__Repair': { 513 | 'min': 0, 514 | 'max': 7043545, 515 | 'cardinality': 165, 516 | 'count': 10000, 517 | 'maxlength': 7, 518 | 'numNulls': 0, 519 | 'skew': 0.03709480948374386, 520 | 'stdev': 97865.08006169728, 521 | 'mean': 3630.2865, 522 | 'median': 0, 523 | 'sample': ['0', '317', '668', '2673', '5218', '7044', '14331', '14463', '21552', '40091'] 524 | }, 525 | 'Cost__Total_$': { 526 | 'min': 0, 527 | 'max': 7043545, 528 | 'cardinality': 196, 529 | 'count': 10000, 530 | 'maxlength': 7, 531 | 'numNulls': 0, 532 | 'skew': 0.03969958970315394, 533 | 'stdev': 102130.21419911268, 534 | 'mean': 4054.5276, 535 | 'median': 0, 536 | 'sample': ['0', '18151', '19133', '20046', '93546', '211146', '304926', '559688', '1715077', 537 | '3811576' 538 | ] 539 | }, 540 | 'Speed_IAS_in_knots': { 541 | 'min': 0, 542 | 'max': 350, 543 | 'cardinality': 123, 544 | 'count': 10000, 545 | 'maxlength': 3, 546 | 'numNulls': 2836, 547 | 'skew': 0.3110428807026861, 548 | 'stdev': 43.51546593453372, 549 | 'mean': 153.53517587939697, 550 | 'median': 140, 551 | 'sample': ['100', '120', '130', '135', '140', '150', '163', '200', '250', 'null'] 552 | }, 553 | 'count': 10000 554 | }; 555 | -------------------------------------------------------------------------------- /typings/chai.d.ts: -------------------------------------------------------------------------------- 1 | // Type definitions for chai 3.2.0 2 | // Project: http://chaijs.com/ 3 | // Definitions by: Jed Mao , 4 | // Bart van der Schoor , 5 | // Andrew Brown , 6 | // Olivier Chevet 7 | // Definitions: https://github.com/borisyankov/DefinitelyTyped 8 | 9 | // 10 | 11 | declare module Chai { 12 | 13 | interface ChaiStatic { 14 | expect: ExpectStatic; 15 | should(): Should; 16 | /** 17 | * Provides a way to extend the internals of Chai 18 | */ 19 | use(fn: (chai: any, utils: any) => void): any; 20 | assert: AssertStatic; 21 | config: Config; 22 | AssertionError: AssertionError; 23 | } 24 | 25 | export interface ExpectStatic extends AssertionStatic { 26 | fail(actual?: any, expected?: any, message?: string, operator?: string): void; 27 | } 28 | 29 | export interface AssertStatic extends Assert { 30 | } 31 | 32 | export interface AssertionStatic { 33 | (target: any, message?: string): Assertion; 34 | } 35 | 36 | interface ShouldAssertion { 37 | equal(value1: any, value2: any, message?: string): void; 38 | Throw: ShouldThrow; 39 | throw: ShouldThrow; 40 | exist(value: any, message?: string): void; 41 | } 42 | 43 | interface Should extends ShouldAssertion { 44 | not: ShouldAssertion; 45 | fail(actual: any, expected: any, message?: string, operator?: string): void; 46 | } 47 | 48 | interface ShouldThrow { 49 | (actual: Function): void; 50 | (actual: Function, expected: string|RegExp, message?: string): void; 51 | (actual: Function, constructor: Error|Function, expected?: string|RegExp, message?: string): void; 52 | } 53 | 54 | interface Assertion extends LanguageChains, NumericComparison, TypeComparison { 55 | not: Assertion; 56 | deep: Deep; 57 | any: KeyFilter; 58 | all: KeyFilter; 59 | a: TypeComparison; 60 | an: TypeComparison; 61 | include: Include; 62 | includes: Include; 63 | contain: Include; 64 | contains: Include; 65 | ok: Assertion; 66 | true: Assertion; 67 | false: Assertion; 68 | null: Assertion; 69 | undefined: Assertion; 70 | NaN: Assertion; 71 | exist: Assertion; 72 | empty: Assertion; 73 | arguments: Assertion; 74 | Arguments: Assertion; 75 | equal: Equal; 76 | equals: Equal; 77 | eq: Equal; 78 | eql: Equal; 79 | eqls: Equal; 80 | property: Property; 81 | ownProperty: OwnProperty; 82 | haveOwnProperty: OwnProperty; 83 | ownPropertyDescriptor: OwnPropertyDescriptor; 84 | haveOwnPropertyDescriptor: OwnPropertyDescriptor; 85 | length: Length; 86 | lengthOf: Length; 87 | match: Match; 88 | matches: Match; 89 | string(string: string, message?: string): Assertion; 90 | keys: Keys; 91 | key(string: string): Assertion; 92 | throw: Throw; 93 | throws: Throw; 94 | Throw: Throw; 95 | respondTo: RespondTo; 96 | respondsTo: RespondTo; 97 | itself: Assertion; 98 | satisfy: Satisfy; 99 | satisfies: Satisfy; 100 | closeTo(expected: number, delta: number, message?: string): Assertion; 101 | members: Members; 102 | increase: PropertyChange; 103 | increases: PropertyChange; 104 | decrease: PropertyChange; 105 | decreases: PropertyChange; 106 | change: PropertyChange; 107 | changes: PropertyChange; 108 | extensible: Assertion; 109 | sealed: Assertion; 110 | frozen: Assertion; 111 | 112 | } 113 | 114 | interface LanguageChains { 115 | to: Assertion; 116 | be: Assertion; 117 | been: Assertion; 118 | is: Assertion; 119 | that: Assertion; 120 | which: Assertion; 121 | and: Assertion; 122 | has: Assertion; 123 | have: Assertion; 124 | with: Assertion; 125 | at: Assertion; 126 | of: Assertion; 127 | same: Assertion; 128 | } 129 | 130 | interface NumericComparison { 131 | above: NumberComparer; 132 | gt: NumberComparer; 133 | greaterThan: NumberComparer; 134 | least: NumberComparer; 135 | gte: NumberComparer; 136 | below: NumberComparer; 137 | lt: NumberComparer; 138 | lessThan: NumberComparer; 139 | most: NumberComparer; 140 | lte: NumberComparer; 141 | within(start: number, finish: number, message?: string): Assertion; 142 | } 143 | 144 | interface NumberComparer { 145 | (value: number, message?: string): Assertion; 146 | } 147 | 148 | interface TypeComparison { 149 | (type: string, message?: string): Assertion; 150 | instanceof: InstanceOf; 151 | instanceOf: InstanceOf; 152 | } 153 | 154 | interface InstanceOf { 155 | (constructor: Object, message?: string): Assertion; 156 | } 157 | 158 | interface Deep { 159 | equal: Equal; 160 | include: Include; 161 | property: Property; 162 | members: Members; 163 | } 164 | 165 | interface KeyFilter { 166 | keys: Keys; 167 | } 168 | 169 | interface Equal { 170 | (value: any, message?: string): Assertion; 171 | } 172 | 173 | interface Property { 174 | (name: string, value?: any, message?: string): Assertion; 175 | } 176 | 177 | interface OwnProperty { 178 | (name: string, message?: string): Assertion; 179 | } 180 | 181 | interface OwnPropertyDescriptor { 182 | (name: string, descriptor: PropertyDescriptor, message?: string): Assertion; 183 | (name: string, message?: string): Assertion; 184 | } 185 | 186 | interface Length extends LanguageChains, NumericComparison { 187 | (length: number, message?: string): Assertion; 188 | } 189 | 190 | interface Include { 191 | (value: Object, message?: string): Assertion; 192 | (value: string, message?: string): Assertion; 193 | (value: number, message?: string): Assertion; 194 | keys: Keys; 195 | members: Members; 196 | any: KeyFilter; 197 | all: KeyFilter; 198 | } 199 | 200 | interface Match { 201 | (regexp: RegExp|string, message?: string): Assertion; 202 | } 203 | 204 | interface Keys { 205 | (...keys: string[]): Assertion; 206 | (keys: any[]): Assertion; 207 | (keys: Object): Assertion; 208 | } 209 | 210 | interface Throw { 211 | (): Assertion; 212 | (expected: string, message?: string): Assertion; 213 | (expected: RegExp, message?: string): Assertion; 214 | (constructor: Error, expected?: string, message?: string): Assertion; 215 | (constructor: Error, expected?: RegExp, message?: string): Assertion; 216 | (constructor: Function, expected?: string, message?: string): Assertion; 217 | (constructor: Function, expected?: RegExp, message?: string): Assertion; 218 | } 219 | 220 | interface RespondTo { 221 | (method: string, message?: string): Assertion; 222 | } 223 | 224 | interface Satisfy { 225 | (matcher: Function, message?: string): Assertion; 226 | } 227 | 228 | interface Members { 229 | (set: any[], message?: string): Assertion; 230 | } 231 | 232 | interface PropertyChange { 233 | (object: Object, prop: string, msg?: string): Assertion; 234 | } 235 | 236 | export interface Assert { 237 | /** 238 | * @param expression Expression to test for truthiness. 239 | * @param message Message to display on error. 240 | */ 241 | (expression: any, message?: string): void; 242 | 243 | fail(actual?: any, expected?: any, msg?: string, operator?: string): void; 244 | 245 | ok(val: any, msg?: string): void; 246 | isOk(val: any, msg?: string): void; 247 | notOk(val: any, msg?: string): void; 248 | isNotOk(val: any, msg?: string): void; 249 | 250 | equal(act: any, exp: any, msg?: string): void; 251 | notEqual(act: any, exp: any, msg?: string): void; 252 | 253 | strictEqual(act: any, exp: any, msg?: string): void; 254 | notStrictEqual(act: any, exp: any, msg?: string): void; 255 | 256 | deepEqual(act: any, exp: any, msg?: string): void; 257 | notDeepEqual(act: any, exp: any, msg?: string): void; 258 | 259 | isTrue(val: any, msg?: string): void; 260 | isFalse(val: any, msg?: string): void; 261 | 262 | isNull(val: any, msg?: string): void; 263 | isNotNull(val: any, msg?: string): void; 264 | 265 | isUndefined(val: any, msg?: string): void; 266 | isDefined(val: any, msg?: string): void; 267 | 268 | isNaN(val: any, msg?: string): void; 269 | isNotNaN(val: any, msg?: string): void; 270 | 271 | isAbove(val: number, abv: number, msg?: string): void; 272 | isBelow(val: number, blw: number, msg?: string): void; 273 | 274 | isFunction(val: any, msg?: string): void; 275 | isNotFunction(val: any, msg?: string): void; 276 | 277 | isObject(val: any, msg?: string): void; 278 | isNotObject(val: any, msg?: string): void; 279 | 280 | isArray(val: any, msg?: string): void; 281 | isNotArray(val: any, msg?: string): void; 282 | 283 | isString(val: any, msg?: string): void; 284 | isNotString(val: any, msg?: string): void; 285 | 286 | isNumber(val: any, msg?: string): void; 287 | isNotNumber(val: any, msg?: string): void; 288 | 289 | isBoolean(val: any, msg?: string): void; 290 | isNotBoolean(val: any, msg?: string): void; 291 | 292 | typeOf(val: any, type: string, msg?: string): void; 293 | notTypeOf(val: any, type: string, msg?: string): void; 294 | 295 | instanceOf(val: any, type: Function, msg?: string): void; 296 | notInstanceOf(val: any, type: Function, msg?: string): void; 297 | 298 | include(exp: string, inc: any, msg?: string): void; 299 | include(exp: any[], inc: any, msg?: string): void; 300 | 301 | notInclude(exp: string, inc: any, msg?: string): void; 302 | notInclude(exp: any[], inc: any, msg?: string): void; 303 | 304 | match(exp: any, re: RegExp, msg?: string): void; 305 | notMatch(exp: any, re: RegExp, msg?: string): void; 306 | 307 | property(obj: Object, prop: string, msg?: string): void; 308 | notProperty(obj: Object, prop: string, msg?: string): void; 309 | deepProperty(obj: Object, prop: string, msg?: string): void; 310 | notDeepProperty(obj: Object, prop: string, msg?: string): void; 311 | 312 | propertyVal(obj: Object, prop: string, val: any, msg?: string): void; 313 | propertyNotVal(obj: Object, prop: string, val: any, msg?: string): void; 314 | 315 | deepPropertyVal(obj: Object, prop: string, val: any, msg?: string): void; 316 | deepPropertyNotVal(obj: Object, prop: string, val: any, msg?: string): void; 317 | 318 | lengthOf(exp: any, len: number, msg?: string): void; 319 | //alias frenzy 320 | throw(fn: Function, msg?: string): void; 321 | throw(fn: Function, regExp: RegExp): void; 322 | throw(fn: Function, errType: Function, msg?: string): void; 323 | throw(fn: Function, errType: Function, regExp: RegExp): void; 324 | 325 | throws(fn: Function, msg?: string): void; 326 | throws(fn: Function, regExp: RegExp): void; 327 | throws(fn: Function, errType: Function, msg?: string): void; 328 | throws(fn: Function, errType: Function, regExp: RegExp): void; 329 | 330 | Throw(fn: Function, msg?: string): void; 331 | Throw(fn: Function, regExp: RegExp): void; 332 | Throw(fn: Function, errType: Function, msg?: string): void; 333 | Throw(fn: Function, errType: Function, regExp: RegExp): void; 334 | 335 | doesNotThrow(fn: Function, msg?: string): void; 336 | doesNotThrow(fn: Function, regExp: RegExp): void; 337 | doesNotThrow(fn: Function, errType: Function, msg?: string): void; 338 | doesNotThrow(fn: Function, errType: Function, regExp: RegExp): void; 339 | 340 | operator(val: any, operator: string, val2: any, msg?: string): void; 341 | closeTo(act: number, exp: number, delta: number, msg?: string): void; 342 | 343 | sameMembers(set1: any[], set2: any[], msg?: string): void; 344 | sameDeepMembers(set1: any[], set2: any[], msg?: string): void; 345 | includeMembers(superset: any[], subset: any[], msg?: string): void; 346 | 347 | ifError(val: any, msg?: string): void; 348 | 349 | isExtensible(obj: {}, msg?: string): void; 350 | extensible(obj: {}, msg?: string): void; 351 | isNotExtensible(obj: {}, msg?: string): void; 352 | notExtensible(obj: {}, msg?: string): void; 353 | 354 | isSealed(obj: {}, msg?: string): void; 355 | sealed(obj: {}, msg?: string): void; 356 | isNotSealed(obj: {}, msg?: string): void; 357 | notSealed(obj: {}, msg?: string): void; 358 | 359 | isFrozen(obj: Object, msg?: string): void; 360 | frozen(obj: Object, msg?: string): void; 361 | isNotFrozen(obj: Object, msg?: string): void; 362 | notFrozen(obj: Object, msg?: string): void; 363 | 364 | 365 | } 366 | 367 | export interface Config { 368 | includeStack: boolean; 369 | } 370 | 371 | export class AssertionError { 372 | constructor(message: string, _props?: any, ssf?: Function); 373 | name: string; 374 | message: string; 375 | showDiff: boolean; 376 | stack: string; 377 | } 378 | } 379 | 380 | declare var chai: Chai.ChaiStatic; 381 | 382 | declare module "chai" { 383 | export = chai; 384 | } 385 | 386 | interface Object { 387 | should: Chai.Assertion; 388 | } 389 | -------------------------------------------------------------------------------- /demo/vrdemo.js: -------------------------------------------------------------------------------- 1 | // Define module using Universal Module Definition pattern 2 | // https://github.com/umdjs/umd/blob/master/returnExports.js 3 | 4 | (function (root, factory) { 5 | if (typeof define === 'function' && define.amd) { 6 | // AMD. Register as an anonymous module. 7 | define(['d3', 'vega', 'vega-lite', 'lodash', 'visrec'],factory); 8 | } else if (typeof exports === 'object') { 9 | // Node. Does not work with strict CommonJS, but 10 | // only CommonJS-like environments that support module.exports, 11 | // like Node. 12 | module.exports = factory( 13 | require('d3'), 14 | require('vega'), 15 | require('vega-lite'), 16 | require('lodash'), 17 | require('visrec') 18 | ); 19 | } else { 20 | // Browser globals (root is window) 21 | factory(root.d3, root.vg, root.vl, root._, root.vr); 22 | } 23 | }(this, function(d3, vg, vl, _, vr){ 24 | var schema, col_indices; 25 | 26 | var HEIGHT_OFFSET = 60, MAX_HEIGHT = 500, MAX_WIDTH = 500; 27 | 28 | var CONFIG = { 29 | showTable: false, 30 | 31 | genAggr: true, 32 | genBin: true, 33 | genTypeCasting: false, 34 | 35 | omitTranpose: true, 36 | omitDotPlotWithExtraEncoding: true, 37 | omitAggrWithAllDimsOnFacets: true 38 | } 39 | 40 | var keys = vg.keys; 41 | 42 | 43 | function getVLType(data_type){ 44 | // return vega-lite's data type 45 | var typeMap = { 46 | "categorical": "O", 47 | "geographic": "G", 48 | "quantitative": "Q", 49 | "datetime": "T", 50 | "count": "Q" 51 | }; 52 | return typeMap[data_type]; 53 | } 54 | 55 | function init(){ 56 | // d3.select("#toggle-presets").on("click", function(){ 57 | // var hide = d3.select("#presets").classed("hide") 58 | // d3.select("#presets").classed("hide", !hide); 59 | // }); 60 | 61 | d3.select("#toggle-config").on("click", function(){ 62 | var hide = d3.select("#config").classed("hide") 63 | d3.select("#config").classed("hide", !hide); 64 | }); 65 | 66 | var configs = d3.select("#config").selectAll("div.cfg") 67 | .data(vl.keys(CONFIG)) 68 | .enter().append("div").attr("class", "cfg") 69 | .append("label"); 70 | 71 | configs.append("input").attr("type","checkbox").attr("class","cfg-check") 72 | .attr("checked", function(d){ return CONFIG[d] || undefined;}) 73 | .on("change", updateSelectedFields); 74 | 75 | 76 | configs.append("span").attr("class","label config").text(function(d){return d;}); 77 | 78 | loadSchema(); 79 | } 80 | 81 | function getConfig(){ 82 | var config = {}; 83 | d3.select("#config").selectAll("div.cfg input.cfg-check") 84 | .each(function(d){ 85 | config[d] = d3.select(this).node().checked; 86 | }); 87 | 88 | console.log("config", config); 89 | return config; 90 | } 91 | 92 | // ----- load schema ----- 93 | function loadSchema(){ 94 | //TODO: use amd-plugin to load json, csv 95 | d3.json("data/birdstrikes/birdstrikes-schema.json", function(_schema) { 96 | //TODO: remove this line after updating csv. 97 | schema = _(_schema).filter(function(d){ return !d.disabled;}) 98 | .sortBy('field_name') 99 | .sortBy(function(col){ 100 | return getVLType(col.data_type) !== "Q" ? 0 : 101 | col.data_type === "count" ? 2 : 1 ; 102 | }) 103 | .map(function(col, i){ 104 | col.key = col['field_name'].replace(/(: )/g, "__").replace(/[\/ ]/g,"_").replace(/[()]/g, ""); 105 | col.index = i; //add index 106 | return col; 107 | }) 108 | .value(); 109 | 110 | col_indices = _.reduce(schema, function(result, col, i){ result[col] = i; return result;}, {}); 111 | 112 | console.log('schema keys', _.pluck(schema,'key').sort()); 113 | console.log('data_types', _(schema).pluck('data_type').uniq().value()); 114 | loadData(); 115 | }); 116 | } 117 | 118 | function updateSelectedFields(){ 119 | var selectedColIndices = []; 120 | 121 | d3.selectAll("#datacols input.datacol").each(function(d, i){ 122 | var selected = d3.select(this).node().checked; 123 | if(selected){ 124 | selectedColIndices.push(d.index); 125 | } 126 | }); 127 | 128 | renderMain(selectedColIndices); 129 | } 130 | 131 | function loadData(){ 132 | // TODO: use other lib to load csv as columns? 133 | // TODO: regenerate csv with all columns 134 | d3.json("data/birdstrikes.json", function(data) { 135 | self.data = data; 136 | console.log("keys", vg.keys(data[0])); 137 | renderColumns(); 138 | }); 139 | } 140 | 141 | function renderColumns(){ 142 | var datacols = d3.select("#datacols").selectAll("div") 143 | .data(schema).enter().append("div").append("label"); 144 | 145 | datacols.append("input").attr("type","checkbox").attr("class","datacol") 146 | .on("change", updateSelectedFields); 147 | 148 | datacols.append("span").attr("class","type").text(function(d){ 149 | return "["+getVLType(d.data_type)+"] "; 150 | }); 151 | 152 | datacols.append("span").attr("class","name").html(function(d){ 153 | return d.field_name; 154 | }); 155 | 156 | // ----- Assume User Selection here ----- 157 | 158 | // 0:O "Aircraft: Airline/Operator" 159 | // 1:O "Aircraft: Make/Model" 160 | // 2:O "Airport: Name" 161 | // 3:Q "Cost: Other" 162 | // 4:Q "Cost: Repair" 163 | // 5:Q "Cost: Total $" 164 | // 6:O "Effect: Amount of damage" 165 | // 7:T "Flight Date" 166 | // 8:# "Number of Strikes" 167 | // 9:G "Origin State" 168 | // 10:Q "Speed (IAS) in knots" 169 | // 11:O "When: Phase of flight" 170 | // 12:O "When: Time of day" 171 | // 13:O "Wildlife: Size" 172 | // 14:O "Wildlife: Species" 173 | 174 | // var colIndicesSet = [ 175 | // [6,5,4], //CxQxQ -- good except some bar + size 176 | // [6,11,5], //CxCxQ 177 | // [6,8], //Cx# 178 | // [2,3], //C(Big)xQ 179 | // [6, 10], //CxQ 180 | // [6,8,5], //Cx#xG 181 | // [10], //Q 182 | // // [4,5], //QxQ 183 | // // [7,8], //Dx# 184 | // // [11,12,13], //OxOxO //FIXME 185 | // //// [6,5,10] //CxQxQ //TODO: speed might be problematic 186 | // ]; 187 | 188 | // var control = d3.select("#control"); 189 | 190 | // var dsel = control.append("select") 191 | // .attr("class", "data") 192 | // .style("width", "300px") 193 | // .on("change", function(){ 194 | // var index = this.options[this.selectedIndex].value; 195 | // render(colIndicesSet[index]) 196 | // }) 197 | // .selectAll("option").data(colIndicesSet) 198 | // .enter().append("option") 199 | // .attr("value", function(d, i){ return i;}) 200 | // .attr("selected", function(d,i){ return i==0? true : undefined;}) 201 | // .text(function(d, i){ return getTitle(d);}); 202 | 203 | // render(colIndicesSet[0]) 204 | } 205 | 206 | function fieldDetailHtml(v){ 207 | return "" + 208 | "" + 209 | (v.aggr ? v.aggr : "") + 210 | (v.bin ? " bin " : "") + 211 | "" + 212 | "" + 213 | (v.name || "") + 214 | "" + 215 | " ("+ v.type + ")"; 216 | } 217 | 218 | function encodingDetails(enc, div){ 219 | div.append("div").html("marktype: "+enc.marktype()+""); 220 | enc.forEach(function(k, v){ 221 | div.append("div").html(k+": "+fieldDetailHtml(v, vl.dataTypeNames[v.type])) 222 | }); 223 | } 224 | 225 | function getTitle(colIndices){ 226 | var cols = colIndices.map(function(i){ return schema[i];}); 227 | 228 | return cols.map(function(col){ 229 | return col['field_name'] + " [" + col['data_type'][0] +"]"; 230 | }).join(","); 231 | } 232 | 233 | function getChartsByFieldSet(fields) { 234 | var config = getConfig(); 235 | var aggr = vr.gen.aggregates([], fields, config); 236 | var chartsByFieldset = aggr.map(function (fields) { 237 | var encodings = vr.gen.charts(fields, 238 | vl.merge(Object.create(config), {genAggr: false}), 239 | { 240 | dataUrl: "data/birdstrikes.json", 241 | viewport: [460, 460], 242 | genAggr: false 243 | }, 244 | true 245 | ).map(function (e) { //add score 246 | var score = vr.rank.encodingScore(e); 247 | e.score = score.score; 248 | e.scoreFeatures = score.features; 249 | return e; 250 | }); 251 | 252 | var diff = vr.cluster.distanceTable(encodings), 253 | clusters = vr.cluster(encodings, 2.5) 254 | .map(function (cluster) { 255 | return cluster.sort(function (i, j) { 256 | return encodings[j].score - encodings[i].score; 257 | }); 258 | }) 259 | .sort(function (c1, c2) { 260 | return encodings[c2[0]].score - encodings[c1[0]].score; 261 | }); 262 | 263 | //console.log("clusters", clusters); 264 | 265 | return { 266 | fields: fields, 267 | encodings: encodings, 268 | diff: diff, 269 | clusters: clusters 270 | }; 271 | 272 | }) 273 | return chartsByFieldset; 274 | } 275 | 276 | function renderMain(selectedColIndices){ 277 | if(selectedColIndices.length === 0) return; 278 | 279 | var selectedCols = selectedColIndices.map(function(i){ return schema[i];}), 280 | selectedColNames = _.pluck(selectedCols, 'field_name'), 281 | selectedColTypes = _.pluck(selectedCols, 'data_type'); 282 | 283 | // ----- Generate Charts ----- 284 | //TODO(kanitw): change schema format to match 285 | var fields = selectedCols.map(function(col){ 286 | if(col.data_type == "count"){ 287 | return {aggr: "count", name:"*"}; 288 | } 289 | var type = getVLType(col.data_type), f; 290 | switch(type){ 291 | 292 | case "Q": 293 | f = {name: col.key, type: "Q", _aggr:"*", _bin:"*"} 294 | return f; 295 | case "O": 296 | default: 297 | return {name: col.key, type:"O"}; 298 | } 299 | }); 300 | 301 | console.log('fields', JSON.stringify(fields)); 302 | 303 | var chartsByFieldSet = getChartsByFieldSet(fields); 304 | 305 | d3.select("#aggr").selectAll("*").remove(); 306 | d3.select("#vis").selectAll("*").remove(); 307 | d3.select("#selected-fields").selectAll("*").remove(); 308 | 309 | var aggrTable = d3.select("#aggr").selectAll("div.fieldset"); 310 | 311 | var enter = aggrTable.data(chartsByFieldSet).enter() 312 | .append("div").attr("class", "fieldset"); 313 | 314 | // data fields 315 | enter.append("div").attr("class", "datafields") 316 | .selectAll("div.datafield").data(function(d){return d.fields;}) 317 | .enter().append("div").attr("class", "datafield") 318 | .html(fieldDetailHtml); 319 | 320 | // top vis 321 | enter.append("div").attr("class","topvis") 322 | .each(renderTopVis); 323 | 324 | enter.append("div").attr("class", "select") 325 | .append("a").attr("href","#") 326 | .text(function(d){ 327 | return d.encodings.length > 1 ? "Expand ("+d.encodings.length+")" : ""; 328 | }) 329 | .on('click', function(d){ 330 | renderEncodingVariations(d) 331 | }); 332 | 333 | // console.log("chartsByFieldset", chartsByFieldset); 334 | // chartsByFieldset.forEach(renderCharts); 335 | } 336 | 337 | var topVisId = 0; //HACK 338 | 339 | function renderTopVis(charts){ 340 | var container = d3.select(this), 341 | clusters = charts.clusters, 342 | encodings = charts.encodings; 343 | 344 | if(clusters.length == 0 || clusters[0].length===0) return; 345 | 346 | var id = "topvis-" + (topVisId++); 347 | 348 | var topIdx = clusters[0][0], 349 | encoding = vl.Encoding.fromSpec(encodings[topIdx]), 350 | stats = vl.data.getStats(data), 351 | spec = vl.compile(encoding, stats); 352 | 353 | //console.log(JSON.stringify(spec, null, " ")); 354 | 355 | appendVis(container, encoding, spec, id); 356 | } 357 | 358 | function appendVis(container, encoding, spec, id){ 359 | container.append("div").attr("id", id) 360 | .style({ 361 | "height": Math.min(+spec.height + HEIGHT_OFFSET, MAX_HEIGHT) + "px", 362 | "max-width": MAX_WIDTH+"px", 363 | "overflow": "scroll" 364 | }); 365 | 366 | if (spec){ 367 | vg.parse.spec(spec, function (vgChart) { 368 | var vis = vgChart({el: '#' + id}); 369 | vis.update(); 370 | vis.on("mouseover", function(event, item) { 371 | console.log(item); 372 | }); 373 | }); 374 | } 375 | 376 | container.append("input").attr({"readonly":1, value: encoding.toShorthand(), class:"shorthand"}) 377 | .style("font-size", "12px"); 378 | } 379 | 380 | function renderEncodingVariations(charts, groupId) { 381 | var encodings = charts.encodings, 382 | diff = charts.diff, 383 | clusters = charts.clusters; 384 | 385 | var dataFields = d3.select("#selected-fields"), 386 | content = d3.select("#vis"); 387 | 388 | content.selectAll("*").remove(); 389 | dataFields.selectAll("*").remove(); 390 | 391 | dataFields.selectAll("div.datafield").data(charts.fields) 392 | .enter().append("div").attr("class", "datafield") 393 | .html(fieldDetailHtml); 394 | 395 | var visIdCounter=0; 396 | 397 | if(CONFIG.showTable){ 398 | renderDistanceTable(content, diff); 399 | } 400 | 401 | clusters.forEach(function (clusterIndices) { 402 | var cluster = clusterIndices.map(function (i) { 403 | var e = encodings[i], 404 | encoding = vl.Encoding.parseJSON(e), 405 | stats = vl.getStats(data), 406 | spec = vl.toVegaSpec(encoding, stats); 407 | return { 408 | encodingJson: e, 409 | encoding: encoding, 410 | spec: spec, 411 | i: i 412 | }; 413 | }); 414 | 415 | var clusterHeight = cluster.reduce(function (h, c) { 416 | var nh = Math.min(+c.spec.height + HEIGHT_OFFSET, MAX_HEIGHT)+120; 417 | return nh > h ? nh : h; 418 | }, 0) 419 | 420 | var chartGroupDiv = content.append("div") 421 | .attr("id", "group") 422 | .attr("class", "row") 423 | .style({ 424 | "background-color": "#fcfcfc", 425 | "overflow-x": "scroll", 426 | "overflow-y": "hidden", 427 | "margin-bottom": "20px", 428 | "white-space": "nowrap", 429 | "height": clusterHeight + "px" 430 | }); 431 | 432 | cluster.forEach(function (o, i) { 433 | if(CONFIG.showOnlyClusterTop && i>0) return; 434 | // console.log('chart', chart, chart.toShorthand()); 435 | var encodingJson = o.encodingJson, 436 | i = o.i, 437 | id = 'vis-' + groupId + "-" + (visIdCounter++), 438 | encoding = o.encoding, 439 | spec = o.spec; 440 | 441 | var chartDiv = chartGroupDiv.append("div") 442 | .style({ 443 | "display": "inline-block", 444 | "margin-right": "10px", 445 | "vertical-align": "top" 446 | }) 447 | var detail = chartDiv.append("div").text("id:"+i+", score:"+encodingJson.score).append("div"); 448 | encodingDetails(encoding, detail); 449 | 450 | appendVis(chartDiv, encoding, spec, id); 451 | }); 452 | }) 453 | } 454 | 455 | function renderDistanceTable(content, diff) { 456 | var table = content.append("table"); 457 | var headerRow = table.append("tr").attr("class", "header-row"); 458 | headerRow.append("th"); 459 | headerRow.selectAll("th.item-col").data(diff) 460 | .enter().append("th").attr("class", "item-col") 461 | .append("b").text(function (d, i) { 462 | return "" + i; 463 | }); 464 | 465 | var rows = table.selectAll("tr.item-row") 466 | .data(diff) 467 | .enter().append("tr").attr("class", "item-row"); 468 | 469 | rows.append("td").append("b").text(function (d, i) { 470 | return i; 471 | }); 472 | rows.selectAll("td.item-cell") 473 | .data(_.identity) 474 | .enter().append("td").attr("class", "item-cell") 475 | .style("text-align", "center") 476 | .style("border", "1px solid #ddd") 477 | .text(function (d) { 478 | return d ? d3.format('.2')(d) : "-"; 479 | }); 480 | } 481 | 482 | init(); 483 | })); --------------------------------------------------------------------------------