├── src ├── favicon.ico ├── components │ ├── fieldlist │ │ ├── fieldlist.scss │ │ ├── fieldlist.html │ │ ├── fieldlist.js │ │ └── fieldlist.spec.js │ ├── functionlist │ │ ├── functionlist.scss │ │ ├── functionlist.html │ │ ├── functionlist.spec.js │ │ └── functionlist.js │ ├── encodingvariations │ │ ├── encodingvariations.scss │ │ ├── encodingvariations.spec.js │ │ ├── encodingvariations.js │ │ └── encodingvariations.html │ ├── vislist │ │ ├── vislist.scss │ │ ├── vislist.spec.js │ │ ├── vislist.js │ │ └── vislist.html │ └── fieldlistitem │ │ ├── fieldlistitem.js │ │ ├── fieldlistitem.scss │ │ ├── fieldlistitem.spec.js │ │ └── fieldlistitem.html ├── assets │ ├── images │ │ ├── field_geo.png │ │ ├── field_text.png │ │ ├── field_time.png │ │ └── field_number.png │ └── normalize.scss ├── app │ ├── main │ │ ├── main.controller.spec.js │ │ ├── main.controller.js │ │ └── main.html │ ├── fields │ │ ├── fields.spec.js │ │ └── fields.js │ ├── visrec │ │ ├── visrec.service.spec.js │ │ └── visrec.service.js │ ├── index.scss │ └── index.js ├── data │ ├── crimea.json │ ├── burtin.json │ ├── driving.json │ ├── barley.json │ ├── iris.json │ ├── population.json │ └── weball26.json └── index.html ├── .gitignore ├── .bowerrc ├── .travis.yml ├── .bower-postinstall ├── .editorconfig ├── gulpfile.js ├── gulp ├── lint.js ├── unit-tests.js ├── bumpver.js ├── watch.js ├── e2e-tests.js ├── util.js ├── inject.js ├── styles.js ├── server.js ├── proxy.js ├── build.js └── gen.js ├── e2e ├── main.po.js └── main.spec.js ├── .jshintrc ├── protractor.conf.js ├── bower.json ├── scripts └── deploy.sh ├── LICENSE ├── .yo-rc.json ├── karma.conf.js ├── package.json └── README.md /src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sandbox/voyager/master/src/favicon.ico -------------------------------------------------------------------------------- /src/components/fieldlist/fieldlist.scss: -------------------------------------------------------------------------------- 1 | .schema { 2 | .tool { 3 | margin-top: 10px; 4 | } 5 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | bower_components/ 3 | .sass-cache/ 4 | .tmp/ 5 | dist/ 6 | npm-debug.log 7 | -------------------------------------------------------------------------------- /src/assets/images/field_geo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sandbox/voyager/master/src/assets/images/field_geo.png -------------------------------------------------------------------------------- /src/assets/images/field_text.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sandbox/voyager/master/src/assets/images/field_text.png -------------------------------------------------------------------------------- /src/assets/images/field_time.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sandbox/voyager/master/src/assets/images/field_time.png -------------------------------------------------------------------------------- /src/assets/images/field_number.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sandbox/voyager/master/src/assets/images/field_number.png -------------------------------------------------------------------------------- /.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "bower_components", 3 | "scripts": { 4 | "postinstall": "./.bower-postinstall" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: ["0.10"] 3 | before_script: 4 | - npm install -g bower 5 | - npm install 6 | - bower install 7 | -------------------------------------------------------------------------------- /src/components/functionlist/functionlist.scss: -------------------------------------------------------------------------------- 1 | label.func-label { 2 | display: inline-block; 3 | min-width: 65px; 4 | margin-right: 5px; 5 | } -------------------------------------------------------------------------------- /.bower-postinstall: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | cp bower_components/angular-tooltips/dist/angular-tooltips.min.css bower_components/angular-tooltips/dist/_angular-tooltips.scss -------------------------------------------------------------------------------- /src/app/main/main.controller.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe('controllers', function(){ 4 | var scope; 5 | 6 | beforeEach(module('voyager')); 7 | 8 | beforeEach(inject(function($rootScope) { 9 | scope = $rootScope.$new(); 10 | })); 11 | }); 12 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var gulp = require('gulp'); 4 | 5 | gulp.paths = { 6 | src: 'src', 7 | dist: 'dist', 8 | tmp: '.tmp', 9 | e2e: 'e2e' 10 | }; 11 | 12 | require('require-dir')('./gulp'); 13 | 14 | gulp.task('default', ['clean'], function () { 15 | gulp.start('build'); 16 | }); 17 | -------------------------------------------------------------------------------- /src/components/functionlist/functionlist.html: -------------------------------------------------------------------------------- 1 |
2 |

Functions

3 | 7 |
8 | -------------------------------------------------------------------------------- /src/components/fieldlist/fieldlist.html: -------------------------------------------------------------------------------- 1 |
2 |
4 | 5 |
6 |
7 | Reset 8 |
9 | 10 |
11 | -------------------------------------------------------------------------------- /gulp/lint.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var gulp = require('gulp'); 4 | var paths = gulp.paths; 5 | var $ = require('gulp-load-plugins')(); 6 | 7 | gulp.task('jshint', function() { 8 | return gulp.src([ 9 | paths.src + '/**/*.js', 10 | '!'+ paths.src + '/vendor/*.js' 11 | ]) 12 | .pipe($.jshint()) 13 | .pipe($.jshint.reporter('jshint-stylish')); 14 | }); 15 | -------------------------------------------------------------------------------- /src/components/encodingvariations/encodingvariations.scss: -------------------------------------------------------------------------------- 1 | .encoding-variations .field-set-info { 2 | margin-bottom: 0px; 3 | } 4 | 5 | .encoding-variations-main{ 6 | overflow: hidden; 7 | } 8 | 9 | .encoding-variations .selected-variation { 10 | min-width: 700px; 11 | position: relative; 12 | align-self: stretch; 13 | flex-grow:0.7; 14 | } 15 | 16 | .encoding-variations .variations { 17 | max-width: 300px; 18 | } 19 | -------------------------------------------------------------------------------- /gulp/unit-tests.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var gulp = require('gulp'); 4 | var karma = require('karma').server; 5 | 6 | function runTests (singleRun, done) { 7 | karma.start({ 8 | configFile: __dirname + '/../karma.conf.js', 9 | singleRun: singleRun 10 | }, function() { done(); }); 11 | } 12 | 13 | gulp.task('test', ['partials'], function (done) { 14 | runTests(true /* singleRun */, done); 15 | }); 16 | 17 | gulp.task('test:auto', ['partials'], function (done) { 18 | runTests(false /* singleRun */, done); 19 | }); 20 | -------------------------------------------------------------------------------- /src/components/vislist/vislist.scss: -------------------------------------------------------------------------------- 1 | .vis-list-header { 2 | .desc { 3 | font-weight: bold; 4 | } 5 | } 6 | 7 | .vis-list { 8 | align-content: flex-start; 9 | margin-bottom: 10px; 10 | } 11 | 12 | // .vis-list .encoding-variations { 13 | // width: 100%; 14 | // max-width: 100%; 15 | // flex-grow: 1; 16 | 17 | // margin: 3px 3px 0 0; 18 | // padding: 8px; 19 | // border: 2px solid #666; 20 | // background-color: white; 21 | 22 | // max-height: 500px; 23 | 24 | // position: relative; 25 | // } 26 | 27 | -------------------------------------------------------------------------------- /src/app/fields/fields.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* global vl:true */ 4 | 5 | describe('Service: Fields', function() { 6 | // load the service's module 7 | beforeEach(module('voyager')); 8 | 9 | beforeEach(module('voyager', function($provide) { 10 | $provide.constant('vl', vl); // vl is loaded by karma 11 | })); 12 | 13 | // instantiate service 14 | var Fields; 15 | beforeEach(inject(function(_Fields_) { 16 | Fields = _Fields_; 17 | })); 18 | 19 | it('should test something', function() { 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /e2e/main.po.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This file uses the Page Object pattern to define the main page for tests 3 | * https://docs.google.com/presentation/d/1B6manhG0zEXkC-H-tPo2vwU06JhL8w9-XCF9oehXzAQ 4 | */ 5 | 6 | 'use strict'; 7 | 8 | var MainPage = function() { 9 | this.jumbEl = element(by.css('.jumbotron')); 10 | this.h1El = this.jumbEl.element(by.css('h1')); 11 | this.imgEl = this.jumbEl.element(by.css('img')); 12 | this.thumbnailEls = element(by.css('body')).all(by.repeater('awesomeThing in awesomeThings')); 13 | }; 14 | 15 | module.exports = new MainPage(); 16 | -------------------------------------------------------------------------------- /src/components/fieldlist/fieldlist.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * @ngdoc directive 5 | * @name voyager.directive:fieldList 6 | * @description 7 | * # fieldList 8 | */ 9 | angular.module('voyager') 10 | .directive('fieldList', function (Dataset, Fields) { 11 | return { 12 | templateUrl: 'components/fieldlist/fieldlist.html', 13 | restrict: 'E', 14 | scope: {}, 15 | link: function postLink (scope /*, element, attrs*/) { 16 | scope.Dataset = Dataset; 17 | scope.Fields = Fields; 18 | } 19 | }; 20 | }); 21 | -------------------------------------------------------------------------------- /src/app/visrec/visrec.service.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* global vl:true */ 4 | /* jshint expr:true */ 5 | 6 | describe('Service: Visrec', function () { 7 | 8 | // load the service's module 9 | beforeEach(module('voyager')); 10 | 11 | beforeEach(module('voyager', function($provide) { 12 | $provide.constant('vl', vl); // vl is loaded by karma 13 | })); 14 | 15 | // instantiate service 16 | var Visrec; 17 | beforeEach(inject(function (_Visrec_) { 18 | Visrec = _Visrec_; 19 | })); 20 | 21 | it('should do something', function () { 22 | expect(Visrec).to.be.ok; 23 | }); 24 | 25 | }); -------------------------------------------------------------------------------- /.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 | "indent": 2, 12 | "quotmark": "single", 13 | "undef": true, 14 | "globals": { 15 | "angular": false, 16 | // Angular Mocks 17 | "inject": false, 18 | // assert 19 | "expect": false, 20 | // JASMINE 21 | "describe": false, 22 | "it": false, 23 | "before": false, 24 | "beforeEach": false, 25 | "after": false, 26 | "afterEach": false 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /gulp/bumpver.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var gulp = require('gulp'); 4 | var $ = require('gulp-load-plugins')(); 5 | 6 | 7 | function inc(importance) { 8 | // get all the files to bump version in 9 | return gulp.src(['./package.json', './bower.json']) 10 | // bump the version number in those files 11 | .pipe($.bump({type: importance})) 12 | // save it back to filesystem 13 | .pipe(gulp.dest('./')); 14 | } 15 | 16 | gulp.task('patch', function() { return inc('patch'); }); 17 | gulp.task('feature', function() { return inc('minor'); }); 18 | gulp.task('release', function() { return inc('major'); }); -------------------------------------------------------------------------------- /e2e/main.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe('The main view', function () { 4 | var page; 5 | 6 | beforeEach(function () { 7 | browser.get('http://localhost:3000/index.html'); 8 | page = require('./main.po'); 9 | }); 10 | 11 | it('should include jumbotron with correct data', function() { 12 | expect(page.h1El.getText()).toBe('\'Allo, \'Allo!'); 13 | expect(page.imgEl.getAttribute('src')).toMatch(/assets\/images\/yeoman.png$/); 14 | expect(page.imgEl.getAttribute('alt')).toBe('I\'m Yeoman'); 15 | }); 16 | 17 | it('list more than 5 awesome things', function () { 18 | expect(page.thumbnailEls.count()).toBeGreaterThan(5); 19 | }); 20 | 21 | }); 22 | -------------------------------------------------------------------------------- /src/components/fieldlistitem/fieldlistitem.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('voyager') 4 | .directive('fieldListItem', function(Dataset, Fields, consts) { 5 | return { 6 | templateUrl: 'components/fieldlistitem/fieldlistitem.html', 7 | restrict: 'E', 8 | replace: true, 9 | scope: { 10 | field: '=' 11 | }, 12 | link: function postLink (scope, element /*, attrs*/) { 13 | scope.consts = consts; 14 | scope.Fields = Fields; 15 | scope.popupContent = element.find('.popup-functions')[0]; 16 | }, 17 | controller: function($scope, Dataset) { 18 | $scope.stats = Dataset.stats[$scope.field.name]; 19 | } 20 | }; 21 | }); 22 | -------------------------------------------------------------------------------- /gulp/watch.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var gulp = require('gulp'); 4 | 5 | var paths = gulp.paths; 6 | 7 | gulp.task('watch', ['inject'], function () { 8 | gulp.watch([ 9 | paths.src + '/*.html', 10 | paths.src + '/{app,components}/**/*.scss', 11 | paths.src + '/{app,components}/**/*.js', 12 | paths.src + '/{app,components}/**/*.html', 13 | paths.src + '/assets/*.scss', 14 | paths.src + '/bower_components/vega-lite/vega-lite.js', 15 | paths.src + '/bower_components/datalib/datalib.js', 16 | paths.src + '/bower_components/viscompass/compass.js', 17 | paths.src + '/bower_components/vega-lite-ui/vlui.js', 18 | paths.src + '/bower_components/vega-lite-ui/vlui.scss', 19 | 'bower.json' 20 | ], ['inject']); 21 | }); 22 | -------------------------------------------------------------------------------- /src/components/vislist/vislist.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* global vl:true */ 4 | 5 | describe('Directive: visList', function () { 6 | 7 | // load the directive's module 8 | beforeEach(module('voyager')); 9 | 10 | beforeEach(module('voyager', function($provide) { 11 | $provide.constant('vl', vl); // vl is loaded by karma 12 | })); 13 | 14 | var element, scope, $compile; 15 | 16 | beforeEach(inject(function ($rootScope, _$compile_) { 17 | scope = $rootScope.$new(); 18 | $compile = _$compile_; 19 | })); 20 | 21 | it('should add vis list element', function () { 22 | element = angular.element(''); 23 | element = $compile(element)(scope); 24 | scope.$digest(); 25 | expect(element.find('.vis-list').length).to.eql(1); 26 | }); 27 | }); 28 | -------------------------------------------------------------------------------- /protractor.conf.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var paths = require('./.yo-rc.json')['generator-gulp-angular'].props.paths; 4 | 5 | // An example configuration file. 6 | exports.config = { 7 | // The address of a running selenium server. 8 | //seleniumAddress: 'http://localhost:4444/wd/hub', 9 | //seleniumServerJar: deprecated, this should be set on node_modules/protractor/config.json 10 | 11 | // Capabilities to be passed to the webdriver instance. 12 | capabilities: { 13 | 'browserName': 'chrome' 14 | }, 15 | 16 | // Spec patterns are relative to the current working directly when 17 | // protractor is called. 18 | specs: [paths.e2e + '/**/*.js'], 19 | 20 | // Options to be passed to Jasmine-node. 21 | jasmineNodeOpts: { 22 | showColors: true, 23 | defaultTimeoutInterval: 30000 24 | } 25 | }; 26 | -------------------------------------------------------------------------------- /src/components/fieldlistitem/fieldlistitem.scss: -------------------------------------------------------------------------------- 1 | $exclude-color: #F2BFBF; 2 | 3 | .field-list-item { 4 | display: flex; 5 | flex-direction: row; 6 | width: 100%; 7 | margin-right: -3px; 8 | 9 | input[type=checkbox] { 10 | flex-shrink: 0; 11 | margin-top: 3px; 12 | margin-right: 3px; 13 | } 14 | 15 | .selection { 16 | color: #ddd; 17 | font-size: 15px; 18 | margin-right: 5px; 19 | margin-top: 2px; 20 | &.fa-check-square { 21 | color: #008CBA; 22 | } 23 | &.fa-minus-circle { 24 | color: $exclude-color; //excluded 25 | } 26 | } 27 | 28 | .field-info { 29 | margin-right: 0; 30 | } 31 | } 32 | 33 | .inclusion a { 34 | .fa-minus-circle { 35 | color: $exclude-color; 36 | } 37 | } 38 | 39 | .inclusion a.inactive, .inclusion a.inactive .fa-minus-circle { 40 | color: #ccc; 41 | &:hover { 42 | color: #125369; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/components/encodingvariations/encodingvariations.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* global vl:true */ 4 | 5 | describe('Directive: encodingVariations', function () { 6 | 7 | // load the directive's module 8 | beforeEach(module('voyager')); 9 | 10 | var element, scope, $compile; 11 | 12 | 13 | beforeEach(module('voyager', function($provide) { 14 | $provide.constant('vl', vl); // vl is loaded by karma 15 | 16 | var Visrec = { 17 | selectedCluster: null 18 | }; 19 | $provide.value('Visrec', Visrec); 20 | })); 21 | 22 | beforeEach(inject(function ($rootScope, _$compile_) { 23 | scope = $rootScope.$new(); 24 | $compile = _$compile_; 25 | })); 26 | 27 | it('should make hidden element visible', function () { 28 | element = angular.element(''); 29 | element = $compile(element)(scope); 30 | scope.$digest(); 31 | 32 | expect(element.find('.wrapper').length).to.eql(1); 33 | }); 34 | }); -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "voyager", 3 | "version": "0.6.3", 4 | "authors": [ 5 | "Interactive Data Lab (http://idl.cs.washington.edu)" 6 | ], 7 | "dependencies": { 8 | "zeptojs": "~1.1.4", 9 | "angular-cookies": "~1.4.1", 10 | "angular-touch": "~1.4.1", 11 | "angular-sanitize": "~1.4.1", 12 | "angular-ui-router": "~0.2.15", 13 | "angular": "~1.4.1", 14 | "papaparse": "~4.1.1", 15 | "tv4": "~1.1.12", 16 | "d3": "~3.5.5", 17 | "angular-zeroclipboard": "~0.4.0", 18 | "jquery": "~2.1.4", 19 | "chronicle": "~1.0.9", 20 | "fontawesome": "~4.3.0", 21 | "angular-tooltips": "~0.1.13", 22 | "angular-order-object-by": "~1.1.0", 23 | "angular-websql": "~1.0.2", 24 | "datalib": "^1.3.0", 25 | "viscompass": "~0.5.8", 26 | "vega-lite": "~0.7.12", 27 | "vega-lite-ui": "~0.7.14" 28 | }, 29 | "devDependencies": { 30 | "angular-mocks": "~1.4.1" 31 | }, 32 | "resolutions": { 33 | "angular": ">=1.3.15" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/app/index.scss: -------------------------------------------------------------------------------- 1 | // FIXME should use normalize.scss version instead 2 | // but I got some errors so I don't want to spend too much time here now. 3 | @import "../assets/normalize.scss"; 4 | 5 | // injector 6 | // endinjector 7 | 8 | $fa-font-path: "../../bower_components/fontawesome/fonts"; 9 | @import "../../bower_components/fontawesome/scss/font-awesome.scss"; 10 | @import "../../bower_components/angular-tooltips/dist/angular-tooltips"; 11 | 12 | .top{ 13 | z-index: 100; 14 | } 15 | 16 | .absolute-top-right { 17 | position: absolute; 18 | top: 0; 19 | right: 0; 20 | } 21 | 22 | 23 | .pane.data-pane { 24 | width: 250px; 25 | flex-shrink: 0; 26 | } 27 | 28 | .relative{ 29 | position: relative; 30 | } 31 | 32 | .pill { 33 | font-weight: bold; 34 | } 35 | 36 | .projection-header { 37 | background: #ddd; 38 | } 39 | 40 | .aggregate-header { 41 | background: #eee; 42 | } 43 | 44 | .pane.vis-list-pane { 45 | flex-grow: 1; 46 | overflow: hidden; 47 | } 48 | 49 | .field-set-info, .schema{ 50 | margin-bottom: 10px; 51 | } 52 | -------------------------------------------------------------------------------- /src/components/fieldlistitem/fieldlistitem.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe('Directive: fieldListItem', function() { 4 | 5 | // load the directive's module 6 | beforeEach(module('voyager')); 7 | 8 | 9 | var element, scope, $compile; 10 | 11 | beforeEach(module('voyager', function($provide) { 12 | var mock = { 13 | schema: ['foo', 'bar', 'baz'], 14 | stats: { 15 | a: {} 16 | }, 17 | onUpdate: [] 18 | }; 19 | $provide.value('Dataset', mock); 20 | })); 21 | 22 | 23 | beforeEach(inject(function ($rootScope, _$compile_) { 24 | scope = $rootScope.$new(); 25 | scope.field = { 26 | selected: false, 27 | name: 'a' 28 | }; 29 | 30 | $compile = _$compile_; 31 | })); 32 | 33 | it('should make hidden element visible', function() { 34 | element = angular.element(''); 35 | element = $compile(element)(scope); 36 | scope.$digest(); 37 | 38 | expect(element.find('.field-info').length).to.eql(1); 39 | }); 40 | }); 41 | -------------------------------------------------------------------------------- /gulp/e2e-tests.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var gulp = require('gulp'); 4 | 5 | var $ = require('gulp-load-plugins')(); 6 | 7 | var browserSync = require('browser-sync'); 8 | 9 | var paths = gulp.paths; 10 | 11 | // Downloads the selenium webdriver 12 | gulp.task('webdriver-update', $.protractor.webdriver_update); 13 | 14 | gulp.task('webdriver-standalone', $.protractor.webdriver_standalone); 15 | 16 | function runProtractor (done) { 17 | 18 | gulp.src(paths.e2e + '/**/*.js') 19 | .pipe($.protractor.protractor({ 20 | configFile: 'protractor.conf.js', 21 | })) 22 | .on('error', function (err) { 23 | // Make sure failed tests cause gulp to exit non-zero 24 | throw err; 25 | }) 26 | .on('end', function () { 27 | // Close browser sync server 28 | browserSync.exit(); 29 | done(); 30 | }); 31 | } 32 | 33 | gulp.task('protractor', ['protractor:src']); 34 | gulp.task('protractor:src', ['serve:e2e', 'webdriver-update'], runProtractor); 35 | gulp.task('protractor:dist', ['serve:e2e-dist', 'webdriver-update'], runProtractor); 36 | -------------------------------------------------------------------------------- /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 | gitsha=$(git rev-parse HEAD) 27 | version=$(cat package.json | jq .version | sed -e 's/^"//' -e 's/"$//') 28 | 29 | git clone git@github.com:uwdata/voyager.git gh-pages 30 | cd gh-pages 31 | git checkout gh-pages 32 | cd .. 33 | gulp 34 | rm -rf dist/.git 35 | mv gh-pages/.git dist 36 | rm -rf gh-pages 37 | cd dist 38 | git add . 39 | git commit -am "release version=$version gitsha=$gitsha" 40 | git push 41 | cd .. 42 | -------------------------------------------------------------------------------- /src/components/functionlist/functionlist.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe('Directive: functionList', function() { 4 | 5 | // load the directive's module 6 | beforeEach(module('voyager')); 7 | 8 | var scope, $compile; 9 | 10 | beforeEach(inject(function ($rootScope, _$compile_) { 11 | scope = $rootScope.$new(); 12 | $compile = _$compile_; 13 | 14 | scope = $rootScope.$new(); 15 | scope.schema = { 16 | properties: { 17 | aggregate: { 18 | supportedEnums: { 19 | Q: ['a', 'b'], 20 | undefined: [] 21 | } 22 | }, 23 | timeUnit: { 24 | supportedEnums: { 25 | T: ['f1','f2'] 26 | } 27 | }, 28 | bin: { 29 | supportedTypes: { 30 | Q: true 31 | } 32 | } 33 | } 34 | }; 35 | scope.pills = { 36 | x: { type: 'Q', name: 'x'}, 37 | y: null, 38 | color: { type: 'T', name: 'c'}, 39 | update: function() {} 40 | }; 41 | scope.encType = 'x'; 42 | scope.encType2 = 'y'; 43 | scope.encType3 = 'color'; 44 | })); 45 | 46 | it('should have correct number of radio', function() { 47 | // FIXME 48 | }); 49 | 50 | }); 51 | -------------------------------------------------------------------------------- /src/components/fieldlist/fieldlist.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* global vl:true */ 4 | 5 | describe('Directive: fieldList', function () { 6 | 7 | // load the directive's module 8 | beforeEach(module('voyager')); 9 | 10 | var element, scope, $compile; 11 | 12 | beforeEach(module('voyager', function($provide) { 13 | var mockDataschema = [{ 14 | name: 'a', 15 | type: 'Q', 16 | }, { 17 | name: 'b', 18 | type: 'O', 19 | }]; 20 | 21 | var mockDataset = { 22 | datasets: [{name: 'foo'}, {name: 'bar'}], 23 | dataset: null, 24 | fieldOrder: function() {return 0;}, 25 | dataschema: mockDataschema, 26 | update: function() {}, 27 | stats: { 28 | a: {}, 29 | b: {} 30 | }, 31 | onUpdate: [] 32 | }; 33 | mockDataset.dataset = mockDataset.datasets[0]; 34 | $provide.value('Dataset', mockDataset); 35 | 36 | $provide.constant('vl', vl); // vl is loaded by karma 37 | })); 38 | 39 | 40 | beforeEach(inject(function ($rootScope, _$compile_) { 41 | scope = $rootScope.$new(); 42 | $compile = _$compile_; 43 | })); 44 | 45 | it('should make hidden element visible', function () { 46 | element = angular.element(''); 47 | element = $compile(element)(scope); 48 | scope.$digest(); 49 | expect(element.find('.field').length).to.eql(2); 50 | }); 51 | }); 52 | -------------------------------------------------------------------------------- /gulp/util.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var gulp = require('gulp'), 4 | fs = require('fs'), 5 | argv = require('yargs').argv, 6 | shell = require('gulp-shell'); 7 | 8 | function find(f, path) { 9 | path = path || ''; 10 | 11 | if (fs.existsSync(path + 'src/app/' + f)) { 12 | return 'src/app/' + f + '/'; 13 | } 14 | if (fs.existsSync(path + 'src/components/' + f)) { 15 | return 'src/components/' + f + '/'; 16 | } 17 | if (fs.existsSync(path + 'gulp/' + f + '.js')) { 18 | return 'gulp/'; 19 | } 20 | return ''; 21 | } 22 | 23 | function fixjsstyle(){ 24 | var opt = '--nojsdoc --max_line_length=120 --disable=200,201,202,210,211,212,213,214,215,216,217,218,219,220,221,222,223,224,225,226,227,230,231,232,233,250,251,252', 25 | fixcmd = 'fixjsstyle '+ opt + ' <%= file.path %>', 26 | gjslintcmd = 'gjslintcmd' + opt + ' <%= file.path %>'; 27 | 28 | if(argv.f){ 29 | var path = find(argv.f); 30 | console.log(path +'.*'); 31 | gulp.src(path +'.*') 32 | .pipe(shell(fixcmd)); 33 | } else { 34 | gulp.src([ 35 | 'src/**/*.js', 36 | 'gulp/*.js', 37 | 'e2e/**/*.js', 38 | 'gulpfile.js', 39 | 'karma.conf.js', 40 | 'protractor.conf.js' 41 | ]) 42 | .pipe(shell([fixcmd, gjslintcmd])); 43 | } 44 | 45 | } 46 | 47 | gulp.task('f', fixjsstyle); 48 | gulp.task('fix', fixjsstyle); 49 | gulp.task('fixjsstyle', fixjsstyle); -------------------------------------------------------------------------------- /gulp/inject.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var gulp = require('gulp'); 4 | 5 | var paths = gulp.paths; 6 | 7 | var $ = require('gulp-load-plugins')(); 8 | 9 | var wiredep = require('wiredep').stream, 10 | series = require('stream-series'); 11 | 12 | gulp.task('inject', ['styles'], function () { 13 | 14 | var injectStyles = gulp.src([ 15 | paths.tmp + '/serve/{app,components}/**/*.css', 16 | '!' + paths.tmp + '/serve/app/vendor.css' 17 | ], { read: false }); 18 | 19 | var injectVendor = gulp.src([ 20 | paths.src + '/vendor/**/*.js' 21 | ], { read: false }); 22 | 23 | var injectScripts = gulp.src([ 24 | paths.src + '/{app,components}/**/*.js', 25 | '!' + paths.src + '/{app,components}/**/*.spec.js', 26 | '!' + paths.src + '/{app,components}/**/*.mock.js' 27 | ]).pipe($.angularFilesort()); 28 | 29 | var injectOptions = { 30 | ignorePath: [paths.src, paths.tmp + '/serve'], 31 | addRootSlash: false 32 | }; 33 | 34 | var wiredepOptions = { 35 | directory: 'bower_components', 36 | exclude: [/bootstrap\.css/, /foundation\.css/], 37 | overrides: { 38 | angular: { 39 | dependencies: { 40 | jquery: '*' 41 | } 42 | } 43 | } 44 | }; 45 | 46 | return gulp.src(paths.src + '/*.html') 47 | .pipe($.inject(injectStyles, injectOptions)) 48 | .pipe($.inject(series(injectVendor, injectScripts), injectOptions)) 49 | .pipe(wiredep(wiredepOptions)) 50 | .pipe(gulp.dest(paths.tmp + '/serve')); 51 | 52 | }); 53 | -------------------------------------------------------------------------------- /src/app/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | /* globals window */ 3 | 4 | angular.module('voyager', ['vlui', 5 | 'zeroclipboard', 6 | '720kb.tooltips', 7 | 'LocalStorageModule', 8 | 'ngOrderObjectBy', 9 | 'angular-websql', 10 | 'Chronicle', 11 | 'ngCookies', 12 | 'ngTouch', 13 | 'ngSanitize', 14 | 'ui.router']) 15 | .constant('_', window._) 16 | .constant('jQuery', window.$) 17 | .constant('vl', window.vl) 18 | .constant('vg', window.vg) 19 | .constant('cp', window.cp) 20 | .constant('tv4', window.tv4) 21 | .constant('Papa', window.Papa) 22 | .constant('Blob', window.Blob) 23 | .constant('URL', window.URL) 24 | .constant('Tether', window.Tether) 25 | .constant('Drop', window.Drop) 26 | .constant('dl', window.dl) 27 | .config(['uiZeroclipConfigProvider', function(uiZeroclipConfigProvider) { 28 | // config ZeroClipboard 29 | uiZeroclipConfigProvider.setZcConf({ 30 | swfPath: 'bower_components/zeroclipboard/dist/ZeroClipboard.swf' 31 | }); 32 | }]) 33 | .config(function ($stateProvider, $urlRouterProvider, consts) { 34 | window.vl.extend(consts, { 35 | debug: true, 36 | debugInList: true, 37 | numInitClusters: 15, 38 | numMoreClusters: 9, 39 | appId: 'voyager', 40 | enableExclude: true 41 | }); 42 | 43 | $stateProvider 44 | .state('home', { 45 | url: '/', 46 | templateUrl: 'app/main/main.html', 47 | controller: 'MainCtrl' 48 | }); 49 | 50 | $urlRouterProvider.otherwise('/'); 51 | }) 52 | ; 53 | -------------------------------------------------------------------------------- /gulp/styles.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var gulp = require('gulp'); 4 | 5 | var paths = gulp.paths; 6 | 7 | var $ = require('gulp-load-plugins')(); 8 | 9 | gulp.task('styles', function () { 10 | 11 | var sassOptions = { 12 | style: 'expanded' 13 | }; 14 | 15 | var injectFiles = gulp.src([ 16 | 'bower_components/vega-lite-ui/vlui.scss', 17 | paths.src + '/{app,components}/**/*.scss', 18 | '!' + paths.src + '/app/index.scss', 19 | '!' + paths.src + '/app/vendor.scss' 20 | ], { read: false }); 21 | 22 | var injectOptions = { 23 | transform: function(filePath) { 24 | filePath = filePath.replace(paths.src + '/app/', ''); 25 | filePath = filePath.replace(paths.src + '/components/', '../components/'); 26 | return '@import \'' + filePath + '\';'; 27 | }, 28 | starttag: '// injector', 29 | endtag: '// endinjector', 30 | addRootSlash: false 31 | }; 32 | 33 | var indexFilter = $.filter('index.scss'); 34 | 35 | return gulp.src([ 36 | paths.src + '/app/index.scss', 37 | paths.src + '/app/vendor.scss' 38 | ]) 39 | .pipe(indexFilter) 40 | .pipe($.inject(injectFiles, injectOptions)) 41 | .pipe(indexFilter.restore()) 42 | // TODO make compass work 43 | // .pipe($.compass({ 44 | // project: paths.src +'/app/', 45 | // css: paths.tmp + '/serve/app/' 46 | // })) 47 | .pipe($.sass(sassOptions)) 48 | .pipe($.autoprefixer()) 49 | .on('error', function handleError(err) { 50 | console.error(err.toString()); 51 | this.emit('end'); 52 | }) 53 | .pipe(gulp.dest(paths.tmp + '/serve/app/')); 54 | }); 55 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /gulp/server.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var gulp = require('gulp'); 4 | 5 | var paths = gulp.paths; 6 | 7 | var util = require('util'); 8 | 9 | var browserSync = require('browser-sync'); 10 | 11 | var middleware = require('./proxy'); 12 | 13 | function browserSyncInit(baseDir, files, browser) { 14 | browser = browser === undefined ? 'google chrome' : browser; 15 | 16 | var routes = null; 17 | if(baseDir === paths.src || (util.isArray(baseDir) && baseDir.indexOf(paths.src) !== -1)) { 18 | routes = { 19 | '/bower_components': 'bower_components' 20 | }; 21 | } 22 | 23 | browserSync.instance = browserSync.init(files, { 24 | startPath: '/', 25 | server: { 26 | baseDir: baseDir, 27 | middleware: middleware, 28 | routes: routes 29 | }, 30 | // port: 3001, 31 | browser: browser 32 | }); 33 | } 34 | 35 | gulp.task('serve', ['watch', 'jshint'], function () { 36 | browserSyncInit([ 37 | paths.tmp + '/serve', 38 | paths.src 39 | ], [ 40 | paths.tmp + '/serve/{app,components}/**/*.css', 41 | paths.src + '/{app,components}/**/*.js', 42 | paths.src + 'src/assets/images/**/*', 43 | paths.tmp + '/serve/*.html', 44 | paths.tmp + '/serve/{app,components}/**/*.html', 45 | paths.src + '/{app,components}/**/*.html' 46 | ]); 47 | 48 | gulp.start('test:auto'); 49 | }); 50 | 51 | gulp.task('serve:dist', ['build'], function () { 52 | browserSyncInit(paths.dist); 53 | }); 54 | 55 | gulp.task('serve:e2e', ['inject'], function () { 56 | browserSyncInit([paths.tmp + '/serve', paths.src], null, []); 57 | }); 58 | 59 | gulp.task('serve:e2e-dist', ['build'], function () { 60 | browserSyncInit(paths.dist, null, []); 61 | }); 62 | -------------------------------------------------------------------------------- /src/data/crimea.json: -------------------------------------------------------------------------------- 1 | [ 2 | { "date": "4/1854", "wounds": 0, "other": 110, "disease": 110 }, 3 | { "date": "5/1854", "wounds": 0, "other": 95, "disease": 105 }, 4 | { "date": "6/1854", "wounds": 0, "other": 40, "disease": 95 }, 5 | { "date": "7/1854", "wounds": 0, "other": 140, "disease": 520 }, 6 | { "date": "8/1854", "wounds": 20, "other": 150, "disease": 800 }, 7 | { "date": "9/1854", "wounds": 220, "other": 230, "disease": 740 }, 8 | { "date": "10/1854", "wounds": 305, "other": 310, "disease": 600 }, 9 | { "date": "11/1854", "wounds": 480, "other": 290, "disease": 820 }, 10 | { "date": "12/1854", "wounds": 295, "other": 310, "disease": 1100 }, 11 | { "date": "1/1855", "wounds": 230, "other": 460, "disease": 1440 }, 12 | { "date": "2/1855", "wounds": 180, "other": 520, "disease": 1270 }, 13 | { "date": "3/1855", "wounds": 155, "other": 350, "disease": 935 }, 14 | { "date": "4/1855", "wounds": 195, "other": 195, "disease": 560 }, 15 | { "date": "5/1855", "wounds": 180, "other": 155, "disease": 550 }, 16 | { "date": "6/1855", "wounds": 330, "other": 130, "disease": 650 }, 17 | { "date": "7/1855", "wounds": 260, "other": 130, "disease": 430 }, 18 | { "date": "8/1855", "wounds": 290, "other": 110, "disease": 490 }, 19 | { "date": "9/1855", "wounds": 355, "other": 100, "disease": 290 }, 20 | { "date": "10/1855", "wounds": 135, "other": 95, "disease": 245 }, 21 | { "date": "11/1855", "wounds": 100, "other": 140, "disease": 325 }, 22 | { "date": "12/1855", "wounds": 40, "other": 120, "disease": 215 }, 23 | { "date": "1/1856", "wounds": 0, "other": 160, "disease": 160 }, 24 | { "date": "2/1856", "wounds": 0, "other": 100, "disease": 100 }, 25 | { "date": "3/1856", "wounds": 0, "other": 125, "disease": 90 } 26 | ] -------------------------------------------------------------------------------- /src/components/encodingvariations/encodingvariations.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * @ngdoc directive 5 | * @name voyager.directive:encodingVariations 6 | * @description 7 | * # encodingVariations 8 | */ 9 | angular.module('voyager') 10 | .directive('encodingVariations', function (Visrec, Bookmarks, consts, $document, _, Logger) { 11 | 12 | return { 13 | templateUrl: 'components/encodingvariations/encodingvariations.html', 14 | restrict: 'E', 15 | replace: true, 16 | scope: {}, 17 | link: function postLink(scope/*, element, attrs*/) { 18 | scope.Visrec = Visrec; 19 | scope.Bookmarks = Bookmarks; 20 | scope.consts = consts; 21 | scope._ = _; 22 | 23 | function escape(e) { 24 | 25 | if (e.keyCode === 27) { 26 | console.log('escape'); 27 | scope.close(); 28 | angular.element($document).off('keydown', escape); 29 | } 30 | } 31 | 32 | angular.element($document).on('keydown', escape); 33 | 34 | scope.isInList = function(fieldSetKey) { 35 | return Visrec.selectedCluster && 36 | fieldSetKey === Visrec.selectedCluster.key; 37 | }; 38 | 39 | scope.select = function(subcluster) { 40 | scope.selectedSubcluster = subcluster; 41 | Logger.logInteraction(Logger.actions.CLUSTER_SELECT, subcluster); 42 | }; 43 | 44 | scope.close = function() { 45 | Logger.logInteraction(Logger.actions.DRILL_DOWN_CLOSE, Visrec.selectedCluster.key); 46 | scope.Visrec.selectedCluster = null; 47 | 48 | }; 49 | 50 | scope.$watch('Visrec.selectedCluster', function(selectedCluster) { 51 | scope.selectedSubcluster = selectedCluster ? selectedCluster[0] : null; 52 | }); 53 | } 54 | }; 55 | }); 56 | -------------------------------------------------------------------------------- /gulp/proxy.js: -------------------------------------------------------------------------------- 1 | /*jshint unused:false */ 2 | 3 | /*************** 4 | 5 | This file allow to configure a proxy system plugged into BrowserSync 6 | in order to redirect backend requests while still serving and watching 7 | files from the web project 8 | 9 | IMPORTANT: The proxy is disabled by default. 10 | 11 | If you want to enable it, watch at the configuration options and finally 12 | change the `module.exports` at the end of the file 13 | 14 | ***************/ 15 | 16 | 'use strict'; 17 | 18 | var httpProxy = require('http-proxy'); 19 | var chalk = require('chalk'); 20 | 21 | /* 22 | * Location of your backend server 23 | */ 24 | var proxyTarget = 'http://server/context/'; 25 | 26 | var proxy = httpProxy.createProxyServer({ 27 | target: proxyTarget 28 | }); 29 | 30 | proxy.on('error', function(error, req, res) { 31 | res.writeHead(500, { 32 | 'Content-Type': 'text/plain' 33 | }); 34 | 35 | console.error(chalk.red('[Proxy]'), error); 36 | }); 37 | 38 | /* 39 | * The proxy middleware is an Express middleware added to BrowserSync to 40 | * handle backend request and proxy them to your backend. 41 | */ 42 | function proxyMiddleware(req, res, next) { 43 | /* 44 | * This test is the switch of each request to determine if the request is 45 | * for a static file to be handled by BrowserSync or a backend request to proxy. 46 | * 47 | * The existing test is a standard check on the files extensions but it may fail 48 | * for your needs. If you can, you could also check on a context in the url which 49 | * may be more reliable but can't be generic. 50 | */ 51 | if (/\.(html|css|js|png|jpg|jpeg|gif|ico|xml|rss|txt|eot|svg|ttf|woff|cur)(\?((r|v|rel|rev)=[\-\.\w]*)?)?$/.test(req.url)) { 52 | next(); 53 | } else { 54 | proxy.web(req, res); 55 | } 56 | } 57 | 58 | /* 59 | * This is where you activate or not your proxy. 60 | * 61 | * The first line activate if and the second one ignored it 62 | */ 63 | 64 | //module.exports = [proxyMiddleware]; 65 | module.exports = []; 66 | -------------------------------------------------------------------------------- /.yo-rc.json: -------------------------------------------------------------------------------- 1 | { 2 | "generator-gulp-angular": { 3 | "props": { 4 | "paths": { 5 | "src": "src", 6 | "dist": "dist", 7 | "e2e": "e2e", 8 | "tmp": ".tmp" 9 | }, 10 | "angularVersion": "~1.3.4", 11 | "angularModules": [ 12 | { 13 | "name": "angular-animate", 14 | "module": "ngAnimate" 15 | }, 16 | { 17 | "name": "angular-cookies", 18 | "module": "ngCookies" 19 | }, 20 | { 21 | "name": "angular-touch", 22 | "module": "ngTouch" 23 | }, 24 | { 25 | "name": "angular-sanitize", 26 | "module": "ngSanitize" 27 | } 28 | ], 29 | "jQuery": { 30 | "name": "zeptojs", 31 | "version": "~1.1.4" 32 | }, 33 | "resource": { 34 | "name": null, 35 | "version": null, 36 | "module": null 37 | }, 38 | "router": { 39 | "name": "angular-ui-router", 40 | "version": "~0.2.13", 41 | "module": "ui.router" 42 | }, 43 | "ui": { 44 | "key": "none", 45 | "name": null, 46 | "version": null, 47 | "module": null 48 | }, 49 | "cssPreprocessor": { 50 | "key": "node-sass", 51 | "extension": "scss", 52 | "module": "gulp-sass", 53 | "version": "~1.1.0" 54 | }, 55 | "jsPreprocessor": { 56 | "key": "none", 57 | "extension": "js", 58 | "srcExtension": "js", 59 | "module": null, 60 | "version": null 61 | }, 62 | "htmlPreprocessor": { 63 | "key": "none", 64 | "extension": "html", 65 | "module": null, 66 | "version": null 67 | }, 68 | "bootstrapComponents": { 69 | "name": null, 70 | "version": null, 71 | "key": null, 72 | "module": null 73 | }, 74 | "foundationComponents": { 75 | "name": null, 76 | "version": null, 77 | "key": null, 78 | "module": null 79 | } 80 | } 81 | } 82 | } -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Data Voyager 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 57 | 58 | -------------------------------------------------------------------------------- /src/components/fieldlistitem/fieldlistitem.html: -------------------------------------------------------------------------------- 1 |
5 | 6 | 11 | 12 | 23 | 24 |
25 | 52 |
53 |
54 | -------------------------------------------------------------------------------- /src/app/fields/fields.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('voyager') 4 | .factory('Fields', function(_, Dataset, vl, cp, Logger){ 5 | 6 | var Fields = { 7 | fields: {}, 8 | highlighted: {}, 9 | selected: [], 10 | selectedKey: null 11 | }; 12 | 13 | function resetField(field) { 14 | field.selected = undefined; 15 | field._any = !vl.field.isTypes(field, ['O', 'N']) && field.aggregate!=='count'; 16 | delete field._raw; 17 | delete field._aggregate; 18 | delete field._timeUnit; 19 | } 20 | 21 | Fields.updateSchema = function(dataschema) { 22 | dataschema = dataschema || Dataset.dataschema; 23 | 24 | Fields.fields = _(dataschema).reduce(function(d, field){ 25 | resetField(field); 26 | d[field.name] = field; 27 | return d; 28 | }, {}); 29 | Fields.highlighted = {}; 30 | }; 31 | 32 | Fields.update = function() { 33 | var list = Fields.getList(); 34 | Fields.selected = list.filter(function(d) { return d.selected; }); 35 | Fields.selectedPKey = cp.gen.projections.key(Fields.selected); 36 | 37 | Logger.logInteraction(Logger.actions.FIELDS_CHANGE, { 38 | selected: Fields.selected, 39 | list: list 40 | }); 41 | 42 | return list; 43 | }; 44 | 45 | Fields.reset = function() { 46 | _.each(Fields.fields, resetField); 47 | }; 48 | 49 | Fields.getList = function() { 50 | var list = _.sortBy(_.values(Fields.fields), function(field) { 51 | return Dataset.fieldOrderBy.typeThenName(field); 52 | }); 53 | return list; 54 | }; 55 | 56 | Fields.setSelected = function(fieldName, val) { 57 | (Fields.fields[fieldName] || {}).selected = val; 58 | }; 59 | 60 | Fields.toggleSelected = function(fieldName) { 61 | var field = Fields.fields[fieldName] || {}; 62 | field.selected = field.selected ? undefined : true; 63 | }; 64 | 65 | Fields.isSelected = function(fieldName) { 66 | return (Fields.fields[fieldName] || {}).selected; 67 | }; 68 | 69 | Fields.setHighlight = function(fieldName, val) { 70 | Fields.highlighted[fieldName] = val; 71 | }; 72 | 73 | Dataset.onUpdate.push(Fields.updateSchema); 74 | 75 | return Fields; 76 | }); 77 | -------------------------------------------------------------------------------- /src/app/main/main.controller.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('voyager') 4 | .controller('MainCtrl', function($scope, $document, Chronicle, Visrec, Config, Dataset, Fields, Bookmarks, Logger, Drop, consts) { 5 | 6 | Config.config.useRawDomain = true; 7 | 8 | $scope.consts = consts; 9 | $scope.canUndo = false; 10 | $scope.canRedo = false; 11 | 12 | $scope.Visrec = Visrec; 13 | $scope.Fields = Fields; 14 | $scope.Dataset = Dataset; 15 | $scope.Config = Config; 16 | $scope.Logger = Logger; 17 | $scope.Bookmarks = Bookmarks; 18 | 19 | $scope.showBookmark = false; 20 | $scope.hideBookmark = function() { 21 | $scope.showBookmark = false; 22 | }; 23 | 24 | Bookmarks.load(); 25 | 26 | Dataset.update(Dataset.dataset).then(function() { 27 | // initially set dataset and update fields 28 | Config.updateDataset(Dataset.dataset); 29 | Fields.updateSchema(Dataset.dataschema); 30 | 31 | $scope.chron = Chronicle.record('Fields.fields', $scope, true, 32 | ['Visrec.numClustersGenerated', 'Dataset.dataset', 'Dataset.dataschema', 'Dataset.stats', 'Config.config']); 33 | 34 | $scope.canUndoRedo = function() { 35 | $scope.canUndo = $scope.chron.canUndo(); 36 | $scope.canRedo = $scope.chron.canRedo(); 37 | }; 38 | $scope.chron.addOnAdjustFunction($scope.canUndoRedo); 39 | $scope.chron.addOnUndoFunction($scope.canUndoRedo); 40 | $scope.chron.addOnRedoFunction($scope.canUndoRedo); 41 | 42 | $scope.chron.addOnUndoFunction(function() { 43 | Logger.logInteraction(Logger.actions.UNDO); 44 | }); 45 | $scope.chron.addOnRedoFunction(function() { 46 | Logger.logInteraction(Logger.actions.REDO); 47 | }); 48 | 49 | angular.element($document).on('keydown', function(e) { 50 | if (e.keyCode === 'Z'.charCodeAt(0) && (e.ctrlKey || e.metaKey) && !e.shiftKey) { 51 | $scope.chron.undo(); 52 | $scope.$digest(); 53 | return false; 54 | } else if (e.keyCode === 'Y'.charCodeAt(0) && (e.ctrlKey || e.metaKey)) { 55 | $scope.chron.redo(); 56 | $scope.$digest(); 57 | return false; 58 | } else if (e.keyCode === 'Z'.charCodeAt(0) && (e.ctrlKey || e.metaKey) && e.shiftKey) { 59 | $scope.chron.redo(); 60 | $scope.$digest(); 61 | return false; 62 | } 63 | }); 64 | }); 65 | 66 | }); 67 | -------------------------------------------------------------------------------- /src/components/vislist/vislist.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * @ngdoc directive 5 | * @name voyager.directive:visList 6 | * @description 7 | * # visList 8 | */ 9 | angular.module('voyager') 10 | .directive('visList', function (Fields, Visrec, vl, jQuery, consts, _, Logger) { 11 | return { 12 | templateUrl: 'components/vislist/vislist.html', 13 | restrict: 'E', 14 | replace: true, 15 | scope: {}, 16 | link: function postLink(scope , element /*, attrs*/) { 17 | scope.Fields = Fields; 18 | scope.Visrec = Visrec; 19 | scope.consts = consts; 20 | scope.shorthands = vl.field.shorthands; 21 | scope.limit = consts.numInitClusters; 22 | 23 | element.bind('scroll', function(){ 24 | if(jQuery(this).scrollTop() + jQuery(this).innerHeight() >= jQuery(this)[0].scrollHeight){ 25 | if (scope.limit < Visrec.fieldSets.length) { 26 | scope.increaseLimit(); 27 | } 28 | } 29 | }); 30 | 31 | scope.increaseLimit = function() { 32 | if(scope.limit + consts.numMoreClusters > Visrec.numClustersGenerated) { 33 | Visrec.update.clusters(scope.limit + consts.numMoreClusters); 34 | } 35 | 36 | scope.limit += consts.numMoreClusters; 37 | Logger.logInteraction(Logger.actions.LOAD_MORE, scope.limit); 38 | }; 39 | 40 | scope.select = function(fieldSet, cluster/*, $index*/) { 41 | Logger.logInteraction(Logger.actions.DRILL_DOWN_OPEN, fieldSet.key); 42 | Visrec.selectedFieldSet = fieldSet; 43 | Visrec.selectedCluster = cluster; 44 | }; 45 | 46 | scope.isInList = function(fieldSetKey) { 47 | return fieldSetKey in Visrec.chartClusters; 48 | }; 49 | 50 | function updateFields() { 51 | scope.limit = consts.numInitClusters; 52 | element.scrollTop(0); // scroll the the top 53 | var fieldList = Fields.update(); 54 | Visrec.update.projections(fieldList); 55 | } 56 | 57 | var dUpdateFields = _.debounce(updateFields, 200, {maxWait: 1500}); 58 | 59 | scope.$watch('Fields.fields', function(fields, oldFields) { 60 | if (!oldFields || _.keys(oldFields).length === 0 ) { // first time! 61 | updateFields(); 62 | } else { 63 | dUpdateFields(); 64 | } 65 | }, true); 66 | 67 | } 68 | }; 69 | }); 70 | -------------------------------------------------------------------------------- /src/components/functionlist/functionlist.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('voyager') 4 | .directive('functionList', function(_, vl, Logger) { 5 | return { 6 | templateUrl: 'components/functionlist/functionlist.html', 7 | restrict: 'E', 8 | scope: { 9 | field: '=' 10 | }, 11 | link: function(scope /*,element, attrs*/) { 12 | var BIN='bin', RAW='raw', COUNT='count', ANY = 'AUTO'; 13 | 14 | scope.func = { 15 | selected: ANY, 16 | list: [ANY] 17 | }; 18 | 19 | function getTimeUnits(type) { 20 | return type === 'T' ? vl.schema.timeUnits : []; 21 | } 22 | 23 | function getAggrs(type) { 24 | return vl.schema.aggregate.supportedEnums[type]; 25 | } 26 | 27 | // when the function select is updated, propagates change the parent 28 | scope.functionChanged = function(selectedFunc) { 29 | var field = scope.field, 30 | type = field ? field.type : ''; 31 | 32 | if (!field) { 33 | return; // not ready 34 | } 35 | 36 | field._bin = selectedFunc === BIN || undefined; 37 | field._aggregate = getAggrs(type).indexOf(selectedFunc) !== -1 ? selectedFunc : undefined; 38 | field._timeUnit = getTimeUnits(type).indexOf(selectedFunc) !== -1 ? selectedFunc : undefined; 39 | field._raw = selectedFunc === RAW || undefined; 40 | field._any = selectedFunc === ANY || undefined; 41 | 42 | Logger.logInteraction(Logger.actions.FUNC_CHANGE, selectedFunc); 43 | }; 44 | 45 | scope.$watch('field', function (field) { 46 | // only run this if schema is not null 47 | if (!field) { 48 | return; 49 | } 50 | 51 | var type = field.name ? field.type : ''; 52 | 53 | if (vl.field.isCount(field)) { 54 | scope.func.list=[COUNT]; 55 | scope.func.selected = COUNT; 56 | } else { 57 | var isO = type==='O'; 58 | scope.func.list = ( isO ? [RAW] : [ANY, RAW]) 59 | .concat(getTimeUnits(type)) 60 | .concat(getAggrs(type).filter(function(x) { return x !== COUNT; })) 61 | .concat(type ==='Q' ? [BIN] : []); 62 | 63 | scope.func.selected = field._bin ? BIN : 64 | field._raw ? RAW : 65 | field._aggregate || field._timeUnit || ( isO ? RAW : ANY ); 66 | } 67 | 68 | }, true); 69 | } 70 | }; 71 | }); 72 | -------------------------------------------------------------------------------- /src/app/main/main.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | 30 |

Data Voyager

31 |
32 | 33 |
34 |
35 |
36 |
37 |
38 | 39 |
40 |

Data

41 | 42 |
43 |
44 |
45 | 46 |
47 |
48 |
49 |
50 |
51 |
52 | 53 |
54 | 55 |
56 |
57 |
58 | 61 | 62 | -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var wiredep = require('wiredep'); 4 | 5 | var bowerDeps = wiredep({ 6 | directory: 'bower_components', 7 | dependencies: true, 8 | devDependencies: true 9 | }); 10 | 11 | var src = 'src', 12 | tmp = '.tmp'; 13 | 14 | var testFiles = [ 15 | // add bind polyfill since Function.prototype.bind is missing from PhantomJS 16 | './node_modules/phantomjs-polyfill/bind-polyfill.js', 17 | ].concat(bowerDeps.js).concat([ 18 | src + '/app/index.js', 19 | src + '/**/*.js', 20 | src + '/vendor/*.js', 21 | tmp + '/partials/templateCacheHtml.js' 22 | ]); 23 | 24 | // console.log('testFiles', testFiles); 25 | 26 | module.exports = function(config) { 27 | config.set({ 28 | 29 | // base path that will be used to resolve all patterns (eg. files, exclude) 30 | basePath: '', 31 | 32 | // frameworks to use 33 | // available frameworks: https://npmjs.org/browse/keyword/karma-adapter 34 | frameworks: ['mocha', 'chai-jquery', 'jquery-1.8.3', 'sinon-chai'], 35 | 36 | plugins: [ 37 | 'karma-mocha', 38 | 'karma-chai', 39 | 'karma-sinon-chai', 40 | 'karma-chrome-launcher', 41 | 'karma-phantomjs-launcher', 42 | 'karma-jquery', 43 | 'karma-chai-jquery', 44 | 'karma-mocha-reporter' 45 | ], 46 | 47 | // list of files / patterns to load in the browser 48 | files: testFiles, 49 | 50 | // list of files to exclude 51 | exclude: [], 52 | 53 | // preprocess matching files before serving them to the browser 54 | // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor 55 | preprocessors: {}, 56 | 57 | // test results reporter to use 58 | // possible values: 'dots', 'progress' 59 | // available reporters: https://npmjs.org/browse/keyword/karma-reporter 60 | reporters: ['mocha'], 61 | 62 | // web server port 63 | port: 9875, 64 | 65 | // enable / disable colors in the output (reporters and logs) 66 | colors: true, 67 | 68 | // level of logging 69 | // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG 70 | logLevel: config.LOG_INFO, 71 | 72 | 73 | // enable / disable watching file and executing tests whenever any file changes 74 | autoWatch: true, 75 | 76 | // start these browsers 77 | // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher 78 | browsers: ['PhantomJS'], 79 | 80 | // Continuous Integration mode 81 | // if true, Karma captures browsers, runs the tests and exits 82 | singleRun: false, 83 | 84 | client: { 85 | mocha: { 86 | reporter: 'html', // change Karma's debug.html to the mocha web reporter 87 | ui: 'bdd' 88 | } 89 | } 90 | }); 91 | }; 92 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "voyager", 3 | "version": "0.6.3", 4 | "description": "Faceted Search User Interface for Data Exploration", 5 | "author": { 6 | "name": "UW Interactive Data Lab", 7 | "url": "http://idl.cs.washington.edu" 8 | }, 9 | "collaborators": [ 10 | "Dominik Moritz (http://domoritz.de)", 11 | "Kanit Wongsuphasawat (http://kanitw.yellowpigz.com)", 12 | "Jeffrey Heer (http://jheer.org)" 13 | ], 14 | "repository": { 15 | "type": "git", 16 | "url": "https://github.com/uwdata/voyager.git" 17 | }, 18 | "license": "BSD-3-Clause", 19 | "scripts": { 20 | "deploy": "npm run lint && npm run test && scripts/deploy.sh", 21 | "lint": "gulp jshint", 22 | "test": "gulp test" 23 | }, 24 | "dependencies": { 25 | "stream-series": "^0.1.1" 26 | }, 27 | "devDependencies": { 28 | "browser-sync": "~2.7.13", 29 | "chai": "^3.0.0", 30 | "chai-jquery": "^2.0.0", 31 | "chalk": "~1.0.0", 32 | "del": "~1.2.0", 33 | "gulp": "~3.9.0", 34 | "gulp-angular-filesort": "~1.1.1", 35 | "gulp-angular-templatecache": "^1.7.0", 36 | "gulp-autoprefixer": "~2.3.1", 37 | "gulp-bump": "^0.3.1", 38 | "gulp-compass": "^2.1.0", 39 | "gulp-consolidate": "~0.1.2", 40 | "gulp-csso": "~1.0.0", 41 | "gulp-filter": "~2.0.2", 42 | "gulp-flatten": "~0.1.0", 43 | "gulp-inject": "~1.3.1", 44 | "gulp-jshint": "~1.11.1", 45 | "gulp-karma": "~0.0.4", 46 | "gulp-load-plugins": "~1.0.0-rc", 47 | "gulp-minify-html": "~1.0.3", 48 | "gulp-ng-annotate": "^1.0.0", 49 | "gulp-protractor": "~1.0.0", 50 | "gulp-rename": "~1.2.2", 51 | "gulp-replace": "~0.5.3", 52 | "gulp-rev": "~5.0.1", 53 | "gulp-rev-replace": "~0.4.2", 54 | "gulp-sass": "~2.0.3", 55 | "gulp-shell": "^0.4.2", 56 | "gulp-size": "~1.2.2", 57 | "gulp-uglify": "~1.2.0", 58 | "gulp-useref": "~1.2.0", 59 | "gulp-util": "^3.0.6", 60 | "http-proxy": "~1.11.1", 61 | "jshint-stylish": "~2.0.1", 62 | "json-stringify-safe": "^5.0.1", 63 | "karma-chai": "^0.1.0", 64 | "karma-chai-jquery": "^1.0.0", 65 | "karma-chrome-launcher": "^0.2.0", 66 | "karma-jquery": "^0.1.0", 67 | "karma-mocha": "^0.2.0", 68 | "karma-mocha-reporter": "^1.0.2", 69 | "karma-phantomjs-launcher": "~0.2.0", 70 | "karma-sinon-chai": "^1.0.0", 71 | "main-bower-files": "~2.8.2", 72 | "mocha": "^2.2.5", 73 | "phantomjs-polyfill": "0.0.1", 74 | "protractor": "~2.1.0", 75 | "request": "^2.58.0", 76 | "require-dir": "~0.3.0", 77 | "tough-cookie": "^2.0.0", 78 | "uglify-save-license": "~0.4.1", 79 | "wiredep": "^3.0.0-beta", 80 | "yargs": "^3.14.0" 81 | }, 82 | "engines": { 83 | "node": ">=0.10.0" 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/components/encodingvariations/encodingvariations.html: -------------------------------------------------------------------------------- 1 |