├── .gitignore ├── Cakefile ├── LICENSE ├── README.md ├── examples ├── bars │ └── index.html ├── multiple_displays │ └── index.html └── styles.css ├── fixtures ├── artists.js ├── countries.js ├── countries_old.js ├── documents.json ├── freebase_countries.js ├── playlists.js └── salesmen.js ├── index.ndg ├── lib ├── compiler.jar ├── data.js ├── jquery-1.6.4.js ├── protovis-d3.2.js ├── protovis-r3.2.js ├── qunit.css ├── qunit.js └── underscore.js ├── src ├── intro.js ├── outro.js ├── scene │ ├── actor.js │ ├── actors │ │ ├── circle.js │ │ ├── label.js │ │ ├── path.js │ │ └── rect.js │ ├── behaviors.js │ ├── commands.js │ ├── display.js │ ├── matrix.js │ ├── scene.js │ ├── traverser.js │ └── tween.js ├── sorted_hash │ ├── aggregators.js │ ├── comparators.js │ └── sorted_hash.js └── uv.js ├── test ├── benchmark │ ├── d3.js │ └── index.html ├── collection │ ├── fixtures │ │ ├── countries.js │ │ ├── nested_collection.js │ │ └── playlists.js │ ├── index.html │ └── testsuite.js ├── data_graph.coffee ├── node │ ├── index.html │ └── testsuite.js ├── scene │ ├── index.html │ └── testsuite.js ├── sorted_hash │ ├── index.html │ └── testsuite.js └── visualization │ ├── index.html │ └── testsuite.js ├── unveil.js └── unveil.min.js /.gitignore: -------------------------------------------------------------------------------- 1 | test_log 2 | pkg 3 | pkg/* 4 | */pkg/* 5 | bundle 6 | bundle/* 7 | doc 8 | *.log 9 | log 10 | !log*.rb 11 | */log 12 | log/* 13 | */log/* 14 | coverage 15 | */coverage 16 | lib/dm-more.rb 17 | *.db 18 | nbproject 19 | .DS_Store 20 | rspec_report.html 21 | *.swp 22 | _Yardoc 23 | */ri 24 | 25 | -------------------------------------------------------------------------------- /Cakefile: -------------------------------------------------------------------------------- 1 | # Build script adpated from http://github.com/jashkenas/coffee-script/Cakefile 2 | # ============================================================================== 3 | 4 | fs = require 'fs' 5 | sys = require 'sys' 6 | CoffeeScript = require 'coffee-script' 7 | {helpers} = require 'coffee-script/lib/helpers' 8 | {spawn, exec} = require 'child_process' 9 | 10 | # ANSI terminal colors. 11 | red = '\033[0;31m' 12 | green = '\033[0;32m' 13 | reset = '\033[0m' 14 | 15 | # Commands 16 | compressionCmd = 'java -jar ./lib/compiler.jar --js unveil.js --js_output_file unveil.min.js' 17 | 18 | # Unveil.js source files 19 | files = [ 20 | 'src/intro.js' 21 | 'src/uv.js' 22 | 'src/scene/matrix.js' 23 | 'src/scene/actor.js' 24 | 'src/scene/traverser.js' 25 | 'src/scene/behaviors.js' 26 | 'src/scene/display.js' 27 | 'src/scene/commands.js' 28 | 'src/scene/scene.js' 29 | 'src/scene/tween.js' 30 | 'src/scene/actors/rect.js' 31 | 'src/scene/actors/label.js' 32 | 'src/scene/actors/circle.js' 33 | 'src/scene/actors/path.js' 34 | 'src/outro.js' 35 | ] 36 | 37 | 38 | # Run a CoffeeScript through the node/coffee interpreter. 39 | run = (args) -> 40 | proc = spawn 'bin/coffee', args 41 | proc.stderr.on 'data', (buffer) -> console.log buffer.toString() 42 | proc.on 'exit', (status) -> process.exit(1) if status != 0 43 | 44 | # Log a message with a color. 45 | log = (message, color, explanation) -> 46 | console.log "#{color or ''}#{message}#{reset} #{explanation or ''}" 47 | 48 | # Build from source 49 | build = -> 50 | content = '' 51 | content += fs.readFileSync(file)+'\n' for file in files 52 | fs.writeFileSync('./unveil.js', content, encoding='utf8') 53 | 54 | # Watch a source file for changes 55 | watch = (file) -> 56 | fs.watchFile file, {persistent: true, interval: 300}, (curr, prev) -> 57 | return if curr.mtime.getTime() is prev.mtime.getTime() 58 | build() 59 | log "Sucessfully rebuilt ./unveil.js at #{curr.mtime}", green 60 | 61 | 62 | task 'build:continuously', 'Build continuously (during development)', -> 63 | watch(file) for file in files 64 | 65 | 66 | task 'build', 'Rebuild from source', -> 67 | build() 68 | log 'Sucessfully built ./unveil.js', green 69 | 70 | 71 | task 'build:full', 'Rebuild and create a compressed version', -> 72 | build() 73 | exec compressionCmd, (err, stdout, stderr) -> 74 | throw err if err 75 | log 'Sucessfully built ./unveil.js and ./unveil.min.js', green 76 | 77 | 78 | task 'test', 'run the test suite', -> 79 | helpers.extend global, require 'assert' 80 | passedTests = failedTests = 0 81 | startTime = new Date 82 | originalOk = ok 83 | helpers.extend global, { 84 | ok: (args...) -> passedTests += 1; originalOk(args...) 85 | CoffeeScript: CoffeeScript 86 | } 87 | 88 | process.on 'exit', -> 89 | time = ((new Date - startTime) / 1000).toFixed(2) 90 | message = "passed #{passedTests} tests in #{time} seconds#{reset}" 91 | if failedTests 92 | log "failed #{failedTests} and #{message}", red 93 | else 94 | log message, green 95 | fs.readdir 'test', (err, files) -> 96 | files.forEach (file) -> 97 | return unless file.match(/\.coffee$/i) 98 | fileName = path.join 'test', file 99 | fs.readFile fileName, (err, code) -> 100 | try 101 | CoffeeScript.run code.toString(), {fileName} 102 | catch err 103 | failedTests += 1 104 | log "failed #{fileName}", red, '\n' + err.stack.toString() 105 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | // 2 | // This is free and unencumbered software released into the public domain. 3 | // It was originally initiated by Michael Aufreiter at Quasipartikel Labs. 4 | // 5 | // The software took inspiration from various great open source projects. 6 | // 7 | // Among them: 8 | // 9 | // - Protovis 10 | // - Processing.js 11 | // - Underscore.js 12 | // - CoffeeScript 13 | // 14 | // Anyone is free to copy, modify, publish, use, compile, sell, or 15 | // distribute this software, either in source code form or as a compiled 16 | // binary, for any purpose, commercial or non-commercial, and by any 17 | // means. 18 | // 19 | // 20 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 21 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 22 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 23 | // IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 24 | // OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 25 | // ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 26 | // OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Unveil.js 2 | ================================================================================ 3 | 4 | Unveil is a data exploration and visualization toolkit that utilizes data-driven 5 | software design. 6 | 7 | [Documentation](http://docs.quasipartikel.at/#/unveil) is under construction but already available. 8 | 9 | 10 | Features: 11 | -------------------------------------------------------------------------------- 12 | 13 | * Data Abstractions 14 | * Collection (for simple collections of similar data items) 15 | * DataGraph (for linked data) 16 | * Scene API 17 | * SceneGraph implementation 18 | * Declarative syntax 19 | * Custom Actors (graphical objects) 20 | * Dynamic Actor Properties (you can assign functions instead of values) 21 | * Motion Tweening 22 | * Commands (e.g. save cpu cycles using event-based framerate determination) 23 | * Multiple Displays (the scene can be projected to one or more canvas elements) 24 | * Adjustable drawing order through Graph Traversers 25 | Choose from DepthFirst or BreadthFirst or implement your own Traverser 26 | * Mouse interaction support on Display level (picking, zooming, paning) 27 | 28 | 29 | Getting started 30 | -------------------------------------------------------------------------------- 31 | 32 | Probably the easiest way to get started with Unveil.js is to install the 33 | [Dejavis](http://github.com/michael/dejavis) sandbox, and let it generate an example 34 | visualization, which you can use as a starting point. 35 | 36 | 37 | Examples 38 | -------------------------------------------------------------------------------- 39 | 40 | * [Linechart](http://dejavis.org/linechart) (sandboxed) 41 | * [Scatterplot](http://dejavis.org/scatterplot) (sandboxed) 42 | * [Bullets](http://dejavis.org/bullets) (sandboxed) 43 | * [Stacks](http://dejavis.org/stacks) (sandboxed) 44 | * [Random Bars](http://quasipartikel.at/unveil/examples/random_bars.html) 45 | * [Linechart](http://quasipartikel.at/unveil/examples/linechart.html) 46 | * [Artist Similarities](http://quasipartikel.at/unveil/examples/artist_similarities.html) 47 | 48 | 49 | New declarative Syntax 50 | -------------------------------------------------------------------------------- 51 | 52 | I took some inspiration from Scene.js's [Scene Definition Format](http://www.google.com/url?sa=D&q=http://scenejs.wikispaces.com/JSON%2BScene%2BDefinition&usg=AFQjCNEk85cBgWeuJ9ZZO3XaXpOc2FgDVA) 53 | to give the whole thing an even more declarative feel. 54 | 55 | Actors as well as the whole Scene can now be specified declaratively using a simple Specification Syntax. 56 | 57 | var scene = new uv.Scene({ 58 | actors: [ 59 | { 60 | id: 'moving_rect', 61 | type: "rect", 62 | x: 50, 63 | y: 70, 64 | width: 200, 65 | height: 150, 66 | actors: [ 67 | { 68 | type: "label", 69 | text: "I'm moving" 70 | } 71 | ] 72 | }, 73 | { 74 | id: 'animated_circle', 75 | type: "circle", 76 | x: 50, 77 | y: 70, 78 | radius: 50, 79 | height: 150 80 | } 81 | ] 82 | }); 83 | 84 | After you you've set up your Scene, you need to specify at least one display: 85 | 86 | var display = scene.display({ 87 | container: 'canvas', 88 | width: 500, 89 | height: 320, 90 | zooming: true, 91 | paning: true 92 | }); 93 | 94 | You can attach actors to displays as well. Those objects are typically unaffected by 95 | view transformations, so they stick on their original display position. Display 96 | objects are meant as an overlay to be drawn after the scene objects. 97 | 98 | display.add({ 99 | { 100 | type: 'label', 101 | text: function() { return 'Frames per second: '+ this.scene.fps } 102 | x: 300, 103 | y: 20 104 | } 105 | }); 106 | 107 | 108 | Since all actors have a unique id you can reference them programmatically and add special behavior (e.g. animation). 109 | 110 | scene.get('moving_rect').bind('mouseover', function() { 111 | this.animate('x', 100, 2000, 'bounceEaseInOut'); 112 | this.animate('y', 200, 2000); 113 | }); 114 | 115 | scene.get('moving_rect').bind('click', function() { 116 | this.animate('rotation', Math.PI/2, 2000); 117 | }); 118 | 119 | scene.get('animated_circle').bind('click', function() { 120 | this.animate('radius', 70, 2000); 121 | }); 122 | 123 | 124 | Supported Browsers 125 | -------------------------------------------------------------------------------- 126 | 127 | * Google Chrome 5+ 128 | * Safari 4+ 129 | * Firefox 3.5+ 130 | * Internet Explorer 9+ 131 | * Opera 10+ 132 | 133 | 134 | Roadmap 135 | -------------------------------------------------------------------------------- 136 | 137 | **Unveil.js 0.1** 138 | 139 | * Display actors (actors that stick on on a display rather than on the scene) 140 | * Add an API to get the top most active actor in case there is more than one 141 | object under the cursor. 142 | * API Docs 143 | 144 | 145 | **Unveil.js Stars** 146 | 147 | There should be a dedicated place for commonly used custom actors (stars). I could imagine 148 | putting them in a Github repo, ready for reuse, contributions, etc. Eventually, an online index for 149 | finding the right star for a certain job would also be nice. -------------------------------------------------------------------------------- /examples/bars/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Unveil.js - Bars 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 109 | 110 | 111 | 112 | 113 |

Unveil.js - Bars

114 |
115 | 116 |
117 |
118 |

Choose property

119 | 123 | 124 |

About

125 | 126 |

Unveil.js provides a simple abstraction layer for visualizations to ease 127 | the process of creating re-usable charts. To accomplish this, a data-driven methodology is used. 128 |

129 | 130 | 131 | 132 |

Code

133 | 134 |

Source Code is available at Github

135 | 136 |
A Quasipartikel production
137 |
138 | 139 | 143 | 148 | 149 | 150 | -------------------------------------------------------------------------------- /examples/multiple_displays/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Unveil.js - Multiple displays 5 | 6 | 7 | 8 | 9 | 10 | 11 | 129 | 130 | 131 | 132 | 133 |

Unveil.js - Multiple displays (Zoom and Pan using the mouse / mousewheel)

134 |
135 |
136 | 137 |
138 |
139 | 140 |
141 |
142 | 143 |
144 |

Info

145 | Demonstrates the support for multiple displays to project the scene (world-coordinates) to one or more displays (canvas elements, 146 | that have a local coordinate system). This conforms to the view transformation in a 3D-game terminology. 147 | It allows the scene to be rendered on various displays that can be zoomed and paned independently. 148 | 149 |

Challenge

150 | 151 | For zooming you want to scale around the current mouse position rather than scaling around the origin. 152 | Zooming and Paning are implemented as separate behaviors that can be activated when you specify 153 | the output display. 154 | 155 |

About

156 | 157 |

Unveil.js provides a simple abstraction layer for visualizations to ease 158 | the process of creating re-usable charts. To accomplish this, a data-driven methodology is used. 159 |

160 | 161 |

Code

162 | 163 |

Source Code is available at Github

164 |
A Quasipartikel production
165 |
166 | 167 | 168 | -------------------------------------------------------------------------------- /examples/styles.css: -------------------------------------------------------------------------------- 1 | body {background: #efefef; font-family: Helvetica, 'Arial'; color: #777; font-size: 11px;} 2 | 3 | a {color: darkblue;} 4 | 5 | a:hover {color: lightblue;} 6 | 7 | #canvas { position: absolute; top: 70px; left: 20px; right: 240px; bottom: 20px; border: 1px solid #ccc; overflow: auto; background: #fff;} 8 | 9 | #info { padding: 10px; background: #fff; position: absolute; width: 200px; right: 10px; top: 70px; bottom: 20px; border: 1px solid #ccc; overflow: auto; } 10 | 11 | h1 {margin-top: 0px; padding-top: 0px; padding: 12px; color: #777;} 12 | 13 | div.notice { font-style: italic; font-size: 10px; } -------------------------------------------------------------------------------- /fixtures/artists.js: -------------------------------------------------------------------------------- 1 | var artists_fixture = {"properties":{"name":{"name":"Artist Name","type":"string","unique":true},"genres":{"name":"Artist Name","type":"string","unique":false},"origin":{"name":"Artist Name","type":"string","unique":true}},"items":{"/en/quickmix":{"name":"Quickmix","genres":["Pop music","Hip Hop"],"origin":"Berlin"},"/en/planningtorock":{"name":"Planningtorock","genres":["Rock music"],"origin":"Berlin"},"/m/0g5whw":{"name":"Mythos","genres":["Rock music"],"origin":"Berlin"},"/en/pink_turns_blue":{"name":"Pink Turns Blue","genres":["Rock music"],"origin":"Berlin"},"/en/icke_er":{"name":"Icke&Er","genres":["Hip Hop"],"origin":"Berlin"},"/en/beatsteaks":{"name":"Beatsteaks","genres":["Rock music"],"origin":"Berlin"},"/en/martini_bros":{"name":"Märtini Brös","genres":["Electronic music"],"origin":"Berlin"},"/en/ulrich_schnauss":{"name":"Ulrich Schnauss","genres":["Electronic music"],"origin":"Berlin"},"/en/peter_baumann":{"name":"Peter Baumann","genres":["Electronic music"],"origin":"Berlin"},"/en/atari_teenage_riot":{"name":"Atari Teenage Riot","genres":["Electronic music"],"origin":"Berlin"},"/en/cluster":{"name":"Cluster","genres":["Electronic music"],"origin":"Berlin"},"/en/real_mccoy":{"name":"Real McCoy","genres":["Pop music","Electronic music"],"origin":"Berlin"},"/en/klaus_schulze":{"name":"Klaus Schulze","genres":["Electronic music"],"origin":"Berlin"},"/en/ash_ra_tempel":{"name":"Ash Ra Tempel","genres":["Electronic music"],"origin":"Berlin"},"/en/17_hippies":{"name":"17 Hippies","genres":["Pop music"],"origin":"Berlin"},"/en/london_86":{"name":"London 86","genres":["Electronic music","Rock music"],"origin":"Berlin"},"/en/vanessa_petruo":{"name":"Vanessa Petruo","genres":["Pop music"],"origin":"Berlin"},"/en/die_arzte":{"name":"Die Ärzte","genres":["Rock music"],"origin":"Berlin"},"/en/the_bosshoss":{"name":"The BossHoss","genres":["Pop music","Rock music"],"origin":"Berlin"},"/en/joy_denalane":{"name":"Joy Denalane","genres":["Jazz"],"origin":"Berlin"},"/en/tangerine_dream":{"name":"Tangerine Dream","genres":["Electronic music"],"origin":"Berlin"},"/en/jerome_froese":{"name":"Jerome Froese","genres":["Electronic music"],"origin":"Berlin"},"/en/modeselektor":{"name":"Modeselektor","genres":["Electronic music"],"origin":"Berlin"},"/en/stereo_total":{"name":"Stereo Total","genres":["Electronic music","Pop music"],"origin":"Berlin"},"/en/gods_of_blitz":{"name":"Gods of Blitz","genres":["Rock music"],"origin":"Berlin"},"/en/kluster":{"name":"Kluster","genres":["Electronic music"],"origin":"Berlin"},"/en/o-jay":{"name":"O-Jay","genres":["Pop music","Electronic music"],"origin":"Berlin"},"/en/monolake":{"name":"Monolake","genres":["Electronic music"],"origin":"Berlin"},"/en/hans_joachim_roedelius":{"name":"Hans-Joachim Roedelius","genres":["Electronic music"],"origin":"Berlin"},"/en/christopher_franke":{"name":"Christopher Franke","genres":["Electronic music"],"origin":"Berlin"},"/en/jeanette_biedermann":{"name":"Jeanette Biedermann","genres":["Pop music","Rock music"],"origin":"Berlin"},"/en/judith_holofernes":{"name":"Judith Holofernes","genres":["Pop music","Rock music"],"origin":"Berlin"},"/m/02z45jg":{"name":"Eruption","genres":["Electronic music"],"origin":"Berlin"},"/en/moebius_plank":{"name":"Moebius & Plank","genres":["Electronic music"],"origin":"Berlin"},"/en/culcha_candela":{"name":"Culcha Candela","genres":["Hip Hop"],"origin":"Berlin"},"/en/jennifer_rostock":{"name":"Jennifer Rostock","genres":["Rock music"],"origin":"Berlin"},"/en/cascada":{"name":"Cascada","genres":["Pop music","Electronic music"],"origin":"Berlin"},"/en/fler":{"name":"Fler","genres":["Hip Hop"],"origin":"Berlin"},"/en/sido":{"name":"Sido","genres":["Hip Hop"],"origin":"Berlin"},"/en/alles_ist_die_sekte":{"name":"Alles ist die Sekte","genres":["Hip Hop"],"origin":"Berlin"},"/en/ben_1981":{"name":"Ben","genres":["Pop music"],"origin":"Berlin"},"/en/wir_sind_helden":{"name":"Wir sind Helden","genres":["Rock music"],"origin":"Berlin"},"/en/clara_hill":{"name":"Clara Hill","genres":["Jazz"],"origin":"Berlin"},"/m/03yn3f0":{"name":"Mike Mareen","genres":["Electronic music"],"origin":"Berlin"},"/en/barbara_morgenstern":{"name":"Barbara Morgenstern","genres":["Electronic music"],"origin":"Berlin"},"/en/dance_or_die":{"name":"Dance or Die","genres":["Electronic music"],"origin":"Berlin"},"/en/cinema_bizarre":{"name":"Cinema Bizarre","genres":["Pop music"],"origin":"Berlin"},"/m/051wcm1":{"name":"Peter Fox","genres":["Hip Hop"],"origin":"Berlin"},"/en/tete_montoliu":{"name":"Tete Montoliu","genres":["Jazz"],"origin":"Barcelona"},"/en/antonio_orozco":{"name":"Antonio Orozco","genres":["Pop music"],"origin":"Barcelona"},"/en/neuronium":{"name":"Neuronium","genres":["Electronic music"],"origin":"Barcelona"},"/m/03f2445":{"name":"Porta","genres":["Hip Hop"],"origin":"Barcelona"},"/en/joe_crepusculo":{"name":"Joe Crepúsculo","genres":["Pop music"],"origin":"Barcelona"},"/en/rebeca":{"name":"Rebeca","genres":["Pop music"],"origin":"Barcelona"},"/m/060zw1":{"name":"Téléphone","genres":["Rock music"],"origin":"Paris"},"/en/claudine_longet":{"name":"Claudine Longet","genres":["Pop music"],"origin":"Paris"},"/en/david_fenech":{"name":"David Fenech","genres":["Electronic music"],"origin":"Paris"},"/en/henri_texier_1945":{"name":"Henri Texier","genres":["Jazz"],"origin":"Paris"},"/en/sylvie_vartan":{"name":"Sylvie Vartan","genres":["Jazz","Rock music","Pop music"],"origin":"Paris"},"/m/04bvtn":{"name":"Stardust","genres":["Electronic music"],"origin":"Paris"},"/en/jean-jacques_goldman":{"name":"Jean-Jacques Goldman","genres":["Rock music","Pop music"],"origin":"Paris"},"/en/stephane_grappelli":{"name":"Stéphane Grappelli","genres":["Jazz"],"origin":"Paris"},"/en/daft_punk":{"name":"Daft Punk","genres":["Electronic music"],"origin":"Paris"},"/en/ilona_mitrecey":{"name":"Ilona Mitrecey","genres":["Pop music"],"origin":"Paris"},"/en/ttc":{"name":"TTC","genres":["Electronic music","Hip Hop"],"origin":"Paris"},"/en/black_devil":{"name":"Black Devil","genres":["Electronic music"],"origin":"Paris"},"/en/herman_dune":{"name":"Herman Düne","genres":["Pop music"],"origin":"Paris"},"/en/electrosexual":{"name":"Electrosexual","genres":["Electronic music"],"origin":"Paris"},"/en/richard_clayderman":{"name":"Richard Clayderman","genres":["Pop music"],"origin":"Paris"},"/en/renaud_garcia-fons":{"name":"Renaud Garcia-Fons","genres":["Jazz"],"origin":"Paris"},"/m/01shqcj":{"name":"Justice","genres":["Electronic music"],"origin":"Paris"},"/en/emma_shapplin":{"name":"Emma Shapplin","genres":["Pop music"],"origin":"Paris"},"/en/serge_gainsbourg":{"name":"Serge Gainsbourg","genres":["Jazz","Electronic music"],"origin":"Paris"},"/en/indochine":{"name":"Indochine","genres":["Rock music"],"origin":"Paris"},"/en/arthur_h":{"name":"Arthur H","genres":["Jazz","Rock music"],"origin":"Paris"},"/en/bisso_na_bisso":{"name":"Bisso Na Bisso","genres":["Hip Hop"],"origin":"Paris"},"/en/georges_garvarentz":{"name":"Georges Garvarentz","genres":["Pop music"],"origin":"Paris"},"/en/sylvain_chauveau":{"name":"Sylvain Chauveau","genres":["Electronic music"],"origin":"Paris"},"/en/yael_naim":{"name":"Yael Naim","genres":["Pop music"],"origin":"Paris"},"/en/uffie":{"name":"Uffie","genres":["Hip Hop"],"origin":"Paris"},"/en/alain_souchon":{"name":"Alain Souchon","genres":["Pop music"],"origin":"Paris"},"/m/02qsn0j":{"name":"Black Strobe","genres":["Electronic music"],"origin":"Paris"},"/en/fabienne_shine":{"name":"Fabienne Shine","genres":["Rock music"],"origin":"Paris"},"/en/rockin_squat":{"name":"Rockin' Squat","genres":["Hip Hop"],"origin":"Paris"},"/en/corynne_charby":{"name":"Corynne Charby","genres":["Pop music"],"origin":"Paris"},"/en/manu_chao":{"name":"Manu Chao","genres":["Jazz","Rock music"],"origin":"Paris"},"/en/david_guetta":{"name":"David Guetta","genres":["Pop music","Hip Hop"],"origin":"Paris"},"/en/johnny_hallyday":{"name":"Johnny Hallyday","genres":["Rock music"],"origin":"Paris"},"/en/charles_aznavour":{"name":"Charles Aznavour","genres":["Jazz","Pop music"],"origin":"Paris"},"/m/03qhkz4":{"name":"Larusso","genres":["Pop music"],"origin":"Paris"},"/m/04gvj4y":{"name":"The Penelopes","genres":["Electronic music"],"origin":"Paris"},"/m/07k6ks_":{"name":"Revolver","genres":["Rock music"],"origin":"Paris"},"/en/the_plastic_people_of_the_universe":{"name":"The Plastic People of the Universe","genres":["Rock music"],"origin":"Prague"},"/en/chaozz":{"name":"Chaozz","genres":["Hip Hop"],"origin":"Prague"},"/en/the_matadors":{"name":"The Matadors","genres":["Rock music"],"origin":"Prague"},"/m/02qnc3r":{"name":"Buldog","genres":["Rock music"],"origin":"Warsaw"},"/en/edyta_bartosiewicz":{"name":"Edyta Bartosiewicz","genres":["Pop music","Rock music"],"origin":"Warsaw"},"/en/wwo":{"name":"WWO","genres":["Hip Hop"],"origin":"Warsaw"},"/en/marek_blizinski":{"name":"Marek Bliziński","genres":["Jazz"],"origin":"Warsaw"},"/en/virgin":{"name":"Virgin","genres":["Pop music","Rock music"],"origin":"Warsaw"},"/en/kazik_staszewski":{"name":"Kazik Staszewski","genres":["Rock music"],"origin":"Warsaw"},"/en/eves_plum":{"name":"Eve's Plum","genres":["Rock music"],"origin":"New York"},"/en/innovaders":{"name":"Innovaders","genres":["Electronic music"],"origin":"New York"},"/en/the_rosenbergs":{"name":"The Rosenbergs","genres":["Rock music"],"origin":"New York"},"/en/breakfast_club":{"name":"Breakfast Club","genres":["Pop music"],"origin":"New York"},"/en/the_longshadows":{"name":"The Longshadows","genres":["Pop music"],"origin":"New York"},"/en/gravediggaz":{"name":"Gravediggaz","genres":["Hip Hop"],"origin":"New York"},"/en/a_million_billion":{"name":"A Million Billion","genres":["Electronic music"],"origin":"New York"},"/en/melissa_jimenez":{"name":"Melissa Jimenez","genres":["Pop music"],"origin":"New York"},"/en/euphoria":{"name":"EUPHORIA","genres":["Pop music"],"origin":"New York"},"/en/tico_torres":{"name":"Tico Torres","genres":["Rock music"],"origin":"New York"},"/m/02rvngm":{"name":"Ulu","genres":["Jazz"],"origin":"New York"},"/en/jazzhole":{"name":"Jazzhole","genres":["Hip Hop"],"origin":"New York"},"/en/mopreme":{"name":"Mopreme","genres":["Hip Hop"],"origin":"New York"},"/en/imani_winds":{"name":"Imani Winds","genres":["Jazz"],"origin":"New York"},"/en/john_benitez":{"name":"John Benitez","genres":["Pop music"],"origin":"New York"},"/en/mike_garson":{"name":"Mike Garson","genres":["Jazz","Rock music"],"origin":"New York"},"/en/reparata_the_delrons":{"name":"Reparata & The Delrons","genres":["Pop music"],"origin":"New York"},"/en/dope":{"name":"Dope","genres":["Rock music"],"origin":"New York"},"/en/mashonda":{"name":"Mashonda","genres":["Hip Hop"],"origin":"New York"},"/en/samantha_maloney":{"name":"Samantha Maloney","genres":["Electronic music"],"origin":"New York"},"/en/toilet_boys":{"name":"Toilet Boys","genres":["Rock music"],"origin":"New York"},"/en/the_strangeurs_chain_reaction":{"name":"The Strangeurs/Chain Reaction","genres":["Rock music","Pop music"],"origin":"New York"},"/en/johnny_thunders_the_heartbreakers":{"name":"Johnny Thunders & the Heartbreakers","genres":["Rock music"],"origin":"New York"},"/en/the_luxury_flats":{"name":"The Luxury Flats","genres":["Rock music"],"origin":"New York"},"/en/rob_derhak":{"name":"Rob Derhak","genres":["Rock music"],"origin":"New York"},"/en/assembly_of_dust":{"name":"Assembly of Dust","genres":["Rock music"],"origin":"New York"},"/en/the_fab_faux":{"name":"The Fab Faux","genres":["Rock music"],"origin":"New York"},"/en/baskervilles":{"name":"Baskervilles","genres":["Pop music","Rock music"],"origin":"New York"},"/en/stellastarr":{"name":"Stellastarr*","genres":["Pop music"],"origin":"New York"},"/en/suffrajett":{"name":"Suffrajett","genres":["Rock music"],"origin":"New York"},"/m/01v4w3q":{"name":"Crossover","genres":["Rock music","Electronic music"],"origin":"New York"},"/en/nydia_caro":{"name":"Nydia Caro","genres":["Pop music"],"origin":"New York"},"/en/e_d_i_amin":{"name":"E.D.I. Amin","genres":["Hip Hop"],"origin":"New York"},"/en/patti_rothberg":{"name":"Patti Rothberg","genres":["Pop music","Rock music"],"origin":"New York"},"/en/scott_harris_project":{"name":"Scott Harris Project","genres":["Pop music","Rock music"],"origin":"New York"},"/en/the_jealous_girlfriends":{"name":"The Jealous Girlfriends","genres":["Rock music"],"origin":"New York"},"/en/vince_melamed":{"name":"Vince Melamed","genres":["Country"],"origin":"New York"},"/en/mark_ronson":{"name":"Mark Ronson","genres":["Pop music","Rock music","Hip Hop"],"origin":"New York"},"/en/smokin_suckaz_wit_logic":{"name":"Smokin' Suckaz wit Logic","genres":["Hip Hop"],"origin":"New York"},"/en/todd_snare":{"name":"Todd Snare","genres":["Rock music","Hip Hop"],"origin":"New York"},"/en/little_annie":{"name":"Little Annie","genres":["Electronic music"],"origin":"New York"},"/en/shanghai_restoration_project":{"name":"Shanghai Restoration Project","genres":["Hip Hop"],"origin":"New York"},"/en/the_dictators":{"name":"The Dictators","genres":["Rock music"],"origin":"New York"},"/en/pin_me_down":{"name":"Pin Me Down","genres":["Pop music","Rock music"],"origin":"New York"},"/en/bobby_mcferrin":{"name":"Bobby McFerrin","genres":["Jazz","Pop music"],"origin":"New York"},"/en/olivia_1980":{"name":"Olivia","genres":["Hip Hop","Pop music"],"origin":"New York"},"/en/al_anderson":{"name":"Al Anderson","genres":["Rock music"],"origin":"New York"},"/en/the_fuzztones":{"name":"The Fuzztones","genres":["Rock music"],"origin":"New York"},"/en/our_daughters_wedding":{"name":"Our Daughter's Wedding","genres":["Electronic music"],"origin":"New York"},"/m/02vzc0h":{"name":"Puracane","genres":["Electronic music"],"origin":"New York"},"/en/the_felice_brothers":{"name":"The Felice Brothers","genres":["Rock music"],"origin":"New York"},"/m/047g1np":{"name":"Even Worse","genres":["Rock music"],"origin":"New York"},"/en/the_cheetah_girls":{"name":"The Cheetah Girls","genres":["Pop music","Hip Hop"],"origin":"New York"},"/en/johnny_mandel":{"name":"Johnny Mandel","genres":["Jazz"],"origin":"New York"},"/en/circle_of_dust":{"name":"Circle of Dust","genres":["Electronic music"],"origin":"New York"},"/en/mindless_self_indulgence":{"name":"Mindless Self Indulgence","genres":["Hip Hop"],"origin":"New York"},"/en/andrea_brachfeld":{"name":"Andrea Brachfeld","genres":["Jazz"],"origin":"New York"},"/en/brand_x":{"name":"Brand X","genres":["Jazz"],"origin":"New York"},"/m/08kdz4":{"name":"Paul Collins","genres":["Rock music"],"origin":"New York"},"/en/galactic":{"name":"Galactic","genres":["Jazz","Rock music","Hip Hop"],"origin":"New Orleans"},"/en/linnzi_zaorski":{"name":"Linnzi Zaorski","genres":["Jazz"],"origin":"New Orleans"},"/en/telefon_tel_aviv":{"name":"Telefon Tel Aviv","genres":["Electronic music"],"origin":"New Orleans"},"/en/big_mike":{"name":"Big Mike","genres":["Hip Hop"],"origin":"New Orleans"},"/en/johnny_indovina":{"name":"Johnny Indovina","genres":["Rock music"],"origin":"New Orleans"},"/en/human_drama":{"name":"Human Drama","genres":["Rock music"],"origin":"New Orleans"},"/en/baby_dodds":{"name":"Baby Dodds","genres":["Jazz"],"origin":"New Orleans"},"/en/louis_prima":{"name":"Louis Prima","genres":["Jazz"],"origin":"New Orleans"},"/en/sound_of_the_blue_heart":{"name":"Sound Of The Blue Heart","genres":["Rock music"],"origin":"New Orleans"},"/en/zue_robertson":{"name":"Zue Robertson","genres":["Jazz"],"origin":"New Orleans"},"/en/tony_parenti":{"name":"Tony Parenti","genres":["Jazz"],"origin":"New Orleans"},"/en/monica_dillon":{"name":"Monica Dillon","genres":["Jazz"],"origin":"New Orleans"},"/en/kermit_ruffins":{"name":"Kermit Ruffins","genres":["Jazz"],"origin":"New Orleans"},"/en/mute_math":{"name":"Mute Math","genres":["Rock music"],"origin":"New Orleans"},"/en/brian_williams_1969":{"name":"Brian Williams","genres":["Hip Hop"],"origin":"New Orleans"},"/en/mannie_fresh":{"name":"Mannie Fresh","genres":["Hip Hop"],"origin":"New Orleans"},"/en/hot_boys":{"name":"Hot Boys","genres":["Hip Hop"],"origin":"New Orleans"},"/en/b_g":{"name":"B.G.","genres":["Hip Hop"],"origin":"New Orleans"},"/en/jason_marsalis":{"name":"Jason Marsalis","genres":["Jazz"],"origin":"New Orleans"},"/en/lil_wayne":{"name":"Lil Wayne","genres":["Hip Hop","Rock music","Pop music"],"origin":"New Orleans"},"/en/master_p":{"name":"Master P","genres":["Hip Hop"],"origin":"New Orleans"},"/en/juvenile":{"name":"Juvenile","genres":["Hip Hop"],"origin":"New Orleans"},"/en/bunk_johnson":{"name":"Bunk Johnson","genres":["Jazz"],"origin":"New Orleans"},"/en/mystikal":{"name":"Mystikal","genres":["Hip Hop"],"origin":"New Orleans"},"/en/red_allen":{"name":"Red Allen","genres":["Jazz"],"origin":"New Orleans"},"/en/alcide_nunez":{"name":"Alcide Nunez","genres":["Jazz"],"origin":"New Orleans"},"/en/sharkey_bonano":{"name":"Sharkey Bonano","genres":["Jazz"],"origin":"New Orleans"},"/en/idris_muhammad":{"name":"Idris Muhammad","genres":["Jazz"],"origin":"New Orleans"},"/en/freddie_keppard":{"name":"Freddie Keppard","genres":["Jazz"],"origin":"New Orleans"},"/m/06c9tq":{"name":"Magic","genres":["Hip Hop"],"origin":"New Orleans"},"/en/michael_white_1954":{"name":"Michael White","genres":["Jazz"],"origin":"New Orleans"},"/en/macrosick":{"name":"Macrosick","genres":["Electronic music"],"origin":"New Orleans"},"/en/the_chee-weez":{"name":"The Chee-Weez","genres":["Country","Rock music"],"origin":"New Orleans"},"/en/exhorder":{"name":"Exhorder","genres":["Rock music"],"origin":"New Orleans"},"/en/the_dixie_cups":{"name":"The Dixie Cups","genres":["Pop music"],"origin":"New Orleans"},"/en/henry_butler":{"name":"Henry Butler","genres":["Jazz"],"origin":"New Orleans"},"/en/cowboy_mouth":{"name":"Cowboy Mouth","genres":["Rock music"],"origin":"New Orleans"},"/en/the_dirty_dozen_brass_band":{"name":"The Dirty Dozen Brass Band","genres":["Jazz"],"origin":"New Orleans"},"/en/lionel_batiste":{"name":"Lionel Batiste","genres":["Jazz"],"origin":"New Orleans"},"/en/silkk_the_shocker":{"name":"Silkk the Shocker","genres":["Hip Hop"],"origin":"New Orleans"},"/en/c_miller":{"name":"C Miller","genres":["Hip Hop"],"origin":"New Orleans"},"/en/dr_john":{"name":"Dr. John","genres":["Rock music"],"origin":"New Orleans"},"/en/terence_blanchard":{"name":"Terence Blanchard","genres":["Jazz"],"origin":"New Orleans"},"/en/danny_barker":{"name":"Danny Barker","genres":["Jazz"],"origin":"New Orleans"},"/en/soulja_slim":{"name":"Soulja Slim","genres":["Hip Hop"],"origin":"New Orleans"},"/en/bardu_ali":{"name":"Bardu Ali","genres":["Jazz"],"origin":"New Orleans"},"/en/bonerama":{"name":"Bonerama","genres":["Jazz","Rock music"],"origin":"New Orleans"},"/en/steve_brown_1890":{"name":"Steve Brown","genres":["Jazz"],"origin":"New Orleans"},"/en/original_dixieland_jazz_band":{"name":"Original Dixieland Jazz Band","genres":["Jazz"],"origin":"New Orleans"},"/m/0d9rnt":{"name":"Trin-i-tee 5:7","genres":["Pop music"],"origin":"New Orleans"},"/en/dawn_angelique_richard":{"name":"Dawn Angelique Richard","genres":["Pop music"],"origin":"New Orleans"},"/en/stanton_moore":{"name":"Stanton Moore","genres":["Rock music"],"origin":"New Orleans"},"/en/buddy_bolden":{"name":"Buddy Bolden","genres":["Jazz"],"origin":"New Orleans"},"/en/klc":{"name":"KLC","genres":["Hip Hop"],"origin":"New Orleans"},"/en/garage_a_trois":{"name":"Garage A Trois","genres":["Jazz","Rock music"],"origin":"New Orleans"},"/m/0gcfss":{"name":"Robert Walter","genres":["Jazz"],"origin":"New Orleans"},"/en/the_radiators":{"name":"The Radiators","genres":["Rock music"],"origin":"New Orleans"},"/en/sweet_emma_barrett":{"name":"\"Sweet Emma\" Barrett","genres":["Jazz"],"origin":"New Orleans"},"/en/lil_romeo":{"name":"Lil Romeo","genres":["Hip Hop"],"origin":"New Orleans"},"/m/0h420x":{"name":"Chemicals","genres":["Rock music"],"origin":"New Orleans"},"/en/club_of_the_sons":{"name":"Club of the Sons","genres":["Electronic music"],"origin":"New Orleans"},"/en/ivan_neville":{"name":"Ivan Neville","genres":["Rock music"],"origin":"New Orleans"},"/en/baby_boy_da_prince":{"name":"Baby Boy Da Prince","genres":["Hip Hop"],"origin":"New Orleans"},"/en/punch_people":{"name":"Punch People","genres":["Rock music"],"origin":"New Orleans"},"/en/hot_8_brass_band":{"name":"Hot 8 Brass Band","genres":["Jazz","Hip Hop"],"origin":"New Orleans"},"/en/the_twilight_singers":{"name":"The Twilight Singers","genres":["Rock music"],"origin":"New Orleans"},"/en/chopper_city_boyz":{"name":"Chopper City Boyz","genres":["Hip Hop"],"origin":"New Orleans"},"/en/christian_scott":{"name":"Christian Scott","genres":["Jazz","Rock music"],"origin":"New Orleans"},"/en/sidney_bechet":{"name":"Sidney Bechet","genres":["Jazz"],"origin":"New Orleans"},"/en/robert_nunez":{"name":"Robert Nunez","genres":["Jazz"],"origin":"New Orleans"},"/en/papa_grows_funk":{"name":"Papa Grows Funk","genres":["Jazz"],"origin":"New Orleans"},"/en/fats_domino":{"name":"Fats Domino","genres":["Jazz","Rock music"],"origin":"New Orleans"},"/en/better_than_ezra":{"name":"Better Than Ezra","genres":["Rock music"],"origin":"New Orleans"},"/en/emile_christian":{"name":"Emile Christian","genres":["Jazz"],"origin":"New Orleans"},"/en/jelly_roll_morton":{"name":"Jelly Roll Morton","genres":["Jazz"],"origin":"New Orleans"},"/en/lil_fizz":{"name":"Lil' Fizz","genres":["Hip Hop"],"origin":"New Orleans"},"/en/wardell_quezergue":{"name":"Wardell Quezergue","genres":["Jazz"],"origin":"New Orleans"},"/en/beats_by_the_pound":{"name":"Beats By the Pound","genres":["Hip Hop"],"origin":"New Orleans"},"/en/gnarls_barkley":{"name":"Gnarls Barkley","genres":["Electronic music","Hip Hop"],"origin":"New Orleans"},"/en/big_tymers":{"name":"Big Tymers","genres":["Hip Hop"],"origin":"New Orleans"},"/en/ellis_marsalis_jr":{"name":"Ellis Marsalis, Jr.","genres":["Jazz"],"origin":"New Orleans"},"/en/rock_whittington":{"name":"Rock Whittington","genres":["Jazz"],"origin":"New Orleans"},"/en/partners-n-crime":{"name":"Partners-N-Crime","genres":["Hip Hop"],"origin":"New Orleans"},"/en/504_boyz":{"name":"504 Boyz","genres":["Hip Hop"],"origin":"New Orleans"},"/en/tru":{"name":"TRU","genres":["Hip Hop"],"origin":"New Orleans"},"/en/blue_lu_barker":{"name":"Blue Lu Barker","genres":["Jazz"],"origin":"New Orleans"},"/m/02w1ym2":{"name":"Lil' D","genres":["Hip Hop"],"origin":"New Orleans"},"/en/greg_hill":{"name":"Greg Hill","genres":["Pop music","Rock music"],"origin":"New Orleans"},"/en/to_be_continued_brass_band":{"name":"To Be Continued Brass Band","genres":["Jazz"],"origin":"New Orleans"},"/en/mac_1977":{"name":"Mac","genres":["Hip Hop"],"origin":"New Orleans"},"/en/the_subdudes":{"name":"The Subdudes","genres":["Rock music"],"origin":"New Orleans"},"/en/preservation_hall_jazz_band":{"name":"Preservation Hall Jazz Band","genres":["Jazz"],"origin":"New Orleans"},"/en/louis_armstrong":{"name":"Louis Armstrong","genres":["Jazz"],"origin":"New Orleans"},"/en/chris_thomas_king":{"name":"Chris Thomas King","genres":["Hip Hop"],"origin":"New Orleans"},"/en/curren_y":{"name":"Curren$y","genres":["Hip Hop"],"origin":"New Orleans"},"/en/mack_maine":{"name":"Mack Maine","genres":["Hip Hop","Jazz","Pop music"],"origin":"New Orleans"},"/m/0407slw":{"name":"Morning 40 Federation","genres":["Rock music"],"origin":"New Orleans"},"/en/victor_goines":{"name":"Victor Goines","genres":["Jazz"],"origin":"New Orleans"},"/en/lloyd":{"name":"Lloyd","genres":["Hip Hop"],"origin":"New Orleans"}}} -------------------------------------------------------------------------------- /fixtures/countries_old.js: -------------------------------------------------------------------------------- 1 | var countries_fixture = { 2 | "properties": { 3 | "name": { 4 | "type": "string", 5 | "property_key": "name", 6 | "unique": true, 7 | "value_key": "name", 8 | "name": "Country Name" 9 | }, 10 | "currency_used": { 11 | "type": "string", 12 | "property_key": "currency_used", 13 | "unique": true, 14 | "value_key": "name", 15 | "name": "Currency used" 16 | }, 17 | "form_of_government": { 18 | "type": "string", 19 | "property_key": "form_of_government", 20 | "unique": false, 21 | "value_key": "name", 22 | "name": "Form of governmennt" 23 | }, 24 | "life_expectancy_male": { 25 | "type": "number", 26 | "unique": true, 27 | "name": "Life expectancy at birth (male)" 28 | }, 29 | "population_0014": { 30 | "type": "number", 31 | "unique": true, 32 | "name": "Population ages 0-14 (% of total)" 33 | }, 34 | "life_expectancy_female": { 35 | "type": "number", 36 | "unique": true, 37 | "name": "Life expectancy at birth (female)" 38 | }, 39 | "official_language": { 40 | "type": "string", 41 | "property_key": "official_language", 42 | "unique": false, 43 | "value_key": "name", 44 | "name": "Official language" 45 | }, 46 | "population": { 47 | "type": "number", 48 | "property_key": "/location/statistical_region/population", 49 | "unique": true, 50 | "value_key": "number", 51 | "name": "Population" 52 | }, 53 | "population_65up": { 54 | "type": "number", 55 | "unique": true, 56 | "name": "Population ages 65 and above (% of total)" 57 | }, 58 | "area": { 59 | "type": "number", 60 | "property_key": "/location/location/area", 61 | "unique": true, 62 | "name": "Area" 63 | }, 64 | "gdp_nominal": { 65 | "type": "number", 66 | "property_key": "/location/statistical_region/gdp_nominal", 67 | "unique": true, 68 | "value_key": "amount", 69 | "name": "GDP nominal" 70 | }, 71 | "date_founded": { 72 | "type": "date", 73 | "property_key": "/location/dated_location/date_founded", 74 | "name": "Date founded", 75 | "unqiue": true 76 | }, 77 | "population_1564": { 78 | "type": "number", 79 | "unique": true, 80 | "name": "Population ages 15-65 (% of total)" 81 | } 82 | }, 83 | "items": { 84 | "/en/angola": { 85 | "name": "Angola", 86 | "currency_used": "Angolan kwanza", 87 | "form_of_government": [ 88 | "Republic", 89 | "Presidential system" 90 | ], 91 | "life_expectancy_male": 45.087, 92 | "population_0014": 45.2844221, 93 | "life_expectancy_female": 49.086, 94 | "official_language": [ 95 | "Portuguese Language" 96 | ], 97 | "population": 18498000, 98 | "population_65up": 2.46411, 99 | "area": 1246700.0, 100 | "gdp_nominal": 95950000000.0, 101 | "date_founded": "1975-11-11", 102 | "population_1564": 52.251468 103 | }, 104 | "/en/algeria": { 105 | "name": "Algeria", 106 | "currency_used": "Algerian dinar", 107 | "form_of_government": [ 108 | "Presidential system", 109 | "Semi-presidential system" 110 | ], 111 | "life_expectancy_male": 70.991, 112 | "population_0014": 27.7493177, 113 | "life_expectancy_female": 73.857, 114 | "official_language": [ 115 | "Arabic language", 116 | "Algerian Arabic", 117 | "French Language" 118 | ], 119 | "population": 34895000, 120 | "population_65up": 4.6111256, 121 | "area": 2381741.0, 122 | "gdp_nominal": 171300000000.0, 123 | "date_founded": "1962", 124 | "population_1564": 67.6395567 125 | }, 126 | "/en/afghanistan": { 127 | "name": "Afghanistan", 128 | "currency_used": "Afghan afghani", 129 | "form_of_government": [ 130 | "Presidential system", 131 | "Islamic republic" 132 | ], 133 | "life_expectancy_male": 43.975, 134 | "population_0014": 46.2996695, 135 | "life_expectancy_female": 43.916, 136 | "official_language": [ 137 | "Persian language", 138 | "Dari", 139 | "Pashto language" 140 | ], 141 | "population": 28150000, 142 | "population_65up": 2.2288251, 143 | "area": 647500.0, 144 | "gdp_nominal": 8842000000.0, 145 | "date_founded": "1919-08-08", 146 | "population_1564": 51.4715055 147 | }, 148 | "/en/aruba": { 149 | "name": "Aruba", 150 | "currency_used": "Aruban florin", 151 | "form_of_government": [ 152 | "Constitutional monarchy" 153 | ], 154 | "life_expectancy_male": 72.108, 155 | "population_0014": 19.7997269, 156 | "life_expectancy_female": 77.399, 157 | "official_language": [ 158 | "Dutch Language", 159 | "Papiamentu Language", 160 | "Spanish Language", 161 | "English Language" 162 | ], 163 | "population": 107000, 164 | "population_65up": 9.2256486, 165 | "area": 193.0, 166 | "gdp_nominal": 4548000000.0, 167 | "date_founded": "1986-01-01", 168 | "population_1564": 70.9746245 169 | }, 170 | "/en/argentina": { 171 | "name": "Argentina", 172 | "currency_used": "Argentinian Peso", 173 | "form_of_government": [ 174 | "Federal republic", 175 | "Presidential system" 176 | ], 177 | "life_expectancy_male": 71.653, 178 | "population_0014": 25.3920037, 179 | "life_expectancy_female": 79.199, 180 | "official_language": [ 181 | "Spanish Language" 182 | ], 183 | "population": 39745613, 184 | "population_65up": 10.5106386, 185 | "area": 2780403.0, 186 | "gdp_nominal": 338700000000.0, 187 | "date_founded": "1816-07-09", 188 | "population_1564": 64.0973577 189 | }, 190 | "/en/albania": { 191 | "name": "Albania", 192 | "currency_used": "Albanian lek", 193 | "form_of_government": [ 194 | "Parliamentary system", 195 | "Parliamentary republic" 196 | ], 197 | "life_expectancy_male": 73.562, 198 | "population_0014": 24.2147097, 199 | "life_expectancy_female": 79.859, 200 | "official_language": [ 201 | "Albanian language" 202 | ], 203 | "population": 3170000, 204 | "population_65up": 9.3330695, 205 | "area": 28748.0, 206 | "gdp_nominal": 13520000000.0, 207 | "date_founded": "1912-11-28", 208 | "population_1564": 66.4522209 209 | }, 210 | "/en/azerbaijan": { 211 | "name": "Azerbaijan", 212 | "currency_used": "Azerbaijani manat", 213 | "form_of_government": [ 214 | "Presidential system", 215 | "Parliamentary republic" 216 | ], 217 | "life_expectancy_male": 67.885, 218 | "population_0014": 24.6212459, 219 | "life_expectancy_female": 72.585, 220 | "official_language": [ 221 | "Azerbaijani language" 222 | ], 223 | "population": 8629900, 224 | "population_65up": 6.7677999, 225 | "area": 86600.0, 226 | "gdp_nominal": 53260000000.0, 227 | "date_founded": "1991-08-30", 228 | "population_1564": 68.6109542 229 | }, 230 | "/en/armenia": { 231 | "name": "Armenia", 232 | "currency_used": "Armenian dram", 233 | "form_of_government": [ 234 | "Presidential system" 235 | ], 236 | "life_expectancy_male": 70.353, 237 | "population_0014": 20.5373009, 238 | "life_expectancy_female": 76.881, 239 | "official_language": [ 240 | "Armenian Language" 241 | ], 242 | "population": 3230100, 243 | "population_65up": 11.5630578, 244 | "area": 29800.0, 245 | "gdp_nominal": 12070000000.0, 246 | "date_founded": "1918-05-28", 247 | "population_1564": 67.8996414 248 | }, 249 | "/en/austria": { 250 | "name": "Austria", 251 | "currency_used": "Euro", 252 | "form_of_government": [ 253 | "Federal republic", 254 | "Parliamentary republic" 255 | ], 256 | "life_expectancy_male": 77.75, 257 | "population_0014": 15.1978338, 258 | "life_expectancy_female": 83.28, 259 | "official_language": [ 260 | "Croatian language", 261 | "Slovenian language", 262 | "Austrian German", 263 | "German Language", 264 | "Hungarian" 265 | ], 266 | "population": 8356700, 267 | "population_65up": 17.0078803, 268 | "area": 83872.0, 269 | "gdp_nominal": 432400000000.0, 270 | "date_founded": "1955-07-27", 271 | "population_1564": 67.7942859 272 | } 273 | } 274 | } 275 | -------------------------------------------------------------------------------- /fixtures/documents.json: -------------------------------------------------------------------------------- 1 | { 2 | "/dc/document": { 3 | "type": "type", 4 | "name": "Document", 5 | "properties": { 6 | "title": { 7 | "name": "Document Title", 8 | "unique": true, 9 | "expected_type": "string" 10 | }, 11 | "entities": { 12 | "name": "Associated Entities", 13 | "unique": false, 14 | "expected_type": "/dc/entity" 15 | }, 16 | "page_count": { 17 | "name": "Page Count", 18 | "unique": true, 19 | "expected_type": "number" 20 | }, 21 | "authors": { 22 | "name": "Authors", 23 | "unique": false, 24 | "expected_type": "string" 25 | } 26 | } 27 | }, 28 | "/dc/entity": { 29 | "type": "type", 30 | "name": "Entity", 31 | "properties": { 32 | "name": { 33 | "name": "Entity Name", 34 | "unique": true, 35 | "expected_type": "string" 36 | }, 37 | "mentions": { 38 | "name": "Mentions", 39 | "unique": false, 40 | "expected_type": "/dc/mention" 41 | } 42 | } 43 | }, 44 | "/dc/mention": { 45 | "name": "Mention", 46 | "type": "type", 47 | "properties": { 48 | "document": { 49 | "name": "Document", 50 | "unique": true, 51 | "expected_type": "/dc/document" 52 | }, 53 | "entity": { 54 | "name": "Entity", 55 | "unique": true, 56 | "expected_type": "/dc/entity" 57 | }, 58 | "page": { 59 | "name": "Occured on page", 60 | "unique": true, 61 | "expected_type": "number" 62 | } 63 | } 64 | }, 65 | "/doc/protovis_introduction": { 66 | "type": "/dc/document", 67 | "properties": { 68 | "title": "Protovis", 69 | "authors": ["Michael Bostock", "Jeffrey Heer"], 70 | "page_count": 8, 71 | "entities": ["/location/stanford", "/location/new_york"] 72 | } 73 | }, 74 | "/doc/unveil_introduction": { 75 | "type": "/dc/document", 76 | "properties": { 77 | "title": "Unveil.js", 78 | "authors": ["Michael Aufreiter", "Lindsay Kay"], 79 | "page_count": 5, 80 | "entities": [] 81 | } 82 | }, 83 | "/doc/processing_js_introduction": { 84 | "type": "/dc/document", 85 | "properties": { 86 | "title": "Processing.js", 87 | "authors": ["Alistair MacDonald", "David Humphrey", "Michael Aufreiter"], 88 | "page_count": 20 89 | } 90 | }, 91 | "/location/stanford": { 92 | "type": "/dc/entity", 93 | "properties": { 94 | "name": "Stanford", 95 | "mentions": ["M0000001"] 96 | } 97 | }, 98 | "/location/new_york": { 99 | "type": "/dc/entity", 100 | "properties": { 101 | "name": "New York", 102 | "mentions": ["M0000002", "M0000003"] 103 | } 104 | }, 105 | "/location/toronto": { 106 | "type": "/dc/entity", 107 | "properties": { 108 | "name": "Toronto", 109 | "mentions": ["M0000004"] 110 | } 111 | }, 112 | "/person/michael_bostock": { 113 | "type": "/dc/entity", 114 | "properties": { 115 | "name": "Michael Bostock", 116 | "mentions": ["M0000005"] 117 | } 118 | }, 119 | "M0000001": { 120 | "type": "/dc/mention", 121 | "properties": { 122 | "document": "/doc/protovis_introduction", 123 | "entity": "/location/stanford", 124 | "page": 2 125 | } 126 | }, 127 | "M0000002": { 128 | "type": "/dc/mention", 129 | "properties": { 130 | "document": "/doc/protovis_introduction", 131 | "entity": "/location/new_york", 132 | "page": 8 133 | } 134 | }, 135 | "M0000003": { 136 | "type": "/dc/mention", 137 | "properties": { 138 | "document": "/doc/processing_js_introduction", 139 | "entity": "/location/new_york", 140 | "page": 5 141 | } 142 | }, 143 | "M0000004": { 144 | "type": "/dc/mention", 145 | "properties": { 146 | "document": "/doc/processing_js_introduction", 147 | "entity": "/location/toronto", 148 | "page": 2 149 | } 150 | }, 151 | "M0000005": { 152 | "type": "/dc/mention", 153 | "properties": { 154 | "document": "/doc/protovis_introduction", 155 | "entity": "/person/michael_bostock", 156 | "page": 1 157 | } 158 | } 159 | } -------------------------------------------------------------------------------- /fixtures/salesmen.js: -------------------------------------------------------------------------------- 1 | var salesmen_fixture = { 2 | properties: { 3 | "name": { 4 | type: "string", 5 | name: "Name", 6 | unique: true 7 | }, 8 | "hardware_turnover": { 9 | name: "Hardware Turnover", 10 | descr: "Monthly hardware turnover (in EUR)", 11 | type: "number", 12 | unique: false, 13 | categories: ["2005", "2006", "2007", "2008", "2009", "2010"] 14 | }, 15 | "solution_turnover": { 16 | name: "Solution Turnover", 17 | descr: "Monthly solution turnover (in EUR)", 18 | type: "number", 19 | unique: false, 20 | categories: ["2005", "2006", "2007", "2008", "2009", "2010"] 21 | } 22 | }, 23 | items: { 24 | "mayer": { 25 | name: "Mayer", 26 | hardware_turnover: [ 27 | 150.2, 28 | 200.2, 29 | 100.2, 30 | 300.2, 31 | 341.3, 32 | 521.2 33 | ], 34 | solution_turnover: [ 35 | 211.2, 36 | 155.2, 37 | 122.2, 38 | 361.5, 39 | 100.5, 40 | 200.8 41 | ] 42 | }, 43 | "mueller": { 44 | name: "Mueller", 45 | hardware_turnover: [ 46 | 300.2, 47 | 111.2, 48 | 421.2, 49 | 948.2, 50 | 800.4, 51 | 300.1 52 | ], 53 | solution_turnover: [ 54 | 111.2, 55 | 230.2, 56 | 531.2, 57 | 444.5, 58 | 555.4, 59 | 238.9 60 | ] 61 | }, 62 | "smith": { 63 | name: "Smith", 64 | hardware_turnover: [ 65 | 700.2, 66 | 400.2, 67 | 604.2, 68 | 200.2, 69 | 120.9, 70 | 111.9 71 | ], 72 | solution_turnover: [ 73 | 177.2, 74 | 655.2, 75 | 422.2, 76 | 433.5, 77 | 333.1, 78 | 211.5 79 | ] 80 | } 81 | } 82 | }; -------------------------------------------------------------------------------- /lib/compiler.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michael/unveil/089eb8f4478cf4ec7bc29217678ea90a0872944e/lib/compiler.jar -------------------------------------------------------------------------------- /lib/qunit.css: -------------------------------------------------------------------------------- 1 | ol#qunit-tests { 2 | font-family:"Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial; 3 | margin:0; 4 | padding:0; 5 | list-style-position:inside; 6 | 7 | font-size: smaller; 8 | } 9 | ol#qunit-tests li{ 10 | padding:0.4em 0.5em 0.4em 2.5em; 11 | border-bottom:1px solid #fff; 12 | font-size:small; 13 | list-style-position:inside; 14 | } 15 | ol#qunit-tests li ol{ 16 | box-shadow: inset 0px 2px 13px #999; 17 | -moz-box-shadow: inset 0px 2px 13px #999; 18 | -webkit-box-shadow: inset 0px 2px 13px #999; 19 | margin-top:0.5em; 20 | margin-left:0; 21 | padding:0.5em; 22 | background-color:#fff; 23 | border-radius:15px; 24 | -moz-border-radius: 15px; 25 | -webkit-border-radius: 15px; 26 | } 27 | ol#qunit-tests li li{ 28 | border-bottom:none; 29 | margin:0.5em; 30 | background-color:#fff; 31 | list-style-position: inside; 32 | padding:0.4em 0.5em 0.4em 0.5em; 33 | } 34 | 35 | ol#qunit-tests li li.pass{ 36 | border-left:26px solid #C6E746; 37 | background-color:#fff; 38 | color:#5E740B; 39 | } 40 | ol#qunit-tests li li.fail{ 41 | border-left:26px solid #EE5757; 42 | background-color:#fff; 43 | color:#710909; 44 | } 45 | ol#qunit-tests li.pass{ 46 | background-color:#D2E0E6; 47 | color:#528CE0; 48 | } 49 | ol#qunit-tests li.fail{ 50 | background-color:#EE5757; 51 | color:#000; 52 | } 53 | ol#qunit-tests li strong { 54 | cursor:pointer; 55 | } 56 | h1#qunit-header{ 57 | background-color:#0d3349; 58 | margin:0; 59 | padding:0.5em 0 0.5em 1em; 60 | color:#fff; 61 | font-family:"Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial; 62 | border-top-right-radius:15px; 63 | border-top-left-radius:15px; 64 | -moz-border-radius-topright:15px; 65 | -moz-border-radius-topleft:15px; 66 | -webkit-border-top-right-radius:15px; 67 | -webkit-border-top-left-radius:15px; 68 | text-shadow: rgba(0, 0, 0, 0.5) 4px 4px 1px; 69 | } 70 | h2#qunit-banner{ 71 | font-family:"Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial; 72 | height:5px; 73 | margin:0; 74 | padding:0; 75 | } 76 | h2#qunit-banner.qunit-pass{ 77 | background-color:#C6E746; 78 | } 79 | h2#qunit-banner.qunit-fail, #qunit-testrunner-toolbar { 80 | background-color:#EE5757; 81 | } 82 | #qunit-testrunner-toolbar { 83 | font-family:"Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial; 84 | padding:0; 85 | /*width:80%;*/ 86 | padding:0em 0 0.5em 2em; 87 | font-size: small; 88 | } 89 | h2#qunit-userAgent { 90 | font-family:"Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial; 91 | background-color:#2b81af; 92 | margin:0; 93 | padding:0; 94 | color:#fff; 95 | font-size: small; 96 | padding:0.5em 0 0.5em 2.5em; 97 | text-shadow: rgba(0, 0, 0, 0.5) 2px 2px 1px; 98 | } 99 | p#qunit-testresult{ 100 | font-family:"Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial; 101 | margin:0; 102 | font-size: small; 103 | color:#2b81af; 104 | border-bottom-right-radius:15px; 105 | border-bottom-left-radius:15px; 106 | -moz-border-radius-bottomright:15px; 107 | -moz-border-radius-bottomleft:15px; 108 | -webkit-border-bottom-right-radius:15px; 109 | -webkit-border-bottom-left-radius:15px; 110 | background-color:#D2E0E6; 111 | padding:0.5em 0.5em 0.5em 2.5em; 112 | } 113 | strong b.fail{ 114 | color:#710909; 115 | } 116 | strong b.pass{ 117 | color:#5E740B; 118 | } 119 | -------------------------------------------------------------------------------- /lib/qunit.js: -------------------------------------------------------------------------------- 1 | /* 2 | * QUnit - A JavaScript Unit Testing Framework 3 | * 4 | * http://docs.jquery.com/QUnit 5 | * 6 | * Copyright (c) 2009 John Resig, Jörn Zaefferer 7 | * Dual licensed under the MIT (MIT-LICENSE.txt) 8 | * and GPL (GPL-LICENSE.txt) licenses. 9 | */ 10 | 11 | (function(window) { 12 | 13 | var QUnit = { 14 | 15 | // Initialize the configuration options 16 | init: function() { 17 | config = { 18 | stats: { all: 0, bad: 0 }, 19 | moduleStats: { all: 0, bad: 0 }, 20 | started: +new Date, 21 | updateRate: 1000, 22 | blocking: false, 23 | autorun: false, 24 | assertions: [], 25 | filters: [], 26 | queue: [] 27 | }; 28 | 29 | var tests = id("qunit-tests"), 30 | banner = id("qunit-banner"), 31 | result = id("qunit-testresult"); 32 | 33 | if ( tests ) { 34 | tests.innerHTML = ""; 35 | } 36 | 37 | if ( banner ) { 38 | banner.className = ""; 39 | } 40 | 41 | if ( result ) { 42 | result.parentNode.removeChild( result ); 43 | } 44 | }, 45 | 46 | // call on start of module test to prepend name to all tests 47 | module: function(name, testEnvironment) { 48 | config.currentModule = name; 49 | 50 | synchronize(function() { 51 | if ( config.currentModule ) { 52 | QUnit.moduleDone( config.currentModule, config.moduleStats.bad, config.moduleStats.all ); 53 | } 54 | 55 | config.currentModule = name; 56 | config.moduleTestEnvironment = testEnvironment; 57 | config.moduleStats = { all: 0, bad: 0 }; 58 | 59 | QUnit.moduleStart( name, testEnvironment ); 60 | }); 61 | }, 62 | 63 | asyncTest: function(testName, expected, callback) { 64 | if ( arguments.length === 2 ) { 65 | callback = expected; 66 | expected = 0; 67 | } 68 | 69 | QUnit.test(testName, expected, callback, true); 70 | }, 71 | 72 | test: function(testName, expected, callback, async) { 73 | var name = testName, testEnvironment, testEnvironmentArg; 74 | 75 | if ( arguments.length === 2 ) { 76 | callback = expected; 77 | expected = null; 78 | } 79 | // is 2nd argument a testEnvironment? 80 | if ( expected && typeof expected === 'object') { 81 | testEnvironmentArg = expected; 82 | expected = null; 83 | } 84 | 85 | if ( config.currentModule ) { 86 | name = config.currentModule + " module: " + name; 87 | } 88 | 89 | if ( !validTest(name) ) { 90 | return; 91 | } 92 | 93 | synchronize(function() { 94 | QUnit.testStart( testName ); 95 | 96 | testEnvironment = extend({ 97 | setup: function() {}, 98 | teardown: function() {} 99 | }, config.moduleTestEnvironment); 100 | if (testEnvironmentArg) { 101 | extend(testEnvironment,testEnvironmentArg); 102 | } 103 | 104 | // allow utility functions to access the current test environment 105 | QUnit.current_testEnvironment = testEnvironment; 106 | 107 | config.assertions = []; 108 | config.expected = expected; 109 | 110 | try { 111 | if ( !config.pollution ) { 112 | saveGlobal(); 113 | } 114 | 115 | testEnvironment.setup.call(testEnvironment); 116 | } catch(e) { 117 | QUnit.ok( false, "Setup failed on " + name + ": " + e.message ); 118 | } 119 | 120 | if ( async ) { 121 | QUnit.stop(); 122 | } 123 | 124 | try { 125 | callback.call(testEnvironment); 126 | } catch(e) { 127 | fail("Test " + name + " died, exception and test follows", e, callback); 128 | QUnit.ok( false, "Died on test #" + (config.assertions.length + 1) + ": " + e.message ); 129 | // else next test will carry the responsibility 130 | saveGlobal(); 131 | 132 | // Restart the tests if they're blocking 133 | if ( config.blocking ) { 134 | start(); 135 | } 136 | } 137 | }); 138 | 139 | synchronize(function() { 140 | try { 141 | checkPollution(); 142 | testEnvironment.teardown.call(testEnvironment); 143 | } catch(e) { 144 | QUnit.ok( false, "Teardown failed on " + name + ": " + e.message ); 145 | } 146 | 147 | try { 148 | QUnit.reset(); 149 | } catch(e) { 150 | fail("reset() failed, following Test " + name + ", exception and reset fn follows", e, reset); 151 | } 152 | 153 | if ( config.expected && config.expected != config.assertions.length ) { 154 | QUnit.ok( false, "Expected " + config.expected + " assertions, but " + config.assertions.length + " were run" ); 155 | } 156 | 157 | var good = 0, bad = 0, 158 | tests = id("qunit-tests"); 159 | 160 | config.stats.all += config.assertions.length; 161 | config.moduleStats.all += config.assertions.length; 162 | 163 | if ( tests ) { 164 | var ol = document.createElement("ol"); 165 | ol.style.display = "none"; 166 | 167 | for ( var i = 0; i < config.assertions.length; i++ ) { 168 | var assertion = config.assertions[i]; 169 | 170 | var li = document.createElement("li"); 171 | li.className = assertion.result ? "pass" : "fail"; 172 | li.appendChild(document.createTextNode(assertion.message || "(no message)")); 173 | ol.appendChild( li ); 174 | 175 | if ( assertion.result ) { 176 | good++; 177 | } else { 178 | bad++; 179 | config.stats.bad++; 180 | config.moduleStats.bad++; 181 | } 182 | } 183 | 184 | var b = document.createElement("strong"); 185 | b.innerHTML = name + " (" + bad + ", " + good + ", " + config.assertions.length + ")"; 186 | 187 | addEvent(b, "click", function() { 188 | var next = b.nextSibling, display = next.style.display; 189 | next.style.display = display === "none" ? "block" : "none"; 190 | }); 191 | 192 | addEvent(b, "dblclick", function(e) { 193 | var target = e && e.target ? e.target : window.event.srcElement; 194 | if ( target.nodeName.toLowerCase() === "strong" ) { 195 | var text = "", node = target.firstChild; 196 | 197 | while ( node.nodeType === 3 ) { 198 | text += node.nodeValue; 199 | node = node.nextSibling; 200 | } 201 | 202 | text = text.replace(/(^\s*|\s*$)/g, ""); 203 | 204 | if ( window.location ) { 205 | window.location.href = window.location.href.match(/^(.+?)(\?.*)?$/)[1] + "?" + encodeURIComponent(text); 206 | } 207 | } 208 | }); 209 | 210 | var li = document.createElement("li"); 211 | li.className = bad ? "fail" : "pass"; 212 | li.appendChild( b ); 213 | li.appendChild( ol ); 214 | tests.appendChild( li ); 215 | 216 | if ( bad ) { 217 | var toolbar = id("qunit-testrunner-toolbar"); 218 | if ( toolbar ) { 219 | toolbar.style.display = "block"; 220 | id("qunit-filter-pass").disabled = null; 221 | id("qunit-filter-missing").disabled = null; 222 | } 223 | } 224 | 225 | } else { 226 | for ( var i = 0; i < config.assertions.length; i++ ) { 227 | if ( !config.assertions[i].result ) { 228 | bad++; 229 | config.stats.bad++; 230 | config.moduleStats.bad++; 231 | } 232 | } 233 | } 234 | 235 | QUnit.testDone( testName, bad, config.assertions.length ); 236 | 237 | if ( !window.setTimeout && !config.queue.length ) { 238 | done(); 239 | } 240 | }); 241 | 242 | if ( window.setTimeout && !config.doneTimer ) { 243 | config.doneTimer = window.setTimeout(function(){ 244 | if ( !config.queue.length ) { 245 | done(); 246 | } else { 247 | synchronize( done ); 248 | } 249 | }, 13); 250 | } 251 | }, 252 | 253 | /** 254 | * Specify the number of expected assertions to gurantee that failed test (no assertions are run at all) don't slip through. 255 | */ 256 | expect: function(asserts) { 257 | config.expected = asserts; 258 | }, 259 | 260 | /** 261 | * Asserts true. 262 | * @example ok( "asdfasdf".length > 5, "There must be at least 5 chars" ); 263 | */ 264 | ok: function(a, msg) { 265 | QUnit.log(a, msg); 266 | 267 | config.assertions.push({ 268 | result: !!a, 269 | message: msg 270 | }); 271 | }, 272 | 273 | /** 274 | * Checks that the first two arguments are equal, with an optional message. 275 | * Prints out both actual and expected values. 276 | * 277 | * Prefered to ok( actual == expected, message ) 278 | * 279 | * @example equal( format("Received {0} bytes.", 2), "Received 2 bytes." ); 280 | * 281 | * @param Object actual 282 | * @param Object expected 283 | * @param String message (optional) 284 | */ 285 | equal: function(actual, expected, message) { 286 | push(expected == actual, actual, expected, message); 287 | }, 288 | 289 | notEqual: function(actual, expected, message) { 290 | push(expected != actual, actual, expected, message); 291 | }, 292 | 293 | deepEqual: function(a, b, message) { 294 | push(QUnit.equiv(a, b), a, b, message); 295 | }, 296 | 297 | notDeepEqual: function(a, b, message) { 298 | push(!QUnit.equiv(a, b), a, b, message); 299 | }, 300 | 301 | strictEqual: function(actual, expected, message) { 302 | push(expected === actual, actual, expected, message); 303 | }, 304 | 305 | notStrictEqual: function(actual, expected, message) { 306 | push(expected !== actual, actual, expected, message); 307 | }, 308 | 309 | start: function() { 310 | // A slight delay, to avoid any current callbacks 311 | if ( window.setTimeout ) { 312 | window.setTimeout(function() { 313 | if ( config.timeout ) { 314 | clearTimeout(config.timeout); 315 | } 316 | 317 | config.blocking = false; 318 | process(); 319 | }, 13); 320 | } else { 321 | config.blocking = false; 322 | process(); 323 | } 324 | }, 325 | 326 | stop: function(timeout) { 327 | config.blocking = true; 328 | 329 | if ( timeout && window.setTimeout ) { 330 | config.timeout = window.setTimeout(function() { 331 | QUnit.ok( false, "Test timed out" ); 332 | QUnit.start(); 333 | }, timeout); 334 | } 335 | }, 336 | 337 | /** 338 | * Resets the test setup. Useful for tests that modify the DOM. 339 | */ 340 | reset: function() { 341 | if ( window.jQuery ) { 342 | jQuery("#main").html( config.fixture ); 343 | jQuery.event.global = {}; 344 | jQuery.ajaxSettings = extend({}, config.ajaxSettings); 345 | } 346 | }, 347 | 348 | /** 349 | * Trigger an event on an element. 350 | * 351 | * @example triggerEvent( document.body, "click" ); 352 | * 353 | * @param DOMElement elem 354 | * @param String type 355 | */ 356 | triggerEvent: function( elem, type, event ) { 357 | if ( document.createEvent ) { 358 | event = document.createEvent("MouseEvents"); 359 | event.initMouseEvent(type, true, true, elem.ownerDocument.defaultView, 360 | 0, 0, 0, 0, 0, false, false, false, false, 0, null); 361 | elem.dispatchEvent( event ); 362 | 363 | } else if ( elem.fireEvent ) { 364 | elem.fireEvent("on"+type); 365 | } 366 | }, 367 | 368 | // Safe object type checking 369 | is: function( type, obj ) { 370 | return Object.prototype.toString.call( obj ) === "[object "+ type +"]"; 371 | }, 372 | 373 | // Logging callbacks 374 | done: function(failures, total) {}, 375 | log: function(result, message) {}, 376 | testStart: function(name) {}, 377 | testDone: function(name, failures, total) {}, 378 | moduleStart: function(name, testEnvironment) {}, 379 | moduleDone: function(name, failures, total) {} 380 | }; 381 | 382 | // Backwards compatibility, deprecated 383 | QUnit.equals = QUnit.equal; 384 | QUnit.same = QUnit.deepEqual; 385 | 386 | // Maintain internal state 387 | var config = { 388 | // The queue of tests to run 389 | queue: [], 390 | 391 | // block until document ready 392 | blocking: true 393 | }; 394 | 395 | // Load paramaters 396 | (function() { 397 | var location = window.location || { search: "", protocol: "file:" }, 398 | GETParams = location.search.slice(1).split('&'); 399 | 400 | for ( var i = 0; i < GETParams.length; i++ ) { 401 | GETParams[i] = decodeURIComponent( GETParams[i] ); 402 | if ( GETParams[i] === "noglobals" ) { 403 | GETParams.splice( i, 1 ); 404 | i--; 405 | config.noglobals = true; 406 | } else if ( GETParams[i].search('=') > -1 ) { 407 | GETParams.splice( i, 1 ); 408 | i--; 409 | } 410 | } 411 | 412 | // restrict modules/tests by get parameters 413 | config.filters = GETParams; 414 | 415 | // Figure out if we're running the tests from a server or not 416 | QUnit.isLocal = !!(location.protocol === 'file:'); 417 | })(); 418 | 419 | // Expose the API as global variables, unless an 'exports' 420 | // object exists, in that case we assume we're in CommonJS 421 | if ( typeof exports === "undefined" || typeof require === "undefined" ) { 422 | extend(window, QUnit); 423 | window.QUnit = QUnit; 424 | } else { 425 | extend(exports, QUnit); 426 | exports.QUnit = QUnit; 427 | } 428 | 429 | if ( typeof document === "undefined" || document.readyState === "complete" ) { 430 | config.autorun = true; 431 | } 432 | 433 | addEvent(window, "load", function() { 434 | // Initialize the config, saving the execution queue 435 | var oldconfig = extend({}, config); 436 | QUnit.init(); 437 | extend(config, oldconfig); 438 | 439 | config.blocking = false; 440 | 441 | var userAgent = id("qunit-userAgent"); 442 | if ( userAgent ) { 443 | userAgent.innerHTML = navigator.userAgent; 444 | } 445 | 446 | var toolbar = id("qunit-testrunner-toolbar"); 447 | if ( toolbar ) { 448 | toolbar.style.display = "none"; 449 | 450 | var filter = document.createElement("input"); 451 | filter.type = "checkbox"; 452 | filter.id = "qunit-filter-pass"; 453 | filter.disabled = true; 454 | addEvent( filter, "click", function() { 455 | var li = document.getElementsByTagName("li"); 456 | for ( var i = 0; i < li.length; i++ ) { 457 | if ( li[i].className.indexOf("pass") > -1 ) { 458 | li[i].style.display = filter.checked ? "none" : ""; 459 | } 460 | } 461 | }); 462 | toolbar.appendChild( filter ); 463 | 464 | var label = document.createElement("label"); 465 | label.setAttribute("for", "qunit-filter-pass"); 466 | label.innerHTML = "Hide passed tests"; 467 | toolbar.appendChild( label ); 468 | 469 | var missing = document.createElement("input"); 470 | missing.type = "checkbox"; 471 | missing.id = "qunit-filter-missing"; 472 | missing.disabled = true; 473 | addEvent( missing, "click", function() { 474 | var li = document.getElementsByTagName("li"); 475 | for ( var i = 0; i < li.length; i++ ) { 476 | if ( li[i].className.indexOf("fail") > -1 && li[i].innerHTML.indexOf('missing test - untested code is broken code') > - 1 ) { 477 | li[i].parentNode.parentNode.style.display = missing.checked ? "none" : "block"; 478 | } 479 | } 480 | }); 481 | toolbar.appendChild( missing ); 482 | 483 | label = document.createElement("label"); 484 | label.setAttribute("for", "qunit-filter-missing"); 485 | label.innerHTML = "Hide missing tests (untested code is broken code)"; 486 | toolbar.appendChild( label ); 487 | } 488 | 489 | var main = id('main'); 490 | if ( main ) { 491 | config.fixture = main.innerHTML; 492 | } 493 | 494 | if ( window.jQuery ) { 495 | config.ajaxSettings = window.jQuery.ajaxSettings; 496 | } 497 | 498 | QUnit.start(); 499 | }); 500 | 501 | function done() { 502 | if ( config.doneTimer && window.clearTimeout ) { 503 | window.clearTimeout( config.doneTimer ); 504 | config.doneTimer = null; 505 | } 506 | 507 | if ( config.queue.length ) { 508 | config.doneTimer = window.setTimeout(function(){ 509 | if ( !config.queue.length ) { 510 | done(); 511 | } else { 512 | synchronize( done ); 513 | } 514 | }, 13); 515 | 516 | return; 517 | } 518 | 519 | config.autorun = true; 520 | 521 | // Log the last module results 522 | if ( config.currentModule ) { 523 | QUnit.moduleDone( config.currentModule, config.moduleStats.bad, config.moduleStats.all ); 524 | } 525 | 526 | var banner = id("qunit-banner"), 527 | tests = id("qunit-tests"), 528 | html = ['Tests completed in ', 529 | +new Date - config.started, ' milliseconds.
', 530 | '', config.stats.all - config.stats.bad, ' tests of ', config.stats.all, ' passed, ', config.stats.bad,' failed.'].join(''); 531 | 532 | if ( banner ) { 533 | banner.className = (config.stats.bad ? "qunit-fail" : "qunit-pass"); 534 | } 535 | 536 | if ( tests ) { 537 | var result = id("qunit-testresult"); 538 | 539 | if ( !result ) { 540 | result = document.createElement("p"); 541 | result.id = "qunit-testresult"; 542 | result.className = "result"; 543 | tests.parentNode.insertBefore( result, tests.nextSibling ); 544 | } 545 | 546 | result.innerHTML = html; 547 | } 548 | 549 | QUnit.done( config.stats.bad, config.stats.all ); 550 | } 551 | 552 | function validTest( name ) { 553 | var i = config.filters.length, 554 | run = false; 555 | 556 | if ( !i ) { 557 | return true; 558 | } 559 | 560 | while ( i-- ) { 561 | var filter = config.filters[i], 562 | not = filter.charAt(0) == '!'; 563 | 564 | if ( not ) { 565 | filter = filter.slice(1); 566 | } 567 | 568 | if ( name.indexOf(filter) !== -1 ) { 569 | return !not; 570 | } 571 | 572 | if ( not ) { 573 | run = true; 574 | } 575 | } 576 | 577 | return run; 578 | } 579 | 580 | function push(result, actual, expected, message) { 581 | message = message || (result ? "okay" : "failed"); 582 | QUnit.ok( result, result ? message + ": " + QUnit.jsDump.parse(expected) : message + ", expected: " + QUnit.jsDump.parse(expected) + " result: " + QUnit.jsDump.parse(actual) ); 583 | } 584 | 585 | function synchronize( callback ) { 586 | config.queue.push( callback ); 587 | 588 | if ( config.autorun && !config.blocking ) { 589 | process(); 590 | } 591 | } 592 | 593 | function process() { 594 | var start = (new Date()).getTime(); 595 | 596 | while ( config.queue.length && !config.blocking ) { 597 | if ( config.updateRate <= 0 || (((new Date()).getTime() - start) < config.updateRate) ) { 598 | config.queue.shift()(); 599 | 600 | } else { 601 | setTimeout( process, 13 ); 602 | break; 603 | } 604 | } 605 | } 606 | 607 | function saveGlobal() { 608 | config.pollution = []; 609 | 610 | if ( config.noglobals ) { 611 | for ( var key in window ) { 612 | config.pollution.push( key ); 613 | } 614 | } 615 | } 616 | 617 | function checkPollution( name ) { 618 | var old = config.pollution; 619 | saveGlobal(); 620 | 621 | var newGlobals = diff( old, config.pollution ); 622 | if ( newGlobals.length > 0 ) { 623 | ok( false, "Introduced global variable(s): " + newGlobals.join(", ") ); 624 | config.expected++; 625 | } 626 | 627 | var deletedGlobals = diff( config.pollution, old ); 628 | if ( deletedGlobals.length > 0 ) { 629 | ok( false, "Deleted global variable(s): " + deletedGlobals.join(", ") ); 630 | config.expected++; 631 | } 632 | } 633 | 634 | // returns a new Array with the elements that are in a but not in b 635 | function diff( a, b ) { 636 | var result = a.slice(); 637 | for ( var i = 0; i < result.length; i++ ) { 638 | for ( var j = 0; j < b.length; j++ ) { 639 | if ( result[i] === b[j] ) { 640 | result.splice(i, 1); 641 | i--; 642 | break; 643 | } 644 | } 645 | } 646 | return result; 647 | } 648 | 649 | function fail(message, exception, callback) { 650 | if ( typeof console !== "undefined" && console.error && console.warn ) { 651 | console.error(message); 652 | console.error(exception); 653 | console.warn(callback.toString()); 654 | 655 | } else if ( window.opera && opera.postError ) { 656 | opera.postError(message, exception, callback.toString); 657 | } 658 | } 659 | 660 | function extend(a, b) { 661 | for ( var prop in b ) { 662 | a[prop] = b[prop]; 663 | } 664 | 665 | return a; 666 | } 667 | 668 | function addEvent(elem, type, fn) { 669 | if ( elem.addEventListener ) { 670 | elem.addEventListener( type, fn, false ); 671 | } else if ( elem.attachEvent ) { 672 | elem.attachEvent( "on" + type, fn ); 673 | } else { 674 | fn(); 675 | } 676 | } 677 | 678 | function id(name) { 679 | return !!(typeof document !== "undefined" && document && document.getElementById) && 680 | document.getElementById( name ); 681 | } 682 | 683 | // Test for equality any JavaScript type. 684 | // Discussions and reference: http://philrathe.com/articles/equiv 685 | // Test suites: http://philrathe.com/tests/equiv 686 | // Author: Philippe Rathé 687 | QUnit.equiv = function () { 688 | 689 | var innerEquiv; // the real equiv function 690 | var callers = []; // stack to decide between skip/abort functions 691 | var parents = []; // stack to avoiding loops from circular referencing 692 | 693 | 694 | // Determine what is o. 695 | function hoozit(o) { 696 | if (QUnit.is("String", o)) { 697 | return "string"; 698 | 699 | } else if (QUnit.is("Boolean", o)) { 700 | return "boolean"; 701 | 702 | } else if (QUnit.is("Number", o)) { 703 | 704 | if (isNaN(o)) { 705 | return "nan"; 706 | } else { 707 | return "number"; 708 | } 709 | 710 | } else if (typeof o === "undefined") { 711 | return "undefined"; 712 | 713 | // consider: typeof null === object 714 | } else if (o === null) { 715 | return "null"; 716 | 717 | // consider: typeof [] === object 718 | } else if (QUnit.is( "Array", o)) { 719 | return "array"; 720 | 721 | // consider: typeof new Date() === object 722 | } else if (QUnit.is( "Date", o)) { 723 | return "date"; 724 | 725 | // consider: /./ instanceof Object; 726 | // /./ instanceof RegExp; 727 | // typeof /./ === "function"; // => false in IE and Opera, 728 | // true in FF and Safari 729 | } else if (QUnit.is( "RegExp", o)) { 730 | return "regexp"; 731 | 732 | } else if (typeof o === "object") { 733 | return "object"; 734 | 735 | } else if (QUnit.is( "Function", o)) { 736 | return "function"; 737 | } else { 738 | return undefined; 739 | } 740 | } 741 | 742 | // Call the o related callback with the given arguments. 743 | function bindCallbacks(o, callbacks, args) { 744 | var prop = hoozit(o); 745 | if (prop) { 746 | if (hoozit(callbacks[prop]) === "function") { 747 | return callbacks[prop].apply(callbacks, args); 748 | } else { 749 | return callbacks[prop]; // or undefined 750 | } 751 | } 752 | } 753 | 754 | var callbacks = function () { 755 | 756 | // for string, boolean, number and null 757 | function useStrictEquality(b, a) { 758 | if (b instanceof a.constructor || a instanceof b.constructor) { 759 | // to catch short annotaion VS 'new' annotation of a declaration 760 | // e.g. var i = 1; 761 | // var j = new Number(1); 762 | return a == b; 763 | } else { 764 | return a === b; 765 | } 766 | } 767 | 768 | return { 769 | "string": useStrictEquality, 770 | "boolean": useStrictEquality, 771 | "number": useStrictEquality, 772 | "null": useStrictEquality, 773 | "undefined": useStrictEquality, 774 | 775 | "nan": function (b) { 776 | return isNaN(b); 777 | }, 778 | 779 | "date": function (b, a) { 780 | return hoozit(b) === "date" && a.valueOf() === b.valueOf(); 781 | }, 782 | 783 | "regexp": function (b, a) { 784 | return hoozit(b) === "regexp" && 785 | a.source === b.source && // the regex itself 786 | a.global === b.global && // and its modifers (gmi) ... 787 | a.ignoreCase === b.ignoreCase && 788 | a.multiline === b.multiline; 789 | }, 790 | 791 | // - skip when the property is a method of an instance (OOP) 792 | // - abort otherwise, 793 | // initial === would have catch identical references anyway 794 | "function": function () { 795 | var caller = callers[callers.length - 1]; 796 | return caller !== Object && 797 | typeof caller !== "undefined"; 798 | }, 799 | 800 | "array": function (b, a) { 801 | var i, j, loop; 802 | var len; 803 | 804 | // b could be an object literal here 805 | if ( ! (hoozit(b) === "array")) { 806 | return false; 807 | } 808 | 809 | len = a.length; 810 | if (len !== b.length) { // safe and faster 811 | return false; 812 | } 813 | 814 | //track reference to avoid circular references 815 | parents.push(a); 816 | for (i = 0; i < len; i++) { 817 | loop = false; 818 | for(j=0;j= 0) { 963 | type = "array"; 964 | } else { 965 | type = typeof obj; 966 | } 967 | return type; 968 | }, 969 | separator:function() { 970 | return this.multiline ? this.HTML ? '
' : '\n' : this.HTML ? ' ' : ' '; 971 | }, 972 | indent:function( extra ) {// extra can be a number, shortcut for increasing-calling-decreasing 973 | if ( !this.multiline ) 974 | return ''; 975 | var chr = this.indentChar; 976 | if ( this.HTML ) 977 | chr = chr.replace(/\t/g,' ').replace(/ /g,' '); 978 | return Array( this._depth_ + (extra||0) ).join(chr); 979 | }, 980 | up:function( a ) { 981 | this._depth_ += a || 1; 982 | }, 983 | down:function( a ) { 984 | this._depth_ -= a || 1; 985 | }, 986 | setParser:function( name, parser ) { 987 | this.parsers[name] = parser; 988 | }, 989 | // The next 3 are exposed so you can use them 990 | quote:quote, 991 | literal:literal, 992 | join:join, 993 | // 994 | _depth_: 1, 995 | // This is the list of parsers, to modify them, use jsDump.setParser 996 | parsers:{ 997 | window: '[Window]', 998 | document: '[Document]', 999 | error:'[ERROR]', //when no parser is found, shouldn't happen 1000 | unknown: '[Unknown]', 1001 | 'null':'null', 1002 | undefined:'undefined', 1003 | 'function':function( fn ) { 1004 | var ret = 'function', 1005 | name = 'name' in fn ? fn.name : (reName.exec(fn)||[])[1];//functions never have name in IE 1006 | if ( name ) 1007 | ret += ' ' + name; 1008 | ret += '('; 1009 | 1010 | ret = [ ret, this.parse( fn, 'functionArgs' ), '){'].join(''); 1011 | return join( ret, this.parse(fn,'functionCode'), '}' ); 1012 | }, 1013 | array: array, 1014 | nodelist: array, 1015 | arguments: array, 1016 | object:function( map ) { 1017 | var ret = [ ]; 1018 | this.up(); 1019 | for ( var key in map ) 1020 | ret.push( this.parse(key,'key') + ': ' + this.parse(map[key]) ); 1021 | this.down(); 1022 | return join( '{', ret, '}' ); 1023 | }, 1024 | node:function( node ) { 1025 | var open = this.HTML ? '<' : '<', 1026 | close = this.HTML ? '>' : '>'; 1027 | 1028 | var tag = node.nodeName.toLowerCase(), 1029 | ret = open + tag; 1030 | 1031 | for ( var a in this.DOMAttrs ) { 1032 | var val = node[this.DOMAttrs[a]]; 1033 | if ( val ) 1034 | ret += ' ' + a + '=' + this.parse( val, 'attribute' ); 1035 | } 1036 | return ret + close + open + '/' + tag + close; 1037 | }, 1038 | functionArgs:function( fn ) {//function calls it internally, it's the arguments part of the function 1039 | var l = fn.length; 1040 | if ( !l ) return ''; 1041 | 1042 | var args = Array(l); 1043 | while ( l-- ) 1044 | args[l] = String.fromCharCode(97+l);//97 is 'a' 1045 | return ' ' + args.join(', ') + ' '; 1046 | }, 1047 | key:quote, //object calls it internally, the key part of an item in a map 1048 | functionCode:'[code]', //function calls it internally, it's the content of the function 1049 | attribute:quote, //node calls it internally, it's an html attribute value 1050 | string:quote, 1051 | date:quote, 1052 | regexp:literal, //regex 1053 | number:literal, 1054 | 'boolean':literal 1055 | }, 1056 | DOMAttrs:{//attributes to dump from nodes, name=>realName 1057 | id:'id', 1058 | name:'name', 1059 | 'class':'className' 1060 | }, 1061 | HTML:false,//if true, entities are escaped ( <, >, \t, space and \n ) 1062 | indentChar:' ',//indentation unit 1063 | multiline:false //if true, items in a collection, are separated by a \n, else just a space. 1064 | }; 1065 | 1066 | return jsDump; 1067 | })(); 1068 | 1069 | })(this); 1070 | -------------------------------------------------------------------------------- /src/intro.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | var root = this; -------------------------------------------------------------------------------- /src/outro.js: -------------------------------------------------------------------------------- 1 | // export namespace 2 | root.uv = uv; 3 | 4 | // Export the uv object for CommonJS. 5 | if (typeof exports !== 'undefined') exports.uv = uv; 6 | })(); -------------------------------------------------------------------------------- /src/scene/actor.js: -------------------------------------------------------------------------------- 1 | // Actor - Graphical object to be attached to the scene graph 2 | // ============================================================================= 3 | 4 | uv.Actor = function(properties) { 5 | Data.Node.call(this); 6 | this.childCount = 0; 7 | 8 | this.properties = uv.extend({ 9 | x: 0, 10 | y: 0, 11 | scaleX: 1, 12 | scaleY: 1, 13 | rotation: 0, 14 | localX: 0, 15 | localY: 0, 16 | localScaleX: 1, 17 | localScaleY: 1, 18 | localRotation: 0, 19 | fillStyle: '#000', 20 | strokeStyle: '#000', 21 | lineWidth: 1, 22 | lineCap: 'butt', 23 | lineJoin: 'miter', 24 | globalAlpha: 1, 25 | miterLimit: 10, 26 | visible: true, 27 | transformMode: 'object' 28 | }, properties); 29 | 30 | // init children 31 | this.replace('children', new Data.Hash()); 32 | 33 | // Init motion tween container 34 | this.tweens = {}; 35 | 36 | // Under mouse cursor 37 | this.active = false; 38 | 39 | // Event handlers 40 | this.handlers = {}; 41 | }; 42 | 43 | // Registration point for custom actors 44 | uv.Actor.registeredActors = {}; 45 | 46 | uv.Actor.prototype = uv.inherit(Data.Node); 47 | 48 | 49 | // Bind event 50 | 51 | uv.Actor.prototype.bind = function(name, fn) { 52 | if (!this.handlers[name]) { 53 | this.handlers[name] = []; 54 | } 55 | this.handlers[name].push(fn); 56 | }; 57 | 58 | 59 | // Trigger event 60 | 61 | uv.Actor.prototype.trigger = function(name) { 62 | var that = this; 63 | if (this.handlers[name]) { 64 | for (var key in this.handlers[name]) { 65 | this.handlers[name][key].apply(that, []); 66 | } 67 | } 68 | }; 69 | 70 | 71 | // Generic factory method that creates an actor based on an Actor Spec 72 | 73 | uv.Actor.create = function(spec) { 74 | var constructor = uv.Actor.registeredActors[spec.type]; 75 | if (!constructor) { 76 | throw "Actor type unregistered: '" + spec.type + "'"; 77 | } 78 | return new constructor(spec); 79 | }; 80 | 81 | 82 | // The actor's unique id 83 | 84 | uv.Actor.prototype.id = function() { 85 | return this.p('id') || this.nodeId; 86 | }; 87 | 88 | uv.Actor.prototype.add = function(spec) { 89 | var actor; 90 | 91 | if (spec instanceof uv.Actor) { 92 | actor = spec; 93 | } else { 94 | actor = uv.Actor.create(spec); 95 | } 96 | 97 | if (!this.scene) { 98 | throw "You can't add childs to actors that don't have a scene reference"; 99 | } 100 | 101 | // Register actor at the scene object 102 | this.scene.registerActor(actor); 103 | 104 | // Register as a child 105 | this.set('children', actor.id(), actor); 106 | actor.parent = this; 107 | 108 | // Call init hook if defined 109 | if (actor.init) { 110 | actor.init(); 111 | } 112 | 113 | // Register children 114 | if (spec.actors) { 115 | 116 | spec.actors.forEach(function(actorSpec) { 117 | actor.add(actorSpec); 118 | }); 119 | } 120 | return actor; 121 | }; 122 | 123 | 124 | uv.Actor.prototype.get = function() { 125 | if (arguments.length === 1) { 126 | return this.scene.actors[arguments[0]]; 127 | } else { 128 | // Delegate to Node#get 129 | return Data.Node.prototype.get.call(this, arguments[0], arguments[1]); 130 | } 131 | }; 132 | 133 | 134 | // Remove child by ID 135 | uv.Actor.prototype.remove = function(matcher) { 136 | var that = this; 137 | if (matcher instanceof Function) { 138 | this.traverse().forEach(function(actor) { 139 | if (matcher(actor)) { 140 | that.scene.remove(actor.id()); 141 | } 142 | }); 143 | } else { 144 | if (this.get('children', matcher)) { 145 | // Remove child 146 | this.all('children').del(matcher); 147 | 148 | // Remove from scene 149 | delete this.scene.actors[matcher]; 150 | delete this.scene.interactiveActors[matcher]; 151 | } 152 | 153 | // Children hunt 154 | this.all('children').each(function(child, key, i) { 155 | child.remove(matcher); 156 | }); 157 | } 158 | }; 159 | 160 | 161 | uv.Actor.prototype.traverse = function() { 162 | return this.scene.properties.traverser(this); 163 | }; 164 | 165 | // Evaluates a property (in case of a function 166 | // the result of the function is returned) 167 | 168 | uv.Actor.prototype.property = function(property, value) { 169 | if (value) { 170 | this.properties[property] = value; 171 | return value; 172 | } else { 173 | if (this.properties[property] instanceof Function) 174 | return this.properties[property].call(this); 175 | else 176 | return this.properties[property]; 177 | } 178 | }; 179 | 180 | uv.Actor.prototype.p = uv.Actor.prototype.property; 181 | 182 | // Registers a Tween on demand 183 | 184 | uv.Actor.prototype.animate = function(properties, duration, easing) { 185 | var scene = this.scene, 186 | tween = new uv.Tween(this.properties) 187 | .to(duration || 1, properties) 188 | .easing(easing || uv.Tween.Easing.Expo.EaseInOut) 189 | .onComplete(function() { 190 | scene.unexecute(uv.cmds.RequestFramerate); 191 | // Remove from registered tweens 192 | uv.TweenManager.remove(tween); 193 | }); 194 | scene.execute(uv.cmds.RequestFramerate); 195 | return tween.start(); 196 | }; 197 | 198 | 199 | // Dynamic Matrices 200 | // ----------------------------------------------------------------------------- 201 | 202 | // TODO: allow users to specify the transformation order (rotate, translate, scale) 203 | 204 | uv.Actor.prototype.tShape = function(x, y) { 205 | return uv.Matrix() 206 | .translate(this.p('localX'), this.p('localY')) 207 | .rotate(this.p('localRotation')) 208 | .scale(this.p('localScaleX'), this.p('localScaleY')); 209 | }; 210 | 211 | uv.Actor.prototype.tWorldParent = function() { 212 | if (this.parent) { 213 | return this.parent._tWorld; 214 | } else { 215 | return uv.Matrix(); 216 | } 217 | }; 218 | 219 | uv.Actor.prototype.tWorld = function() { 220 | return this.tWorldParent() 221 | .translate(this.p('x'), this.p('y')) 222 | .rotate(this.p('rotation')) 223 | .scale(this.p('scaleX'), this.p('scaleY')); 224 | }; 225 | 226 | // Compiles and caches the current World Transformation Matrix 227 | 228 | uv.Actor.prototype.compileMatrix = function() { 229 | this.update(); 230 | this._tWorld = this.tWorld(); 231 | 232 | if (this.all('children')) { 233 | this.all('children').each(function(child, key, i) { 234 | child.compileMatrix(); 235 | }); 236 | } 237 | }; 238 | 239 | 240 | // Drawing, masking and rendering 241 | // ----------------------------------------------------------------------------- 242 | 243 | uv.Actor.prototype.update = function() { 244 | // Update motion tweens 245 | uv.TweenManager.update(); 246 | }; 247 | 248 | uv.Actor.prototype.applyStyles = function(ctx) { 249 | ctx.fillStyle = this.p('fillStyle'); 250 | ctx.strokeStyle = this.p('strokeStyle'); 251 | ctx.lineWidth = this.p('lineWidth'); 252 | ctx.lineCap = this.p('lineCap'); 253 | ctx.lineJoin = this.p('lineJoin'); 254 | ctx.globalAlpha = this.p('globalAlpha'); 255 | ctx.miterLimit = this.p('miterLimit'); 256 | }; 257 | 258 | uv.Actor.prototype.draw = function(ctx) {}; 259 | 260 | uv.Actor.prototype.checkActive = function(ctx, mouseX, mouseY) { 261 | var p = new uv.Point(mouseX,mouseY); 262 | 263 | // TODO: Add proper check for statically rendered actors, 264 | // based on this.scene.activeDisplay's view matrix 265 | var pnew = this._tWorld.inverse().transformPoint(p); 266 | mouseX = pnew.x; 267 | mouseY = pnew.y; 268 | 269 | if (this.bounds && ctx.isPointInPath) { 270 | this.drawBounds(ctx); 271 | if (ctx.isPointInPath(mouseX, mouseY)) 272 | this.active = true; 273 | else 274 | this.active = false; 275 | } 276 | return this.active; 277 | }; 278 | 279 | // Bounds used for mouse picking 280 | 281 | uv.Actor.prototype.drawBounds = function(ctx) { 282 | var bounds = this.bounds(), 283 | start, v; 284 | start = bounds.shift(); 285 | ctx.beginPath(); 286 | ctx.moveTo(start.x, start.y); 287 | while (v = bounds.shift()) { 288 | ctx.lineTo(v.x, v.y); 289 | } 290 | ctx.lineTo(start.x, start.y); 291 | }; 292 | 293 | 294 | uv.Actor.prototype.render = function(ctx, tView) { 295 | if (!this.p('visible')) return; 296 | this.applyStyles(ctx); 297 | this.transform(ctx, tView); 298 | this.draw(ctx, tView); 299 | }; 300 | 301 | 302 | // Applies the transformation matrix 303 | uv.Actor.prototype.transform = function(ctx, tView) { 304 | var m = this.tShape().concat(tView).concat(this._tWorld), 305 | t; 306 | if (this.p('transformMode') === 'origin') { 307 | // Extract the translation of the matrix 308 | t = m.transformPoint(uv.Point(0,0)); 309 | ctx.setTransform(1, 0, 0, 1, t.x, t.y); 310 | } else { 311 | ctx.setTransform(m.a, m.b, m.c, m.d, m.tx, m.ty); 312 | } 313 | }; -------------------------------------------------------------------------------- /src/scene/actors/circle.js: -------------------------------------------------------------------------------- 1 | // Circle 2 | // ============================================================================= 3 | 4 | uv.Circle = function(properties) { 5 | // super call 6 | uv.Actor.call(this, uv.extend({ 7 | radius: 20, 8 | strokeWeight: 2, 9 | lineWidth: 0, 10 | strokeStyle: '#fff' 11 | }, properties)); 12 | }; 13 | 14 | uv.Actor.registeredActors.circle = uv.Circle; 15 | 16 | uv.Circle.prototype = uv.inherit(uv.Actor); 17 | 18 | uv.Circle.prototype.bounds = function() { 19 | return [ 20 | { x: -this.p('radius'), y: -this.p('radius') }, 21 | { x: this.p('radius'), y: -this.p('radius') }, 22 | { x: this.p('radius'), y: this.p('radius') }, 23 | { x: -this.p('radius'), y: this.p('radius') } 24 | ]; 25 | }; 26 | 27 | uv.Circle.prototype.draw = function(ctx, tView) { 28 | ctx.fillStyle = this.p('fillStyle'); 29 | ctx.strokeStyle = this.p('strokeStyle'); 30 | ctx.lineWidth = this.p('lineWidth'); 31 | 32 | ctx.beginPath(); 33 | ctx.arc(0,0,this.p('radius'),0,Math.PI*2, false); 34 | ctx.closePath(); 35 | if (this.p('lineWidth') > 0) { 36 | ctx.stroke(); 37 | } 38 | ctx.fill(); 39 | }; 40 | -------------------------------------------------------------------------------- /src/scene/actors/label.js: -------------------------------------------------------------------------------- 1 | // Label 2 | // ============================================================================= 3 | uv.Label = function(properties) { 4 | // super call 5 | uv.Actor.call(this, uv.extend({ 6 | text: '', 7 | textAlign: 'start', 8 | font: '12px Helvetica, Arial', 9 | fillStyle: '#444', 10 | lineWidth: 0, 11 | backgroundStyle: '#eee', 12 | background: false 13 | }, properties)); 14 | }; 15 | 16 | uv.Actor.registeredActors.label = uv.Label; 17 | 18 | uv.Label.prototype = uv.inherit(uv.Actor); 19 | 20 | uv.Label.prototype.draw = function(ctx, tView) { 21 | ctx.font = this.p('font'); 22 | 23 | ctx.textAlign = this.p('textAlign'); 24 | ctx.fillText(this.p('text'), 0, 0); 25 | }; 26 | -------------------------------------------------------------------------------- /src/scene/actors/path.js: -------------------------------------------------------------------------------- 1 | // Path 2 | // ============================================================================= 3 | 4 | uv.Path = function(properties) { 5 | // super call 6 | uv.Actor.call(this, uv.extend({ 7 | points: [], 8 | lineWidth: 1, 9 | strokeStyle: '#000', 10 | fillStyle: '' 11 | }, properties)); 12 | 13 | this.transformedPoints = this.points = [].concat(this.p('points')); 14 | }; 15 | 16 | uv.Actor.registeredActors.path = uv.Path; 17 | 18 | uv.Path.prototype = uv.inherit(uv.Actor); 19 | 20 | uv.Path.prototype.transform = function(ctx, tView) { 21 | this.transformedPoints = this.points = [].concat(this.p('points')); 22 | if (this.p('transformMode') === 'origin') { 23 | var m = this.tShape().concat(tView).concat(this._tWorld); 24 | 25 | ctx.setTransform(1, 0, 0, 1, 0, 0); 26 | this.transformedPoints = this.points.map(function(p) { 27 | var tp = m.transformPoint(p), 28 | tcp1 = m.transformPoint(uv.Point(p.cp1x, p.cp1y)), 29 | tcp2 = m.transformPoint(uv.Point(p.cp2x, p.cp2y)), 30 | result; 31 | result = {x: tp.x, y: tp.y}; 32 | if (p.cp1x && p.cp1y) { 33 | result.cp1x = tcp1.x; 34 | result.cp1y = tcp1.y; 35 | } 36 | if (p.cp2x && p.cp2y) { 37 | result.cp2x = tcp2.x; 38 | result.cp2y = tcp2.y; 39 | } 40 | return result; 41 | }); 42 | } else { 43 | uv.Actor.prototype.transform.call(this, ctx, tView); 44 | } 45 | }; 46 | 47 | 48 | uv.Path.prototype.draw = function(ctx, tView) { 49 | var points = [].concat(this.transformedPoints), 50 | v; 51 | 52 | if (points.length >= 1) { 53 | ctx.beginPath(); 54 | v = points.shift(); 55 | ctx.moveTo(v.x, v.y); 56 | while (v = points.shift()) { 57 | if (v.cp1x && v.cp2x) { 58 | ctx.bezierCurveTo(v.cp1x, v.cp1y, v.cp2x,v.cp2y, v.x, v.y); 59 | } else if (v.cp1x) { 60 | ctx.quadraticCurveTo(v.cp1x, v.cp1y, v.x, v.y); 61 | } else { 62 | ctx.lineTo(v.x, v.y); 63 | } 64 | } 65 | if (this.p('lineWidth') > 0 && this.p('strokeStyle') !== '') { 66 | ctx.stroke(); 67 | } 68 | 69 | if (this.p('fillStyle') !== '') { 70 | ctx.fill(); 71 | } 72 | ctx.closePath(); 73 | } 74 | }; 75 | -------------------------------------------------------------------------------- /src/scene/actors/rect.js: -------------------------------------------------------------------------------- 1 | // Rect 2 | // ============================================================================= 3 | 4 | uv.Rect = function(properties) { 5 | // super call 6 | uv.Actor.call(this, uv.extend({ 7 | width: 0, 8 | height: 0, 9 | fillStyle: '#777', 10 | strokeStyle: '#000', 11 | lineWidth: 0 12 | }, properties)); 13 | }; 14 | 15 | uv.Actor.registeredActors.rect = uv.Rect; 16 | 17 | uv.Rect.prototype = uv.inherit(uv.Actor); 18 | 19 | uv.Rect.prototype.bounds = function() { 20 | return [ 21 | { x: 0, y: 0 }, 22 | { x: this.p('width'), y: 0 }, 23 | { x: this.p('width'), y: this.p('height') }, 24 | { x: 0, y: this.p('height') } 25 | ]; 26 | }; 27 | 28 | uv.Rect.prototype.draw = function(ctx, tView) { 29 | if (this.p('fillStyle')) { 30 | ctx.fillRect(0, 0, this.p('width'), this.p('height')); 31 | } 32 | 33 | if (this.p('lineWidth') > 0) { 34 | ctx.strokeRect(0, 0, this.p('width'), this.p('height')); 35 | } 36 | }; 37 | -------------------------------------------------------------------------------- /src/scene/behaviors.js: -------------------------------------------------------------------------------- 1 | uv.behaviors = {}; 2 | 3 | uv.behaviors.adjust = function(display, m) { 4 | var b = display.bounds(); 5 | // clamp to scene boundaries 6 | if (display.bounded) { 7 | m.a = m.d = Math.max(1, m.a); 8 | m.tx = Math.max(b.x, Math.min(0, m.tx)); 9 | m.ty = Math.max(b.y, Math.min(0, m.ty)); 10 | } 11 | return m; 12 | }; 13 | 14 | uv.behaviors.Zoom = function(display) { 15 | function mouseWheel(e) { 16 | var delta = (e.wheelDelta / 120 || -e.detail), 17 | m = display.tView.scale( 18 | 1+0.005 * delta, 19 | 1+0.005 * delta, 20 | uv.Point(display.scene.mouseX, display.scene.mouseY) 21 | ); 22 | display.tView = (delta < 0) ? uv.behaviors.adjust(display, m) : m; 23 | display.trigger('viewChange'); 24 | } 25 | display.canvas.addEventListener("mousewheel", mouseWheel, false); 26 | display.canvas.addEventListener("DOMMouseScroll", mouseWheel, false); 27 | }; 28 | 29 | uv.behaviors.Pan = function(display) { 30 | var pos, // initial mouse position 31 | view, // cached view matrix 32 | panning = false; 33 | 34 | function mouseDown() { 35 | p = uv.Point(display.mouseX, display.mouseY); 36 | view = display.tView; 37 | panning = true; 38 | } 39 | 40 | function mouseMove() { 41 | if (!panning) return; 42 | var x = (display.mouseX - p.x), 43 | y = (display.mouseY - p.y), 44 | m = uv.Matrix.translation(x, y).concat(view); 45 | display.tView = uv.behaviors.adjust(display, m); 46 | display.trigger('viewChange'); 47 | } 48 | 49 | function release() { 50 | panning = false; 51 | } 52 | 53 | display.canvas.addEventListener("mousedown", mouseDown, false); 54 | display.canvas.addEventListener("mousemove", mouseMove, false); 55 | display.canvas.addEventListener("mouseup", release, false); 56 | display.canvas.addEventListener("mouseout", release, false); 57 | }; -------------------------------------------------------------------------------- /src/scene/commands.js: -------------------------------------------------------------------------------- 1 | // Commands 2 | // ============================================================================= 3 | // 4 | // Commands are used to modify properties on the scene. They can be executed 5 | // one or many times, and they can be unexecuted to recover the original state 6 | 7 | uv.cmds = {}; 8 | 9 | uv.cmds.RequestFramerate = function(scene, opts) { 10 | this.scene = scene; 11 | this.requests = 0; 12 | this.framerate = opts.framerate; 13 | this.originalFramerate = this.scene.framerate; 14 | }; 15 | 16 | uv.cmds.RequestFramerate.className = 'RequestFramerate'; 17 | 18 | uv.cmds.RequestFramerate.prototype.execute = function() { 19 | this.requests += 1; 20 | this.scene.setFramerate(this.framerate); 21 | }; 22 | 23 | uv.cmds.RequestFramerate.prototype.unexecute = function() { 24 | this.requests -= 1; 25 | if (this.requests <= 0) { 26 | this.scene.setFramerate(this.originalFramerate); 27 | } 28 | }; -------------------------------------------------------------------------------- /src/scene/display.js: -------------------------------------------------------------------------------- 1 | uv.Display = function(scene, properties) { 2 | var that = this; 3 | 4 | // super call 5 | uv.Actor.call(this, uv.extend({ 6 | fillStyle: '' 7 | }, properties)); 8 | 9 | this.scene = scene; 10 | this.element = document.getElementById(properties.container); 11 | this.canvas = document.createElement("canvas"); 12 | this.canvas.setAttribute('width', properties.width); 13 | this.canvas.setAttribute('height', properties.height); 14 | this.canvas.style.position = 'relative'; 15 | this.element.appendChild(this.canvas); 16 | 17 | this.width = properties.width; 18 | this.height = properties.height; 19 | 20 | this.bounded = properties.bounded || true; 21 | 22 | this.ctx = this.canvas.getContext("2d"); 23 | this.tView = uv.Matrix(); 24 | 25 | // attach behaviors 26 | if (properties.zooming) { 27 | this.zoombehavior = new uv.behaviors.Zoom(this); 28 | } 29 | 30 | if (properties.panning) { 31 | this.panbehavior = new uv.behaviors.Pan(this); 32 | } 33 | 34 | // Register mouse events 35 | function mouseMove(e) { 36 | var mat = that.tView.inverse(), 37 | pos; 38 | 39 | if (e.offsetX) { 40 | pos = new uv.Point(e.offsetX, e.offsetY); 41 | } else if (e.layerX) { 42 | pos = new uv.Point(e.layerX, e.layerY); 43 | } 44 | 45 | if (pos) { 46 | that.mouseX = pos.x; 47 | that.mouseY = pos.y; 48 | worldPos = mat.transformPoint(pos); 49 | that.scene.mouseX = parseInt(worldPos.x, 10); 50 | that.scene.mouseY = parseInt(worldPos.y, 10); 51 | that.scene.activeDisplay = that; 52 | } 53 | } 54 | 55 | function mouseOut() { 56 | that.scene.mouseX = NaN; 57 | that.scene.mouseY = NaN; 58 | } 59 | 60 | function interact() { 61 | that.scene.trigger('interact'); 62 | } 63 | 64 | function click() { 65 | uv.each(that.scene.activeActors, function(a) { 66 | a.trigger('click'); 67 | }); 68 | } 69 | 70 | this.canvas.addEventListener("mousemove", interact, false); 71 | this.canvas.addEventListener("DOMMouseScroll", interact, false); 72 | this.canvas.addEventListener("mousemove", mouseMove, false); 73 | this.canvas.addEventListener("mousewheel", interact, false); 74 | this.canvas.addEventListener("mouseout", mouseOut, false); 75 | this.canvas.addEventListener("click", click, false); 76 | }; 77 | 78 | uv.Display.prototype = uv.inherit(uv.Actor); 79 | 80 | // Convert world pos to display pos 81 | 82 | uv.Display.prototype.displayPos = function(point) { 83 | return this.tView.transformPoint(pos); 84 | }; 85 | 86 | uv.Display.prototype.zoom = function(point) { 87 | return this.tView.a; 88 | }; 89 | 90 | // Convert display pos to world pos 91 | 92 | uv.Display.prototype.worldPos = function(pos) { 93 | return this.tView.inverse().transformPoint(pos); 94 | }; 95 | 96 | // Yield bounds used for viewport constraining 97 | 98 | uv.Display.prototype.bounds = function() { 99 | // Consider area that doesn't fit on the display 100 | var dx = Math.max(0, this.scene.p('width') - this.width), 101 | dy = Math.max(0, this.scene.p('width') - this.width); 102 | 103 | return { 104 | x: (1 - this.tView.a) * this.width - this.tView.a * dx, 105 | y: (1 - this.tView.a) * this.height - this.tView.a * dy 106 | }; 107 | }; 108 | 109 | // Updates the display (on every frame) 110 | 111 | uv.Display.prototype.refresh = function() { 112 | var that = this, 113 | actors, 114 | displayActors; 115 | 116 | 117 | this.ctx.clearRect(0,0, this.width, this.height); 118 | // Scene background 119 | if (this.scene.p('fillStyle') !== '') { 120 | this.ctx.fillStyle = this.scene.p('fillStyle'); 121 | this.ctx.fillRect(0, 0, this.width, this.height); 122 | } 123 | 124 | this.ctx.save(); 125 | 126 | actors = this.scene.traverse(); 127 | actors.shift(); 128 | uv.each(actors, function(actor, index) { 129 | actor.render(that.ctx, that.tView); 130 | }); 131 | 132 | // Draw the display components 133 | displayActors = this.traverse(); 134 | actors.shift(); 135 | uv.each(displayActors, function(actor, index) { 136 | actor.render(that.ctx, uv.Matrix()); 137 | }); 138 | 139 | this.ctx.restore(); 140 | }; -------------------------------------------------------------------------------- /src/scene/matrix.js: -------------------------------------------------------------------------------- 1 | // Matrix.js v1.1.0 2 | // ========================================================================== 3 | // Copyright (c) 2010 STRd6 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 11 | // 12 | // The above copyright notice and this permission notice shall be included in 13 | // all copies or substantial portions of the Software. 14 | // 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | // THE SOFTWARE. 22 | // 23 | // Loosely based on flash: 24 | // http://www.adobe.com/livedocs/flash/9.0/ActionScriptLangRefV3/flash/geom/Matrix.html 25 | 26 | (function() { 27 | /** 28 | * Create a new point with given x and y coordinates. If no arguments are given 29 | * defaults to (0, 0). 30 | */ 31 | function Point(x, y) { 32 | return { 33 | /** 34 | * The x coordinate of this point. 35 | * @name x 36 | * @fieldOf Point# 37 | */ 38 | x: x || 0, 39 | /** 40 | * The y coordinate of this point. 41 | * @name y 42 | * @fieldOf Point# 43 | */ 44 | y: y || 0, 45 | /** 46 | * Adds a point to this one and returns the new point. 47 | * @name add 48 | * @methodOf Point# 49 | * 50 | * @param {Point} other The point to add this point to. 51 | * @returns A new point, the sum of both. 52 | * @type Point 53 | */ 54 | add: function(other) { 55 | return Point(this.x + other.x, this.y + other.y); 56 | } 57 | }; 58 | } 59 | 60 | /** 61 | * @param {Point} p1 62 | * @param {Point} p2 63 | * @returns The Euclidean distance between two points. 64 | */ 65 | Point.distance = function(p1, p2) { 66 | return Math.sqrt(Math.pow(p2.x - p1.x, 2) + Math.pow(p2.y - p1.y, 2)); 67 | }; 68 | 69 | /** 70 | * If you have two dudes, one standing at point p1, and the other 71 | * standing at point p2, then this method will return the direction 72 | * that the dude standing at p1 will need to face to look at p2. 73 | * @param {Point} p1 The starting point. 74 | * @param {Point} p2 The ending point. 75 | * @returns The direction from p1 to p2 in radians. 76 | */ 77 | Point.direction = function(p1, p2) { 78 | return Math.atan2( 79 | p2.y - p1.y, 80 | p2.x - p1.x 81 | ); 82 | }; 83 | 84 | /** 85 | *
 86 |    *  _        _
 87 |    * | a  c tx  |
 88 |    * | b  d ty  |
 89 |    * |_0  0  1 _|
 90 |    * 
91 | * Creates a matrix for 2d affine transformations. 92 | * 93 | * concat, inverse, rotate, scale and translate return new matrices with the 94 | * transformations applied. The matrix is not modified in place. 95 | * 96 | * Returns the identity matrix when called with no arguments. 97 | * @name Matrix 98 | * @param {Number} [a] 99 | * @param {Number} [b] 100 | * @param {Number} [c] 101 | * @param {Number} [d] 102 | * @param {Number} [tx] 103 | * @param {Number} [ty] 104 | * @constructor 105 | */ 106 | function Matrix(a, b, c, d, tx, ty) { 107 | a = a !== undefined ? a : 1; 108 | d = d !== undefined ? d : 1; 109 | 110 | return { 111 | /** 112 | * @name a 113 | * @fieldOf Matrix# 114 | */ 115 | a: a, 116 | /** 117 | * @name b 118 | * @fieldOf Matrix# 119 | */ 120 | b: b || 0, 121 | /** 122 | * @name c 123 | * @fieldOf Matrix# 124 | */ 125 | c: c || 0, 126 | /** 127 | * @name d 128 | * @fieldOf Matrix# 129 | */ 130 | d: d, 131 | /** 132 | * @name tx 133 | * @fieldOf Matrix# 134 | */ 135 | tx: tx || 0, 136 | /** 137 | * @name ty 138 | * @fieldOf Matrix# 139 | */ 140 | ty: ty || 0, 141 | 142 | /** 143 | * Returns the result of this matrix multiplied by another matrix 144 | * combining the geometric effects of the two. In mathematical terms, 145 | * concatenating two matrixes is the same as combining them using matrix multiplication. 146 | * If this matrix is A and the matrix passed in is B, the resulting matrix is A x B 147 | * http://mathworld.wolfram.com/MatrixMultiplication.html 148 | * @name concat 149 | * @methodOf Matrix# 150 | * 151 | * @param {Matrix} matrix The matrix to multiply this matrix by. 152 | * @returns The result of the matrix multiplication, a new matrix. 153 | * @type Matrix 154 | */ 155 | concat: function(matrix) { 156 | return Matrix( 157 | this.a * matrix.a + this.c * matrix.b, 158 | this.b * matrix.a + this.d * matrix.b, 159 | this.a * matrix.c + this.c * matrix.d, 160 | this.b * matrix.c + this.d * matrix.d, 161 | this.a * matrix.tx + this.c * matrix.ty + this.tx, 162 | this.b * matrix.tx + this.d * matrix.ty + this.ty 163 | ); 164 | }, 165 | 166 | /** 167 | * Given a point in the pretransform coordinate space, returns the coordinates of 168 | * that point after the transformation occurs. Unlike the standard transformation 169 | * applied using the transformPoint() method, the deltaTransformPoint() method's 170 | * transformation does not consider the translation parameters tx and ty. 171 | * @name deltaTransformPoint 172 | * @methodOf Matrix# 173 | * @see #transformPoint 174 | * 175 | * @return A new point transformed by this matrix ignoring tx and ty. 176 | * @type Point 177 | */ 178 | deltaTransformPoint: function(point) { 179 | return Point( 180 | this.a * point.x + this.c * point.y, 181 | this.b * point.x + this.d * point.y 182 | ); 183 | }, 184 | 185 | /** 186 | * Returns the inverse of the matrix. 187 | * http://mathworld.wolfram.com/MatrixInverse.html 188 | * @name inverse 189 | * @methodOf Matrix# 190 | * 191 | * @returns A new matrix that is the inverse of this matrix. 192 | * @type Matrix 193 | */ 194 | inverse: function() { 195 | var determinant = this.a * this.d - this.b * this.c; 196 | return Matrix( 197 | this.d / determinant, 198 | -this.b / determinant, 199 | -this.c / determinant, 200 | this.a / determinant, 201 | (this.c * this.ty - this.d * this.tx) / determinant, 202 | (this.b * this.tx - this.a * this.ty) / determinant 203 | ); 204 | }, 205 | 206 | /** 207 | * Returns a new matrix that corresponds this matrix multiplied by a 208 | * a rotation matrix. 209 | * @name rotate 210 | * @methodOf Matrix# 211 | * @see Matrix.rotation 212 | * 213 | * @param {Number} theta Amount to rotate in radians. 214 | * @param {Point} [aboutPoint] The point about which this rotation occurs. Defaults to (0,0). 215 | * @returns A new matrix, rotated by the specified amount. 216 | * @type Matrix 217 | */ 218 | rotate: function(theta, aboutPoint) { 219 | return this.concat(Matrix.rotation(theta, aboutPoint)); 220 | }, 221 | 222 | /** 223 | * Returns a new matrix that corresponds this matrix multiplied by a 224 | * a scaling matrix. 225 | * @name scale 226 | * @methodOf Matrix# 227 | * @see Matrix.scale 228 | * 229 | * @param {Number} sx 230 | * @param {Number} [sy] 231 | * @param {Point} [aboutPoint] The point that remains fixed during the scaling 232 | * @type Matrix 233 | */ 234 | scale: function(sx, sy, aboutPoint) { 235 | return this.concat(Matrix.scale(sx, sy, aboutPoint)); 236 | }, 237 | 238 | /** 239 | * Returns the result of applying the geometric transformation represented by the 240 | * Matrix object to the specified point. 241 | * @name transformPoint 242 | * @methodOf Matrix# 243 | * @see #deltaTransformPoint 244 | * 245 | * @returns A new point with the transformation applied. 246 | * @type Point 247 | */ 248 | transformPoint: function(point) { 249 | return Point( 250 | this.a * point.x + this.c * point.y + this.tx, 251 | this.b * point.x + this.d * point.y + this.ty 252 | ); 253 | }, 254 | 255 | /** 256 | * Translates the matrix along the x and y axes, as specified by the tx and ty parameters. 257 | * @name translate 258 | * @methodOf Matrix# 259 | * @see Matrix.translation 260 | * 261 | * @param {Number} tx The translation along the x axis. 262 | * @param {Number} ty The translation along the y axis. 263 | * @returns A new matrix with the translation applied. 264 | * @type Matrix 265 | */ 266 | translate: function(tx, ty) { 267 | return this.concat(Matrix.translation(tx, ty)); 268 | } 269 | }; 270 | } 271 | 272 | /** 273 | * Creates a matrix transformation that corresponds to the given rotation, 274 | * around (0,0) or the specified point. 275 | * @see Matrix#rotate 276 | * 277 | * @param {Number} theta Rotation in radians. 278 | * @param {Point} [aboutPoint] The point about which this rotation occurs. Defaults to (0,0). 279 | * @returns 280 | * @type Matrix 281 | */ 282 | Matrix.rotation = function(theta, aboutPoint) { 283 | var rotationMatrix = Matrix( 284 | Math.cos(theta), 285 | Math.sin(theta), 286 | -Math.sin(theta), 287 | Math.cos(theta) 288 | ); 289 | 290 | if(aboutPoint) { 291 | rotationMatrix = 292 | Matrix.translation(aboutPoint.x, aboutPoint.y).concat( 293 | rotationMatrix 294 | ).concat( 295 | Matrix.translation(-aboutPoint.x, -aboutPoint.y) 296 | ); 297 | } 298 | 299 | return rotationMatrix; 300 | }; 301 | 302 | /** 303 | * Returns a matrix that corresponds to scaling by factors of sx, sy along 304 | * the x and y axis respectively. 305 | * If only one parameter is given the matrix is scaled uniformly along both axis. 306 | * If the optional aboutPoint parameter is given the scaling takes place 307 | * about the given point. 308 | * @see Matrix#scale 309 | * 310 | * @param {Number} sx The amount to scale by along the x axis or uniformly if no sy is given. 311 | * @param {Number} [sy] The amount to scale by along the y axis. 312 | * @param {Point} [aboutPoint] The point about which the scaling occurs. Defaults to (0,0). 313 | * @returns A matrix transformation representing scaling by sx and sy. 314 | * @type Matrix 315 | */ 316 | Matrix.scale = function(sx, sy, aboutPoint) { 317 | sy = sy || sx; 318 | 319 | var scaleMatrix = Matrix(sx, 0, 0, sy); 320 | 321 | if(aboutPoint) { 322 | scaleMatrix = 323 | Matrix.translation(aboutPoint.x, aboutPoint.y).concat( 324 | scaleMatrix 325 | ).concat( 326 | Matrix.translation(-aboutPoint.x, -aboutPoint.y) 327 | ); 328 | } 329 | 330 | return scaleMatrix; 331 | }; 332 | 333 | /** 334 | * Returns a matrix that corresponds to a translation of tx, ty. 335 | * @see Matrix#translate 336 | * 337 | * @param {Number} tx The amount to translate in the x direction. 338 | * @param {Number} ty The amount to translate in the y direction. 339 | * @return A matrix transformation representing a translation by tx and ty. 340 | * @type Matrix 341 | */ 342 | Matrix.translation = function(tx, ty) { 343 | return Matrix(1, 0, 0, 1, tx, ty); 344 | }; 345 | 346 | /** 347 | * A constant representing the identity matrix. 348 | * @name IDENTITY 349 | * @fieldOf Matrix 350 | */ 351 | Matrix.IDENTITY = Matrix(); 352 | /** 353 | * A constant representing the horizontal flip transformation matrix. 354 | * @name HORIZONTAL_FLIP 355 | * @fieldOf Matrix 356 | */ 357 | Matrix.HORIZONTAL_FLIP = Matrix(-1, 0, 0, 1); 358 | /** 359 | * A constant representing the vertical flip transformation matrix. 360 | * @name VERTICAL_FLIP 361 | * @fieldOf Matrix 362 | */ 363 | Matrix.VERTICAL_FLIP = Matrix(1, 0, 0, -1); 364 | 365 | // Export to Unveil 366 | uv.Point = Point; 367 | uv.Matrix = Matrix; 368 | }()); 369 | 370 | -------------------------------------------------------------------------------- /src/scene/scene.js: -------------------------------------------------------------------------------- 1 | // Scene 2 | // ============================================================================= 3 | 4 | uv.Scene = function(properties) { 5 | var that = this; 6 | 7 | // super call 8 | uv.Actor.call(this, uv.extend({ 9 | width: 0, 10 | height: 0, 11 | fillStyle: '', 12 | idleFramerate: 0, 13 | framerate: 50, 14 | traverser: uv.traverser.DepthFirst 15 | }, properties)); 16 | 17 | 18 | this.mouseX = NaN; 19 | this.mouseY = NaN; 20 | 21 | // Keeps track of actors that capture mouse events 22 | this.interactiveActors = {}; 23 | 24 | // Currently active actors (under cursor) 25 | this.activeActors = []; 26 | 27 | // Keep track of all Actors 28 | this.actors = {}; 29 | 30 | // The scene property references the Scene an Actor belongs to 31 | this.scene = this; 32 | 33 | // Attached Displays 34 | this.displays = []; 35 | if (properties.displays) { 36 | uv.each(properties.displays, function(display) { 37 | that.displays.push(new uv.Display(that, display)); 38 | }); 39 | } 40 | 41 | this.activeDisplay = this.displays[0]; 42 | this.fps = 0; 43 | 44 | this.framerate = this.p('idleFramerate'); 45 | 46 | // Commands hook in here 47 | this.commands = {}; 48 | this.register(uv.cmds.RequestFramerate, {framerate: this.p('framerate')}); 49 | 50 | // Register actors 51 | if (properties.actors) { 52 | uv.each(properties.actors, function(actorSpec) { 53 | that.add(actorSpec); 54 | }); 55 | } 56 | 57 | var timeout; 58 | var requested = false; 59 | 60 | // Listen to interaction 61 | this.bind('interact', function() { 62 | if (!requested) { 63 | that.execute(uv.cmds.RequestFramerate); 64 | requested = true; 65 | } 66 | clearTimeout(timeout); 67 | timeout = setTimeout(function() { 68 | requested = false; 69 | that.unexecute(uv.cmds.RequestFramerate); 70 | }, 1000); 71 | }); 72 | }; 73 | 74 | uv.Scene.prototype = uv.inherit(uv.Actor); 75 | 76 | uv.Scene.prototype.registerActor = function(actor) { 77 | var id = actor.id(); 78 | if (this.actors[id]) 79 | throw "ID '" + id + "' already registered."; 80 | 81 | // Set the scene reference 82 | actor.scene = this; 83 | 84 | // Register actor in scene space 85 | this.actors[id] = actor; 86 | 87 | // Register as interactive 88 | if (actor.p('interactive')) { 89 | this.interactiveActors[actor.id()] = actor; 90 | } 91 | }; 92 | 93 | uv.Scene.prototype.start = function() { 94 | this.running = true; 95 | this.trigger('start'); 96 | this.loop(); 97 | this.checkActiveActors(); 98 | }; 99 | 100 | uv.Scene.prototype.setFramerate = function(framerate) { 101 | this.framerate = framerate; 102 | clearTimeout(this.nextLoop); 103 | clearTimeout(this.nextPick); 104 | this.loop(); 105 | this.checkActiveActors(); 106 | }; 107 | 108 | // The draw loop 109 | 110 | uv.Scene.prototype.loop = function() { 111 | var that = this, 112 | start, duration; 113 | 114 | if (this.running) { 115 | this.fps = (1000/duration < that.framerate) ? 1000/duration : that.framerate; 116 | start = new Date().getTime(); 117 | this.render(); 118 | duration = new Date().getTime()-start; 119 | if (this.framerate > 0) { 120 | this.nextLoop = setTimeout(function() { that.loop(); }, (1000/that.framerate)-duration); 121 | } 122 | } 123 | }; 124 | 125 | uv.Scene.prototype.render = function() { 126 | this.trigger('frame'); 127 | this.compileMatrix(); 128 | this.refreshDisplays(); 129 | }; 130 | 131 | uv.Scene.prototype.stop = function(options) { 132 | this.running = false; 133 | this.trigger('stop'); 134 | }; 135 | 136 | uv.Scene.prototype.checkActiveActors = function() { 137 | var ctx = this.displays[0].ctx, 138 | that = this, 139 | prevActiveActors = this.activeActors; 140 | 141 | if (this.running) { 142 | if (this.scene.mouseX !== NaN) { 143 | 144 | this.activeActors = []; 145 | uv.each(this.interactiveActors, function(actor) { 146 | var active = actor.checkActive(ctx, that.scene.mouseX, that.scene.mouseY); 147 | if (active) { 148 | that.activeActors.push(actor); 149 | if (!uv.include(prevActiveActors, actor)) { 150 | actor.trigger('mouseover'); 151 | } 152 | } else { 153 | if (uv.include(prevActiveActors, actor)) { 154 | actor.trigger('mouseout'); 155 | } 156 | } 157 | }); 158 | } 159 | if (that.framerate > 0) { 160 | this.nextPick = setTimeout(function() { 161 | that.checkActiveActors(); 162 | }, 1000/Math.min(that.framerate, 15)); 163 | } 164 | } 165 | }; 166 | 167 | 168 | uv.Scene.prototype.refreshDisplays = function() { 169 | uv.each(this.displays, function(d) { 170 | d.compileMatrix(); 171 | d.refresh(); 172 | }); 173 | }; 174 | 175 | uv.Scene.prototype.display = function(display) { 176 | var d = new uv.Display(this, display); 177 | this.displays.push(d); 178 | return d; 179 | }; 180 | 181 | // Commands 182 | // ----------------------------------------------------------------------------- 183 | 184 | uv.Scene.prototype.register = function(cmd, options) { 185 | this.commands[cmd.className] = new cmd(this, options); 186 | }; 187 | 188 | uv.Scene.prototype.execute = function(cmd) { 189 | this.commands[cmd.className].execute(); 190 | }; 191 | 192 | uv.Scene.prototype.unexecute = function(cmd) { 193 | this.commands[cmd.className].unexecute(); 194 | }; -------------------------------------------------------------------------------- /src/scene/traverser.js: -------------------------------------------------------------------------------- 1 | uv.traverser = {}; 2 | 3 | uv.traverser.BreadthFirst = function(root) { 4 | var queue = [], 5 | nodes = [], 6 | node; 7 | 8 | queue.push(root); // enqueue 9 | while (queue.length > 0) { 10 | node = queue.shift(); // dequeue 11 | if (node.p('visible')) { 12 | nodes.push(node); 13 | // Enqueue children 14 | node.all('children').each(function(node, key, index) { 15 | queue.push(node); 16 | }); 17 | } 18 | } 19 | return nodes; 20 | }; 21 | 22 | uv.traverser.DepthFirst = function(root) { 23 | var stack = [], 24 | nodes = [], 25 | node; 26 | 27 | stack.push(root); 28 | while (stack.length > 0) { 29 | node = stack.pop(); 30 | if (node.p('visible')) { 31 | nodes.push(node); 32 | // Push children 33 | node.all('children').each(function(node, key, index) { 34 | stack.push(node); 35 | }); 36 | } 37 | } 38 | return nodes; 39 | }; 40 | -------------------------------------------------------------------------------- /src/scene/tween.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author sole / http://soledadpenades.com/ 3 | * @author mr.doob / http://mrdoob.com/ 4 | * Easing equations by Robert Penner http://www.robertpenner.com/easing/ (BSD license) 5 | */ 6 | 7 | uv.TweenManager = uv.TweenManager || ( function() { 8 | var i, time, tweens = []; 9 | 10 | this.add = function (tween) { 11 | tweens.push(tween); 12 | }; 13 | 14 | this.remove = function (tween) { 15 | for (var i = 0, l = tweens.length; i < l; i++) { 16 | if (tween == tweens[ i ]) { 17 | tweens.splice(i, 1); 18 | return; 19 | } 20 | } 21 | }; 22 | 23 | this.update = function() { 24 | i = 0; 25 | time = new Date().getTime(); 26 | while( i < tweens.length ) { 27 | tweens[ i ].update( time ) ? i++ : tweens.splice( i, 1 ); 28 | } 29 | }; 30 | return this; 31 | })(), 32 | 33 | // uv.Tween = uv.Tween || {}; 34 | 35 | uv.Tween = function ( object ) { 36 | uv.TweenManager.add( this ); 37 | 38 | var _object = object, 39 | _valuesStart = {}, 40 | _valuesChange = {}, 41 | _valuesTo = {}, 42 | _duration = 1000, 43 | _delayTime = 0, 44 | _startTime = null, 45 | _easingFunction = uv.Tween.Easing.Back.EaseInOut, 46 | _nextTween = null, 47 | _onUpdateFunction = null, 48 | _onCompleteFunction = null, 49 | _completed = false; 50 | 51 | this.to = function( duration, properties ) { 52 | _duration = duration * 1000; 53 | for ( var property in properties ) { 54 | if ( _object[ property ] === null ) { 55 | continue; 56 | } 57 | // The current values are read when the tween starts; 58 | // here we only store the final desired values 59 | _valuesTo[ property ] = properties[ property ]; 60 | } 61 | return this; 62 | }; 63 | 64 | this.start = function() { 65 | _completed = false; 66 | _startTime = new Date().getTime() + _delayTime; 67 | for ( var property in _valuesTo ) { 68 | if ( _object[ property ] === null ) { 69 | continue; 70 | } 71 | _valuesStart[ property ] = _object[ property ]; 72 | _valuesChange[ property ] = _valuesTo[ property ] - _object[ property ]; 73 | } 74 | return this; 75 | } 76 | 77 | this.delay = function ( amount ) { 78 | _delayTime = amount * 1000; 79 | return this; 80 | }; 81 | 82 | this.easing = function ( easing ) { 83 | _easingFunction = easing; 84 | return this; 85 | }; 86 | 87 | this.chain = function ( chainedTween ) { 88 | _nextTween = chainedTween; 89 | } 90 | 91 | this.onUpdate = function ( onUpdateFunction ) { 92 | _onUpdateFunction = onUpdateFunction; 93 | return this; 94 | }; 95 | 96 | this.onComplete = function ( onCompleteFunction ) { 97 | _onCompleteFunction = onCompleteFunction; 98 | return this; 99 | }; 100 | 101 | this.update = function ( time ) { 102 | var property, elapsed; 103 | 104 | if ( time < _startTime || _startTime === null) { 105 | return true; 106 | } 107 | 108 | if ( _completed ) { 109 | return (_nextTween === null); 110 | } 111 | elapsed = time - _startTime; 112 | 113 | if( elapsed > _duration ) { 114 | 115 | _completed = true; 116 | _startTime = null; 117 | 118 | if(_onCompleteFunction !== null) { 119 | _onCompleteFunction(); 120 | } 121 | 122 | if(_nextTween !== null) { 123 | _nextTween.start(); 124 | return true; // this tween cannot be safely destroyed 125 | } else { 126 | return false; // no associated tweens, tween can be destroyed 127 | } 128 | } 129 | 130 | for ( property in _valuesChange ) { 131 | _object[ property ] = _easingFunction(elapsed, _valuesStart[ property ], _valuesChange[ property ], _duration ); 132 | } 133 | 134 | if ( _onUpdateFunction !== null ) { 135 | _onUpdateFunction.apply(_object); 136 | } 137 | return true; 138 | }; 139 | 140 | this.destroy = function () { 141 | uv.TweenManager.remove(this); 142 | }; 143 | }; 144 | 145 | 146 | uv.Tween.Easing = { Back: {}, Elastic: {}, Expo: {}, Linear: {} }; 147 | 148 | uv.Tween.Easing.Back.EaseIn = function( t, b, c, d ) { 149 | var s = 1.70158; 150 | return c * ( t /= d ) * t * ( ( s + 1 ) * t - s ) + b; 151 | }; 152 | 153 | uv.Tween.Easing.Back.EaseOut = function( t, b, c, d ) { 154 | var s = 1.70158; 155 | return c * ( ( t = t / d - 1 ) * t * ( ( s + 1 ) * t + s ) + 1 ) + b; 156 | }; 157 | 158 | uv.Tween.Easing.Back.EaseInOut = function( t, b, c, d ) { 159 | var s = 1.70158; 160 | if ( ( t /= d / 2 ) < 1 ) return c / 2 * ( t * t * ( ( ( s *= ( 1.525 ) ) + 1 ) * t - s ) ) + b; 161 | return c / 2 * ( ( t -= 2 ) * t * ( ( ( s *= ( 1.525 ) ) + 1 ) * t + s ) + 2 ) + b; 162 | }; 163 | 164 | uv.Tween.Easing.Elastic.EaseIn = function( t, b, c, d ) { 165 | if ( t == 0 ) return b; 166 | if ( ( t /= d ) == 1 ) return b + c; 167 | var p = d * .3; 168 | var a = c; 169 | var s = p / 4; 170 | return - ( a * Math.pow( 2, 10 * ( t -= 1 ) ) * Math.sin( ( t * d - s ) * ( 2 * Math.PI ) / p ) ) + b; 171 | }; 172 | 173 | uv.Tween.Easing.Elastic.EaseOut = function( t, b, c, d ) { 174 | if ( t == 0 ) return b; 175 | if ( ( t /= d ) == 1 ) return b + c; 176 | var p = d * .3; 177 | var a = c; 178 | var s = p / 4; 179 | return ( a * Math.pow( 2, - 10 * t ) * Math.sin( ( t * d - s ) * ( 2 * Math.PI ) / p ) + c + b ); 180 | }; 181 | 182 | uv.Tween.Easing.Elastic.EaseInOut = function(t, b, c, d) { 183 | if ( t == 0 ) return b; 184 | if ( ( t /= d / 2 ) == 2 ) return b + c; 185 | var p = d * ( .3 * 1.5 ); 186 | var a = c; 187 | var s = p / 4; 188 | if ( t < 1 ) return - .5 * ( a * Math.pow( 2, 10 * ( t -= 1 ) ) * Math.sin( ( t * d - s ) * ( 2 * Math.PI ) / p ) ) + b; 189 | return a * Math.pow( 2, - 10 * ( t -= 1 ) ) * Math.sin( ( t * d - s ) * ( 2 * Math.PI ) / p ) * .5 + c + b; 190 | }; 191 | 192 | uv.Tween.Easing.Expo.EaseIn = function(t, b, c, d) { 193 | return ( t == 0) ? b : c * Math.pow( 2, 10 * ( t / d - 1 ) ) + b; 194 | }; 195 | 196 | uv.Tween.Easing.Expo.EaseOut = function(t, b, c, d) { 197 | return ( t == d ) ? b + c : c * ( - Math.pow( 2, - 10 * t / d) + 1) + b; 198 | }; 199 | 200 | uv.Tween.Easing.Expo.EaseInOut = function(t, b, c, d) { 201 | if ( t == 0 ) return b; 202 | if ( t == d ) return b+c; 203 | if ( ( t /= d / 2 ) < 1) return c / 2 * Math.pow( 2, 10 * ( t - 1 ) ) + b; 204 | return c / 2 * ( - Math.pow( 2, - 10 * --t ) + 2) + b; 205 | }; 206 | 207 | uv.Tween.Easing.Linear.EaseNone = function (t, b, c, d) { 208 | return c*t/d + b; 209 | }; 210 | 211 | uv.Tween.Easing.Linear.EaseIn = function (t, b, c, d) { 212 | return c*t/d + b; 213 | }; 214 | 215 | uv.Tween.Easing.Linear.EaseOut = function (t, b, c, d) { 216 | return c*t/d + b; 217 | }; 218 | 219 | uv.Tween.Easing.Linear.EaseInOut = function (t, b, c, d) { 220 | return c*t/d + b; 221 | }; 222 | -------------------------------------------------------------------------------- /src/sorted_hash/aggregators.js: -------------------------------------------------------------------------------- 1 | // Aggregators 2 | //----------------------------------------------------------------------------- 3 | 4 | uv.Aggregators = {}; 5 | 6 | uv.Aggregators.SUM = function (values) { 7 | var result = 0; 8 | 9 | values.each(function(index, value) { 10 | result += value; 11 | }); 12 | 13 | return result; 14 | }; 15 | 16 | uv.Aggregators.MIN = function (values) { 17 | var result = Infinity; 18 | values.each(function(index, value) { 19 | if (value < result) { 20 | result = value; 21 | } 22 | }); 23 | return result; 24 | }; 25 | 26 | uv.Aggregators.MAX = function (values) { 27 | var result = -Infinity; 28 | values.each(function(index, value) { 29 | if (value > result) { 30 | result = value; 31 | } 32 | }); 33 | return result; 34 | }; 35 | 36 | uv.Aggregators.AVG = function (values) { 37 | return uv.Aggregators.SUM(values) / values.length; 38 | }; 39 | 40 | uv.Aggregators.COUNT = function (values) { 41 | return values.length; 42 | }; 43 | 44 | -------------------------------------------------------------------------------- /src/sorted_hash/comparators.js: -------------------------------------------------------------------------------- 1 | // Comparators 2 | //----------------------------------------------------------------------------- 3 | 4 | uv.Comparators = {}; 5 | 6 | uv.Comparators.ASC = function(item1, item2) { 7 | return item1.value === item2.value ? 0 : (item1.value < item2.value ? -1 : 1); 8 | }; 9 | 10 | uv.Comparators.DESC = function(item1, item2) { 11 | return item1.value === item2.value ? 0 : (item1.value > item2.value ? -1 : 1); 12 | }; 13 | 14 | -------------------------------------------------------------------------------- /src/sorted_hash/sorted_hash.js: -------------------------------------------------------------------------------- 1 | // SortedHash 2 | // ============================================================================= 3 | 4 | // Constructor 5 | // Initializes a Sorted Hash 6 | uv.SortedHash = function (data) { 7 | var that = this; 8 | this.data = {}; 9 | this.keyOrder = []; 10 | this.length = 0; 11 | 12 | if (data instanceof Array) { 13 | uv.each(data, function(datum, index) { 14 | that.set(index, datum); 15 | }); 16 | } else if (data instanceof Object) { 17 | uv.each(data, function(datum, key) { 18 | that.set(key, datum); 19 | }); 20 | } 21 | }; 22 | 23 | 24 | // Returns a copy of the sorted hash 25 | // Used by transformation methods 26 | uv.SortedHash.prototype.clone = function () { 27 | var copy = new uv.SortedHash(); 28 | copy.length = this.length; 29 | uv.each(this.data, function(value, key) { 30 | copy.data[key] = value; 31 | }); 32 | copy.keyOrder = this.keyOrder.slice(0, this.keyOrder.length); 33 | return copy; 34 | }; 35 | 36 | // Set a value at a given key 37 | // Parameters: 38 | // * key [String] 39 | uv.SortedHash.prototype.set = function (key, value) { 40 | if (key === undefined) 41 | return this; 42 | if (!this.data[key]) { 43 | this.keyOrder.push(key); 44 | this.length += 1; 45 | } 46 | this.data[key] = value; 47 | return this; 48 | }; 49 | 50 | // Get value at given key 51 | // Parameters: 52 | // * key [String] 53 | uv.SortedHash.prototype.get = function (key) { 54 | return this.data[key]; 55 | }; 56 | 57 | // Remove entry at given key 58 | // Parameters: 59 | // * key [String] 60 | uv.SortedHash.prototype.del = function (key) { 61 | if (this.data[key]) { 62 | this.keyOrder.splice($.inArray(key, this.keyOrder), 1); 63 | delete this.data[key]; 64 | this.length -= 1; 65 | } 66 | return this; 67 | }; 68 | 69 | // Get value at given index 70 | // Parameters: 71 | // * index [Number] 72 | uv.SortedHash.prototype.at = function (index) { 73 | var key = this.keyOrder[index]; 74 | return this.data[key]; 75 | }; 76 | 77 | // Get first item 78 | uv.SortedHash.prototype.first = function () { 79 | return this.at(0); 80 | }; 81 | 82 | // Get last item 83 | uv.SortedHash.prototype.last = function () { 84 | return this.at(this.length-1); 85 | }; 86 | 87 | // Returns for an index the corresponding key 88 | // Parameters: 89 | // * index [Number] 90 | uv.SortedHash.prototype.key = function (index) { 91 | return this.keyOrder[index]; 92 | }; 93 | 94 | // Iterate over values contained in the SortedHash 95 | // Parameters: 96 | // * [Function] 97 | uv.SortedHash.prototype.each = function (f) { 98 | var that = this; 99 | uv.each(this.keyOrder, function(key, index) { 100 | f.call(that, index, that.data[key]); 101 | }); 102 | return this; 103 | }; 104 | 105 | // Iterate over values contained in the SortedHash 106 | // Parameters: 107 | // * [Function] 108 | uv.SortedHash.prototype.eachKey = function (f) { 109 | var that = this; 110 | uv.each(this.keyOrder, function (key, index) { 111 | f.call(that, key, that.data[key]); 112 | }); 113 | return this; 114 | }; 115 | 116 | 117 | // Convert to an ordinary JavaScript Array containing 118 | // the values 119 | // 120 | // Returns: 121 | // * Array of items 122 | uv.SortedHash.prototype.values = function () { 123 | var result = []; 124 | this.eachKey(function(key, value) { 125 | result.push(value); 126 | }); 127 | return result; 128 | }; 129 | 130 | // Convert to an ordinary JavaScript Array containing 131 | // key value pairs — used for sorting 132 | // 133 | // Returns: 134 | // * Array of key value pairs 135 | uv.SortedHash.prototype.toArray = function () { 136 | var result = []; 137 | 138 | this.eachKey(function(key, value) { 139 | result.push({key: key, value: value}); 140 | }); 141 | 142 | return result; 143 | }; 144 | 145 | 146 | // Map the SortedHash to your needs 147 | // Parameters: 148 | // * [Function] 149 | uv.SortedHash.prototype.map = function (f) { 150 | var result = this.clone(), 151 | that = this; 152 | result.each(function(index, item) { 153 | result.data[that.key(index)] = f.call(result, item); 154 | }); 155 | return result; 156 | }; 157 | 158 | // Select items that match some conditions expressed by a matcher function 159 | // Parameters: 160 | // * [Function] matcher function 161 | uv.SortedHash.prototype.select = function (f) { 162 | var result = new uv.SortedHash(), 163 | that = this; 164 | 165 | this.eachKey(function(key, value) { 166 | if (f.call(that, key, value)) { 167 | result.set(key, value); 168 | } 169 | }); 170 | return result; 171 | }; 172 | 173 | // Performs a sort on the SortedHash 174 | // Parameters: 175 | // * comparator [Function] A comparator function 176 | // Returns: 177 | // * The now re-sorted SortedHash (for chaining) 178 | uv.SortedHash.prototype.sort = function (comparator) { 179 | var result = this.clone(); 180 | sortedKeys = result.toArray().sort(comparator); 181 | 182 | // update keyOrder 183 | result.keyOrder = $.map(sortedKeys, function(k) { 184 | return k.key; 185 | }); 186 | 187 | return result; 188 | }; 189 | 190 | 191 | // Performs an intersection with the given SortedHash 192 | // Parameters: 193 | // * sortedHash [SortedHash] 194 | uv.SortedHash.prototype.intersect = function(sortedHash) { 195 | var that = this, 196 | result = new uv.SortedHash(); 197 | 198 | this.eachKey(function(key, value) { 199 | sortedHash.eachKey(function(key2, value2) { 200 | if (key === key2) { 201 | result.set(key, value); 202 | } 203 | }); 204 | }); 205 | return result; 206 | }; 207 | 208 | // Performs an union with the given SortedHash 209 | // Parameters: 210 | // * sortedHash [SortedHash] 211 | uv.SortedHash.prototype.union = function(sortedHash) { 212 | var that = this, 213 | result = new uv.SortedHash(); 214 | 215 | this.eachKey(function(key, value) { 216 | if (!result.get(key)) 217 | result.set(key, value); 218 | }); 219 | sortedHash.eachKey(function(key, value) { 220 | if (!result.get(key)) 221 | result.set(key, value); 222 | }); 223 | return result; 224 | }; -------------------------------------------------------------------------------- /src/uv.js: -------------------------------------------------------------------------------- 1 | // Unveil.js 2 | // ============================================================================= 3 | 4 | var uv = {}; 5 | 6 | // Constants 7 | // ----------------------------------------------------------------------------- 8 | 9 | uv.EPSILON = 0.0001; 10 | uv.MAX_FLOAT = 3.4028235e+38; 11 | uv.MIN_FLOAT = -3.4028235e+38; 12 | uv.MAX_INT = 2147483647; 13 | uv.MIN_INT = -2147483648; 14 | 15 | // Utilities 16 | // ----------------------------------------------------------------------------- 17 | 18 | uv.inherit = function (f) { 19 | function G() {} 20 | G.prototype = f.prototype || f; 21 | return new G(); 22 | }; 23 | 24 | uv.each = function(obj, iterator, context) { 25 | if (obj.forEach) { 26 | obj.forEach(iterator, context); 27 | } else { 28 | for (var key in obj) { 29 | if (hasOwnProperty.call(obj, key)) iterator.call(context, obj[key], key, obj); 30 | } 31 | } 32 | return obj; 33 | }; 34 | 35 | uv.rest = function(array, index, guard) { 36 | return Array.prototype.slice.call(array, (index === undefined || guard) ? 1 : index); 37 | }; 38 | 39 | uv.include = function(arr, target) { 40 | return arr.indexOf(target) != -1; 41 | }; 42 | 43 | uv.isArray = function(obj) { 44 | return toString.call(obj) === '[object Array]'; 45 | }; 46 | 47 | uv.select = uv.filter = function(obj, iterator, context) { 48 | if (obj.filter === Array.prototype.filter) 49 | return obj.filter(iterator, context); 50 | var results = []; 51 | uv.each(obj, function(value, index, list) { 52 | iterator.call(context, value, index, list) && results.push(value); 53 | }); 54 | return results; 55 | }; 56 | 57 | uv.extend = function(obj) { 58 | uv.rest(arguments).forEach(function(source) { 59 | for (var prop in source) obj[prop] = source[prop]; 60 | }); 61 | return obj; 62 | }; -------------------------------------------------------------------------------- /test/benchmark/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Unveil.js - Performance comparison with D3.js 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 29 | 30 | 31 | 313 | 314 | 315 | 316 | 317 |

Unveil.js - Performance comparison with D3.js

318 |
319 |
320 |
321 |

About

322 |

Unveil.js provides a simple abstraction layer for visualizations to ease 323 | the process of creating re-usable charts. To accomplish this, a data-driven methodology is used. 324 |

325 | 326 |

Code

327 | 328 |

Source Code is available at Github

329 | 330 |
A Quasipartikel production
331 |
332 | 333 | 334 | -------------------------------------------------------------------------------- /test/collection/fixtures/countries.js: -------------------------------------------------------------------------------- 1 | var countries_fixture = { 2 | "items": { 3 | "austria": { 4 | "name": "Austria", 5 | "official_language": "Croatian language", 6 | "form_of_government": [ 7 | "Federal republic", 8 | "Parliamentary republic" 9 | ], 10 | "currency_used": "Euro", 11 | "population": 8356700, 12 | "gdp_nominal": 432400000000.0, 13 | "area": 83872.0, 14 | "date_founded": "1955-07-27" 15 | }, 16 | "uk": { 17 | "name": "United Kingdom", 18 | "official_language": "English Language", 19 | "form_of_government": [ 20 | "Parliamentary system", 21 | "Constitutional monarchy" 22 | ], 23 | "currency_used": "UK \u00a3", 24 | "population": 61612300, 25 | "gdp_nominal": 2787000000000.0, 26 | "area": 244820.0, 27 | "date_founded": "1707-05-01" 28 | }, 29 | "usa": { 30 | "name": "United States of America", 31 | "official_language": "English Language", 32 | "form_of_government": [ 33 | "Federal republic", 34 | "Constitution", 35 | "Democracy", 36 | "Republic", 37 | "Presidential system", 38 | "Constitutional republic" 39 | ], 40 | "currency_used": "US$", 41 | "population": 306108000, 42 | "gdp_nominal": 14330000000000.0, 43 | "area": 9826675.0, 44 | "date_founded": "1776-07-04" 45 | }, 46 | "ger": { 47 | "name": "Germany", 48 | "official_language": "German Language", 49 | "form_of_government": [ 50 | "Federal republic", 51 | "Democracy", 52 | "Parliamentary republic" 53 | ], 54 | "currency_used": "Euro", 55 | "population": 82062200, 56 | "gdp_nominal": 3818000000000.0, 57 | "area": 357092.9, 58 | "date_founded": "1949-05-23" 59 | }, 60 | "fra": { 61 | "name": "France", 62 | "official_language": "French Language", 63 | "form_of_government": [ 64 | "Republic", 65 | "Semi-presidential system" 66 | ], 67 | "currency_used": "Euro", 68 | "population": 65073482, 69 | "gdp_nominal": 2987000000000.0, 70 | "area": 674843.0, 71 | "date_founded": "1792" 72 | }, 73 | "ita": { 74 | "name": "Italy", 75 | "official_language": "Italian Language", 76 | "form_of_government": [ 77 | "Parliamentary republic" 78 | ], 79 | "currency_used": "Euro", 80 | "population": 60090400, 81 | "gdp_nominal": 2399000000000.0, 82 | "area": 301338.0, 83 | "date_founded": "1861-03-17" 84 | } 85 | }, 86 | "properties": { 87 | "name": { 88 | "name": "Country Name", 89 | "type": "string", 90 | "property_key": "name", 91 | "value_key": "name", 92 | "unique": true 93 | }, 94 | "official_language": { 95 | "name": "Official language", 96 | "type": "string", 97 | "property_key": "official_language", 98 | "value_key": "name", 99 | "unique": true 100 | }, 101 | "form_of_government": { 102 | "name": "Form of governmennt", 103 | "type": "string", 104 | "property_key": "form_of_government", 105 | "value_key": "name", 106 | "unique": false 107 | }, 108 | "currency_used": { 109 | "name": "Currency used", 110 | "type": "string", 111 | "property_key": "currency_used", 112 | "value_key": "name", 113 | "unique": true 114 | }, 115 | "population": { 116 | "name": "Population", 117 | "type": "number", 118 | "property_key": "/location/statistical_region/population", 119 | "value_key": "number", 120 | "unique": true 121 | }, 122 | "gdp_nominal": { 123 | "name": "GDP nominal", 124 | "type": "number", 125 | "property_key": "/location/statistical_region/gdp_nominal", 126 | "value_key": "amount", 127 | "unique": true 128 | }, 129 | "area": { 130 | "name": "Area", 131 | "type": "number", 132 | "property_key": "/location/location/area", 133 | "unique": true 134 | }, 135 | "date_founded": { 136 | "name": "Date founded", 137 | "property_key": "/location/dated_location/date_founded", 138 | "type": "date", 139 | "unqiue": true 140 | } 141 | } 142 | } -------------------------------------------------------------------------------- /test/collection/fixtures/nested_collection.js: -------------------------------------------------------------------------------- 1 | var nested_collection_fixture = { 2 | "items": { 3 | "metallica": { 4 | "name": "Metallica", 5 | "similar_artists": { 6 | "korn": { 7 | "name": "Korn", 8 | "score": 0.8 9 | }, 10 | "acdc": { 11 | "name": "AC/DC", 12 | "score": 0.7 13 | } 14 | } 15 | } 16 | }, 17 | "properties": { 18 | "name": { 19 | "name": "Artist Name", 20 | "type": "string", 21 | "unique": true 22 | }, 23 | "similar_artists": { 24 | "name": "Similar Artists", 25 | "type": "collection", 26 | "unique": true, 27 | "properties": { 28 | "name": { 29 | "name": "Artist Name", 30 | "type": "string", 31 | "unique": true 32 | }, 33 | "score": { 34 | "name": "Similarity Score", 35 | "type": "number", 36 | "unique": true 37 | } 38 | } 39 | } 40 | } 41 | }; -------------------------------------------------------------------------------- /test/collection/fixtures/playlists.js: -------------------------------------------------------------------------------- 1 | var playlists_fixture = { 2 | "properties": { 3 | "artists": { 4 | "name": "Artists", 5 | "type": "string", 6 | "unique": false 7 | } 8 | }, 9 | "items": { 10 | "playlist#1": { 11 | "artists": [ 12 | "Foo Fighters", 13 | "Coldplay", 14 | "Metallica" 15 | ] 16 | }, 17 | "playlist#2": { 18 | "artists": [ 19 | "Radiohead", 20 | "Korn" 21 | ] 22 | }, 23 | "playlist#3": { 24 | "artists": [ 25 | "Radiohead", 26 | "Metallica", 27 | "Foo Fighters" 28 | ] 29 | }, 30 | "playlist#4": { 31 | "artists": [ 32 | "Korn", 33 | "Metallica", 34 | "Coldplay" 35 | ] 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /test/collection/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Collection API - TestSuite 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 |

Collection Test Suite

38 |

39 |
40 |

41 |
    42 | 43 | 44 | -------------------------------------------------------------------------------- /test/collection/testsuite.js: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------------------------- 2 | // Collection API 3 | //----------------------------------------------------------------------------- 4 | 5 | module("Collection"); 6 | 7 | var c = new uv.Collection(countries_fixture); 8 | 9 | test("has some properties", function() { 10 | ok(c.get('properties', 'area') instanceof uv.Node); 11 | ok(c.get('properties', 'currency_used') instanceof uv.Node); 12 | ok(c.get('properties', 'doesnotexit') === undefined); 13 | }); 14 | 15 | test("property is connected to values", function() { 16 | var governmentForms = c.get('properties', 'form_of_government'); 17 | }); 18 | 19 | test("read item property values", function() { 20 | var item = c.get('items', 'austria'); 21 | ok(item.value('name') === 'Austria'); 22 | ok(item.value('area') === 83872); 23 | ok(item.values('form_of_government').length === 2); 24 | }); 25 | 26 | test("get values of a property", function() { 27 | var population = c.get('properties', 'population'); 28 | ok(population.all('values').length === 6); 29 | }); 30 | 31 | // useful for non-unique properties 32 | test("get value of a property", function() { 33 | var population = c.get('properties', 'population'); 34 | ok(population.value('values') === 8356700); 35 | }); 36 | 37 | //----------------------------------------------------------------------------- 38 | // Aggregation of property values using Aggregators 39 | //----------------------------------------------------------------------------- 40 | 41 | test("Aggregators.*", function() { 42 | var values = new uv.SortedHash(); 43 | values.set('0', 4); 44 | values.set('1', 5); 45 | values.set('2', -3); 46 | values.set('3', 1); 47 | 48 | ok(uv.Aggregators.SUM(values) === 7); 49 | ok(uv.Aggregators.MIN(values) === -3); 50 | ok(uv.Aggregators.MAX(values) === 5); 51 | ok(uv.Aggregators.COUNT(values) === 4); 52 | }); 53 | 54 | test("allow aggregation of property values", function() { 55 | var population = c.get("properties", "population"); 56 | ok(population.aggregate(uv.Aggregators.MIN) === 8356700); 57 | ok(population.aggregate(uv.Aggregators.MAX) === 306108000); 58 | }); 59 | 60 | //----------------------------------------------------------------------------- 61 | // Nested Collection 62 | //----------------------------------------------------------------------------- 63 | 64 | nc = new uv.Collection(nested_collection_fixture); 65 | 66 | test("should have a unique property value that is a collection", function() { 67 | var item = nc.get("items", "metallica"); 68 | 69 | ok(item.value('name') === 'Metallica'); 70 | 71 | // get associated collection 72 | var similarArtists = item.first('similar_artists'); 73 | ok(similarArtists instanceof uv.Collection); 74 | 75 | // inspect similar artists 76 | ok(similarArtists.all('items').length === 2); 77 | 78 | // most similar artist is Korn 79 | ok(similarArtists.all('items').first().value('name') === 'Korn'); 80 | 81 | // 2nd similar artist is AC/DC 82 | ok(similarArtists.all('items').last().value('name') === 'AC/DC'); 83 | 84 | }); 85 | 86 | 87 | //----------------------------------------------------------------------------- 88 | // Co-Occurences Operation 89 | //----------------------------------------------------------------------------- 90 | 91 | var pl = new uv.Collection(playlists_fixture); 92 | 93 | module("Transformers"); 94 | 95 | test("co_occurences_baccigalupo", function() { 96 | var result = pl.transform('coOccurrencesBaccigalupo', {property: 'artists', knn: 2}); 97 | ok(result.get('properties', 'similar_items').name === "Similar Items"); 98 | }); 99 | 100 | test("co_occurences_pachet", function() { 101 | var result = pl.transform('coOccurrences', {property: 'artists', knn: 2}); 102 | ok(result.get('properties', 'similar_items').name === "Similar Items"); 103 | }); 104 | 105 | //----------------------------------------------------------------------------- 106 | // SortedHash and Node integration tests 107 | //----------------------------------------------------------------------------- 108 | 109 | module("SortedHash"); 110 | 111 | test("SortedHash#sort and SortedHash#map", function() { 112 | var countries = c.all("items"), 113 | sortedCountries = countries.sort(function(item1, item2) { 114 | // sort by population 115 | var value1 = item1.value.value('population'), 116 | value2 = item2.value.value('population'); 117 | return value1 === value2 ? 0 : (value1 < value2 ? -1 : 1); 118 | }), 119 | countryNames; 120 | 121 | // map to just names 122 | countryNames = sortedCountries.map(function (country) { 123 | return country.value('name'); 124 | }); 125 | 126 | ok(countryNames.at(0) === 'Austria'); 127 | ok(countryNames.at(1) === 'Italy'); 128 | ok(countryNames.at(2) === 'United Kingdom'); 129 | ok(countryNames.at(3) === 'France'); 130 | ok(countryNames.at(4) === 'Germany'); 131 | ok(countryNames.at(5) === 'United States of America'); 132 | }); 133 | 134 | test("SortedHash#select", function() { 135 | var countries = c.all("items"), 136 | euro_countries = countries.select(function (key, c) { 137 | return c.value('currency_used') === 'Euro'; 138 | }); 139 | 140 | // map to just names 141 | countryNames = euro_countries.map(function (country) { 142 | return country.value('name'); 143 | }); 144 | 145 | ok(countryNames.at(0) === 'Austria'); 146 | ok(countryNames.at(1) === 'Germany'); 147 | ok(countryNames.at(2) === 'France'); 148 | ok(countryNames.at(3) === 'Italy'); 149 | }); 150 | 151 | 152 | module('Criterion'); 153 | 154 | //----------------------------------------------------------------------------- 155 | // Criterion API 156 | //----------------------------------------------------------------------------- 157 | 158 | test('Criterion.operators.CONTAINS', function() { 159 | var matchedItems = uv.Criterion.operators.CONTAINS(c, 'name', 'Austria'); 160 | ok(matchedItems.length === 1); 161 | ok(matchedItems.at(0).value('name') === 'Austria'); 162 | }); 163 | 164 | test('Criterion.operators.GT', function() { 165 | var matchedItems = uv.Criterion.operators.GT(c, 'population', 70000000); 166 | ok(matchedItems.length === 2); 167 | ok(matchedItems.at(0).value('population') > 70000000); 168 | ok(matchedItems.at(1).value('population') > 70000000); 169 | }); 170 | 171 | 172 | test("nested criteria", function() { 173 | // the root criterion takes it all 174 | var criteria = new uv.Criterion('AND'), 175 | filteredCollection; 176 | 177 | criteria.add(new uv.Criterion('GT', 'population', 10000000)); 178 | criteria.add(new uv.Criterion('OR') 179 | .add(new uv.Criterion('CONTAINS', 'official_language', 'English Language')) 180 | .add(new uv.Criterion('CONTAINS', 'official_language', 'German Language'))); 181 | 182 | filteredCollection = c.filter(criteria); 183 | ok(filteredCollection.all('items').length === 3); 184 | }); 185 | 186 | 187 | -------------------------------------------------------------------------------- /test/data_graph.coffee: -------------------------------------------------------------------------------- 1 | require '../unveil' 2 | require '../lib/underscore' 3 | fs = require 'fs' 4 | sys = require 'sys' 5 | 6 | # Prepare fixture 7 | documents = JSON.parse(fs.readFileSync('./fixtures/documents.json')) 8 | 9 | # Invoke a DataGraph 10 | # ------------------------------------------------------------------------------ 11 | 12 | graph = new uv.DataGraph(documents) 13 | 14 | ok graph != undefined 15 | ok graph.all('types').length == 3 16 | 17 | ok graph.get('types', '/dc/document') instanceof uv.Type 18 | ok graph.get('types', '/dc/entity') instanceof uv.Type 19 | ok graph.get('types', '/dc/mention') instanceof uv.Type 20 | 21 | # Type inspection 22 | # ------------------------------------------------------------------------------ 23 | 24 | document_type = graph.get('types', '/dc/document') 25 | ok document_type.all('properties').length == 4 26 | ok document_type.key == '/dc/document' 27 | ok document_type.name == 'Document' 28 | 29 | 30 | # Property inspection 31 | # ------------------------------------------------------------------------------ 32 | 33 | entities_property = document_type.get('properties', 'entities') 34 | ok entities_property.name == 'Associated Entities' 35 | ok entities_property.expected_type == '/dc/entity' 36 | 37 | 38 | # Resource inspection 39 | # ------------------------------------------------------------------------------ 40 | 41 | protovis = graph.get('resources', '/doc/protovis_introduction') 42 | unveil = graph.get('resources', '/doc/unveil_introduction') 43 | processingjs = graph.get('resources', '/doc/processing_js_introduction') 44 | mention = graph.get('resources', 'M0000003') 45 | anotherMention = graph.get('resources', 'M0000003') 46 | 47 | ok protovis instanceof uv.Resource 48 | ok mention instanceof uv.Resource 49 | ok anotherMention instanceof uv.Resource 50 | 51 | # There are four different access scenarios: 52 | # For convenience there's a get method, which always returns the right result depending on the 53 | # schema information. However, internally, every property of a resource is represented as a 54 | # non-unique SortedHash of Node objects, even if it's a unique property. So if 55 | # you want to be explicit you should use the native methods of the Node API. 56 | 57 | # 1. Unique value types (one value) 58 | # .............................................................................. 59 | 60 | ok protovis.get('page_count') == 8 61 | ok protovis.get('title') == 'Protovis' 62 | 63 | # internally delegates to 64 | ok protovis.get('page_count') == 8 65 | 66 | # 2. Non-unique value types (many values) 67 | # .............................................................................. 68 | 69 | ok protovis.get('authors').length == 2 70 | ok protovis.get('authors').at(0) == 'Michael Bostock' 71 | ok protovis.get('authors').at(1) == 'Jeffrey Heer' 72 | 73 | # internally delegates to 74 | ok protovis.values('authors').length == 2 75 | 76 | # 3. Unique object types (one resource) 77 | # .............................................................................. 78 | 79 | ok mention.get('entity').key == '/location/new_york' 80 | 81 | # internally delegates to 82 | ok mention.first('entity').key == '/location/new_york' 83 | 84 | # 4. Non-unique object types 85 | # .............................................................................. 86 | 87 | ok protovis.get('entities').length == 2 88 | ok protovis.get('entities').at(0).key == '/location/stanford' 89 | ok protovis.get('entities').at(1).key == '/location/new_york' 90 | 91 | # internally delgates to 92 | ok protovis.all('entities').length == 2 93 | 94 | 95 | # References to the same resource should result in object equality. 96 | ok mention.first('entity') == anotherMention.first('entity') 97 | 98 | 99 | # Navigating around 100 | # ------------------------------------------------------------------------------ 101 | 102 | # Hop from a document to the second entity, picking the 2nd mention and go 103 | # to the associated document of this mention. 104 | 105 | protovis.get('entities').at(1) # => Entity#/location/new_york 106 | .get('mentions').at(1) # => Mention#M0000003 107 | .get('document') # => /doc/processing_js_introduction 108 | .key == '/doc/processing_js_introduction' 109 | 110 | 111 | # Querying information 112 | # ------------------------------------------------------------------------------ 113 | 114 | cities = graph.all('resources').select (key, res) -> 115 | /or/.test(res.get('name')) 116 | 117 | ok cities.length == 3 118 | ok cities.get('/location/new_york') 119 | ok cities.get('/location/toronto') 120 | ok cities.get('/location/stanford') 121 | 122 | 123 | # Value identity 124 | # ------------------------------------------------------------------------------ 125 | 126 | # If the values of a property are shared among resources they should have 127 | # the same identity as well. 128 | 129 | ok unveil.all('authors').at(0) == processingjs.all('authors').at(2) 130 | ok unveil.get('authors').at(0) == 'Michael Aufreiter' 131 | ok processingjs.get('authors').at(2) == 'Michael Aufreiter' 132 | 133 | # This allows questions like: 134 | # Show me all unique values of a certain property e.g. /dc/document.authors 135 | 136 | ok protovis.type.get('properties', 'authors').all('values').length == 6 137 | -------------------------------------------------------------------------------- /test/node/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Node API - TestSuite 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 |

    Node Test Suite

    24 |

    25 |
    26 |

    27 |
      28 | 29 | 30 | -------------------------------------------------------------------------------- /test/node/testsuite.js: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------------------------- 2 | // Node API 3 | //----------------------------------------------------------------------------- 4 | 5 | var austrian, english, german, eu, austria, germany, uk; 6 | 7 | module("Node", { 8 | setup: function() { 9 | austrian = new uv.Node({value: 'Austrian'}); 10 | english = new uv.Node({value: 'English'}); 11 | german = new uv.Node({value: 'German'}); 12 | 13 | // confederations 14 | eu = new uv.Node(); 15 | 16 | // countries 17 | austria = new uv.Node(); 18 | germany = new uv.Node(); 19 | uk = new uv.Node(); 20 | 21 | // people 22 | barroso = new uv.Node({value: 'Barroso'}); 23 | 24 | // connect some nodes 25 | austria.set('languages', 'at', austrian); 26 | austria.set('languages', 'ger', german); 27 | 28 | eu.set('president', 'barroso', barroso); 29 | }, 30 | teardown: function() { 31 | 32 | } 33 | }); 34 | 35 | test("get connected nodes", function() { 36 | // order should be preserved 37 | ok(austria.all('languages') instanceof uv.SortedHash); // => returns a SortedHash 38 | ok(austria.all('languages').at(0) === austrian); 39 | ok(austria.all('languages').at(1) === german); 40 | 41 | ok(austria.get('languages', 'at') === austrian); 42 | ok(austria.get('languages', 'ger') === german); 43 | }); 44 | 45 | test("get first connected node", function() { 46 | ok(eu.first('president') instanceof uv.Node); 47 | ok(eu.first('president').val === 'Barroso'); 48 | }); 49 | 50 | test("iteration of connected nodes", function() { 51 | var nodes = []; 52 | austria.all('languages').each(function(index, node) { 53 | nodes.push(node); 54 | }); 55 | ok(nodes.length === 2); 56 | }); 57 | 58 | test("Node#list", function() { 59 | // non-unqiue property 60 | ok(austria.all('languages').length === 2); 61 | ok(austria.all('languages').get('at') === austrian); 62 | ok(austria.all('languages').get('ger') === german); 63 | 64 | // unique property 65 | ok(eu.all('president').length === 1); 66 | ok(eu.values('president').first() === 'Barroso'); 67 | }); 68 | 69 | test("Node#values", function() { 70 | var values = austria.values('languages'); 71 | 72 | // for non-unique properties 73 | ok(values.at(0) === 'Austrian'); 74 | ok(values.at(1) === 'German'); 75 | ok(values.get('at') === 'Austrian'); 76 | ok(values.get('ger') === 'German'); 77 | 78 | // for unique properties 79 | ok(eu.values('president').at(0) === 'Barroso'); 80 | }); 81 | 82 | test("Node#value", function() { 83 | var values = austria.values('languages'); 84 | 85 | // for non-unique properties 86 | ok(austria.value('languages') === 'Austrian'); 87 | 88 | // for unique properties 89 | ok(eu.value('president') === 'Barroso'); 90 | }); 91 | 92 | test("Allows null as a key for property values", function() { 93 | var root = new uv.Node({value: 'RootNode'}); 94 | var nullNode = new uv.Node({value: null}); 95 | root.set('values', null, nullNode); 96 | ok(root.value('values', null) === null); 97 | }); 98 | 99 | -------------------------------------------------------------------------------- /test/scene/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Scene API - TestSuite 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 |

      Scene API Test Suite

      22 |

      23 |
      24 |

      25 |
        26 |
        27 | 28 | 29 | -------------------------------------------------------------------------------- /test/scene/testsuite.js: -------------------------------------------------------------------------------- 1 | var scene; 2 | 3 | module("Scene", { 4 | setup: function() { 5 | scene = new uv.Scene({ 6 | displays: [ 7 | { 8 | container: 'canvas', 9 | width: 200, 10 | height: 150, 11 | zooming: true, 12 | panning: true 13 | } 14 | ], 15 | fillStyle: '#ccc', 16 | actors: [ 17 | { 18 | type: 'rect', 19 | id: 'my_rect', 20 | x: 20, 21 | y: 50, 22 | width: 20, 23 | height: 20, 24 | interactive: true 25 | }, 26 | { 27 | type: 'circle', 28 | id: 'my_circle', 29 | x: 100, 30 | y: 30, 31 | radius: 5, 32 | actors: [ 33 | { 34 | id: 'my_label', 35 | type: 'label', 36 | text: 'LBL1' 37 | }, 38 | { 39 | id: 'my_second_label', 40 | type: 'label', 41 | text: 'Search me' 42 | }, 43 | { 44 | id: 'my_third_label', 45 | type: 'label', 46 | text: 'Search me' 47 | } 48 | ] 49 | } 50 | ] 51 | }); 52 | }, 53 | teardown: function() { 54 | // delete scene; 55 | } 56 | }); 57 | 58 | 59 | test("Actor#remove", function() { 60 | ok(scene.all('children').length === 2); 61 | ok(scene.actors['my_rect']); 62 | ok(scene.interactiveActors['my_rect']); 63 | 64 | scene.remove('my_rect'); 65 | ok(scene.all('children').length === 1); 66 | ok(Object.keys(scene.actors).length === 4); 67 | ok(!scene.actors['my_rect']); 68 | ok(!scene.interactiveActors['my_rect']); 69 | 70 | scene.remove('my_label'); 71 | ok(scene.get('my_circle').all('children').length === 2); 72 | ok(Object.keys(scene.actors).length === 3); 73 | 74 | // Using a matcher function (for batch removals) 75 | scene.remove(function(a) { 76 | return a.p('text') === 'Search me' 77 | }); 78 | ok(Object.keys(scene.actors).length === 1); 79 | ok(!scene.get('my_third_label')); 80 | ok(!scene.get('my_second_label')); 81 | }); -------------------------------------------------------------------------------- /test/sorted_hash/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | SortedHash API - TestSuite 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 |

        SortedHash Test Suite

        27 |

        28 |
        29 |

        30 |
          31 | 32 | 33 | -------------------------------------------------------------------------------- /test/sorted_hash/testsuite.js: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------------------------- 2 | // SortedHash API 3 | // An awesome data structure you've always been missing in JavaScript 4 | //----------------------------------------------------------------------------- 5 | 6 | var items; 7 | 8 | module("SortedHash", { 9 | setup: function() { 10 | items = new uv.SortedHash(); 11 | items.set("at", "Austria"); 12 | items.set("de", "Germany"); 13 | }, 14 | teardown: function() { 15 | delete items; 16 | } 17 | }); 18 | 19 | test("construction from Array", function() { 20 | var numbers = new uv.SortedHash([1, 2, 5, 10]); 21 | 22 | ok(numbers.at(0) === 1); 23 | ok(numbers.at(1) === 2); 24 | ok(numbers.at(2) === 5); 25 | ok(numbers.at(3) === 10); 26 | 27 | // key equals index 28 | ok(numbers.get(0) === 1); 29 | ok(numbers.get(1) === 2); 30 | ok(numbers.get(2) === 5); 31 | ok(numbers.get(3) === 10); 32 | 33 | ok(numbers.length === 4); 34 | }); 35 | 36 | test("construction from Hash", function() { 37 | var countries = new uv.SortedHash({ 38 | 'at': 'Austria', 39 | 'de': 'Germany', 40 | 'ch': 'Switzerland' 41 | }); 42 | 43 | // please note: order is undetermined since native javascript hashes 44 | // are not sorted. please perform a sort() operation after construction 45 | // if you want to rely on item ordering. 46 | ok(countries.get('at') === 'Austria'); 47 | ok(countries.get('de') === 'Germany'); 48 | ok(countries.get("ch") === "Switzerland"); 49 | ok(countries.length === 3); 50 | }); 51 | 52 | test("insertion", function() { 53 | items.set("ch", "Switzerland"); 54 | ok(items.length === 3); 55 | }); 56 | 57 | test("value overwrite", function() { 58 | items.get("at") === "Austria"; 59 | items.set("at", "Österreich"); 60 | ok(items.length === 2); 61 | ok(items.get("at") === "Österreich"); 62 | }); 63 | 64 | test("clone", function() { 65 | // TODO: add some assertions 66 | items.clone(); 67 | }); 68 | 69 | test("key", function() { 70 | ok(items.key(0) === "at"); 71 | ok(items.key(1) === "de"); 72 | }); 73 | 74 | 75 | test("hash semantics", function() { 76 | var keys = []; 77 | var values = []; 78 | 79 | ok(items.get("at") === "Austria"); 80 | ok(items.get("de") === "Germany"); 81 | 82 | // order is also reflected in eachKey 83 | items.eachKey(function(key, value) { 84 | keys.push(key); 85 | values.push(value); 86 | }); 87 | 88 | ok(keys.length === 2 && values.length === 2); 89 | ok(keys[0] === 'at'); 90 | ok(keys[1] === 'de'); 91 | ok(values[0] === 'Austria'); 92 | ok(values[1] === 'Germany'); 93 | }); 94 | 95 | test("array semantics", function() { 96 | var values = []; 97 | items.get(0) === "Austria"; 98 | items.get(1) === "Germany"; 99 | items.length === 1; 100 | 101 | items.each(function(index, value) { 102 | values.push(value); 103 | }); 104 | 105 | ok(values.length === 2); 106 | ok(values[0] === 'Austria'); 107 | ok(values[1] === 'Germany'); 108 | 109 | ok(items.first() === "Austria"); 110 | ok(items.last() === "Germany"); 111 | 112 | }); 113 | 114 | 115 | test("SortedHash#del", function() { 116 | items.set("ch", "Switzerland"); 117 | items.del('de'); 118 | ok(items.length === 2); 119 | ok(items.keyOrder.length === 2); 120 | ok(items.get('de') === undefined); 121 | }); 122 | 123 | 124 | test("SortedHash#each", function() { 125 | var enumerated = []; 126 | items.each(function(index, item) { 127 | enumerated.push(item); 128 | }); 129 | 130 | ok(enumerated[0]==="Austria"); 131 | ok(enumerated[1]==="Germany"); 132 | }); 133 | 134 | test("SortedHash#values", function() { 135 | items.set("ch", "Switzerland"); 136 | var values = items.values(); 137 | 138 | ok(values[0] === "Austria"); 139 | ok(values[1] === "Germany"); 140 | ok(values[2] === "Switzerland"); 141 | }); 142 | 143 | test("SortedHash#sort", function() { 144 | items.set("ch", "Switzerland"); 145 | 146 | ok(items.at(0)==="Austria"); 147 | ok(items.at(1)==="Germany"); 148 | ok(items.at(2)==="Switzerland"); 149 | 150 | // sort descending 151 | var sortedItems = items.sort(uv.Comparators.DESC); 152 | 153 | ok(sortedItems.at(0)==="Switzerland"); 154 | ok(sortedItems.at(1)==="Germany"); 155 | ok(sortedItems.at(2)==="Austria"); 156 | }); 157 | 158 | 159 | test("SortedHash#map", function() { 160 | var mappedItems = items.map(function (item) { 161 | return item.slice(0, 3); 162 | }); 163 | 164 | // leave original SortedHash untouched 165 | ok(items.get('at') === 'Austria'); 166 | ok(items.get('de') === 'Germany'); 167 | ok(items.at(0) === 'Austria'); 168 | ok(items.at(1) === 'Germany'); 169 | 170 | ok(mappedItems.get('at') === 'Aus'); 171 | ok(mappedItems.get('de') === 'Ger'); 172 | 173 | ok(mappedItems.at(0) === 'Aus'); 174 | ok(mappedItems.at(1) === 'Ger'); 175 | }); 176 | 177 | test("SortedHash#select", function() { 178 | var selectedItems = items.select(function (key, i) { 179 | return i === 'Austria'; 180 | }); 181 | 182 | // leave original SortedHash untouched 183 | ok(items.get('at') === 'Austria'); 184 | ok(items.get('de') === 'Germany'); 185 | ok(items.at(0) === 'Austria'); 186 | ok(items.at(1) === 'Germany'); 187 | 188 | ok(selectedItems.at(0) === 'Austria'); 189 | ok(selectedItems.get("at") === 'Austria'); 190 | ok(selectedItems.length === 1); 191 | }); 192 | 193 | test("SortedHash#intersect", function() { 194 | var items2 = new uv.SortedHash(), 195 | intersected; 196 | 197 | items2.set('fr', 'France'); 198 | items2.set('at', 'Austria'); 199 | 200 | // leave original SortedHashes untouched 201 | ok(items.get('at') === 'Austria'); 202 | ok(items.get('de') === 'Germany'); 203 | ok(items.at(0) === 'Austria'); 204 | ok(items.at(1) === 'Germany'); 205 | 206 | ok(items2.get('fr') === 'France'); 207 | ok(items2.get('at') === 'Austria'); 208 | ok(items2.at(0) === 'France'); 209 | ok(items2.at(1) === 'Austria'); 210 | 211 | intersected = items.intersect(items2); 212 | ok(intersected.length === 1); 213 | ok(intersected.get('at') === 'Austria'); 214 | }); 215 | 216 | 217 | test("SortedHash#union", function() { 218 | var items2 = new uv.SortedHash(), 219 | unitedItems; 220 | 221 | items2.set('fr', 'France'); 222 | items2.set('at', 'Austria'); 223 | 224 | // leave original SortedHashes untouched 225 | ok(items.get('at') === 'Austria'); 226 | ok(items.get('de') === 'Germany'); 227 | ok(items.at(0) === 'Austria'); 228 | ok(items.at(1) === 'Germany'); 229 | 230 | ok(items2.get('fr') === 'France'); 231 | ok(items2.get('at') === 'Austria'); 232 | ok(items2.at(0) === 'France'); 233 | ok(items2.at(1) === 'Austria'); 234 | 235 | unitedItems = items.union(items2); 236 | ok(unitedItems.length === 3); 237 | ok(unitedItems.get('at') === 'Austria'); 238 | ok(unitedItems.get('de') === 'Germany'); 239 | ok(unitedItems.get('fr') === 'France'); 240 | }); 241 | 242 | test("fail prevention", function() { 243 | items.set(null, 'Netherlands'); 244 | items.set(undefined, 'Netherlands'); 245 | items.set('null_value', null); 246 | items.set('undefined_value', undefined); 247 | ok(items.length === 4); 248 | }); -------------------------------------------------------------------------------- /test/visualization/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Visualization API - TestSuite 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 |

          Visualization API Test Suite

          35 |

          36 |
          37 |

          38 |
            39 | 40 | 41 | -------------------------------------------------------------------------------- /test/visualization/testsuite.js: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------------------------- 2 | // Visualization API Test Suite 3 | //----------------------------------------------------------------------------- 4 | 5 | var c, vis; 6 | 7 | module("Visualization", { 8 | setup: function() { 9 | c = new uv.Collection(countries_fixture); 10 | vis = new uv.Barchart(c, { 11 | measures: ['population', 'area', 'gdp_nominal'], 12 | params: {} 13 | }); 14 | }, 15 | teardown: function() { 16 | delete c; 17 | } 18 | }); 19 | 20 | test("Construction", function() { 21 | ok(vis instanceof uv.Visualization); 22 | ok(vis instanceof uv.Barchart); 23 | ok(vis.collection instanceof uv.Collection); 24 | }); 25 | 26 | test('Visualization#property', function() { 27 | ok(vis.property(0).key === 'population'); 28 | ok(vis.property(1).key === 'area'); 29 | ok(vis.property(2).key === 'gdp_nominal'); 30 | }); 31 | 32 | test("Verification", function() { 33 | // valid with three number measures 34 | ok(vis.isValid()); 35 | 36 | // not valid with zero specified measures 37 | vis.measures = []; 38 | ok(!vis.isValid()); 39 | 40 | // not valid with one non-number measure 41 | vis.measures = ['official_language']; 42 | ok(!vis.isValid()); 43 | 44 | // not valid with any non-number measure 45 | vis.measures = ['population', 'official_language', 'area']; 46 | ok(!vis.isValid()); 47 | 48 | // not valid with any non-number measure 49 | vis.measures = ['population', 'area']; 50 | ok(vis.isValid()); 51 | }); 52 | 53 | 54 | -------------------------------------------------------------------------------- /unveil.min.js: -------------------------------------------------------------------------------- 1 | (function(){var c={};c.EPSILON=1.0E-4;c.MAX_FLOAT=3.4028235E38;c.MIN_FLOAT=-3.4028235E38;c.MAX_INT=2147483647;c.MIN_INT=-2147483648;c.inherit=function(a){function b(){}b.prototype=a.prototype||a;return new b};c.each=function(a,b,d){if(a.forEach)a.forEach(b,d);else for(var e in a)hasOwnProperty.call(a,e)&&b.call(d,a[e],e,a);return a};c.rest=function(a,b,d){return Array.prototype.slice.call(a,b===undefined||d?1:b)};c.include=function(a,b){return a.indexOf(b)!=-1};c.isArray=function(a){return toString.call(a)=== 2 | "[object Array]"};c.select=c.filter=function(a,b,d){if(a.filter===Array.prototype.filter)return a.filter(b,d);var e=[];c.each(a,function(f,i,j){b.call(d,f,i,j)&&e.push(f)});return e};c.extend=function(a){c.rest(arguments).forEach(function(b){for(var d in b)a[d]=b[d]});return a};(function(){function a(d,e){return{x:d||0,y:e||0,add:function(f){return a(this.x+f.x,this.y+f.y)}}}function b(d,e,f,i,j,n){d=d!==undefined?d:1;i=i!==undefined?i:1;return{a:d,b:e||0,c:f||0,d:i,tx:j||0,ty:n||0,concat:function(g){return b(this.a* 3 | g.a+this.c*g.b,this.b*g.a+this.d*g.b,this.a*g.c+this.c*g.d,this.b*g.c+this.d*g.d,this.a*g.tx+this.c*g.ty+this.tx,this.b*g.tx+this.d*g.ty+this.ty)},deltaTransformPoint:function(g){return a(this.a*g.x+this.c*g.y,this.b*g.x+this.d*g.y)},inverse:function(){var g=this.a*this.d-this.b*this.c;return b(this.d/g,-this.b/g,-this.c/g,this.a/g,(this.c*this.ty-this.d*this.tx)/g,(this.b*this.tx-this.a*this.ty)/g)},rotate:function(g,k){return this.concat(b.rotation(g,k))},scale:function(g,k,m){return this.concat(b.scale(g, 4 | k,m))},transformPoint:function(g){return a(this.a*g.x+this.c*g.y+this.tx,this.b*g.x+this.d*g.y+this.ty)},translate:function(g,k){return this.concat(b.translation(g,k))}}}a.distance=function(d,e){return Math.sqrt(Math.pow(e.x-d.x,2)+Math.pow(e.y-d.y,2))};a.direction=function(d,e){return Math.atan2(e.y-d.y,e.x-d.x)};b.rotation=function(d,e){var f=b(Math.cos(d),Math.sin(d),-Math.sin(d),Math.cos(d));if(e)f=b.translation(e.x,e.y).concat(f).concat(b.translation(-e.x,-e.y));return f};b.scale=function(d, 5 | e,f){e=e||d;d=b(d,0,0,e);if(f)d=b.translation(f.x,f.y).concat(d).concat(b.translation(-f.x,-f.y));return d};b.translation=function(d,e){return b(1,0,0,1,d,e)};b.IDENTITY=b();b.HORIZONTAL_FLIP=b(-1,0,0,1);b.VERTICAL_FLIP=b(1,0,0,-1);c.Point=a;c.Matrix=b})();c.Actor=function(a){Data.Node.call(this);this.childCount=0;this.properties=c.extend({x:0,y:0,scaleX:1,scaleY:1,rotation:0,localX:0,localY:0,localScaleX:1,localScaleY:1,localRotation:0,fillStyle:"#000",strokeStyle:"#000",lineWidth:1,lineCap:"butt", 6 | lineJoin:"miter",globalAlpha:1,miterLimit:10,visible:true,transformMode:"object"},a);this.replace("children",new Data.Hash);this.tweens={};this.active=false;this.handlers={}};c.Actor.registeredActors={};c.Actor.prototype=c.inherit(Data.Node);c.Actor.prototype.bind=function(a,b){this.handlers[a]||(this.handlers[a]=[]);this.handlers[a].push(b)};c.Actor.prototype.trigger=function(a){if(this.handlers[a])for(var b in this.handlers[a])this.handlers[a][b].apply(this,[])};c.Actor.create=function(a){var b= 7 | c.Actor.registeredActors[a.type];if(!b)throw"Actor type unregistered: '"+a.type+"'";return new b(a)};c.Actor.prototype.id=function(){return this.p("id")||this.nodeId};c.Actor.prototype.add=function(a){var b;b=a instanceof c.Actor?a:c.Actor.create(a);if(!this.scene)throw"You can't add childs to actors that don't have a scene reference";this.scene.registerActor(b);this.set("children",b.id(),b);b.parent=this;b.init&&b.init();a.actors&&a.actors.forEach(function(d){b.add(d)});return b};c.Actor.prototype.get= 8 | function(){return arguments.length===1?this.scene.actors[arguments[0]]:Data.Node.prototype.get.call(this,arguments[0],arguments[1])};c.Actor.prototype.remove=function(a){var b=this;if(a instanceof Function)this.traverse().forEach(function(d){a(d)&&b.scene.remove(d.id())});else{if(this.get("children",a)){this.all("children").del(a);delete this.scene.actors[a];delete this.scene.interactiveActors[a]}this.all("children").each(function(d){d.remove(a)})}};c.Actor.prototype.traverse=function(){return this.scene.properties.traverser(this)}; 9 | c.Actor.prototype.property=function(a,b){if(b)return this.properties[a]=b;else return this.properties[a]instanceof Function?this.properties[a].call(this):this.properties[a]};c.Actor.prototype.p=c.Actor.prototype.property;c.Actor.prototype.animate=function(a,b,d){var e=this.scene,f=(new c.Tween(this.properties)).to(b||1,a).easing(d||c.Tween.Easing.Expo.EaseInOut).onComplete(function(){e.unexecute(c.cmds.RequestFramerate);c.TweenManager.remove(f)});e.execute(c.cmds.RequestFramerate);return f.start()}; 10 | c.Actor.prototype.tShape=function(){return c.Matrix().translate(this.p("localX"),this.p("localY")).rotate(this.p("localRotation")).scale(this.p("localScaleX"),this.p("localScaleY"))};c.Actor.prototype.tWorldParent=function(){return this.parent?this.parent._tWorld:c.Matrix()};c.Actor.prototype.tWorld=function(){return this.tWorldParent().translate(this.p("x"),this.p("y")).rotate(this.p("rotation")).scale(this.p("scaleX"),this.p("scaleY"))};c.Actor.prototype.compileMatrix=function(){this.update();this._tWorld= 11 | this.tWorld();this.all("children")&&this.all("children").each(function(a){a.compileMatrix()})};c.Actor.prototype.update=function(){c.TweenManager.update()};c.Actor.prototype.applyStyles=function(a){a.fillStyle=this.p("fillStyle");a.strokeStyle=this.p("strokeStyle");a.lineWidth=this.p("lineWidth");a.lineCap=this.p("lineCap");a.lineJoin=this.p("lineJoin");a.globalAlpha=this.p("globalAlpha");a.miterLimit=this.p("miterLimit")};c.Actor.prototype.draw=function(){};c.Actor.prototype.checkActive=function(a, 12 | b,d){b=new c.Point(b,d);d=this._tWorld.inverse().transformPoint(b);b=d.x;d=d.y;if(this.bounds&&a.isPointInPath){this.drawBounds(a);this.active=a.isPointInPath(b,d)?true:false}return this.active};c.Actor.prototype.drawBounds=function(a){var b=this.bounds(),d,e;d=b.shift();a.beginPath();for(a.moveTo(d.x,d.y);e=b.shift();)a.lineTo(e.x,e.y);a.lineTo(d.x,d.y)};c.Actor.prototype.render=function(a,b){if(this.p("visible")){this.applyStyles(a);this.transform(a,b);this.draw(a,b)}};c.Actor.prototype.transform= 13 | function(a,b){var d=this.tShape().concat(b).concat(this._tWorld);if(this.p("transformMode")==="origin"){d=d.transformPoint(c.Point(0,0));a.setTransform(1,0,0,1,d.x,d.y)}else a.setTransform(d.a,d.b,d.c,d.d,d.tx,d.ty)};c.traverser={};c.traverser.BreadthFirst=function(a){var b=[],d=[];for(b.push(a);b.length>0;){a=b.shift();if(a.p("visible")){d.push(a);a.all("children").each(function(e){b.push(e)})}}return d};c.traverser.DepthFirst=function(a){var b=[],d=[];for(b.push(a);b.length>0;){a=b.pop();if(a.p("visible")){d.push(a); 14 | a.all("children").each(function(e){b.push(e)})}}return d};c.behaviors={};c.behaviors.adjust=function(a,b){var d=a.bounds();if(a.bounded){b.a=b.d=Math.max(1,b.a);b.tx=Math.max(d.x,Math.min(0,b.tx));b.ty=Math.max(d.y,Math.min(0,b.ty))}return b};c.behaviors.Zoom=function(a){function b(d){d=d.wheelDelta/120||-d.detail;var e=a.tView.scale(1+0.0050*d,1+0.0050*d,c.Point(a.scene.mouseX,a.scene.mouseY));a.tView=d<0?c.behaviors.adjust(a,e):e;a.trigger("viewChange")}a.canvas.addEventListener("mousewheel",b, 15 | false);a.canvas.addEventListener("DOMMouseScroll",b,false)};c.behaviors.Pan=function(a){function b(){e=false}var d,e=false;a.canvas.addEventListener("mousedown",function(){p=c.Point(a.mouseX,a.mouseY);d=a.tView;e=true},false);a.canvas.addEventListener("mousemove",function(){if(e){var f=c.Matrix.translation(a.mouseX-p.x,a.mouseY-p.y).concat(d);a.tView=c.behaviors.adjust(a,f);a.trigger("viewChange")}},false);a.canvas.addEventListener("mouseup",b,false);a.canvas.addEventListener("mouseout",b,false)}; 16 | c.Display=function(a,b){function d(){e.scene.trigger("interact")}var e=this;c.Actor.call(this,c.extend({fillStyle:""},b));this.scene=a;this.element=document.getElementById(b.container);this.canvas=document.createElement("canvas");this.canvas.setAttribute("width",b.width);this.canvas.setAttribute("height",b.height);this.canvas.style.position="relative";this.element.appendChild(this.canvas);this.width=b.width;this.height=b.height;this.bounded=b.bounded||true;this.ctx=this.canvas.getContext("2d");this.tView= 17 | c.Matrix();if(b.zooming)this.zoombehavior=new c.behaviors.Zoom(this);if(b.panning)this.panbehavior=new c.behaviors.Pan(this);this.canvas.addEventListener("mousemove",d,false);this.canvas.addEventListener("DOMMouseScroll",d,false);this.canvas.addEventListener("mousemove",function(f){var i=e.tView.inverse(),j;if(f.offsetX)j=new c.Point(f.offsetX,f.offsetY);else if(f.layerX)j=new c.Point(f.layerX,f.layerY);if(j){e.mouseX=j.x;e.mouseY=j.y;worldPos=i.transformPoint(j);e.scene.mouseX=parseInt(worldPos.x, 18 | 10);e.scene.mouseY=parseInt(worldPos.y,10);e.scene.activeDisplay=e}},false);this.canvas.addEventListener("mousewheel",d,false);this.canvas.addEventListener("mouseout",function(){e.scene.mouseX=NaN;e.scene.mouseY=NaN},false);this.canvas.addEventListener("click",function(){c.each(e.scene.activeActors,function(f){f.trigger("click")})},false)};c.Display.prototype=c.inherit(c.Actor);c.Display.prototype.displayPos=function(){return this.tView.transformPoint(pos)};c.Display.prototype.zoom=function(){return this.tView.a}; 19 | c.Display.prototype.worldPos=function(a){return this.tView.inverse().transformPoint(a)};c.Display.prototype.bounds=function(){var a=Math.max(0,this.scene.p("width")-this.width),b=Math.max(0,this.scene.p("width")-this.width);return{x:(1-this.tView.a)*this.width-this.tView.a*a,y:(1-this.tView.a)*this.height-this.tView.a*b}};c.Display.prototype.refresh=function(){var a=this,b,d;this.ctx.clearRect(0,0,this.width,this.height);if(this.scene.p("fillStyle")!==""){this.ctx.fillStyle=this.scene.p("fillStyle"); 20 | this.ctx.fillRect(0,0,this.width,this.height)}this.ctx.save();b=this.scene.traverse();b.shift();c.each(b,function(e){e.render(a.ctx,a.tView)});d=this.traverse();b.shift();c.each(d,function(e){e.render(a.ctx,c.Matrix())});this.ctx.restore()};c.cmds={};c.cmds.RequestFramerate=function(a,b){this.scene=a;this.requests=0;this.framerate=b.framerate;this.originalFramerate=this.scene.framerate};c.cmds.RequestFramerate.className="RequestFramerate";c.cmds.RequestFramerate.prototype.execute=function(){this.requests+= 21 | 1;this.scene.setFramerate(this.framerate)};c.cmds.RequestFramerate.prototype.unexecute=function(){this.requests-=1;this.requests<=0&&this.scene.setFramerate(this.originalFramerate)};c.Scene=function(a){var b=this;c.Actor.call(this,c.extend({width:0,height:0,fillStyle:"",idleFramerate:0,framerate:50,traverser:c.traverser.DepthFirst},a));this.mouseY=this.mouseX=NaN;this.interactiveActors={};this.activeActors=[];this.actors={};this.scene=this;this.displays=[];a.displays&&c.each(a.displays,function(f){b.displays.push(new c.Display(b, 22 | f))});this.activeDisplay=this.displays[0];this.fps=0;this.framerate=this.p("idleFramerate");this.commands={};this.register(c.cmds.RequestFramerate,{framerate:this.p("framerate")});a.actors&&c.each(a.actors,function(f){b.add(f)});var d,e=false;this.bind("interact",function(){if(!e){b.execute(c.cmds.RequestFramerate);e=true}clearTimeout(d);d=setTimeout(function(){e=false;b.unexecute(c.cmds.RequestFramerate)},1E3)})};c.Scene.prototype=c.inherit(c.Actor);c.Scene.prototype.registerActor=function(a){var b= 23 | a.id();if(this.actors[b])throw"ID '"+b+"' already registered.";a.scene=this;this.actors[b]=a;if(a.p("interactive"))this.interactiveActors[a.id()]=a};c.Scene.prototype.start=function(){this.running=true;this.trigger("start");this.loop();this.checkActiveActors()};c.Scene.prototype.setFramerate=function(a){this.framerate=a;clearTimeout(this.nextLoop);clearTimeout(this.nextPick);this.loop();this.checkActiveActors()};c.Scene.prototype.loop=function(){var a=this,b;if(this.running){this.fps=1E3/b0)this.nextLoop=setTimeout(function(){a.loop()},1E3/a.framerate-b)}};c.Scene.prototype.render=function(){this.trigger("frame");this.compileMatrix();this.refreshDisplays()};c.Scene.prototype.stop=function(){this.running=false;this.trigger("stop")};c.Scene.prototype.checkActiveActors=function(){var a=this.displays[0].ctx,b=this,d=this.activeActors;if(this.running){if(this.scene.mouseX!==NaN){this.activeActors= 25 | [];c.each(this.interactiveActors,function(e){if(e.checkActive(a,b.scene.mouseX,b.scene.mouseY)){b.activeActors.push(e);c.include(d,e)||e.trigger("mouseover")}else c.include(d,e)&&e.trigger("mouseout")})}if(b.framerate>0)this.nextPick=setTimeout(function(){b.checkActiveActors()},1E3/Math.min(b.framerate,15))}};c.Scene.prototype.refreshDisplays=function(){c.each(this.displays,function(a){a.compileMatrix();a.refresh()})};c.Scene.prototype.display=function(a){a=new c.Display(this,a);this.displays.push(a); 26 | return a};c.Scene.prototype.register=function(a,b){this.commands[a.className]=new a(this,b)};c.Scene.prototype.execute=function(a){this.commands[a.className].execute()};c.Scene.prototype.unexecute=function(a){this.commands[a.className].unexecute()};c.TweenManager=c.TweenManager||function(){var a,b,d=[];this.add=function(e){d.push(e)};this.remove=function(e){for(var f=0,i=d.length;ff){o=true;j=null;m!==null&&m();if(g!==null){g.start();return true}else return false}for(l in d)a[l]=n(h,b[l],d[l],f);k!==null&&k.apply(a);return true};this.destroy=function(){c.TweenManager.remove(this)}};c.Tween.Easing={Back:{},Elastic:{},Expo:{},Linear:{}};c.Tween.Easing.Back.EaseIn=function(a,b,d,e){return d*(a/=e)*a*(2.70158*a-1.70158)+ 29 | b};c.Tween.Easing.Back.EaseOut=function(a,b,d,e){return d*((a=a/e-1)*a*(2.70158*a+1.70158)+1)+b};c.Tween.Easing.Back.EaseInOut=function(a,b,d,e){var f=1.70158;if((a/=e/2)<1)return d/2*a*a*(((f*=1.525)+1)*a-f)+b;return d/2*((a-=2)*a*(((f*=1.525)+1)*a+f)+2)+b};c.Tween.Easing.Elastic.EaseIn=function(a,b,d,e){if(a==0)return b;if((a/=e)==1)return b+d;var f=e*0.3,i=f/4;return-(d*Math.pow(2,10*(a-=1))*Math.sin((a*e-i)*2*Math.PI/f))+b};c.Tween.Easing.Elastic.EaseOut=function(a,b,d,e){if(a==0)return b;if((a/= 30 | e)==1)return b+d;var f=e*0.3,i=f/4;return d*Math.pow(2,-10*a)*Math.sin((a*e-i)*2*Math.PI/f)+d+b};c.Tween.Easing.Elastic.EaseInOut=function(a,b,d,e){if(a==0)return b;if((a/=e/2)==2)return b+d;var f=e*0.3*1.5,i=f/4;if(a<1)return-0.5*d*Math.pow(2,10*(a-=1))*Math.sin((a*e-i)*2*Math.PI/f)+b;return d*Math.pow(2,-10*(a-=1))*Math.sin((a*e-i)*2*Math.PI/f)*0.5+d+b};c.Tween.Easing.Expo.EaseIn=function(a,b,d,e){return a==0?b:d*Math.pow(2,10*(a/e-1))+b};c.Tween.Easing.Expo.EaseOut=function(a,b,d,e){return a== 31 | e?b+d:d*(-Math.pow(2,-10*a/e)+1)+b};c.Tween.Easing.Expo.EaseInOut=function(a,b,d,e){if(a==0)return b;if(a==e)return b+d;if((a/=e/2)<1)return d/2*Math.pow(2,10*(a-1))+b;return d/2*(-Math.pow(2,-10*--a)+2)+b};c.Tween.Easing.Linear.EaseNone=function(a,b,d,e){return d*a/e+b};c.Tween.Easing.Linear.EaseIn=function(a,b,d,e){return d*a/e+b};c.Tween.Easing.Linear.EaseOut=function(a,b,d,e){return d*a/e+b};c.Tween.Easing.Linear.EaseInOut=function(a,b,d,e){return d*a/e+b};c.Rect=function(a){c.Actor.call(this, 32 | c.extend({width:0,height:0,fillStyle:"#777",strokeStyle:"#000",lineWidth:0},a))};c.Actor.registeredActors.rect=c.Rect;c.Rect.prototype=c.inherit(c.Actor);c.Rect.prototype.bounds=function(){return[{x:0,y:0},{x:this.p("width"),y:0},{x:this.p("width"),y:this.p("height")},{x:0,y:this.p("height")}]};c.Rect.prototype.draw=function(a){this.p("fillStyle")&&a.fillRect(0,0,this.p("width"),this.p("height"));this.p("lineWidth")>0&&a.strokeRect(0,0,this.p("width"),this.p("height"))};c.Label=function(a){c.Actor.call(this, 33 | c.extend({text:"",textAlign:"start",font:"12px Helvetica, Arial",fillStyle:"#444",lineWidth:0,backgroundStyle:"#eee",background:false},a))};c.Actor.registeredActors.label=c.Label;c.Label.prototype=c.inherit(c.Actor);c.Label.prototype.draw=function(a){a.font=this.p("font");a.textAlign=this.p("textAlign");a.fillText(this.p("text"),0,0)};c.Circle=function(a){c.Actor.call(this,c.extend({radius:20,strokeWeight:2,lineWidth:0,strokeStyle:"#fff"},a))};c.Actor.registeredActors.circle=c.Circle;c.Circle.prototype= 34 | c.inherit(c.Actor);c.Circle.prototype.bounds=function(){return[{x:-this.p("radius"),y:-this.p("radius")},{x:this.p("radius"),y:-this.p("radius")},{x:this.p("radius"),y:this.p("radius")},{x:-this.p("radius"),y:this.p("radius")}]};c.Circle.prototype.draw=function(a){a.fillStyle=this.p("fillStyle");a.strokeStyle=this.p("strokeStyle");a.lineWidth=this.p("lineWidth");a.beginPath();a.arc(0,0,this.p("radius"),0,Math.PI*2,false);a.closePath();this.p("lineWidth")>0&&a.stroke();a.fill()};c.Path=function(a){c.Actor.call(this, 35 | c.extend({points:[],lineWidth:1,strokeStyle:"#000",fillStyle:""},a));this.transformedPoints=this.points=[].concat(this.p("points"))};c.Actor.registeredActors.path=c.Path;c.Path.prototype=c.inherit(c.Actor);c.Path.prototype.transform=function(a,b){this.transformedPoints=this.points=[].concat(this.p("points"));if(this.p("transformMode")==="origin"){var d=this.tShape().concat(b).concat(this._tWorld);a.setTransform(1,0,0,1,0,0);this.transformedPoints=this.points.map(function(e){var f=d.transformPoint(e), 36 | i=d.transformPoint(c.Point(e.cp1x,e.cp1y)),j=d.transformPoint(c.Point(e.cp2x,e.cp2y));f={x:f.x,y:f.y};if(e.cp1x&&e.cp1y){f.cp1x=i.x;f.cp1y=i.y}if(e.cp2x&&e.cp2y){f.cp2x=j.x;f.cp2y=j.y}return f})}else c.Actor.prototype.transform.call(this,a,b)};c.Path.prototype.draw=function(a){var b=[].concat(this.transformedPoints),d;if(b.length>=1){a.beginPath();d=b.shift();for(a.moveTo(d.x,d.y);d=b.shift();)if(d.cp1x&&d.cp2x)a.bezierCurveTo(d.cp1x,d.cp1y,d.cp2x,d.cp2y,d.x,d.y);else d.cp1x?a.quadraticCurveTo(d.cp1x, 37 | d.cp1y,d.x,d.y):a.lineTo(d.x,d.y);this.p("lineWidth")>0&&this.p("strokeStyle")!==""&&a.stroke();this.p("fillStyle")!==""&&a.fill();a.closePath()}};this.uv=c;if(typeof exports!=="undefined")exports.uv=c})(); 38 | --------------------------------------------------------------------------------