├── .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 |
120 | Population
121 | GDP per capita
122 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
--------------------------------------------------------------------------------