├── .gitignore ├── 01-dbmon ├── .meteor │ ├── .gitignore │ ├── release │ ├── platforms │ ├── .finished-upgraders │ ├── packages │ ├── .id │ └── versions ├── ENV.js ├── styles.css └── client │ ├── memory.js │ ├── index.html │ ├── dbmon.html │ └── dbmon.js ├── 06-itunes-style-interface ├── .meteor │ ├── .gitignore │ ├── release │ ├── platforms │ ├── .finished-upgraders │ ├── .id │ ├── packages │ └── versions ├── client │ ├── arrow.coffee │ ├── arrow.html │ ├── index.coffee │ ├── index.html │ ├── album.html │ ├── albums.html │ ├── albums.coffee │ └── album.coffee ├── public │ ├── R-227671-1076173016.bmp.jpg │ ├── R-2447594-1284566470.gif.jpg │ ├── R-291038-1095516916.gif.jpg │ ├── R-291183-1096116904.jpg.jpg │ ├── R-749414-1329809579.jpeg.jpg │ ├── R-1472338-1222265760.jpeg.jpg │ ├── R-1563653-1228697990.jpeg.jpg │ ├── R-1646356-1234303215.jpeg.jpg │ ├── R-1830898-1246317858.jpeg.jpg │ ├── R-1978764-1274819281.jpeg.jpg │ ├── R-2159557-1323270394.jpeg.jpg │ ├── R-2366230-1282923547.jpeg.jpg │ ├── R-2709365-1297526181.jpeg.jpg │ ├── R-119870-1353499194-8399.jpeg.jpg │ ├── R-4471320-1365810323-8252.png.jpg │ ├── R-673587-1360949274-6329.jpeg.jpg │ ├── R-1226138-1393532248-7305.jpeg.jpg │ ├── R-2045770-1360952357-4411.jpeg.jpg │ ├── R-4548894-1368047918-8950.jpeg.jpg │ ├── R-4928843-1379709382-2088.jpeg.jpg │ ├── R-5251115-1388757674-4136.jpeg.jpg │ ├── R-5283708-1389551852-3864.jpeg.jpg │ ├── R-5354108-1391298591-9910.jpeg.jpg │ ├── R-5527251-1395674311-9055.jpeg.jpg │ ├── R-5555750-1396442448-5948.jpeg.jpg │ ├── R-5669676-1419686855-3510.jpeg.jpg │ ├── R-5685454-1399888274-8456.jpeg.jpg │ ├── R-6037721-1409466261-6307.jpeg.jpg │ ├── R-6347905-1417032940-3492.jpeg.jpg │ ├── R-6386665-1417975610-5551.jpeg.jpg │ └── R-6432244-1419282815-8594.jpeg.jpg └── lib │ ├── 0createStore.coffee │ ├── Releases.coffee │ ├── Colors.coffee │ ├── BOB.coffee │ └── color-thief.js └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | -------------------------------------------------------------------------------- /01-dbmon/.meteor/.gitignore: -------------------------------------------------------------------------------- 1 | local 2 | -------------------------------------------------------------------------------- /01-dbmon/.meteor/release: -------------------------------------------------------------------------------- 1 | METEOR@1.0.3.1 2 | -------------------------------------------------------------------------------- /01-dbmon/.meteor/platforms: -------------------------------------------------------------------------------- 1 | server 2 | browser 3 | -------------------------------------------------------------------------------- /06-itunes-style-interface/.meteor/.gitignore: -------------------------------------------------------------------------------- 1 | local 2 | -------------------------------------------------------------------------------- /06-itunes-style-interface/.meteor/release: -------------------------------------------------------------------------------- 1 | METEOR@1.0.3.1 2 | -------------------------------------------------------------------------------- /06-itunes-style-interface/.meteor/platforms: -------------------------------------------------------------------------------- 1 | server 2 | browser 3 | -------------------------------------------------------------------------------- /01-dbmon/ENV.js: -------------------------------------------------------------------------------- 1 | ENV = {}; 2 | ENV.rows = 100; 3 | ENV.timeout = 0; 4 | -------------------------------------------------------------------------------- /06-itunes-style-interface/client/arrow.coffee: -------------------------------------------------------------------------------- 1 | Template.arrow.helpers 2 | borderBottomColor: -> 3 | Colors.get(@id)?.bg 4 | -------------------------------------------------------------------------------- /01-dbmon/styles.css: -------------------------------------------------------------------------------- 1 | .Query { 2 | position: relative; 3 | } 4 | 5 | .Query:hover .popover { 6 | left: -100%; 7 | width: 100%; 8 | display: block; 9 | } 10 | 11 | -------------------------------------------------------------------------------- /06-itunes-style-interface/public/R-227671-1076173016.bmp.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitar/reactconf-2015-HYPE/HEAD/06-itunes-style-interface/public/R-227671-1076173016.bmp.jpg -------------------------------------------------------------------------------- /06-itunes-style-interface/public/R-2447594-1284566470.gif.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitar/reactconf-2015-HYPE/HEAD/06-itunes-style-interface/public/R-2447594-1284566470.gif.jpg -------------------------------------------------------------------------------- /06-itunes-style-interface/public/R-291038-1095516916.gif.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitar/reactconf-2015-HYPE/HEAD/06-itunes-style-interface/public/R-291038-1095516916.gif.jpg -------------------------------------------------------------------------------- /06-itunes-style-interface/public/R-291183-1096116904.jpg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitar/reactconf-2015-HYPE/HEAD/06-itunes-style-interface/public/R-291183-1096116904.jpg.jpg -------------------------------------------------------------------------------- /06-itunes-style-interface/public/R-749414-1329809579.jpeg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitar/reactconf-2015-HYPE/HEAD/06-itunes-style-interface/public/R-749414-1329809579.jpeg.jpg -------------------------------------------------------------------------------- /06-itunes-style-interface/public/R-1472338-1222265760.jpeg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitar/reactconf-2015-HYPE/HEAD/06-itunes-style-interface/public/R-1472338-1222265760.jpeg.jpg -------------------------------------------------------------------------------- /06-itunes-style-interface/public/R-1563653-1228697990.jpeg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitar/reactconf-2015-HYPE/HEAD/06-itunes-style-interface/public/R-1563653-1228697990.jpeg.jpg -------------------------------------------------------------------------------- /06-itunes-style-interface/public/R-1646356-1234303215.jpeg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitar/reactconf-2015-HYPE/HEAD/06-itunes-style-interface/public/R-1646356-1234303215.jpeg.jpg -------------------------------------------------------------------------------- /06-itunes-style-interface/public/R-1830898-1246317858.jpeg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitar/reactconf-2015-HYPE/HEAD/06-itunes-style-interface/public/R-1830898-1246317858.jpeg.jpg -------------------------------------------------------------------------------- /06-itunes-style-interface/public/R-1978764-1274819281.jpeg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitar/reactconf-2015-HYPE/HEAD/06-itunes-style-interface/public/R-1978764-1274819281.jpeg.jpg -------------------------------------------------------------------------------- /06-itunes-style-interface/public/R-2159557-1323270394.jpeg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitar/reactconf-2015-HYPE/HEAD/06-itunes-style-interface/public/R-2159557-1323270394.jpeg.jpg -------------------------------------------------------------------------------- /06-itunes-style-interface/public/R-2366230-1282923547.jpeg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitar/reactconf-2015-HYPE/HEAD/06-itunes-style-interface/public/R-2366230-1282923547.jpeg.jpg -------------------------------------------------------------------------------- /06-itunes-style-interface/public/R-2709365-1297526181.jpeg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitar/reactconf-2015-HYPE/HEAD/06-itunes-style-interface/public/R-2709365-1297526181.jpeg.jpg -------------------------------------------------------------------------------- /06-itunes-style-interface/public/R-119870-1353499194-8399.jpeg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitar/reactconf-2015-HYPE/HEAD/06-itunes-style-interface/public/R-119870-1353499194-8399.jpeg.jpg -------------------------------------------------------------------------------- /06-itunes-style-interface/public/R-4471320-1365810323-8252.png.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitar/reactconf-2015-HYPE/HEAD/06-itunes-style-interface/public/R-4471320-1365810323-8252.png.jpg -------------------------------------------------------------------------------- /06-itunes-style-interface/public/R-673587-1360949274-6329.jpeg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitar/reactconf-2015-HYPE/HEAD/06-itunes-style-interface/public/R-673587-1360949274-6329.jpeg.jpg -------------------------------------------------------------------------------- /06-itunes-style-interface/public/R-1226138-1393532248-7305.jpeg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitar/reactconf-2015-HYPE/HEAD/06-itunes-style-interface/public/R-1226138-1393532248-7305.jpeg.jpg -------------------------------------------------------------------------------- /06-itunes-style-interface/public/R-2045770-1360952357-4411.jpeg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitar/reactconf-2015-HYPE/HEAD/06-itunes-style-interface/public/R-2045770-1360952357-4411.jpeg.jpg -------------------------------------------------------------------------------- /06-itunes-style-interface/public/R-4548894-1368047918-8950.jpeg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitar/reactconf-2015-HYPE/HEAD/06-itunes-style-interface/public/R-4548894-1368047918-8950.jpeg.jpg -------------------------------------------------------------------------------- /06-itunes-style-interface/public/R-4928843-1379709382-2088.jpeg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitar/reactconf-2015-HYPE/HEAD/06-itunes-style-interface/public/R-4928843-1379709382-2088.jpeg.jpg -------------------------------------------------------------------------------- /06-itunes-style-interface/public/R-5251115-1388757674-4136.jpeg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitar/reactconf-2015-HYPE/HEAD/06-itunes-style-interface/public/R-5251115-1388757674-4136.jpeg.jpg -------------------------------------------------------------------------------- /06-itunes-style-interface/public/R-5283708-1389551852-3864.jpeg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitar/reactconf-2015-HYPE/HEAD/06-itunes-style-interface/public/R-5283708-1389551852-3864.jpeg.jpg -------------------------------------------------------------------------------- /06-itunes-style-interface/public/R-5354108-1391298591-9910.jpeg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitar/reactconf-2015-HYPE/HEAD/06-itunes-style-interface/public/R-5354108-1391298591-9910.jpeg.jpg -------------------------------------------------------------------------------- /06-itunes-style-interface/public/R-5527251-1395674311-9055.jpeg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitar/reactconf-2015-HYPE/HEAD/06-itunes-style-interface/public/R-5527251-1395674311-9055.jpeg.jpg -------------------------------------------------------------------------------- /06-itunes-style-interface/public/R-5555750-1396442448-5948.jpeg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitar/reactconf-2015-HYPE/HEAD/06-itunes-style-interface/public/R-5555750-1396442448-5948.jpeg.jpg -------------------------------------------------------------------------------- /06-itunes-style-interface/public/R-5669676-1419686855-3510.jpeg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitar/reactconf-2015-HYPE/HEAD/06-itunes-style-interface/public/R-5669676-1419686855-3510.jpeg.jpg -------------------------------------------------------------------------------- /06-itunes-style-interface/public/R-5685454-1399888274-8456.jpeg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitar/reactconf-2015-HYPE/HEAD/06-itunes-style-interface/public/R-5685454-1399888274-8456.jpeg.jpg -------------------------------------------------------------------------------- /06-itunes-style-interface/public/R-6037721-1409466261-6307.jpeg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitar/reactconf-2015-HYPE/HEAD/06-itunes-style-interface/public/R-6037721-1409466261-6307.jpeg.jpg -------------------------------------------------------------------------------- /06-itunes-style-interface/public/R-6347905-1417032940-3492.jpeg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitar/reactconf-2015-HYPE/HEAD/06-itunes-style-interface/public/R-6347905-1417032940-3492.jpeg.jpg -------------------------------------------------------------------------------- /06-itunes-style-interface/public/R-6386665-1417975610-5551.jpeg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitar/reactconf-2015-HYPE/HEAD/06-itunes-style-interface/public/R-6386665-1417975610-5551.jpeg.jpg -------------------------------------------------------------------------------- /06-itunes-style-interface/public/R-6432244-1419282815-8594.jpeg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitar/reactconf-2015-HYPE/HEAD/06-itunes-style-interface/public/R-6432244-1419282815-8594.jpeg.jpg -------------------------------------------------------------------------------- /06-itunes-style-interface/lib/0createStore.coffee: -------------------------------------------------------------------------------- 1 | @createStore = (loadFn) -> 2 | cache = {} 3 | 4 | get: (id) -> 5 | return null unless id 6 | 7 | cache[id] = loadFn id unless cache[id] 8 | cache[id].get() 9 | -------------------------------------------------------------------------------- /06-itunes-style-interface/lib/Releases.coffee: -------------------------------------------------------------------------------- 1 | API = 'https://api.discogs.com/releases' 2 | 3 | @Releases = createStore (id) -> 4 | result = new ReactiveVar null 5 | 6 | HTTP.get "#{API}/#{id}", (error, response) -> 7 | result.set response.data 8 | 9 | result 10 | -------------------------------------------------------------------------------- /01-dbmon/.meteor/.finished-upgraders: -------------------------------------------------------------------------------- 1 | # This file contains information which helps Meteor properly upgrade your 2 | # app when you run 'meteor update'. You should check it into version control 3 | # with your project. 4 | 5 | notices-for-0.9.0 6 | notices-for-0.9.1 7 | 0.9.4-platform-file 8 | -------------------------------------------------------------------------------- /06-itunes-style-interface/client/arrow.html: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /06-itunes-style-interface/.meteor/.finished-upgraders: -------------------------------------------------------------------------------- 1 | # This file contains information which helps Meteor properly upgrade your 2 | # app when you run 'meteor update'. You should check it into version control 3 | # with your project. 4 | 5 | notices-for-0.9.0 6 | notices-for-0.9.1 7 | 0.9.4-platform-file 8 | -------------------------------------------------------------------------------- /01-dbmon/.meteor/packages: -------------------------------------------------------------------------------- 1 | # Meteor packages used by this project, one per line. 2 | # Check this file (and the other files in this directory) into your repository. 3 | # 4 | # 'meteor add' and 'meteor remove' will edit this file for you, 5 | # but you can also edit it by hand. 6 | 7 | meteor-platform 8 | autopublish 9 | insecure 10 | reactive-var 11 | -------------------------------------------------------------------------------- /01-dbmon/.meteor/.id: -------------------------------------------------------------------------------- 1 | # This file contains a token that is unique to your project. 2 | # Check it into your repository along with the rest of this directory. 3 | # It can be used for purposes such as: 4 | # - ensuring you don't accidentally deploy one app on top of another 5 | # - providing package authors with aggregated statistics 6 | 7 | pua4m4wbqq6g1pk94dr 8 | -------------------------------------------------------------------------------- /06-itunes-style-interface/.meteor/.id: -------------------------------------------------------------------------------- 1 | # This file contains a token that is unique to your project. 2 | # Check it into your repository along with the rest of this directory. 3 | # It can be used for purposes such as: 4 | # - ensuring you don't accidentally deploy one app on top of another 5 | # - providing package authors with aggregated statistics 6 | 7 | wqea8w12zozl4eavi87 8 | -------------------------------------------------------------------------------- /06-itunes-style-interface/client/index.coffee: -------------------------------------------------------------------------------- 1 | Router.route '/', 2 | name: 'index' 3 | template: 'index' 4 | 5 | Router.route '/album/:id', 6 | name: 'album' 7 | template: 'albums' 8 | 9 | action: -> 10 | Tracker.nonreactive => 11 | @state.set 'oldId', @state.get 'id' 12 | @state.set 'id', @params.id if Releases.get(@params.id) and Colors.get(@params.id) 13 | @render() 14 | -------------------------------------------------------------------------------- /06-itunes-style-interface/.meteor/packages: -------------------------------------------------------------------------------- 1 | # Meteor packages used by this project, one per line. 2 | # Check this file (and the other files in this directory) into your repository. 3 | # 4 | # 'meteor add' and 'meteor remove' will edit this file for you, 5 | # but you can also edit it by hand. 6 | 7 | meteor-platform 8 | autopublish 9 | insecure 10 | coffeescript 11 | reactive-var 12 | http 13 | iron:router 14 | -------------------------------------------------------------------------------- /01-dbmon/client/memory.js: -------------------------------------------------------------------------------- 1 | Meteor.startup(function () { 2 | var stats = new MemoryStats(); 3 | stats.domElement.style.position = 'fixed'; 4 | stats.domElement.style.right = '0px'; 5 | stats.domElement.style.bottom = '0px'; 6 | document.body.appendChild( stats.domElement ); 7 | requestAnimationFrame(function rAFloop(){ 8 | stats.update(); 9 | requestAnimationFrame(rAFloop); 10 | }) 11 | }); 12 | -------------------------------------------------------------------------------- /06-itunes-style-interface/client/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | HYPE! 4 | 9 | 10 | 11 | 14 | -------------------------------------------------------------------------------- /01-dbmon/client/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | dbmon (Blaze) 6 | 7 | 8 | 9 | 10 |
11 | {{> dbmon}} 12 |
13 | 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | HYPE! 2 | ==== 3 | 4 | Meteor versions of demo programs from [Hype! React Conf talk from 2015](https://www.youtube.com/watch?v=z5e7kWSHWTg) 5 | ([original source code](https://github.com/ryanflorence/reactconf-2015-HYPE)). 6 | 7 | * `01-dbmon` – http://dbmon.meteor.com/ 8 | * `06-itunes-style-interface` – http://itunes.meteor.com/ 9 | 10 | Running 11 | ------- 12 | 13 | ```sh 14 | curl https://install.meteor.com/ | sh 15 | cd 16 | meteor 17 | open http://localhost:3000 18 | ``` 19 | -------------------------------------------------------------------------------- /06-itunes-style-interface/client/album.html: -------------------------------------------------------------------------------- 1 | 11 | 12 | 17 | -------------------------------------------------------------------------------- /06-itunes-style-interface/lib/Colors.coffee: -------------------------------------------------------------------------------- 1 | dark = (r, g, b) -> 2 | # YIQ equation from http://24ways.org/2010/calculating-color-contrast 3 | yiq = (r * 299 + g * 587 + b * 114) / 1000 4 | yiq < 128 5 | 6 | light = (r, g, b) -> 7 | not dark r, g, b 8 | 9 | @Colors = createStore (id) -> 10 | result = new ReactiveVar null 11 | 12 | img = new Image() 13 | img.onload = -> 14 | thief = new ColorThief() 15 | [r, g, b] = thief.getColor img 16 | bg = "rgb(#{r}, #{g}, #{b})" 17 | fg = if light r, g, b then '#000' else '#fff' 18 | result.set {bg, fg} 19 | img.src = BOB[id] 20 | 21 | result 22 | -------------------------------------------------------------------------------- /06-itunes-style-interface/client/albums.html: -------------------------------------------------------------------------------- 1 | 4 | 5 | 10 | 11 | 14 | 15 | 26 | 27 | 36 | -------------------------------------------------------------------------------- /01-dbmon/client/dbmon.html: -------------------------------------------------------------------------------- 1 | 10 | 11 | 21 | 22 | 30 | 31 | 42 | -------------------------------------------------------------------------------- /01-dbmon/.meteor/versions: -------------------------------------------------------------------------------- 1 | application-configuration@1.0.4 2 | autopublish@1.0.2 3 | autoupdate@1.1.5 4 | base64@1.0.2 5 | binary-heap@1.0.2 6 | blaze@2.0.4 7 | blaze-tools@1.0.2 8 | boilerplate-generator@1.0.2 9 | callback-hook@1.0.2 10 | check@1.0.4 11 | ddp@1.0.14 12 | deps@1.0.6 13 | ejson@1.0.5 14 | fastclick@1.0.2 15 | follower-livedata@1.0.3 16 | geojson-utils@1.0.2 17 | html-tools@1.0.3 18 | htmljs@1.0.3 19 | http@1.0.10 20 | id-map@1.0.2 21 | insecure@1.0.2 22 | jquery@1.11.3 23 | json@1.0.2 24 | launch-screen@1.0.1 25 | livedata@1.0.12 26 | logging@1.0.6 27 | meteor@1.1.4 28 | meteor-platform@1.2.1 29 | minifiers@1.1.3 30 | minimongo@1.0.6 31 | mobile-status-bar@1.0.2 32 | mongo@1.0.11 33 | observe-sequence@1.0.4 34 | ordered-dict@1.0.2 35 | random@1.0.2 36 | reactive-dict@1.0.5 37 | reactive-var@1.0.4 38 | reload@1.1.2 39 | retry@1.0.2 40 | routepolicy@1.0.4 41 | session@1.0.5 42 | spacebars@1.0.5 43 | spacebars-compiler@1.0.4 44 | templating@1.0.11 45 | tracker@1.0.5 46 | ui@1.0.5 47 | underscore@1.0.2 48 | url@1.0.3 49 | webapp@1.1.6 50 | webapp-hashing@1.0.2 51 | -------------------------------------------------------------------------------- /06-itunes-style-interface/client/albums.coffee: -------------------------------------------------------------------------------- 1 | IMAGE_SIZE = 100 2 | IMAGE_MARGIN = 10 3 | 4 | viewportWidth = new ReactiveVar $(window).width() 5 | 6 | Meteor.startup -> 7 | $(window).resize (event) -> 8 | viewportWidth.set $(window).width() 9 | return 10 | 11 | calcAlbumsPerRow = -> 12 | fullWidth = IMAGE_SIZE + (IMAGE_MARGIN * 2) 13 | Math.floor(viewportWidth.get() / fullWidth) 14 | 15 | Template.releases.helpers 16 | rows: -> 17 | albumsPerRow = calcAlbumsPerRow() 18 | 19 | Object.keys(BOB).reduce (rows, key, index) -> 20 | rows.push [] if index % albumsPerRow is 0 21 | rows[rows.length - 1].push id: key, file: BOB[key] 22 | rows 23 | , 24 | [] 25 | 26 | Template.column.helpers 27 | IMAGE_MARGIN: -> 28 | IMAGE_MARGIN 29 | 30 | IMAGE_SIZE: -> 31 | IMAGE_SIZE 32 | 33 | isCurrent: -> 34 | Iron.controller().state.get('id') is @id 35 | 36 | release = (data, fieldName) -> 37 | currentAlbum = _.findWhere data, id: Iron.controller().state.get(fieldName) 38 | return unless currentAlbum 39 | Releases.get currentAlbum.id 40 | 41 | Template.release.helpers 42 | currentRelease: -> 43 | release @, 'id' 44 | 45 | previousRelease: -> 46 | release @, 'oldId' 47 | -------------------------------------------------------------------------------- /06-itunes-style-interface/.meteor/versions: -------------------------------------------------------------------------------- 1 | application-configuration@1.0.4 2 | autopublish@1.0.2 3 | autoupdate@1.1.5 4 | base64@1.0.2 5 | binary-heap@1.0.2 6 | blaze@2.0.4 7 | blaze-tools@1.0.2 8 | boilerplate-generator@1.0.2 9 | callback-hook@1.0.2 10 | check@1.0.4 11 | coffeescript@1.0.5 12 | ddp@1.0.14 13 | deps@1.0.6 14 | ejson@1.0.5 15 | fastclick@1.0.2 16 | follower-livedata@1.0.3 17 | geojson-utils@1.0.2 18 | html-tools@1.0.3 19 | htmljs@1.0.3 20 | http@1.0.10 21 | id-map@1.0.2 22 | insecure@1.0.2 23 | iron:controller@1.0.7 24 | iron:core@1.0.7 25 | iron:dynamic-template@1.0.7 26 | iron:layout@1.0.7 27 | iron:location@1.0.7 28 | iron:middleware-stack@1.0.7 29 | iron:router@1.0.7 30 | iron:url@1.0.7 31 | jquery@1.11.3 32 | json@1.0.2 33 | launch-screen@1.0.1 34 | livedata@1.0.12 35 | logging@1.0.6 36 | meteor@1.1.4 37 | meteor-platform@1.2.1 38 | minifiers@1.1.3 39 | minimongo@1.0.6 40 | mobile-status-bar@1.0.2 41 | mongo@1.0.11 42 | observe-sequence@1.0.4 43 | ordered-dict@1.0.2 44 | random@1.0.2 45 | reactive-dict@1.0.5 46 | reactive-var@1.0.4 47 | reload@1.1.2 48 | retry@1.0.2 49 | routepolicy@1.0.4 50 | session@1.0.5 51 | spacebars@1.0.5 52 | spacebars-compiler@1.0.4 53 | templating@1.0.11 54 | tracker@1.0.5 55 | ui@1.0.5 56 | underscore@1.0.2 57 | url@1.0.3 58 | webapp@1.1.6 59 | webapp-hashing@1.0.2 60 | -------------------------------------------------------------------------------- /06-itunes-style-interface/client/album.coffee: -------------------------------------------------------------------------------- 1 | isOld = (data) -> 2 | oldId = Iron.controller().state.get('oldId') 3 | # Release API result id is a number, so we have to stringify 4 | # it because we are using string IDs everywhere else. 5 | oldId is "#{ data.id }" 6 | 7 | Template.album.created = -> 8 | if isOld @data 9 | # Set the height of the tracklist initially to the "auto" 10 | # so that we can animate it shrinking. 11 | @_height = new ReactiveVar 'auto' 12 | else 13 | # Set the height of the tracklist initially to the "0" 14 | # so that we can animate it growing. 15 | @_height = new ReactiveVar '0px' 16 | 17 | Template.album.rendered = -> 18 | if isOld @data 19 | # After the tracklist has been added to the DOM, set fixed height. 20 | @_height.set "#{ @$('.container').outerHeight() }px" 21 | 22 | # And then shrink it. 23 | Meteor.defer => 24 | @_height.set '0px' 25 | 26 | else 27 | @autorun (computation) => 28 | # Registers a data context dependency. 29 | Template.currentData() 30 | Tracker.afterFlush => 31 | # After the tracklist has been updated in the DOM, 32 | # resize it to the new height with animation. 33 | @_height.set "#{ @$('.container').outerHeight() }px" 34 | 35 | Template.album.helpers 36 | fg: -> 37 | Colors.get(@id)?.fg 38 | 39 | bg: -> 40 | Colors.get(@id)?.bg 41 | 42 | height: -> 43 | Template.instance()._height.get() 44 | -------------------------------------------------------------------------------- /06-itunes-style-interface/lib/BOB.coffee: -------------------------------------------------------------------------------- 1 | @BOB = 2 | 119870: '/R-119870-1353499194-8399.jpeg.jpg' 3 | 227671: '/R-227671-1076173016.bmp.jpg' 4 | 291038: '/R-291038-1095516916.gif.jpg' 5 | 291183: '/R-291183-1096116904.jpg.jpg' 6 | 673587: '/R-673587-1360949274-6329.jpeg.jpg' 7 | 749414: '/R-749414-1329809579.jpeg.jpg' 8 | 1226138: '/R-1226138-1393532248-7305.jpeg.jpg' 9 | 1472338: '/R-1472338-1222265760.jpeg.jpg' 10 | 1563653: '/R-1563653-1228697990.jpeg.jpg' 11 | 1646356: '/R-1646356-1234303215.jpeg.jpg' 12 | 1830898: '/R-1830898-1246317858.jpeg.jpg' 13 | 1978764: '/R-1978764-1274819281.jpeg.jpg' 14 | 2045770: '/R-2045770-1360952357-4411.jpeg.jpg' 15 | 2159557: '/R-2159557-1323270394.jpeg.jpg' 16 | 2366230: '/R-2366230-1282923547.jpeg.jpg' 17 | 2447594: '/R-2447594-1284566470.gif.jpg' 18 | 2709365: '/R-2709365-1297526181.jpeg.jpg' 19 | 4471320: '/R-4471320-1365810323-8252.png.jpg' 20 | 4548894: '/R-4548894-1368047918-8950.jpeg.jpg' 21 | 4928843: '/R-4928843-1379709382-2088.jpeg.jpg' 22 | 5251115: '/R-5251115-1388757674-4136.jpeg.jpg' 23 | 5283708: '/R-5283708-1389551852-3864.jpeg.jpg' 24 | 5354108: '/R-5354108-1391298591-9910.jpeg.jpg' 25 | 5527251: '/R-5527251-1395674311-9055.jpeg.jpg' 26 | 5555750: '/R-5555750-1396442448-5948.jpeg.jpg' 27 | 5669676: '/R-5669676-1419686855-3510.jpeg.jpg' 28 | 5685454: '/R-5685454-1399888274-8456.jpeg.jpg' 29 | 6037721: '/R-6037721-1409466261-6307.jpeg.jpg' 30 | 6347905: '/R-6347905-1417032940-3492.jpeg.jpg' 31 | 6386665: '/R-6386665-1417975610-5551.jpeg.jpg' 32 | 6432244: '/R-6432244-1419282815-8594.jpeg.jpg' 33 | -------------------------------------------------------------------------------- /01-dbmon/client/dbmon.js: -------------------------------------------------------------------------------- 1 | var start = Date.now(); 2 | var loadCount = 0; 3 | 4 | function getData() { 5 | // generate some dummy data 6 | var data = { 7 | start_at: new Date().getTime() / 1000, 8 | databases: {} 9 | }; 10 | 11 | for (var i = 1; i <= ENV.rows; i++) { 12 | data.databases["cluster" + i] = { 13 | queries: [] 14 | }; 15 | 16 | data.databases["cluster" + i + "slave"] = { 17 | queries: [] 18 | }; 19 | } 20 | 21 | Object.keys(data.databases).forEach(function(dbname) { 22 | var info = data.databases[dbname]; 23 | 24 | var r = Math.floor((Math.random() * 10) + 1); 25 | for (var i = 0; i < r; i++) { 26 | var q = { 27 | canvas_action: null, 28 | canvas_context_id: null, 29 | canvas_controller: null, 30 | canvas_hostname: null, 31 | canvas_job_tag: null, 32 | canvas_pid: null, 33 | elapsed: Math.random() * 15, 34 | query: "SELECT blah FROM something", 35 | waiting: Math.random() < 0.5 36 | }; 37 | 38 | if (Math.random() < 0.2) { 39 | q.query = " in transaction"; 40 | } 41 | 42 | if (Math.random() < 0.1) { 43 | q.query = "vacuum"; 44 | } 45 | 46 | info.queries.push(q); 47 | } 48 | 49 | info.queries = info.queries.sort(function(a, b) { 50 | return b.elapsed - a.elapsed; 51 | }); 52 | }); 53 | 54 | return data; 55 | } 56 | 57 | var _base; 58 | 59 | (_base = String.prototype).lpad || (_base.lpad = function(padding, toLength) { 60 | return padding.repeat((toLength - this.length) / padding.length).concat(this); 61 | }); 62 | 63 | Template.query.helpers({ 64 | className: function () { 65 | var className = "elapsed short"; 66 | if (this.elapsed >= 10.0) { 67 | className = "elapsed warn_long"; 68 | } 69 | else if (this.elapsed >= 1.0) { 70 | className = "elapsed warn"; 71 | } 72 | return className; 73 | }, 74 | 75 | formatElapsed: function (value) { 76 | var str = parseFloat(value).toFixed(2); 77 | if (value > 60) { 78 | var minutes = Math.floor(value / 60); 79 | var comps = (value % 60).toFixed(2).split('.'); 80 | var seconds = comps[0].lpad('0', 2); 81 | var ms = comps[1]; 82 | str = minutes + ":" + seconds + "." + ms; 83 | } 84 | return str; 85 | } 86 | }); 87 | 88 | Template.sample.helpers({ 89 | countClassName: function () { 90 | var countClassName = "label"; 91 | if (this.queries.length >= 20) { 92 | countClassName += " label-important"; 93 | } 94 | else if (this.queries.length >= 10) { 95 | countClassName += " label-warning"; 96 | } 97 | else { 98 | countClassName += " label-success"; 99 | } 100 | return countClassName; 101 | }, 102 | 103 | topFiveQueries: function () { 104 | var topFiveQueries = this.queries.slice(0, 5); 105 | while (topFiveQueries.length < 5) { 106 | topFiveQueries.push({ query: "" }); 107 | } 108 | return topFiveQueries; 109 | } 110 | }); 111 | 112 | Template.database.helpers({ 113 | lastSample: function () { 114 | return this.samples[this.samples.length - 1]; 115 | } 116 | }); 117 | 118 | Template.dbmon.created = function () { 119 | this._databases = new ReactiveVar({}); 120 | 121 | this._loadSamples = function () { 122 | var databases = this._databases.get(); 123 | 124 | loadCount++; 125 | var newData = getData(); 126 | 127 | Object.keys(newData.databases).forEach(function(dbname) { 128 | var sampleInfo = newData.databases[dbname]; 129 | 130 | if (!databases[dbname]) { 131 | databases[dbname] = { 132 | name: dbname, 133 | samples: [] 134 | } 135 | } 136 | 137 | var samples = databases[dbname].samples; 138 | samples.push({ 139 | time: newData.start_at, 140 | queries: sampleInfo.queries 141 | }); 142 | if (samples.length > 5) { 143 | samples.splice(0, samples.length - 5); 144 | } 145 | }.bind(this)); 146 | 147 | this._databases.set(databases); 148 | 149 | Meteor.setTimeout(this._loadSamples, ENV.timeout); 150 | }.bind(this); 151 | }; 152 | 153 | Template.dbmon.rendered = function () { 154 | this._loadSamples(); 155 | }; 156 | 157 | Template.dbmon.helpers({ 158 | databases: function () { 159 | var databasesArray = []; 160 | var databases = Template.instance()._databases.get(); 161 | Object.keys(databases).forEach(function(dbname) { 162 | databasesArray.push({ 163 | _id: dbname, 164 | dbname: dbname, 165 | samples: databases[dbname].samples 166 | }); 167 | }); 168 | return databasesArray; 169 | } 170 | }); 171 | -------------------------------------------------------------------------------- /06-itunes-style-interface/lib/color-thief.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Color Thief v2.0 3 | * by Lokesh Dhakar - http://www.lokeshdhakar.com 4 | * 5 | * License 6 | * ------- 7 | * Creative Commons Attribution 2.5 License: 8 | * http://creativecommons.org/licenses/by/2.5/ 9 | * 10 | * Thanks 11 | * ------ 12 | * Nick Rabinowitz - For creating quantize.js. 13 | * John Schulz - For clean up and optimization. @JFSIII 14 | * Nathan Spady - For adding drag and drop support to the demo page. 15 | * 16 | */ 17 | 18 | /* 19 | CanvasImage Class 20 | Class that wraps the html image element and canvas. 21 | It also simplifies some of the canvas context manipulation 22 | with a set of helper functions. 23 | */ 24 | var CanvasImage = function (image) { 25 | this.canvas = document.createElement('canvas'); 26 | this.context = this.canvas.getContext('2d'); 27 | 28 | document.body.appendChild(this.canvas); 29 | 30 | this.width = this.canvas.width = image.width; 31 | this.height = this.canvas.height = image.height; 32 | 33 | this.context.drawImage(image, 0, 0, this.width, this.height); 34 | }; 35 | 36 | CanvasImage.prototype.clear = function () { 37 | this.context.clearRect(0, 0, this.width, this.height); 38 | }; 39 | 40 | CanvasImage.prototype.update = function (imageData) { 41 | this.context.putImageData(imageData, 0, 0); 42 | }; 43 | 44 | CanvasImage.prototype.getPixelCount = function () { 45 | return this.width * this.height; 46 | }; 47 | 48 | CanvasImage.prototype.getImageData = function () { 49 | return this.context.getImageData(0, 0, this.width, this.height); 50 | }; 51 | 52 | CanvasImage.prototype.removeCanvas = function () { 53 | this.canvas.parentNode.removeChild(this.canvas); 54 | }; 55 | 56 | 57 | ColorThief = function () {}; 58 | 59 | /* 60 | * getColor(sourceImage[, quality]) 61 | * returns {r: num, g: num, b: num} 62 | * 63 | * Use the median cut algorithm provided by quantize.js to cluster similar 64 | * colors and return the base color from the largest cluster. 65 | * 66 | * Quality is an optional argument. It needs to be an integer. 0 is the highest quality settings. 67 | * 10 is the default. There is a trade-off between quality and speed. The bigger the number, the 68 | * faster a color will be returned but the greater the likelihood that it will not be the visually 69 | * most dominant color. 70 | * 71 | * */ 72 | ColorThief.prototype.getColor = function(sourceImage, quality) { 73 | var palette = this.getPalette(sourceImage, 5, quality); 74 | var dominantColor = palette[0]; 75 | return dominantColor; 76 | }; 77 | 78 | 79 | /* 80 | * getPalette(sourceImage[, colorCount, quality]) 81 | * returns array[ {r: num, g: num, b: num}, {r: num, g: num, b: num}, ...] 82 | * 83 | * Use the median cut algorithm provided by quantize.js to cluster similar colors. 84 | * 85 | * colorCount determines the size of the palette; the number of colors returned. If not set, it 86 | * defaults to 10. 87 | * 88 | * BUGGY: Function does not always return the requested amount of colors. It can be +/- 2. 89 | * 90 | * quality is an optional argument. It needs to be an integer. 0 is the highest quality settings. 91 | * 10 is the default. There is a trade-off between quality and speed. The bigger the number, the 92 | * faster the palette generation but the greater the likelihood that colors will be missed. 93 | * 94 | * 95 | */ 96 | ColorThief.prototype.getPalette = function(sourceImage, colorCount, quality) { 97 | 98 | if (typeof colorCount === 'undefined') { 99 | colorCount = 10; 100 | } 101 | if (typeof quality === 'undefined') { 102 | quality = 10; 103 | } 104 | 105 | // Create custom CanvasImage object 106 | var image = new CanvasImage(sourceImage); 107 | var imageData = image.getImageData(); 108 | var pixels = imageData.data; 109 | var pixelCount = image.getPixelCount(); 110 | 111 | // Store the RGB values in an array format suitable for quantize function 112 | var pixelArray = []; 113 | for (var i = 0, offset, r, g, b, a; i < pixelCount; i = i + quality) { 114 | offset = i * 4; 115 | r = pixels[offset + 0]; 116 | g = pixels[offset + 1]; 117 | b = pixels[offset + 2]; 118 | a = pixels[offset + 3]; 119 | // If pixel is mostly opaque and not white 120 | if (a >= 125) { 121 | if (!(r > 250 && g > 250 && b > 250)) { 122 | pixelArray.push([r, g, b]); 123 | } 124 | } 125 | } 126 | 127 | // Send array to quantize function which clusters values 128 | // using median cut algorithm 129 | var cmap = MMCQ.quantize(pixelArray, colorCount); 130 | var palette = cmap.palette(); 131 | 132 | // Clean up 133 | image.removeCanvas(); 134 | 135 | return palette; 136 | }; 137 | 138 | 139 | 140 | 141 | /*! 142 | * quantize.js Copyright 2008 Nick Rabinowitz. 143 | * Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php 144 | */ 145 | 146 | // fill out a couple protovis dependencies 147 | /*! 148 | * Block below copied from Protovis: http://mbostock.github.com/protovis/ 149 | * Copyright 2010 Stanford Visualization Group 150 | * Licensed under the BSD License: http://www.opensource.org/licenses/bsd-license.php 151 | */ 152 | if (!pv) { 153 | var pv = { 154 | map: function(array, f) { 155 | var o = {}; 156 | return f ? array.map(function(d, i) { o.index = i; return f.call(o, d); }) : array.slice(); 157 | }, 158 | naturalOrder: function(a, b) { 159 | return (a < b) ? -1 : ((a > b) ? 1 : 0); 160 | }, 161 | sum: function(array, f) { 162 | var o = {}; 163 | return array.reduce(f ? function(p, d, i) { o.index = i; return p + f.call(o, d); } : function(p, d) { return p + d; }, 0); 164 | }, 165 | max: function(array, f) { 166 | return Math.max.apply(null, f ? pv.map(array, f) : array); 167 | } 168 | }; 169 | } 170 | 171 | 172 | 173 | /** 174 | * Basic Javascript port of the MMCQ (modified median cut quantization) 175 | * algorithm from the Leptonica library (http://www.leptonica.com/). 176 | * Returns a color map you can use to map original pixels to the reduced 177 | * palette. Still a work in progress. 178 | * 179 | * @author Nick Rabinowitz 180 | * @example 181 | 182 | // array of pixels as [R,G,B] arrays 183 | var myPixels = [[190,197,190], [202,204,200], [207,214,210], [211,214,211], [205,207,207] 184 | // etc 185 | ]; 186 | var maxColors = 4; 187 | 188 | var cmap = MMCQ.quantize(myPixels, maxColors); 189 | var newPalette = cmap.palette(); 190 | var newPixels = myPixels.map(function(p) { 191 | return cmap.map(p); 192 | }); 193 | 194 | */ 195 | var MMCQ = (function() { 196 | // private constants 197 | var sigbits = 5, 198 | rshift = 8 - sigbits, 199 | maxIterations = 1000, 200 | fractByPopulations = 0.75; 201 | 202 | // get reduced-space color index for a pixel 203 | function getColorIndex(r, g, b) { 204 | return (r << (2 * sigbits)) + (g << sigbits) + b; 205 | } 206 | 207 | // Simple priority queue 208 | function PQueue(comparator) { 209 | var contents = [], 210 | sorted = false; 211 | 212 | function sort() { 213 | contents.sort(comparator); 214 | sorted = true; 215 | } 216 | 217 | return { 218 | push: function(o) { 219 | contents.push(o); 220 | sorted = false; 221 | }, 222 | peek: function(index) { 223 | if (!sorted) sort(); 224 | if (index===undefined) index = contents.length - 1; 225 | return contents[index]; 226 | }, 227 | pop: function() { 228 | if (!sorted) sort(); 229 | return contents.pop(); 230 | }, 231 | size: function() { 232 | return contents.length; 233 | }, 234 | map: function(f) { 235 | return contents.map(f); 236 | }, 237 | debug: function() { 238 | if (!sorted) sort(); 239 | return contents; 240 | } 241 | }; 242 | } 243 | 244 | // 3d color space box 245 | function VBox(r1, r2, g1, g2, b1, b2, histo) { 246 | var vbox = this; 247 | vbox.r1 = r1; 248 | vbox.r2 = r2; 249 | vbox.g1 = g1; 250 | vbox.g2 = g2; 251 | vbox.b1 = b1; 252 | vbox.b2 = b2; 253 | vbox.histo = histo; 254 | } 255 | VBox.prototype = { 256 | volume: function(force) { 257 | var vbox = this; 258 | if (!vbox._volume || force) { 259 | vbox._volume = ((vbox.r2 - vbox.r1 + 1) * (vbox.g2 - vbox.g1 + 1) * (vbox.b2 - vbox.b1 + 1)); 260 | } 261 | return vbox._volume; 262 | }, 263 | count: function(force) { 264 | var vbox = this, 265 | histo = vbox.histo; 266 | if (!vbox._count_set || force) { 267 | var npix = 0, 268 | i, j, k; 269 | for (i = vbox.r1; i <= vbox.r2; i++) { 270 | for (j = vbox.g1; j <= vbox.g2; j++) { 271 | for (k = vbox.b1; k <= vbox.b2; k++) { 272 | index = getColorIndex(i,j,k); 273 | npix += (histo[index] || 0); 274 | } 275 | } 276 | } 277 | vbox._count = npix; 278 | vbox._count_set = true; 279 | } 280 | return vbox._count; 281 | }, 282 | copy: function() { 283 | var vbox = this; 284 | return new VBox(vbox.r1, vbox.r2, vbox.g1, vbox.g2, vbox.b1, vbox.b2, vbox.histo); 285 | }, 286 | avg: function(force) { 287 | var vbox = this, 288 | histo = vbox.histo; 289 | if (!vbox._avg || force) { 290 | var ntot = 0, 291 | mult = 1 << (8 - sigbits), 292 | rsum = 0, 293 | gsum = 0, 294 | bsum = 0, 295 | hval, 296 | i, j, k, histoindex; 297 | for (i = vbox.r1; i <= vbox.r2; i++) { 298 | for (j = vbox.g1; j <= vbox.g2; j++) { 299 | for (k = vbox.b1; k <= vbox.b2; k++) { 300 | histoindex = getColorIndex(i,j,k); 301 | hval = histo[histoindex] || 0; 302 | ntot += hval; 303 | rsum += (hval * (i + 0.5) * mult); 304 | gsum += (hval * (j + 0.5) * mult); 305 | bsum += (hval * (k + 0.5) * mult); 306 | } 307 | } 308 | } 309 | if (ntot) { 310 | vbox._avg = [~~(rsum/ntot), ~~(gsum/ntot), ~~(bsum/ntot)]; 311 | } else { 312 | // console.log('empty box'); 313 | vbox._avg = [ 314 | ~~(mult * (vbox.r1 + vbox.r2 + 1) / 2), 315 | ~~(mult * (vbox.g1 + vbox.g2 + 1) / 2), 316 | ~~(mult * (vbox.b1 + vbox.b2 + 1) / 2) 317 | ]; 318 | } 319 | } 320 | return vbox._avg; 321 | }, 322 | contains: function(pixel) { 323 | var vbox = this, 324 | rval = pixel[0] >> rshift; 325 | gval = pixel[1] >> rshift; 326 | bval = pixel[2] >> rshift; 327 | return (rval >= vbox.r1 && rval <= vbox.r2 && 328 | gval >= vbox.g1 && gval <= vbox.g2 && 329 | bval >= vbox.b1 && bval <= vbox.b2); 330 | } 331 | }; 332 | 333 | // Color map 334 | function CMap() { 335 | this.vboxes = new PQueue(function(a,b) { 336 | return pv.naturalOrder( 337 | a.vbox.count()*a.vbox.volume(), 338 | b.vbox.count()*b.vbox.volume() 339 | ); 340 | }); 341 | } 342 | CMap.prototype = { 343 | push: function(vbox) { 344 | this.vboxes.push({ 345 | vbox: vbox, 346 | color: vbox.avg() 347 | }); 348 | }, 349 | palette: function() { 350 | return this.vboxes.map(function(vb) { return vb.color; }); 351 | }, 352 | size: function() { 353 | return this.vboxes.size(); 354 | }, 355 | map: function(color) { 356 | var vboxes = this.vboxes; 357 | for (var i=0; i 251 391 | var idx = vboxes.length-1, 392 | highest = vboxes[idx].color; 393 | if (highest[0] > 251 && highest[1] > 251 && highest[2] > 251) 394 | vboxes[idx].color = [255,255,255]; 395 | } 396 | }; 397 | 398 | // histo (1-d array, giving the number of pixels in 399 | // each quantized region of color space), or null on error 400 | function getHisto(pixels) { 401 | var histosize = 1 << (3 * sigbits), 402 | histo = new Array(histosize), 403 | index, rval, gval, bval; 404 | pixels.forEach(function(pixel) { 405 | rval = pixel[0] >> rshift; 406 | gval = pixel[1] >> rshift; 407 | bval = pixel[2] >> rshift; 408 | index = getColorIndex(rval, gval, bval); 409 | histo[index] = (histo[index] || 0) + 1; 410 | }); 411 | return histo; 412 | } 413 | 414 | function vboxFromPixels(pixels, histo) { 415 | var rmin=1000000, rmax=0, 416 | gmin=1000000, gmax=0, 417 | bmin=1000000, bmax=0, 418 | rval, gval, bval; 419 | // find min/max 420 | pixels.forEach(function(pixel) { 421 | rval = pixel[0] >> rshift; 422 | gval = pixel[1] >> rshift; 423 | bval = pixel[2] >> rshift; 424 | if (rval < rmin) rmin = rval; 425 | else if (rval > rmax) rmax = rval; 426 | if (gval < gmin) gmin = gval; 427 | else if (gval > gmax) gmax = gval; 428 | if (bval < bmin) bmin = bval; 429 | else if (bval > bmax) bmax = bval; 430 | }); 431 | return new VBox(rmin, rmax, gmin, gmax, bmin, bmax, histo); 432 | } 433 | 434 | function medianCutApply(histo, vbox) { 435 | if (!vbox.count()) return; 436 | 437 | var rw = vbox.r2 - vbox.r1 + 1, 438 | gw = vbox.g2 - vbox.g1 + 1, 439 | bw = vbox.b2 - vbox.b1 + 1, 440 | maxw = pv.max([rw, gw, bw]); 441 | // only one pixel, no split 442 | if (vbox.count() == 1) { 443 | return [vbox.copy()]; 444 | } 445 | /* Find the partial sum arrays along the selected axis. */ 446 | var total = 0, 447 | partialsum = [], 448 | lookaheadsum = [], 449 | i, j, k, sum, index; 450 | if (maxw == rw) { 451 | for (i = vbox.r1; i <= vbox.r2; i++) { 452 | sum = 0; 453 | for (j = vbox.g1; j <= vbox.g2; j++) { 454 | for (k = vbox.b1; k <= vbox.b2; k++) { 455 | index = getColorIndex(i,j,k); 456 | sum += (histo[index] || 0); 457 | } 458 | } 459 | total += sum; 460 | partialsum[i] = total; 461 | } 462 | } 463 | else if (maxw == gw) { 464 | for (i = vbox.g1; i <= vbox.g2; i++) { 465 | sum = 0; 466 | for (j = vbox.r1; j <= vbox.r2; j++) { 467 | for (k = vbox.b1; k <= vbox.b2; k++) { 468 | index = getColorIndex(j,i,k); 469 | sum += (histo[index] || 0); 470 | } 471 | } 472 | total += sum; 473 | partialsum[i] = total; 474 | } 475 | } 476 | else { /* maxw == bw */ 477 | for (i = vbox.b1; i <= vbox.b2; i++) { 478 | sum = 0; 479 | for (j = vbox.r1; j <= vbox.r2; j++) { 480 | for (k = vbox.g1; k <= vbox.g2; k++) { 481 | index = getColorIndex(j,k,i); 482 | sum += (histo[index] || 0); 483 | } 484 | } 485 | total += sum; 486 | partialsum[i] = total; 487 | } 488 | } 489 | partialsum.forEach(function(d,i) { 490 | lookaheadsum[i] = total-d; 491 | }); 492 | function doCut(color) { 493 | var dim1 = color + '1', 494 | dim2 = color + '2', 495 | left, right, vbox1, vbox2, d2, count2=0; 496 | for (i = vbox[dim1]; i <= vbox[dim2]; i++) { 497 | if (partialsum[i] > total / 2) { 498 | vbox1 = vbox.copy(); 499 | vbox2 = vbox.copy(); 500 | left = i - vbox[dim1]; 501 | right = vbox[dim2] - i; 502 | if (left <= right) 503 | d2 = Math.min(vbox[dim2] - 1, ~~(i + right / 2)); 504 | else d2 = Math.max(vbox[dim1], ~~(i - 1 - left / 2)); 505 | // avoid 0-count boxes 506 | while (!partialsum[d2]) d2++; 507 | count2 = lookaheadsum[d2]; 508 | while (!count2 && partialsum[d2-1]) count2 = lookaheadsum[--d2]; 509 | // set dimensions 510 | vbox1[dim2] = d2; 511 | vbox2[dim1] = vbox1[dim2] + 1; 512 | // console.log('vbox counts:', vbox.count(), vbox1.count(), vbox2.count()); 513 | return [vbox1, vbox2]; 514 | } 515 | } 516 | 517 | } 518 | // determine the cut planes 519 | return maxw == rw ? doCut('r') : 520 | maxw == gw ? doCut('g') : 521 | doCut('b'); 522 | } 523 | 524 | function quantize(pixels, maxcolors) { 525 | // short-circuit 526 | if (!pixels.length || maxcolors < 2 || maxcolors > 256) { 527 | // console.log('wrong number of maxcolors'); 528 | return false; 529 | } 530 | 531 | // XXX: check color content and convert to grayscale if insufficient 532 | 533 | var histo = getHisto(pixels), 534 | histosize = 1 << (3 * sigbits); 535 | 536 | // check that we aren't below maxcolors already 537 | var nColors = 0; 538 | histo.forEach(function() { nColors++; }); 539 | if (nColors <= maxcolors) { 540 | // XXX: generate the new colors from the histo and return 541 | } 542 | 543 | // get the beginning vbox from the colors 544 | var vbox = vboxFromPixels(pixels, histo), 545 | pq = new PQueue(function(a,b) { return pv.naturalOrder(a.count(), b.count()); }); 546 | pq.push(vbox); 547 | 548 | // inner function to do the iteration 549 | function iter(lh, target) { 550 | var ncolors = 1, 551 | niters = 0, 552 | vbox; 553 | while (niters < maxIterations) { 554 | vbox = lh.pop(); 555 | if (!vbox.count()) { /* just put it back */ 556 | lh.push(vbox); 557 | niters++; 558 | continue; 559 | } 560 | // do the cut 561 | var vboxes = medianCutApply(histo, vbox), 562 | vbox1 = vboxes[0], 563 | vbox2 = vboxes[1]; 564 | 565 | if (!vbox1) { 566 | // console.log("vbox1 not defined; shouldn't happen!"); 567 | return; 568 | } 569 | lh.push(vbox1); 570 | if (vbox2) { /* vbox2 can be null */ 571 | lh.push(vbox2); 572 | ncolors++; 573 | } 574 | if (ncolors >= target) return; 575 | if (niters++ > maxIterations) { 576 | // console.log("infinite loop; perhaps too few pixels!"); 577 | return; 578 | } 579 | } 580 | } 581 | 582 | // first set of colors, sorted by population 583 | iter(pq, fractByPopulations * maxcolors); 584 | 585 | // Re-sort by the product of pixel occupancy times the size in color space. 586 | var pq2 = new PQueue(function(a,b) { 587 | return pv.naturalOrder(a.count()*a.volume(), b.count()*b.volume()); 588 | }); 589 | while (pq.size()) { 590 | pq2.push(pq.pop()); 591 | } 592 | 593 | // next set - generate the median cuts using the (npix * vol) sorting. 594 | iter(pq2, maxcolors - pq2.size()); 595 | 596 | // calculate the actual colors 597 | var cmap = new CMap(); 598 | while (pq2.size()) { 599 | cmap.push(pq2.pop()); 600 | } 601 | 602 | return cmap; 603 | } 604 | 605 | return { 606 | quantize: quantize 607 | }; 608 | })(); 609 | --------------------------------------------------------------------------------