├── .gitignore ├── config └── twitter_reader.rb ├── lib ├── viewer │ ├── README.md │ ├── package.json │ ├── server_modules │ │ ├── options.coffee │ │ └── mock.coffee │ ├── static │ │ ├── streamgraph.html │ │ ├── viewer.css │ │ ├── index.html │ │ ├── streamgraph_viewer.js │ │ ├── viewer.js │ │ ├── colorsets.css │ │ └── colorsets.json │ ├── server.coffee │ └── server.js └── tweitgeist │ ├── storm │ ├── extract_message_bolt.rb │ ├── extract_hashtags_bolt.rb │ ├── rolling_count_bolt.rb │ ├── twitter_stream_spout.rb │ ├── rank_bolt.rb │ ├── merge_bolt.rb │ └── tweitgeist_topology.rb │ ├── twitter │ ├── twitter_stream.rb │ └── twitter_reader.rb │ └── rolling_counter.rb ├── spec ├── spec_helper.rb └── tweitgeist │ └── rolling_counter_spec.rb ├── Rakefile ├── CHANGELOG.md ├── Gemfile └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | examples/ 3 | lib/viewer/node_modules/ 4 | .rvmrc 5 | storm 6 | ivy 7 | .bundle 8 | Gemfile.lock 9 | -------------------------------------------------------------------------------- /config/twitter_reader.rb: -------------------------------------------------------------------------------- 1 | module Tweitgeist 2 | CONFIG = { 3 | :twitter_user => "USER", 4 | :twitter_pwd => "PWD", 5 | } 6 | end -------------------------------------------------------------------------------- /lib/viewer/README.md: -------------------------------------------------------------------------------- 1 | Defaults: 2 | 3 | node server.js 4 | 5 | W/ Params: 6 | 7 | node server --port 6000 --host locahost --redis-port 1234 --redis-host 127.0.0.1 -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | $:.unshift File.dirname(__FILE__) + '/../' 2 | $:.unshift File.dirname(__FILE__) + '/../lib/' 3 | $:.unshift File.dirname(__FILE__) + '/../spec' 4 | 5 | require 'rspec' 6 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'bundler/setup' 2 | require 'rake' 3 | require 'rspec/core/rake_task' 4 | 5 | task :default => :spec 6 | 7 | task :spec do 8 | RSpec::Core::RakeTask.new 9 | end 10 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 1.0.0, 04-19-2012 2 | - initial release 3 | 4 | # 1.1.0, 06-06-2012 5 | - added project Gemfile 6 | - update to RedStorm 0.5.2 7 | 8 | # 1.2.0, 10-17-2012 9 | - update to RedStorm 0.6.4 -------------------------------------------------------------------------------- /lib/tweitgeist/storm/extract_message_bolt.rb: -------------------------------------------------------------------------------- 1 | require 'json' 2 | 3 | module Tweitgeist 4 | class ExtractMessageBolt < RedStorm::SimpleBolt 5 | 6 | on_receive do |tuple| 7 | json = tuple.getString(0) 8 | JSON.parse(json)["text"] 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /lib/tweitgeist/storm/extract_hashtags_bolt.rb: -------------------------------------------------------------------------------- 1 | require 'json' 2 | require 'twitter-text' 3 | 4 | module Tweitgeist 5 | class ExtractHashtagsBolt < RedStorm::SimpleBolt 6 | include Twitter::Extractor 7 | 8 | on_receive do |tuple| 9 | hashtags = extract_hashtags(tuple.getString(0)).select{|h| h.size > 3}.map{|h| ["##{h.upcase}"]} 10 | hashtags.empty? ? nil : hashtags 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source :rubygems 2 | 3 | platform :jruby do 4 | gem 'redstorm', '~> 0.6.4' 5 | end 6 | 7 | platform :mri do 8 | gem 'twitter-stream', '~> 0.1.16' 9 | gem 'redis', '~> 3.0.2' 10 | gem 'hiredis', '~> 0.4.5' 11 | end 12 | 13 | group :test do 14 | gem 'rake' 15 | gem 'rspec', '~> 2.11.0' 16 | end 17 | 18 | group :topology do 19 | gem 'redis', '~> 3.0.2', :platforms => :jruby 20 | gem 'json', :platforms => :jruby 21 | gem 'twitter-text', '~> 1.5.0', :platform => :jruby 22 | end -------------------------------------------------------------------------------- /lib/tweitgeist/storm/rolling_count_bolt.rb: -------------------------------------------------------------------------------- 1 | require 'json' 2 | require 'lib/tweitgeist/rolling_counter' 3 | 4 | module Tweitgeist 5 | class RollingCountBolt < RedStorm::SimpleBolt 6 | on_init do 7 | # 30 buckets of 10 seconds 8 | @counter = RollingCounter.new(60, 10) {|hashtag, count| unanchored_emit(hashtag, count)} 9 | end 10 | 11 | on_receive do |tuple| 12 | hashtag = tuple.getString(0) 13 | count = @counter.add(hashtag) 14 | [hashtag, count] 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /lib/viewer/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tweitgeist-viewer", 3 | "preferGlobal": "false", 4 | "version": "0.1.2", 5 | "author": "Francois Lafortune ", 6 | "description": "Tweitgeist demo viewer", 7 | "contributors": [ 8 | { 9 | "name": "Colin Surprenant", 10 | "email": "colin.surprenant@gmail.com" 11 | }, 12 | { 13 | "name": "Nicholas Brochu", 14 | "email": "info@nicholasbrochu.com" 15 | } 16 | ], 17 | "main": "server.js", 18 | "dependencies" : { 19 | "redis" : "0.7.x", 20 | "express" : "2.5.x" 21 | }, 22 | "license": "Apache 2.0", 23 | "engine": { 24 | "node": ">=0.4" 25 | } 26 | } -------------------------------------------------------------------------------- /lib/viewer/server_modules/options.coffee: -------------------------------------------------------------------------------- 1 | # defaults 2 | port = 80 3 | host = "127.0.0.1" 4 | redis_host = "127.0.0.1" 5 | redis_port = 6379 6 | mock = false 7 | 8 | # parse args 9 | exports.parse = (argv) -> 10 | argv.forEach (val, index, params) -> 11 | port = parseFloat(params[index + 1]) if /^\-\-port$/.test(val) 12 | host = String(params[index + 1]).trim() if /^\-\-host$/.test(val) 13 | redis_port = parseFloat(params[index + 1]) if /^\-\-redis-port$/.test(val) 14 | redis_host = String(params[index + 1]).trim() if /^\-\-redis-host$/.test(val) 15 | mock = true if /\-\-mock|\-m/.test(val) 16 | return port: port, host: host, redis_port: redis_port, redis_host: redis_host, mock: mock -------------------------------------------------------------------------------- /lib/tweitgeist/storm/twitter_stream_spout.rb: -------------------------------------------------------------------------------- 1 | require 'redis' 2 | require 'thread' 3 | 4 | module Tweitgeist 5 | 6 | class TwitterStreamSpout < RedStorm::SimpleSpout 7 | on_send {@q.pop if @q.size > 0} 8 | 9 | on_init do 10 | @q = Queue.new 11 | @redis_reader = detach_redis_reader 12 | end 13 | 14 | private 15 | 16 | def detach_redis_reader 17 | Thread.new do 18 | Thread.current.abort_on_exception = true 19 | 20 | redis = Redis.new(:host => "localhost", :port => 6379) 21 | loop do 22 | if data = redis.blpop("twitter_stream", 0) 23 | @q << data[1] 24 | end 25 | end 26 | end 27 | end 28 | 29 | end 30 | end -------------------------------------------------------------------------------- /lib/tweitgeist/storm/rank_bolt.rb: -------------------------------------------------------------------------------- 1 | require 'thread' 2 | 3 | module Tweitgeist 4 | class RankBolt < RedStorm::SimpleBolt 5 | TOP_N = 10 6 | FLUSH_INTERVAL = 2 7 | 8 | on_init do 9 | @rankings = Hash.new 10 | @last = Time.now.to_i 11 | @rankings_lock = Mutex.new 12 | @flusher = detach_flusher 13 | end 14 | 15 | on_receive :emit => false do |tuple| 16 | hashtag, count = tuple.getString(0), tuple.getLong(1) 17 | @rankings_lock.synchronize {@rankings[hashtag] = count} 18 | end 19 | 20 | private 21 | 22 | def detach_flusher 23 | Thread.new do 24 | Thread.current.abort_on_exception = true 25 | sleep(FLUSH_INTERVAL) 26 | 27 | loop do 28 | sorted = nil 29 | @rankings_lock.synchronize do 30 | sorted = @rankings.sort{|a, b| b[1] <=> a[1]} # decreasing order on count 31 | @rankings.delete(sorted.pop[0]) while sorted.size > TOP_N 32 | end 33 | unanchored_emit(sorted.to_json) unless sorted.empty? 34 | 35 | sleep(FLUSH_INTERVAL) 36 | end 37 | end 38 | end 39 | 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /lib/viewer/server_modules/mock.coffee: -------------------------------------------------------------------------------- 1 | semirandom = ( min, mod )-> Math.floor Math.random()*mod+min 2 | exports.lpop = ( arg, callback )-> 3 | # note: these numbers mean nothing and are completely arbitrary! 4 | trends = [ 5 | [ "#ICEWEASELS", semirandom( 123, 50 ) ] 6 | [ "#ACE", semirandom( 98, 50 ) ] 7 | [ "#BELARUS", semirandom( 87, 50 ) ] 8 | [ "#CHUMBUKIT", semirandom( 76, 50 ) ] 9 | [ "#CANHAZ", semirandom( 65, 50 ) ] 10 | [ "#YARR", semirandom( 54, 50 ) ] 11 | [ "#YANILIVE", semirandom( 43, 50 ) ] 12 | [ "#NONBELIEBER", semirandom( 32, 50 ) ] 13 | [ "#YMMV", semirandom( 32, 50 ) ] 14 | [ "#DEADBEEF", semirandom( 32, 50 ) ] 15 | ] 16 | randoms = [ "#THEDUDE", "#COWABUNGA", "#WOWSERS" , "#SHINJUKU" , "#NEEDIUM", "#MYFACE", "#BORING" ] 17 | onOccasion = ( Math.floor( Math.random()*2 )%2 is 1 ) 18 | if onOccasion 19 | randidx = Math.floor Math.random()*randoms.length 20 | trendidx = Math.floor Math.random()*trends.length 21 | trends[ trendidx ][0] = randoms[randidx] 22 | callback( null, trends ) 23 | return trends 24 | -------------------------------------------------------------------------------- /lib/tweitgeist/storm/merge_bolt.rb: -------------------------------------------------------------------------------- 1 | require 'redis' 2 | 3 | module Tweitgeist 4 | class MergeBolt < RedStorm::SimpleBolt 5 | TOP_N = 10 6 | PUSH_INTERVAL = 5 7 | HISTORY_SIZE = (24*60*60)/PUSH_INTERVAL # 24h 8 | 9 | on_init do 10 | @rankings = Hash.new 11 | @last_time = Time.now.to_i 12 | @redis = Redis.new(:host => "localhost", :port => 6379) 13 | end 14 | 15 | on_receive :emit => false do |tuple| 16 | # tuple is [[hashtag1, count1], [hashtag2, count2], ...] 17 | update = Hash[*JSON.parse(tuple.getString(0)).flatten] 18 | 19 | @rankings.merge!(update) 20 | sorted = @rankings.sort{|a, b| b[1] <=> a[1]} # decreasing order on count 21 | @rankings.delete(sorted.pop[0]) while sorted.size > TOP_N 22 | 23 | # poor's man delayed push, ok since we receive 'infrequent' tuples 24 | if (now = Time.now.to_i) > (@last_time + PUSH_INTERVAL) 25 | @last_time = now 26 | unless sorted.empty? 27 | @redis.pipelined do 28 | @redis.rpush('rankings', sorted.to_json) 29 | 30 | @redis.lpush('past_rankings', {'created_at' => now, 'rankings' => sorted}.to_json) 31 | @redis.ltrim('past_rankings', 0, HISTORY_SIZE - 1) 32 | end 33 | end 34 | end 35 | end 36 | 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /lib/tweitgeist/storm/tweitgeist_topology.rb: -------------------------------------------------------------------------------- 1 | require 'red_storm' 2 | 3 | require 'lib/tweitgeist/storm/twitter_stream_spout' 4 | require 'lib/tweitgeist/storm/extract_message_bolt' 5 | require 'lib/tweitgeist/storm/extract_hashtags_bolt' 6 | require 'lib/tweitgeist/storm/rolling_count_bolt' 7 | require 'lib/tweitgeist/storm/rank_bolt' 8 | require 'lib/tweitgeist/storm/merge_bolt' 9 | 10 | module Tweitgeist 11 | 12 | class TweitgeistTopology < RedStorm::SimpleTopology 13 | spout TwitterStreamSpout do 14 | output_fields :tweet 15 | end 16 | 17 | bolt ExtractMessageBolt, :parallelism => 3 do 18 | source TwitterStreamSpout, :shuffle 19 | output_fields :message 20 | end 21 | 22 | bolt ExtractHashtagsBolt, :parallelism => 3 do 23 | source ExtractMessageBolt, :shuffle 24 | output_fields :hashtag 25 | end 26 | 27 | bolt RollingCountBolt, :parallelism => 3 do 28 | source ExtractHashtagsBolt, :fields => ["hashtag"] 29 | output_fields :hashtag, :count 30 | end 31 | 32 | bolt RankBolt, :parallelism => 3 do 33 | source RollingCountBolt, :fields => ["hashtag"] 34 | output_fields :json_rankings 35 | end 36 | 37 | bolt MergeBolt, :parallelism => 1 do 38 | source RankBolt, :global 39 | output_fields :json_rankings 40 | end 41 | 42 | configure do |env| 43 | debug false 44 | case env 45 | when :local 46 | max_task_parallelism 10 47 | when :cluster 48 | num_workers 20 49 | max_spout_pending 5000 50 | end 51 | end 52 | end 53 | end -------------------------------------------------------------------------------- /lib/viewer/static/streamgraph.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Tweitgeist 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 29 | 30 |
31 |

Hover over an area to view Hashtag

32 |
33 |
34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /lib/viewer/server.coffee: -------------------------------------------------------------------------------- 1 | express = require 'express' 2 | redis = require 'redis' 3 | fs = require 'fs' 4 | config = require('./server_modules/options.coffee').parse(process.argv) 5 | mock = require './server_modules/mock.coffee' 6 | client = if config.mock then mock else redis.createClient(config.redis_port, config.redis_host) 7 | info = JSON.parse fs.readFileSync 'package.json' 8 | server = express.createServer() 9 | 10 | # multi user fix™ 11 | last_pop = null 12 | last_rate = 0 13 | pop_interval = 1000 14 | 15 | # all environments 16 | server.configure -> 17 | server.use express.methodOverride() 18 | server.use express.bodyParser() 19 | server.use server.router 20 | 21 | # development 22 | server.configure "development", -> 23 | server.use express["static"](__dirname + "/static") 24 | server.use express.errorHandler( 25 | dumpExceptions: true 26 | showStack: true 27 | ) 28 | 29 | # production 30 | server.configure "production", -> 31 | server.use express["static"](__dirname + "/static", 32 | maxAge: oneYear 33 | ) 34 | server.use express.errorHandler() 35 | 36 | server.get "/rankings.json", (req, res, next) -> 37 | res.json last_pop 38 | 39 | server.get "/stats.json", (req, res, next) -> 40 | res.json "{\"connections\":" + server.connections + ",\"stream_rate\":" + last_rate + "}" 41 | 42 | poll_rankings = () -> 43 | client.lpop "rankings", (error, data) -> 44 | if error then return console.log error 45 | else if data 46 | last_pop = if typeof data is 'string' then JSON.parse(data) else data 47 | setTimeout poll_rankings, pop_interval 48 | 49 | poll_rankings() 50 | 51 | poll_stream_rate = ()-> 52 | client.lpop "stream_rate", (error, data) -> 53 | if error then return console.log error 54 | else if data then last_rate = data 55 | setTimeout poll_stream_rate, 2000 56 | 57 | poll_stream_rate() 58 | 59 | server.listen config.port, config.host, -> 60 | console.log( 61 | "\ntweitgeist node server v#{info.version}\n" + 62 | "\nstarted listening on host #{config.host} port #{config.port}" 63 | ) 64 | if config.mock 65 | console.log "using mock data" 66 | else 67 | console.log "using redis on host #{config.redis_host} port #{config.redis_port}" -------------------------------------------------------------------------------- /lib/viewer/static/viewer.css: -------------------------------------------------------------------------------- 1 | html{background-image: -moz-linear-gradient(-90deg, #333 , #000 ); 2 | background-image: -webkit-gradient(linear, 0 top, 0 bottom, from( #333 ), to( #000 )); 3 | height:100%; 4 | } 5 | body{padding-top:60px;background:none;height:100%;margin-bottom:-60px;} 6 | body > .container{ 7 | height:600px;position:relative; /* multiple of 100 will avoid %->px rounding errors */ 8 | } 9 | .container .bar{ 10 | background-image: -moz-linear-gradient(-90deg, #fff , #000 ); 11 | background-image: -webkit-gradient(linear, 0 top, 0 bottom, from( #fff ), to( #000 )); 12 | padding:0;margin:0; 13 | height:100%;width:20%; 14 | display:block; 15 | float:left; 16 | overflow:hidden; 17 | } 18 | .container .cell{ 19 | opacity:0.8;overflow:hidden; 20 | text-align:center; 21 | white-space:nowrap; 22 | position:relative; 23 | } 24 | .cell .counter{ 25 | position:absolute; 26 | top:2px; 27 | left:3px; 28 | font-size:11px; 29 | } 30 | .cell .helper{ 31 | display:inline; 32 | position:relative; 33 | top:8px; 34 | left:20px; 35 | padding:2px; 36 | font-size:12px; 37 | font-weight:bold; 38 | -webkit-border-radius: 6px; 39 | -moz-border-radius: 6px; 40 | border-radius: 6px; 41 | } 42 | .container .bar{ 43 | -webkit-box-shadow: 0 0 6px #000; 44 | -moz-box-shadow: 0 0 6px #000; 45 | -o-box-shadow: 0 0 6px #000; 46 | box-shadow: 0 0 6px #000; 47 | } 48 | .container .bar:first-of-type, .container .bar:first-of-type .cell:first-of-type{ 49 | -webkit-border-top-left-radius: 10px; 50 | -moz-border-top-left-radius: 10px; 51 | border-top-left-radius: 10px; 52 | } 53 | .container .bar:first-of-type, .container .bar:first-of-type .cell:last-of-type{ 54 | -webkit-border-bottom-left-radius: 10px; 55 | -moz-border-bottom-left-radius: 10px; 56 | border-bottom-left-radius: 10px; 57 | 58 | } 59 | .container .bar:last-of-type, .container .bar:last-of-type .cell:first-of-type{ 60 | -webkit-border-top-right-radius: 10px; 61 | -moz-border-top-right-radius: 10px; 62 | border-top-right-radius: 10px; 63 | } 64 | .container .bar:last-of-type, .container .bar:last-of-type .cell:last-of-type{ 65 | -webkit-border-bottom-right-radius: 10px; 66 | -moz-border-bottom-right-radius: 10px; 67 | border-bottom-right-radius: 10px; 68 | } 69 | 70 | path:hover { 71 | opacity:0.95; 72 | } 73 | -------------------------------------------------------------------------------- /lib/tweitgeist/twitter/twitter_stream.rb: -------------------------------------------------------------------------------- 1 | require 'twitter/json_stream' 2 | require 'eventmachine' 3 | 4 | module Tweitgeist 5 | 6 | class TwitterStream 7 | 8 | def initialize(options = {}) 9 | @options = options 10 | 11 | # default empty handlers 12 | @on_item = lambda{|item|} 13 | @on_close = lambda{} 14 | @on_error = lambda{|message|} 15 | @on_failure = lambda{|message|} 16 | @on_reconnect= lambda{|timeout, retries|} 17 | @stream = nil 18 | end 19 | 20 | def on_item(&block) 21 | @on_item = lambda do |item| 22 | begin 23 | block.call(item) 24 | rescue 25 | stop 26 | @on_failure.call("on_item exception #{$!.message}, #{$!.backtrace.join("\n")}") 27 | end 28 | end 29 | end 30 | 31 | def on_close(&block) 32 | @on_close = lambda do 33 | begin 34 | block.call 35 | rescue 36 | stop 37 | @on_failure.call("on_close exception=#{$!.inspect}\n#{$!.backtrace.join("\n")}") 38 | end 39 | end 40 | end 41 | 42 | def on_error(&block) 43 | @on_error = lambda do |message| 44 | begin 45 | block.call(message) 46 | rescue 47 | stop 48 | @on_failure.call("on_error exception=#{$!.inspect}\n#{$!.backtrace.join("\n")}") 49 | end 50 | end 51 | end 52 | 53 | def on_reconnect(&block) 54 | @on_reconnect = lambda do |timeout, retries| 55 | begin 56 | block.call(timeout, retries) 57 | rescue 58 | stop 59 | @on_failure.call("on_item exception=#{$!.inspect}\n#{$!.backtrace.join("\n")}") 60 | end 61 | end 62 | end 63 | 64 | def on_failure(&block) 65 | @on_failure = lambda do |message| 66 | begin 67 | block.call(message) 68 | rescue 69 | stop 70 | puts("on_failure exception=#{$!.inspect}\n#{$!.backtrace.join("\n")}") 71 | end 72 | end 73 | end 74 | 75 | def stop 76 | EventMachine.stop_event_loop if EventMachine.reactor_running? 77 | end 78 | 79 | def run 80 | EventMachine.run do 81 | @stream = Twitter::JSONStream.connect(@options) 82 | 83 | # attach callbacks to EM stream 84 | @stream.each_item(&@on_item) 85 | @stream.on_close(&@on_close) 86 | @stream.on_error(&@on_error) 87 | @stream.on_reconnect(&@on_reconnect) 88 | @stream.on_max_reconnects{|timeout, retries| @on_failure.call("failed after max reconnect=#{retries.to_s} using timeout=#{timeout.to_s}")} 89 | end 90 | end 91 | 92 | end 93 | 94 | end 95 | -------------------------------------------------------------------------------- /lib/tweitgeist/twitter/twitter_reader.rb: -------------------------------------------------------------------------------- 1 | $:.unshift File.dirname(__FILE__) + '/../../../' 2 | 3 | require 'bundler/setup' 4 | require 'redis' 5 | require 'thread' 6 | require 'lib/tweitgeist/twitter/twitter_stream' 7 | require 'config/twitter_reader' 8 | 9 | module Tweitgeist 10 | 11 | class TwitterReader 12 | attr_accessor :config 13 | 14 | STATS_INTERVAL = 10 15 | 16 | def initialize 17 | @redis = Redis.new(:host => "localhost", :port => 6379, :driver => :hiredis) 18 | @tweets = Queue.new 19 | @tweets_count = 0 20 | @tweets_count_mutex = Mutex.new 21 | @flusher = detach_flusher 22 | end 23 | 24 | def start 25 | stream = TwitterStream.new(:path => '/1/statuses/sample.json', :auth => "#{CONFIG[:twitter_user]}:#{CONFIG[:twitter_pwd]}") 26 | 27 | puts("TwitterReader starting") 28 | 29 | i = 0 30 | stream.on_item do |item| 31 | @tweets << item 32 | @tweets_count_mutex.synchronize {@tweets_count += 50} if (i += 1) % 50 == 0 33 | end 34 | 35 | stream.on_error {|message| puts("stream error=#{message}")} 36 | stream.on_failure {|message| puts("stream failure=#{message}")} 37 | stream.on_reconnect {|timeout, retries| puts("stream reconnect timeout=#{timeout}, retries=#{retries}")} 38 | 39 | puts("opening stream connection") 40 | begin 41 | stream.run 42 | ensure 43 | puts("closing stream connection") 44 | stream.stop 45 | end 46 | end 47 | 48 | private 49 | 50 | def detach_flusher 51 | Thread.new do 52 | Thread.current.abort_on_exception = true 53 | 54 | previous_tweets_count = @tweets_count_mutex.synchronize {@tweets_count} 55 | stats_start = Time.now.to_i 56 | loop do 57 | sleep(1) 58 | 59 | if (size = @tweets.size) > 0 60 | @redis.pipelined do 61 | size.times.each {@redis.rpush("twitter_stream", @tweets.pop)} 62 | end 63 | end 64 | 65 | if (elapsed = (Time.now.to_i - stats_start)) >= STATS_INTERVAL 66 | tweets_count = @tweets_count_mutex.synchronize {@tweets_count} 67 | rate = (tweets_count - previous_tweets_count) / elapsed 68 | previous_tweets_count = tweets_count 69 | stats_start = Time.now.to_i 70 | @redis.rpush("stream_rate", rate) 71 | 72 | puts("twitter reader: rate=#{rate}/s, queue size=#{size}, tweets count=#{tweets_count}") 73 | end 74 | end 75 | end 76 | end 77 | 78 | end 79 | end 80 | 81 | loop do 82 | begin 83 | Tweitgeist::TwitterReader.new.start 84 | rescue 85 | puts("TwitterReader exception=##{$!.inspect}") 86 | end 87 | end -------------------------------------------------------------------------------- /lib/viewer/server.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 1.3.1 2 | (function() { 3 | var client, express, host, last_pop, last_rate, poll_rankings, poll_stream_rate, pop_interval, port, redis, redis_host, redis_port, server; 4 | 5 | port = 80; 6 | 7 | host = "127.0.0.1"; 8 | 9 | redis_host = "127.0.0.1"; 10 | 11 | redis_port = 6379; 12 | 13 | last_pop = null; 14 | 15 | last_rate = 0; 16 | 17 | pop_interval = 1000; 18 | 19 | process.argv.forEach(function(val, index, params) { 20 | if (/\-\-port/.test(val)) { 21 | port = parseFloat(params[index + 1]); 22 | } 23 | if (/\-\-host/.test(val)) { 24 | host = String(params[index + 1]).trim(); 25 | } 26 | if (/\-\-redis-port/.test(val)) { 27 | redis_port = parseFloat(params[index + 1]); 28 | } 29 | if (/\-\-redis-host/.test(val)) { 30 | return redis_host = String(params[index + 1]).trim(); 31 | } 32 | }); 33 | 34 | express = require("express"); 35 | 36 | redis = require("redis"); 37 | 38 | client = redis.createClient(redis_port, redis_host); 39 | 40 | server = express.createServer(); 41 | 42 | server.configure(function() { 43 | server.use(express.methodOverride()); 44 | server.use(express.bodyParser()); 45 | return server.use(server.router); 46 | }); 47 | 48 | server.configure("development", function() { 49 | server.use(express["static"](__dirname + "/static")); 50 | return server.use(express.errorHandler({ 51 | dumpExceptions: true, 52 | showStack: true 53 | })); 54 | }); 55 | 56 | server.configure("production", function() { 57 | server.use(express["static"](__dirname + "/static", { 58 | maxAge: oneYear 59 | })); 60 | return server.use(express.errorHandler()); 61 | }); 62 | 63 | server.get("/rankings.json", function(req, res, next) { 64 | return res.json(last_pop); 65 | }); 66 | 67 | server.get("/stats.json", function(req, res, next) { 68 | return res.json("{\"connections\":" + server.connections + ",\"stream_rate\":" + last_rate + "}"); 69 | }); 70 | 71 | poll_rankings = function() { 72 | return client.lpop("rankings", function(error, data) { 73 | if (error) { 74 | return console.log(error); 75 | } else if (data) { 76 | last_pop = data; 77 | } 78 | return setTimeout(poll_rankings, pop_interval); 79 | }); 80 | }; 81 | 82 | poll_rankings(); 83 | 84 | poll_stream_rate = function() { 85 | return client.lpop("stream_rate", function(error, data) { 86 | if (error) { 87 | return console.log(error); 88 | } else if (data) { 89 | last_rate = data; 90 | } 91 | return setTimeout(poll_stream_rate, 2000); 92 | }); 93 | }; 94 | 95 | poll_stream_rate(); 96 | 97 | server.listen(port, host); 98 | 99 | console.log("Started listening on " + host + ":" + port + " /w redis connection " + redis_host + ":" + redis_port); 100 | 101 | }).call(this); 102 | -------------------------------------------------------------------------------- /lib/tweitgeist/rolling_counter.rb: -------------------------------------------------------------------------------- 1 | require 'thread' 2 | 3 | module Tweitgeist 4 | class RollingCounter 5 | attr_writer :active_bucket 6 | 7 | # @param bucket_count [Fixnum] buckets ring size 8 | # @param bucket_seconds [Fixnum] bucket timeout in seconds 9 | # @param options [Hash] options 10 | # @option options [Boolean] :cleaner => false to disable automatic bucket expiration 11 | # @yield [Object, Fixnum] call block upon bucket expiration with updated count for key 12 | def initialize(bucket_count, bucket_seconds, options = {}, &on_clean) 13 | @bucket_count = bucket_count 14 | @bucket_seconds = bucket_seconds 15 | @on_clean = on_clean 16 | @cleaner_thread = detach_cleaner if options[:cleaner] != false 17 | @counter = Hash.new{|h, k| h[k] = Array.new(bucket_count, 0)} 18 | @counter_lock = Mutex.new 19 | @active_bucket = nil 20 | end 21 | 22 | # @param key [Object] increment count by 1 for given key 23 | # @return [Fixnum] return the total count for given key 24 | def add(key) 25 | @counter_lock.synchronize do 26 | @counter[key][active_bucket] += 1 27 | @counter[key].reduce(:+) 28 | end 29 | end 30 | 31 | # @param key [Object] key for required count 32 | # @return [Fixnum] return the total count for given key 33 | def count(key) 34 | @counter_lock.synchronize do 35 | @counter[key].reduce(:+) 36 | end 37 | end 38 | 39 | # zero bucket for all keys, delete useless keys, fire callbacks for any changed key 40 | # @param clean_bucket [Fixnum] bucket number to clean 41 | def clean(clean_bucket) 42 | callbacks = [] 43 | zeroed = [] 44 | 45 | @counter_lock.synchronize do 46 | @counter.each do |key, buckets| 47 | saved_count = buckets[clean_bucket] 48 | buckets[clean_bucket] = 0 49 | total = buckets.reduce(:+) 50 | callbacks << [key, total] unless saved_count.zero? 51 | zeroed << key if total.zero? 52 | end 53 | 54 | # delete keys outside the hash iteration 55 | zeroed.each{|key| @counter.delete(key)} 56 | end 57 | 58 | # execute callbacks outside synchronize block 59 | callbacks.each{|key, total| @on_clean.call(key, total) if @on_clean} 60 | end 61 | 62 | # @return [Fixnum] return set active_bucket or calc from current time 63 | def active_bucket 64 | @active_bucket || (Time.now.to_i / @bucket_seconds) % @bucket_count 65 | end 66 | 67 | private 68 | 69 | def detach_cleaner 70 | Thread.new do 71 | Thread.current.abort_on_exception = true 72 | 73 | last_bucket = active_bucket 74 | 75 | loop do 76 | if (current_bucket = active_bucket) != last_bucket 77 | next_bucket = (current_bucket + 1) % @bucket_count 78 | clean(next_bucket) 79 | last_bucket = current_bucket 80 | end 81 | 82 | sleep(1) 83 | end 84 | end 85 | end 86 | 87 | 88 | end 89 | end -------------------------------------------------------------------------------- /lib/viewer/static/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Tweitgeist 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 19 | 20 | 21 | 22 | 23 | 24 | 34 | 35 | 36 | 37 | 38 | 52 | 53 |
54 |

Live Top 10 Trending Hashtags on Twitter

55 |
56 | 57 | Fork me on GitHub 58 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | -------------------------------------------------------------------------------- /lib/viewer/static/streamgraph_viewer.js: -------------------------------------------------------------------------------- 1 | var pollstate = 0; 2 | var polldelay = 2500; 3 | 4 | var vizData = []; 5 | var vizDataMap = {}; 6 | 7 | var currentStreamgraphData = []; 8 | var newStreamgraphData = []; 9 | 10 | var width = 950; 11 | var height = 600; 12 | var mx = 199; 13 | var my = 0; 14 | 15 | var color = d3.interpolateRgb("#009fff", "#fff"); 16 | var colors = []; 17 | 18 | var hashtags = []; 19 | 20 | var decorate = function( json ) { 21 | var decorated = json.reduce( function( mod, pair ) { 22 | var tag = pair[0]; 23 | var score = pair[1]; 24 | mod[tag] = { tag: tag, score: score }; 25 | return mod; 26 | }, {} ); 27 | 28 | return decorated; 29 | }; 30 | 31 | var render = function( json ) { 32 | Object(vizData).forEach( function (data){ 33 | data.shift(); 34 | 35 | Object(data).forEach(function(item, i) { 36 | item["x"] = i; 37 | }); 38 | 39 | data.push({x: 199, y: 0}); 40 | }) 41 | 42 | Object.keys( json ).forEach( function ( key ) { 43 | if (typeof vizDataMap[key] == "undefined") { 44 | vizDataMap[key] = vizData.length; 45 | colors.push(color(Math.random())); 46 | hashtags.push(key); 47 | } 48 | 49 | if (typeof vizData[vizDataMap[key]] == "undefined") { 50 | vizData[vizDataMap[key]] = []; 51 | 52 | i = 0; 53 | for (i = 0; i <= (mx - 1); i = i+1) { 54 | vizData[vizDataMap[key]][i] = {x: i, y: 0}; 55 | } 56 | 57 | vizData[vizDataMap[key]].push({x: 199, y: json[key]["score"]}); 58 | } 59 | else { 60 | vizData[vizDataMap[key]][199] = {x: 199, y: json[key]["score"]}; 61 | } 62 | }); 63 | 64 | newStreamgraphData = d3.layout.stack().offset("wiggle")(vizData); 65 | 66 | my = d3.max(newStreamgraphData, function(d) { 67 | return d3.max(d, function(d) { 68 | return d.y0 + d.y; 69 | }); 70 | }); 71 | 72 | area = d3.svg.area() 73 | .x(function(d) { return d.x * width / mx; }) 74 | .y0(function(d) { return height - d.y0 * height / my; }) 75 | .y1(function(d) { return height - (d.y + d.y0) * height / my; }); 76 | 77 | d3.select("#graph").remove(); 78 | 79 | vis = d3.select("#viewer") 80 | .append("svg") 81 | .attr("width", width) 82 | .attr("height", height) 83 | .attr("id", "graph"); 84 | 85 | vis.selectAll("path") 86 | .data(newStreamgraphData) 87 | .enter().append("path") 88 | .style("fill", function(item,i) { 89 | return colors[i]; 90 | }) 91 | .attr("d", area) 92 | .attr("class", "svg_tooltip") 93 | .attr("title", function(item,i) { 94 | return hashtags[i]; 95 | }); 96 | 97 | $('.svg_tooltip').hover(function () { 98 | $("#hashtag_anchor").html($(this).attr("title")); 99 | }); 100 | }; 101 | 102 | var poll = function() { 103 | if( pollstate === 0){ 104 | pollstate = 1 105 | var fetch = $.getJSON( 'rankings.json' ); 106 | fetch.fail( function( error ){ 107 | throw "Could not fetch rankings"; 108 | }); 109 | fetch.done( function( json) { 110 | if( json ){ 111 | var decorated = decorate( json ) 112 | render( decorated ); 113 | } 114 | }); 115 | fetch.always( function() { 116 | pollstate = 0 117 | setTimeout( poll, polldelay ); 118 | }); 119 | }else{ 120 | setTimeout( poll, polldelay ); 121 | }; 122 | }; 123 | 124 | $(function() { poll() }); -------------------------------------------------------------------------------- /spec/tweitgeist/rolling_counter_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'tweitgeist/rolling_counter' 3 | 4 | describe Tweitgeist::RollingCounter do 5 | 6 | it "should initialize" do 7 | rc = Tweitgeist::RollingCounter.new(3, 10) 8 | rc = Tweitgeist::RollingCounter.new(3, 10, {:cleaner => false}) 9 | rc = Tweitgeist::RollingCounter.new(3, 10, {:cleaner => false}) {puts("hello")} 10 | rc = Tweitgeist::RollingCounter.new(3, 10) {puts("hello")} 11 | end 12 | 13 | it "should add with single bucket" do 14 | rc = Tweitgeist::RollingCounter.new(3, 10, {:cleaner => false}) 15 | rc.active_bucket = 0 16 | 17 | rc.count("foo").should == 0 18 | rc.add("foo").should == 1 19 | rc.add("foo").should == 2 20 | 21 | rc.count("bar").should == 0 22 | rc.add("bar").should == 1 23 | rc.add("bar").should == 2 24 | end 25 | 26 | it "should add with multiple buckets" do 27 | rc = Tweitgeist::RollingCounter.new(3, 10, {:cleaner => false}) 28 | 29 | rc.active_bucket = 0 30 | rc.count("foo").should == 0 31 | rc.add("foo").should == 1 32 | rc.add("foo").should == 2 33 | 34 | rc.count("bar").should == 0 35 | rc.add("bar").should == 1 36 | rc.add("bar").should == 2 37 | 38 | rc.active_bucket = 1 39 | rc.count("foo").should == 2 40 | rc.add("foo").should == 3 41 | rc.add("foo").should == 4 42 | 43 | rc.count("bar").should == 2 44 | rc.add("bar").should == 3 45 | rc.add("bar").should == 4 46 | end 47 | 48 | it "should clean bucket" do 49 | rc = Tweitgeist::RollingCounter.new(3, 10, {:cleaner => false}) 50 | 51 | rc.active_bucket = 0 52 | rc.count("foo").should == 0 53 | rc.add("foo").should == 1 54 | rc.add("foo").should == 2 55 | 56 | rc.count("bar").should == 0 57 | rc.add("bar").should == 1 58 | rc.add("bar").should == 2 59 | 60 | rc.active_bucket = 1 61 | rc.count("foo").should == 2 62 | rc.add("foo").should == 3 63 | rc.add("foo").should == 4 64 | 65 | rc.count("bar").should == 2 66 | rc.add("bar").should == 3 67 | rc.add("bar").should == 4 68 | 69 | rc.active_bucket = 2 70 | rc.count("foo").should == 4 71 | rc.add("foo").should == 5 72 | rc.add("foo").should == 6 73 | 74 | rc.count("bar").should == 4 75 | rc.add("bar").should == 5 76 | rc.add("bar").should == 6 77 | 78 | rc.clean(0) 79 | rc.count("foo").should == 4 80 | rc.count("bar").should == 4 81 | 82 | rc.clean(1) 83 | rc.count("foo").should == 2 84 | rc.count("bar").should == 2 85 | 86 | rc.clean(2) 87 | rc.count("foo").should == 0 88 | rc.count("bar").should == 0 89 | end 90 | 91 | it "should call on_clean" do 92 | cleaned = [] 93 | rc = Tweitgeist::RollingCounter.new(3, 10, {:cleaner => false}) {|key, total| cleaned << [key, total]} 94 | 95 | rc.active_bucket = 0 96 | rc.add("foo").should == 1 97 | rc.add("foo").should == 2 98 | rc.add("bar").should == 1 99 | rc.add("bar").should == 2 100 | rc.clean(0) 101 | cleaned.should == [["foo", 0], ["bar", 0]] 102 | 103 | cleaned = [] 104 | rc.active_bucket = 0 105 | rc.add("foo").should == 1 106 | rc.add("foo").should == 2 107 | rc.add("bar").should == 1 108 | rc.add("bar").should == 2 109 | 110 | rc.active_bucket = 1 111 | rc.add("foo").should == 3 112 | rc.add("foo").should == 4 113 | rc.add("bar").should == 3 114 | rc.add("bar").should == 4 115 | 116 | rc.clean(0) 117 | cleaned.should == [["foo", 2], ["bar", 2]] 118 | end 119 | 120 | end 121 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Tweitgeist v1.2.0 2 | 3 | Tweitgeist analyses the Twitter Spitzer hose and compute in realtime the top trending hashtags using [RedStorm](https://github.com/colinsurprenant/redstorm)/[Storm](https://github.com/nathanmarz/storm). What makes this interesting other than being a cool Storm example, is the fact that this architecture will work at **full Twitter Firehose scale** without much modifications. 4 | 5 | - See the [slideshare presentation](http://www.slideshare.net/colinsurprenant/twitter-big-data) about Twitter Big Data and Tweitgeist. 6 | - See the live demo on [http://tweitgeist.colinsurprenant.com/](http://tweitgeist.colinsurprenant.com/) 7 | 8 | There are three components: 9 | 10 | - The Twitter Spitzer stream reader which pushes messages in a Redis queue 11 | - The Redstorm analyser which read the Twitter stream queue, computes the trending hashtags and output the top N list every 5 seconds in a Redis queue 12 | - The viewer UI for the visualization 13 | 14 | ## Dependencies 15 | 16 | This has been tested on OSX 10.6+, Linux 11.10 & 12.04 using JRuby 1.6.x for the RedStorm topology and Ruby 1.9.x for the Twitter Spitzer hose reader. 17 | 18 | ## Installation 19 | 20 | - [Redis](http://redis.io/) is required 21 | - [RVM](http://beginrescueend.com/) is highly recommended as you will need to work with both Ruby/JRuby and different gemsets. 22 | 23 | ### Redstorm backend 24 | 25 | - requires JRuby 1.6.x 26 | 27 | - set JRuby in 1.9 mode by default 28 | 29 | ``` sh 30 | export JRUBY_OPTS=--1.9 31 | ``` 32 | 33 | - install the RedStorm gem using bundler with the supplied Gemfile 34 | 35 | ``` sh 36 | $ bundle install 37 | ``` 38 | 39 | - run RedStorm installation 40 | 41 | ``` sh 42 | $ bundle exec redstorm install 43 | ``` 44 | 45 | - package the topology required gems 46 | 47 | ``` sh 48 | $ bundle exec redstorm bundle topology 49 | ``` 50 | 51 | - if you plan on running the topology on a cluster, package the topology jar 52 | 53 | ``` sh 54 | bundle exec redstorm jar lib/tweitgeist/ 55 | ``` 56 | 57 | ### Twitter Spitzer stream reader 58 | 59 | - requires Ruby 1.9.x 60 | 61 | - install required gems using bundler with the supplied Gemfile 62 | 63 | ``` sh 64 | $ bundle install 65 | ``` 66 | 67 | ### Viewer 68 | 69 | - requires Node.js 70 | 71 | ``` sh 72 | $ sudo apt-get install nodejs 73 | ``` 74 | 75 | - requires npm 76 | 77 | ``` sh 78 | $ sudo apt-get install npm 79 | ``` 80 | 81 | - install CoffeeScript if you want to modify the Node.js server 82 | 83 | ``` sh 84 | $ npm install -g coffee-script 85 | ``` 86 | 87 | - install other dependencies 88 | 89 | ``` sh 90 | $ cd lib/viewer 91 | $ npm install . 92 | ``` 93 | 94 | ## Usage overview 95 | 96 | ### Redstorm backend 97 | 98 | - requires JRuby 1.6.x 99 | 100 | - set JRuby in 1.9 mode by default 101 | 102 | ``` sh 103 | export JRUBY_OPTS=--1.9 104 | ``` 105 | 106 | #### RedStorm backend in **local** mode. 107 | 108 | ``` sh 109 | $ bundle exec redstorm local lib/tweitgeist/storm/tweitgeist_topology.rb 110 | ``` 111 | 112 | #### RedStorm backend in **remote cluster** mode. 113 | 114 | - add your cluster info to `~/.storm/storm.yaml` see [setting up a Storm development environment](https://github.com/nathanmarz/storm/wiki/Setting-up-development-environment) 115 | 116 | - make sure your locally installed storm distribution `bin/` directory is in your $PATH 117 | 118 | ``` sh 119 | $ bundle exec redstorm cluster lib/tweitgeist/storm/tweitgeist_topology.rb 120 | ``` 121 | 122 | 123 | ### Twitter Spitzer stream reader 124 | 125 | - requires Ruby 1.9.x 126 | 127 | - edit `config/twitter_reader.rb` to add your credentials 128 | 129 | ``` sh 130 | $ ruby lib/tweitgeist/twitter/twitter_reader.rb 131 | ``` 132 | 133 | ### Viewer 134 | 135 | ``` sh 136 | $ coffee server.coffee --port 8080 --host 127.0.0.1 --redis-port 6379 --redis-host 127.0.0.1 137 | ``` 138 | 139 | or (with simulated data in case of no redis) 140 | 141 | ``` sh 142 | $ coffee server.coffee --port 8080 --host 127.0.0.1 --mock 143 | ``` 144 | 145 | 146 | ## Author 147 | Colin Surprenant, [@colinsurprenant][twitter], [https://github.com/colinsurprenant][github], colin.surprenant@gmail.com 148 | 149 | ## Contributors 150 | Francois Lafortune, [@quickredfox](http://twitter.com/quickredfox), [https://github.com/quickredfox](http://github.com/quickredfox), code@quickredfox.at 151 | 152 | Nicholas Brochu, [@nbrochu](http://twitter.com/nbrochu), [https://github.com/nbrochu](http://github.com/nbrochu), info@nicholasbrochu.com 153 | 154 | ## License 155 | Tweitgeist is distributed under the Apache License, Version 2.0. 156 | 157 | [twitter]: http://twitter.com/colinsurprenant 158 | [github]: https://github.com/colinsurprenant 159 | -------------------------------------------------------------------------------- /lib/viewer/static/viewer.js: -------------------------------------------------------------------------------- 1 | $.holdReady(true); 2 | 3 | var colorsets = null 4 | , pollstate = 0 5 | , polldelay = 3000 6 | , mappedColors = {} 7 | , fetchcolorsets = $.getJSON('colorsets.json'); 8 | 9 | fetchcolorsets.fail(function() { 10 | throw "Cannot load essential data."; 11 | }); 12 | 13 | fetchcolorsets.done(function(json) { 14 | colorsets = json; 15 | return $.holdReady(false); 16 | }); 17 | 18 | var getMappingForString = function(string) { 19 | if (!mappedColors[string]) { 20 | mappedColors[string] = colorsets.map[colorsets.hex[Math.floor(Math.random() * colorsets.hex.length)]]; 21 | } 22 | return mappedColors[string]; 23 | }; 24 | 25 | var decorate = function( json ) { 26 | // pre calculate real total of all counts 27 | var real_total = 0; 28 | json.reduce( function( mod, pair ) { 29 | real_total += pair[1]; 30 | }, {} ); 31 | 32 | // create tag hashmap and assign percentage of real_total to each items 33 | var decorated = json.reduce( function( mod, pair ) { 34 | var tag = pair[0]; 35 | var score = pair[1]; 36 | var percent = (score*100) / real_total; 37 | mod[tag] = { tag: tag, score: score, percent: percent}; 38 | return mod; 39 | }, {} ); 40 | 41 | // fine tune each item percentage to make sure each item are not less 42 | // than 5% and reduce percentage of all other bigger items. repeat 43 | // until we are within [99%..101%] 44 | var min_percent = 4; 45 | var new_total = 0; 46 | var small_count = 0; 47 | do { 48 | new_total = 0; 49 | small_count = 0; 50 | 51 | Object.keys( decorated ).forEach( function ( key ) { 52 | var item = decorated[key]; 53 | if (item.percent < min_percent) { 54 | small_count += 1; 55 | item.percent = min_percent; 56 | } 57 | new_total += item.percent; 58 | 59 | decorated[key] = item; 60 | }); 61 | 62 | if (new_total > 100) { 63 | var big_count = 10 - small_count; 64 | var skew = (new_total - 100) / big_count 65 | 66 | new_total = 0 67 | Object.keys( decorated ).forEach( function ( key ) { 68 | var item = decorated[key]; 69 | if (item.percent >= min_percent + skew) { 70 | item.percent -= skew; 71 | } 72 | new_total += item.percent; 73 | decorated[key] = item; 74 | }); 75 | } 76 | } while (Math.round(new_total) < 99 || Math.round(new_total) > 101); 77 | 78 | // to avoid %->px rounding errors, round all percents 79 | // and make sure total is exactly 100 by padding the first element 80 | // which should be the biggest (unless they are all equals) 81 | new_total = 0 82 | Object.keys( decorated ).forEach( function ( key ) { 83 | var item = decorated[key]; 84 | item.percent = Math.round(item.percent); 85 | new_total += item.percent; 86 | decorated[key] = item; 87 | }); 88 | var first_key = Object.keys(decorated)[0]; 89 | var item = decorated[first_key]; 90 | item.percent = item.percent + (100 - new_total); 91 | decorated[first_key] = item; 92 | 93 | // finally set height and mapping on each items 94 | Object.keys( decorated ).forEach( function ( key ) { 95 | var item = decorated[key]; 96 | item.height = item.percent; 97 | item.mapping = getMappingForString( key ); 98 | decorated[key] = item; 99 | }); 100 | 101 | return decorated; 102 | }; 103 | 104 | var render = function( json ) { 105 | var tags = Object.keys( json ); 106 | var $bar = $( '
').addClass('bar').appendTo('#viewer'); 107 | tags.forEach( function(tag) { 108 | var data = json[tag]; 109 | var $cell = $('
').addClass( 'cell ' + data.mapping.selector ); 110 | var $helper = $('').attr({ 111 | "class": "helper", 112 | "href" : 'https://twitter.com/#!/search/realtime/' + encodeURIComponent(tag), 113 | "title": tag 114 | }).text( tag ) 115 | var $counter = $('
').addClass('counter').text( data.score ); 116 | $cell.data(data).css('height', data.height+'%' ); 117 | $cell.append( $counter ); 118 | $cell.append( $helper ); 119 | $cell.appendTo( $bar ); 120 | }); 121 | }; 122 | 123 | var cleanup = function(argument) { 124 | var bars = $('.bar') 125 | if( bars.length > 5 ){ 126 | var n = bars.length-5 127 | $('.bar:lt('+n+')').remove() 128 | }; 129 | }; 130 | 131 | var poll = function() { 132 | if( pollstate === 0){ 133 | pollstate = 1 134 | var fetch = $.getJSON( 'rankings.json' ); 135 | fetch.fail( function( error ){ 136 | throw "Could not fetch rankings"; 137 | }); 138 | fetch.done( function( json) { 139 | if( json ){ 140 | var decorated = decorate( json ) 141 | render( decorated ); 142 | cleanup() 143 | } 144 | }); 145 | fetch.always( function() { 146 | pollstate = 0 147 | setTimeout( poll, polldelay ); 148 | }); 149 | }else{ 150 | setTimeout( poll, polldelay ); 151 | }; 152 | }; 153 | 154 | $(function() { poll() }); 155 | $('#reset-colors').on( 'click', function() { mappedColors = {}; }); 156 | 157 | -------------------------------------------------------------------------------- /lib/viewer/static/colorsets.css: -------------------------------------------------------------------------------- 1 | 2 | .colorset-000033{ background:#000033;color:#FFFFFF} .colorset-000033 .helper{ background:rgba(0,0,0,0.4);color:#FFFFFF} .colorset-000033 .helper a{ color:#FFFFFF} 3 | .colorset-000066{ background:#000066;color:#FFFFFF} .colorset-000066 .helper{ background:rgba(0,0,0,0.4);color:#FFFFFF} .colorset-000066 .helper a{ color:#FFFFFF} 4 | .colorset-000099{ background:#000099;color:#FFFFFF} .colorset-000099 .helper{ background:rgba(0,0,0,0.4);color:#FFFFFF} .colorset-000099 .helper a{ color:#FFFFFF} 5 | .colorset-0000CC{ background:#0000CC;color:#FFFFFF} .colorset-0000CC .helper{ background:rgba(0,0,0,0.4);color:#FFFFFF} .colorset-0000CC .helper a{ color:#FFFFFF} 6 | .colorset-0000FF{ background:#0000FF;color:#FFFFFF} .colorset-0000FF .helper{ background:rgba(0,0,0,0.4);color:#FFFFFF} .colorset-0000FF .helper a{ color:#FFFFFF} 7 | .colorset-003333{ background:#003333;color:#FFFFFF} .colorset-003333 .helper{ background:rgba(0,0,0,0.4);color:#FFFFFF} .colorset-003333 .helper a{ color:#FFFFFF} 8 | .colorset-003366{ background:#003366;color:#FFFFFF} .colorset-003366 .helper{ background:rgba(0,0,0,0.4);color:#FFFFFF} .colorset-003366 .helper a{ color:#FFFFFF} 9 | .colorset-003399{ background:#003399;color:#FFFFFF} .colorset-003399 .helper{ background:rgba(0,0,0,0.4);color:#FFFFFF} .colorset-003399 .helper a{ color:#FFFFFF} 10 | .colorset-0033CC{ background:#0033CC;color:#FFFFFF} .colorset-0033CC .helper{ background:rgba(0,0,0,0.4);color:#FFFFFF} .colorset-0033CC .helper a{ color:#FFFFFF} 11 | .colorset-0033FF{ background:#0033FF;color:#FFFFFF} .colorset-0033FF .helper{ background:rgba(0,0,0,0.4);color:#FFFFFF} .colorset-0033FF .helper a{ color:#FFFFFF} 12 | .colorset-006633{ background:#006633;color:#FFFFFF} .colorset-006633 .helper{ background:rgba(0,0,0,0.4);color:#FFFFFF} .colorset-006633 .helper a{ color:#FFFFFF} 13 | .colorset-006666{ background:#006666;color:#FFFFFF} .colorset-006666 .helper{ background:rgba(0,0,0,0.4);color:#FFFFFF} .colorset-006666 .helper a{ color:#FFFFFF} 14 | .colorset-006699{ background:#006699;color:#FFFFFF} .colorset-006699 .helper{ background:rgba(0,0,0,0.4);color:#FFFFFF} .colorset-006699 .helper a{ color:#FFFFFF} 15 | .colorset-0066CC{ background:#0066CC;color:#FFFFFF} .colorset-0066CC .helper{ background:rgba(0,0,0,0.4);color:#FFFFFF} .colorset-0066CC .helper a{ color:#FFFFFF} 16 | .colorset-0066FF{ background:#0066FF;color:#FFFFFF} .colorset-0066FF .helper{ background:rgba(0,0,0,0.4);color:#FFFFFF} .colorset-0066FF .helper a{ color:#FFFFFF} 17 | .colorset-009933{ background:#009933;color:#FFFFFF} .colorset-009933 .helper{ background:rgba(0,0,0,0.4);color:#FFFFFF} .colorset-009933 .helper a{ color:#FFFFFF} 18 | .colorset-009966{ background:#009966;color:#FFFFFF} .colorset-009966 .helper{ background:rgba(0,0,0,0.4);color:#FFFFFF} .colorset-009966 .helper a{ color:#FFFFFF} 19 | .colorset-009999{ background:#009999;color:#FFFFFF} .colorset-009999 .helper{ background:rgba(0,0,0,0.4);color:#FFFFFF} .colorset-009999 .helper a{ color:#FFFFFF} 20 | .colorset-0099CC{ background:#0099CC;color:#FFFFFF} .colorset-0099CC .helper{ background:rgba(0,0,0,0.4);color:#FFFFFF} .colorset-0099CC .helper a{ color:#FFFFFF} 21 | .colorset-0099FF{ background:#0099FF;color:#FFFFFF} .colorset-0099FF .helper{ background:rgba(0,0,0,0.4);color:#FFFFFF} .colorset-0099FF .helper a{ color:#FFFFFF} 22 | .colorset-00CC33{ background:#00CC33;color:#000000} .colorset-00CC33 .helper{ background:rgba(255,255,255,0.4);color:#000000} .colorset-00CC33 .helper a{ color:#000000} 23 | .colorset-00CC66{ background:#00CC66;color:#000000} .colorset-00CC66 .helper{ background:rgba(255,255,255,0.4);color:#000000} .colorset-00CC66 .helper a{ color:#000000} 24 | .colorset-00CC99{ background:#00CC99;color:#000000} .colorset-00CC99 .helper{ background:rgba(255,255,255,0.4);color:#000000} .colorset-00CC99 .helper a{ color:#000000} 25 | .colorset-00CCCC{ background:#00CCCC;color:#000000} .colorset-00CCCC .helper{ background:rgba(255,255,255,0.4);color:#000000} .colorset-00CCCC .helper a{ color:#000000} 26 | .colorset-00CCFF{ background:#00CCFF;color:#000000} .colorset-00CCFF .helper{ background:rgba(255,255,255,0.4);color:#000000} .colorset-00CCFF .helper a{ color:#000000} 27 | .colorset-00FF33{ background:#00FF33;color:#000000} .colorset-00FF33 .helper{ background:rgba(255,255,255,0.4);color:#000000} .colorset-00FF33 .helper a{ color:#000000} 28 | .colorset-00FF66{ background:#00FF66;color:#000000} .colorset-00FF66 .helper{ background:rgba(255,255,255,0.4);color:#000000} .colorset-00FF66 .helper a{ color:#000000} 29 | .colorset-00FF99{ background:#00FF99;color:#000000} .colorset-00FF99 .helper{ background:rgba(255,255,255,0.4);color:#000000} .colorset-00FF99 .helper a{ color:#000000} 30 | .colorset-00FFCC{ background:#00FFCC;color:#000000} .colorset-00FFCC .helper{ background:rgba(255,255,255,0.4);color:#000000} .colorset-00FFCC .helper a{ color:#000000} 31 | .colorset-00FFFF{ background:#00FFFF;color:#000000} .colorset-00FFFF .helper{ background:rgba(255,255,255,0.4);color:#000000} .colorset-00FFFF .helper a{ color:#000000} 32 | .colorset-330033{ background:#330033;color:#FFFFFF} .colorset-330033 .helper{ background:rgba(0,0,0,0.4);color:#FFFFFF} .colorset-330033 .helper a{ color:#FFFFFF} 33 | .colorset-330066{ background:#330066;color:#FFFFFF} .colorset-330066 .helper{ background:rgba(0,0,0,0.4);color:#FFFFFF} .colorset-330066 .helper a{ color:#FFFFFF} 34 | .colorset-330099{ background:#330099;color:#FFFFFF} .colorset-330099 .helper{ background:rgba(0,0,0,0.4);color:#FFFFFF} .colorset-330099 .helper a{ color:#FFFFFF} 35 | .colorset-3300CC{ background:#3300CC;color:#FFFFFF} .colorset-3300CC .helper{ background:rgba(0,0,0,0.4);color:#FFFFFF} .colorset-3300CC .helper a{ color:#FFFFFF} 36 | .colorset-3300FF{ background:#3300FF;color:#FFFFFF} .colorset-3300FF .helper{ background:rgba(0,0,0,0.4);color:#FFFFFF} .colorset-3300FF .helper a{ color:#FFFFFF} 37 | .colorset-333333{ background:#333333;color:#FFFFFF} .colorset-333333 .helper{ background:rgba(0,0,0,0.4);color:#FFFFFF} .colorset-333333 .helper a{ color:#FFFFFF} 38 | .colorset-333366{ background:#333366;color:#FFFFFF} .colorset-333366 .helper{ background:rgba(0,0,0,0.4);color:#FFFFFF} .colorset-333366 .helper a{ color:#FFFFFF} 39 | .colorset-333399{ background:#333399;color:#FFFFFF} .colorset-333399 .helper{ background:rgba(0,0,0,0.4);color:#FFFFFF} .colorset-333399 .helper a{ color:#FFFFFF} 40 | .colorset-3333CC{ background:#3333CC;color:#FFFFFF} .colorset-3333CC .helper{ background:rgba(0,0,0,0.4);color:#FFFFFF} .colorset-3333CC .helper a{ color:#FFFFFF} 41 | .colorset-3333FF{ background:#3333FF;color:#FFFFFF} .colorset-3333FF .helper{ background:rgba(0,0,0,0.4);color:#FFFFFF} .colorset-3333FF .helper a{ color:#FFFFFF} 42 | .colorset-336633{ background:#336633;color:#FFFFFF} .colorset-336633 .helper{ background:rgba(0,0,0,0.4);color:#FFFFFF} .colorset-336633 .helper a{ color:#FFFFFF} 43 | .colorset-336666{ background:#336666;color:#FFFFFF} .colorset-336666 .helper{ background:rgba(0,0,0,0.4);color:#FFFFFF} .colorset-336666 .helper a{ color:#FFFFFF} 44 | .colorset-336699{ background:#336699;color:#FFFFFF} .colorset-336699 .helper{ background:rgba(0,0,0,0.4);color:#FFFFFF} .colorset-336699 .helper a{ color:#FFFFFF} 45 | .colorset-3366CC{ background:#3366CC;color:#FFFFFF} .colorset-3366CC .helper{ background:rgba(0,0,0,0.4);color:#FFFFFF} .colorset-3366CC .helper a{ color:#FFFFFF} 46 | .colorset-3366FF{ background:#3366FF;color:#FFFFFF} .colorset-3366FF .helper{ background:rgba(0,0,0,0.4);color:#FFFFFF} .colorset-3366FF .helper a{ color:#FFFFFF} 47 | .colorset-339933{ background:#339933;color:#FFFFFF} .colorset-339933 .helper{ background:rgba(0,0,0,0.4);color:#FFFFFF} .colorset-339933 .helper a{ color:#FFFFFF} 48 | .colorset-339966{ background:#339966;color:#FFFFFF} .colorset-339966 .helper{ background:rgba(0,0,0,0.4);color:#FFFFFF} .colorset-339966 .helper a{ color:#FFFFFF} 49 | .colorset-339999{ background:#339999;color:#FFFFFF} .colorset-339999 .helper{ background:rgba(0,0,0,0.4);color:#FFFFFF} .colorset-339999 .helper a{ color:#FFFFFF} 50 | .colorset-3399CC{ background:#3399CC;color:#000000} .colorset-3399CC .helper{ background:rgba(255,255,255,0.4);color:#000000} .colorset-3399CC .helper a{ color:#000000} 51 | .colorset-3399FF{ background:#3399FF;color:#000000} .colorset-3399FF .helper{ background:rgba(255,255,255,0.4);color:#000000} .colorset-3399FF .helper a{ color:#000000} 52 | .colorset-33CC33{ background:#33CC33;color:#000000} .colorset-33CC33 .helper{ background:rgba(255,255,255,0.4);color:#000000} .colorset-33CC33 .helper a{ color:#000000} 53 | .colorset-33CC66{ background:#33CC66;color:#000000} .colorset-33CC66 .helper{ background:rgba(255,255,255,0.4);color:#000000} .colorset-33CC66 .helper a{ color:#000000} 54 | .colorset-33CC99{ background:#33CC99;color:#000000} .colorset-33CC99 .helper{ background:rgba(255,255,255,0.4);color:#000000} .colorset-33CC99 .helper a{ color:#000000} 55 | .colorset-33CCCC{ background:#33CCCC;color:#000000} .colorset-33CCCC .helper{ background:rgba(255,255,255,0.4);color:#000000} .colorset-33CCCC .helper a{ color:#000000} 56 | .colorset-33CCFF{ background:#33CCFF;color:#000000} .colorset-33CCFF .helper{ background:rgba(255,255,255,0.4);color:#000000} .colorset-33CCFF .helper a{ color:#000000} 57 | .colorset-33FF33{ background:#33FF33;color:#000000} .colorset-33FF33 .helper{ background:rgba(255,255,255,0.4);color:#000000} .colorset-33FF33 .helper a{ color:#000000} 58 | .colorset-33FF66{ background:#33FF66;color:#000000} .colorset-33FF66 .helper{ background:rgba(255,255,255,0.4);color:#000000} .colorset-33FF66 .helper a{ color:#000000} 59 | .colorset-33FF99{ background:#33FF99;color:#000000} .colorset-33FF99 .helper{ background:rgba(255,255,255,0.4);color:#000000} .colorset-33FF99 .helper a{ color:#000000} 60 | .colorset-33FFCC{ background:#33FFCC;color:#000000} .colorset-33FFCC .helper{ background:rgba(255,255,255,0.4);color:#000000} .colorset-33FFCC .helper a{ color:#000000} 61 | .colorset-33FFFF{ background:#33FFFF;color:#000000} .colorset-33FFFF .helper{ background:rgba(255,255,255,0.4);color:#000000} .colorset-33FFFF .helper a{ color:#000000} 62 | .colorset-660033{ background:#660033;color:#FFFFFF} .colorset-660033 .helper{ background:rgba(0,0,0,0.4);color:#FFFFFF} .colorset-660033 .helper a{ color:#FFFFFF} 63 | .colorset-660066{ background:#660066;color:#FFFFFF} .colorset-660066 .helper{ background:rgba(0,0,0,0.4);color:#FFFFFF} .colorset-660066 .helper a{ color:#FFFFFF} 64 | .colorset-660099{ background:#660099;color:#FFFFFF} .colorset-660099 .helper{ background:rgba(0,0,0,0.4);color:#FFFFFF} .colorset-660099 .helper a{ color:#FFFFFF} 65 | .colorset-6600CC{ background:#6600CC;color:#FFFFFF} .colorset-6600CC .helper{ background:rgba(0,0,0,0.4);color:#FFFFFF} .colorset-6600CC .helper a{ color:#FFFFFF} 66 | .colorset-6600FF{ background:#6600FF;color:#FFFFFF} .colorset-6600FF .helper{ background:rgba(0,0,0,0.4);color:#FFFFFF} .colorset-6600FF .helper a{ color:#FFFFFF} 67 | .colorset-663333{ background:#663333;color:#FFFFFF} .colorset-663333 .helper{ background:rgba(0,0,0,0.4);color:#FFFFFF} .colorset-663333 .helper a{ color:#FFFFFF} 68 | .colorset-663366{ background:#663366;color:#FFFFFF} .colorset-663366 .helper{ background:rgba(0,0,0,0.4);color:#FFFFFF} .colorset-663366 .helper a{ color:#FFFFFF} 69 | .colorset-663399{ background:#663399;color:#FFFFFF} .colorset-663399 .helper{ background:rgba(0,0,0,0.4);color:#FFFFFF} .colorset-663399 .helper a{ color:#FFFFFF} 70 | .colorset-6633CC{ background:#6633CC;color:#FFFFFF} .colorset-6633CC .helper{ background:rgba(0,0,0,0.4);color:#FFFFFF} .colorset-6633CC .helper a{ color:#FFFFFF} 71 | .colorset-6633FF{ background:#6633FF;color:#FFFFFF} .colorset-6633FF .helper{ background:rgba(0,0,0,0.4);color:#FFFFFF} .colorset-6633FF .helper a{ color:#FFFFFF} 72 | .colorset-666633{ background:#666633;color:#FFFFFF} .colorset-666633 .helper{ background:rgba(0,0,0,0.4);color:#FFFFFF} .colorset-666633 .helper a{ color:#FFFFFF} 73 | .colorset-666666{ background:#666666;color:#FFFFFF} .colorset-666666 .helper{ background:rgba(0,0,0,0.4);color:#FFFFFF} .colorset-666666 .helper a{ color:#FFFFFF} 74 | .colorset-666699{ background:#666699;color:#FFFFFF} .colorset-666699 .helper{ background:rgba(0,0,0,0.4);color:#FFFFFF} .colorset-666699 .helper a{ color:#FFFFFF} 75 | .colorset-6666CC{ background:#6666CC;color:#FFFFFF} .colorset-6666CC .helper{ background:rgba(0,0,0,0.4);color:#FFFFFF} .colorset-6666CC .helper a{ color:#FFFFFF} 76 | .colorset-6666FF{ background:#6666FF;color:#FFFFFF} .colorset-6666FF .helper{ background:rgba(0,0,0,0.4);color:#FFFFFF} .colorset-6666FF .helper a{ color:#FFFFFF} 77 | .colorset-669933{ background:#669933;color:#000000} .colorset-669933 .helper{ background:rgba(255,255,255,0.4);color:#000000} .colorset-669933 .helper a{ color:#000000} 78 | .colorset-669966{ background:#669966;color:#000000} .colorset-669966 .helper{ background:rgba(255,255,255,0.4);color:#000000} .colorset-669966 .helper a{ color:#000000} 79 | .colorset-669999{ background:#669999;color:#000000} .colorset-669999 .helper{ background:rgba(255,255,255,0.4);color:#000000} .colorset-669999 .helper a{ color:#000000} 80 | .colorset-6699CC{ background:#6699CC;color:#000000} .colorset-6699CC .helper{ background:rgba(255,255,255,0.4);color:#000000} .colorset-6699CC .helper a{ color:#000000} 81 | .colorset-6699FF{ background:#6699FF;color:#000000} .colorset-6699FF .helper{ background:rgba(255,255,255,0.4);color:#000000} .colorset-6699FF .helper a{ color:#000000} 82 | .colorset-66CC33{ background:#66CC33;color:#000000} .colorset-66CC33 .helper{ background:rgba(255,255,255,0.4);color:#000000} .colorset-66CC33 .helper a{ color:#000000} 83 | .colorset-66CC66{ background:#66CC66;color:#000000} .colorset-66CC66 .helper{ background:rgba(255,255,255,0.4);color:#000000} .colorset-66CC66 .helper a{ color:#000000} 84 | .colorset-66CC99{ background:#66CC99;color:#000000} .colorset-66CC99 .helper{ background:rgba(255,255,255,0.4);color:#000000} .colorset-66CC99 .helper a{ color:#000000} 85 | .colorset-66CCCC{ background:#66CCCC;color:#000000} .colorset-66CCCC .helper{ background:rgba(255,255,255,0.4);color:#000000} .colorset-66CCCC .helper a{ color:#000000} 86 | .colorset-66CCFF{ background:#66CCFF;color:#000000} .colorset-66CCFF .helper{ background:rgba(255,255,255,0.4);color:#000000} .colorset-66CCFF .helper a{ color:#000000} 87 | .colorset-66FF33{ background:#66FF33;color:#000000} .colorset-66FF33 .helper{ background:rgba(255,255,255,0.4);color:#000000} .colorset-66FF33 .helper a{ color:#000000} 88 | .colorset-66FF66{ background:#66FF66;color:#000000} .colorset-66FF66 .helper{ background:rgba(255,255,255,0.4);color:#000000} .colorset-66FF66 .helper a{ color:#000000} 89 | .colorset-66FF99{ background:#66FF99;color:#000000} .colorset-66FF99 .helper{ background:rgba(255,255,255,0.4);color:#000000} .colorset-66FF99 .helper a{ color:#000000} 90 | .colorset-66FFCC{ background:#66FFCC;color:#000000} .colorset-66FFCC .helper{ background:rgba(255,255,255,0.4);color:#000000} .colorset-66FFCC .helper a{ color:#000000} 91 | .colorset-66FFFF{ background:#66FFFF;color:#000000} .colorset-66FFFF .helper{ background:rgba(255,255,255,0.4);color:#000000} .colorset-66FFFF .helper a{ color:#000000} 92 | .colorset-990033{ background:#990033;color:#FFFFFF} .colorset-990033 .helper{ background:rgba(0,0,0,0.4);color:#FFFFFF} .colorset-990033 .helper a{ color:#FFFFFF} 93 | .colorset-990066{ background:#990066;color:#FFFFFF} .colorset-990066 .helper{ background:rgba(0,0,0,0.4);color:#FFFFFF} .colorset-990066 .helper a{ color:#FFFFFF} 94 | .colorset-990099{ background:#990099;color:#FFFFFF} .colorset-990099 .helper{ background:rgba(0,0,0,0.4);color:#FFFFFF} .colorset-990099 .helper a{ color:#FFFFFF} 95 | .colorset-9900CC{ background:#9900CC;color:#FFFFFF} .colorset-9900CC .helper{ background:rgba(0,0,0,0.4);color:#FFFFFF} .colorset-9900CC .helper a{ color:#FFFFFF} 96 | .colorset-9900FF{ background:#9900FF;color:#FFFFFF} .colorset-9900FF .helper{ background:rgba(0,0,0,0.4);color:#FFFFFF} .colorset-9900FF .helper a{ color:#FFFFFF} 97 | .colorset-993333{ background:#993333;color:#FFFFFF} .colorset-993333 .helper{ background:rgba(0,0,0,0.4);color:#FFFFFF} .colorset-993333 .helper a{ color:#FFFFFF} 98 | .colorset-993366{ background:#993366;color:#FFFFFF} .colorset-993366 .helper{ background:rgba(0,0,0,0.4);color:#FFFFFF} .colorset-993366 .helper a{ color:#FFFFFF} 99 | .colorset-993399{ background:#993399;color:#FFFFFF} .colorset-993399 .helper{ background:rgba(0,0,0,0.4);color:#FFFFFF} .colorset-993399 .helper a{ color:#FFFFFF} 100 | .colorset-9933CC{ background:#9933CC;color:#FFFFFF} .colorset-9933CC .helper{ background:rgba(0,0,0,0.4);color:#FFFFFF} .colorset-9933CC .helper a{ color:#FFFFFF} 101 | .colorset-9933FF{ background:#9933FF;color:#FFFFFF} .colorset-9933FF .helper{ background:rgba(0,0,0,0.4);color:#FFFFFF} .colorset-9933FF .helper a{ color:#FFFFFF} 102 | .colorset-996633{ background:#996633;color:#FFFFFF} .colorset-996633 .helper{ background:rgba(0,0,0,0.4);color:#FFFFFF} .colorset-996633 .helper a{ color:#FFFFFF} 103 | .colorset-996666{ background:#996666;color:#FFFFFF} .colorset-996666 .helper{ background:rgba(0,0,0,0.4);color:#FFFFFF} .colorset-996666 .helper a{ color:#FFFFFF} 104 | .colorset-996699{ background:#996699;color:#FFFFFF} .colorset-996699 .helper{ background:rgba(0,0,0,0.4);color:#FFFFFF} .colorset-996699 .helper a{ color:#FFFFFF} 105 | .colorset-9966CC{ background:#9966CC;color:#000000} .colorset-9966CC .helper{ background:rgba(255,255,255,0.4);color:#000000} .colorset-9966CC .helper a{ color:#000000} 106 | .colorset-9966FF{ background:#9966FF;color:#000000} .colorset-9966FF .helper{ background:rgba(255,255,255,0.4);color:#000000} .colorset-9966FF .helper a{ color:#000000} 107 | .colorset-999933{ background:#999933;color:#000000} .colorset-999933 .helper{ background:rgba(255,255,255,0.4);color:#000000} .colorset-999933 .helper a{ color:#000000} 108 | .colorset-999966{ background:#999966;color:#000000} .colorset-999966 .helper{ background:rgba(255,255,255,0.4);color:#000000} .colorset-999966 .helper a{ color:#000000} 109 | .colorset-999999{ background:#999999;color:#000000} .colorset-999999 .helper{ background:rgba(255,255,255,0.4);color:#000000} .colorset-999999 .helper a{ color:#000000} 110 | .colorset-9999CC{ background:#9999CC;color:#000000} .colorset-9999CC .helper{ background:rgba(255,255,255,0.4);color:#000000} .colorset-9999CC .helper a{ color:#000000} 111 | .colorset-9999FF{ background:#9999FF;color:#000000} .colorset-9999FF .helper{ background:rgba(255,255,255,0.4);color:#000000} .colorset-9999FF .helper a{ color:#000000} 112 | .colorset-99CC33{ background:#99CC33;color:#000000} .colorset-99CC33 .helper{ background:rgba(255,255,255,0.4);color:#000000} .colorset-99CC33 .helper a{ color:#000000} 113 | .colorset-99CC66{ background:#99CC66;color:#000000} .colorset-99CC66 .helper{ background:rgba(255,255,255,0.4);color:#000000} .colorset-99CC66 .helper a{ color:#000000} 114 | .colorset-99CC99{ background:#99CC99;color:#000000} .colorset-99CC99 .helper{ background:rgba(255,255,255,0.4);color:#000000} .colorset-99CC99 .helper a{ color:#000000} 115 | .colorset-99CCCC{ background:#99CCCC;color:#000000} .colorset-99CCCC .helper{ background:rgba(255,255,255,0.4);color:#000000} .colorset-99CCCC .helper a{ color:#000000} 116 | .colorset-99CCFF{ background:#99CCFF;color:#000000} .colorset-99CCFF .helper{ background:rgba(255,255,255,0.4);color:#000000} .colorset-99CCFF .helper a{ color:#000000} 117 | .colorset-99FF33{ background:#99FF33;color:#000000} .colorset-99FF33 .helper{ background:rgba(255,255,255,0.4);color:#000000} .colorset-99FF33 .helper a{ color:#000000} 118 | .colorset-99FF66{ background:#99FF66;color:#000000} .colorset-99FF66 .helper{ background:rgba(255,255,255,0.4);color:#000000} .colorset-99FF66 .helper a{ color:#000000} 119 | .colorset-99FF99{ background:#99FF99;color:#000000} .colorset-99FF99 .helper{ background:rgba(255,255,255,0.4);color:#000000} .colorset-99FF99 .helper a{ color:#000000} 120 | .colorset-99FFCC{ background:#99FFCC;color:#000000} .colorset-99FFCC .helper{ background:rgba(255,255,255,0.4);color:#000000} .colorset-99FFCC .helper a{ color:#000000} 121 | .colorset-99FFFF{ background:#99FFFF;color:#000000} .colorset-99FFFF .helper{ background:rgba(255,255,255,0.4);color:#000000} .colorset-99FFFF .helper a{ color:#000000} 122 | .colorset-CC0033{ background:#CC0033;color:#FFFFFF} .colorset-CC0033 .helper{ background:rgba(0,0,0,0.4);color:#FFFFFF} .colorset-CC0033 .helper a{ color:#FFFFFF} 123 | .colorset-CC0066{ background:#CC0066;color:#FFFFFF} .colorset-CC0066 .helper{ background:rgba(0,0,0,0.4);color:#FFFFFF} .colorset-CC0066 .helper a{ color:#FFFFFF} 124 | .colorset-CC0099{ background:#CC0099;color:#FFFFFF} .colorset-CC0099 .helper{ background:rgba(0,0,0,0.4);color:#FFFFFF} .colorset-CC0099 .helper a{ color:#FFFFFF} 125 | .colorset-CC00CC{ background:#CC00CC;color:#FFFFFF} .colorset-CC00CC .helper{ background:rgba(0,0,0,0.4);color:#FFFFFF} .colorset-CC00CC .helper a{ color:#FFFFFF} 126 | .colorset-CC00FF{ background:#CC00FF;color:#FFFFFF} .colorset-CC00FF .helper{ background:rgba(0,0,0,0.4);color:#FFFFFF} .colorset-CC00FF .helper a{ color:#FFFFFF} 127 | .colorset-CC3333{ background:#CC3333;color:#FFFFFF} .colorset-CC3333 .helper{ background:rgba(0,0,0,0.4);color:#FFFFFF} .colorset-CC3333 .helper a{ color:#FFFFFF} 128 | .colorset-CC3366{ background:#CC3366;color:#FFFFFF} .colorset-CC3366 .helper{ background:rgba(0,0,0,0.4);color:#FFFFFF} .colorset-CC3366 .helper a{ color:#FFFFFF} 129 | .colorset-CC3399{ background:#CC3399;color:#FFFFFF} .colorset-CC3399 .helper{ background:rgba(0,0,0,0.4);color:#FFFFFF} .colorset-CC3399 .helper a{ color:#FFFFFF} 130 | .colorset-CC33CC{ background:#CC33CC;color:#FFFFFF} .colorset-CC33CC .helper{ background:rgba(0,0,0,0.4);color:#FFFFFF} .colorset-CC33CC .helper a{ color:#FFFFFF} 131 | .colorset-CC33FF{ background:#CC33FF;color:#FFFFFF} .colorset-CC33FF .helper{ background:rgba(0,0,0,0.4);color:#FFFFFF} .colorset-CC33FF .helper a{ color:#FFFFFF} 132 | .colorset-CC6633{ background:#CC6633;color:#000000} .colorset-CC6633 .helper{ background:rgba(255,255,255,0.4);color:#000000} .colorset-CC6633 .helper a{ color:#000000} 133 | .colorset-CC6666{ background:#CC6666;color:#000000} .colorset-CC6666 .helper{ background:rgba(255,255,255,0.4);color:#000000} .colorset-CC6666 .helper a{ color:#000000} 134 | .colorset-CC6699{ background:#CC6699;color:#000000} .colorset-CC6699 .helper{ background:rgba(255,255,255,0.4);color:#000000} .colorset-CC6699 .helper a{ color:#000000} 135 | .colorset-CC66CC{ background:#CC66CC;color:#000000} .colorset-CC66CC .helper{ background:rgba(255,255,255,0.4);color:#000000} .colorset-CC66CC .helper a{ color:#000000} 136 | .colorset-CC66FF{ background:#CC66FF;color:#000000} .colorset-CC66FF .helper{ background:rgba(255,255,255,0.4);color:#000000} .colorset-CC66FF .helper a{ color:#000000} 137 | .colorset-CC9933{ background:#CC9933;color:#000000} .colorset-CC9933 .helper{ background:rgba(255,255,255,0.4);color:#000000} .colorset-CC9933 .helper a{ color:#000000} 138 | .colorset-CC9966{ background:#CC9966;color:#000000} .colorset-CC9966 .helper{ background:rgba(255,255,255,0.4);color:#000000} .colorset-CC9966 .helper a{ color:#000000} 139 | .colorset-CC9999{ background:#CC9999;color:#000000} .colorset-CC9999 .helper{ background:rgba(255,255,255,0.4);color:#000000} .colorset-CC9999 .helper a{ color:#000000} 140 | .colorset-CC99CC{ background:#CC99CC;color:#000000} .colorset-CC99CC .helper{ background:rgba(255,255,255,0.4);color:#000000} .colorset-CC99CC .helper a{ color:#000000} 141 | .colorset-CC99FF{ background:#CC99FF;color:#000000} .colorset-CC99FF .helper{ background:rgba(255,255,255,0.4);color:#000000} .colorset-CC99FF .helper a{ color:#000000} 142 | .colorset-CCCC33{ background:#CCCC33;color:#000000} .colorset-CCCC33 .helper{ background:rgba(255,255,255,0.4);color:#000000} .colorset-CCCC33 .helper a{ color:#000000} 143 | .colorset-CCCC66{ background:#CCCC66;color:#000000} .colorset-CCCC66 .helper{ background:rgba(255,255,255,0.4);color:#000000} .colorset-CCCC66 .helper a{ color:#000000} 144 | .colorset-CCCC99{ background:#CCCC99;color:#000000} .colorset-CCCC99 .helper{ background:rgba(255,255,255,0.4);color:#000000} .colorset-CCCC99 .helper a{ color:#000000} 145 | .colorset-CCCCCC{ background:#CCCCCC;color:#000000} .colorset-CCCCCC .helper{ background:rgba(255,255,255,0.4);color:#000000} .colorset-CCCCCC .helper a{ color:#000000} 146 | .colorset-CCCCFF{ background:#CCCCFF;color:#000000} .colorset-CCCCFF .helper{ background:rgba(255,255,255,0.4);color:#000000} .colorset-CCCCFF .helper a{ color:#000000} 147 | .colorset-CCFF33{ background:#CCFF33;color:#000000} .colorset-CCFF33 .helper{ background:rgba(255,255,255,0.4);color:#000000} .colorset-CCFF33 .helper a{ color:#000000} 148 | .colorset-CCFF66{ background:#CCFF66;color:#000000} .colorset-CCFF66 .helper{ background:rgba(255,255,255,0.4);color:#000000} .colorset-CCFF66 .helper a{ color:#000000} 149 | .colorset-CCFF99{ background:#CCFF99;color:#000000} .colorset-CCFF99 .helper{ background:rgba(255,255,255,0.4);color:#000000} .colorset-CCFF99 .helper a{ color:#000000} 150 | .colorset-CCFFCC{ background:#CCFFCC;color:#000000} .colorset-CCFFCC .helper{ background:rgba(255,255,255,0.4);color:#000000} .colorset-CCFFCC .helper a{ color:#000000} 151 | .colorset-CCFFFF{ background:#CCFFFF;color:#000000} .colorset-CCFFFF .helper{ background:rgba(255,255,255,0.4);color:#000000} .colorset-CCFFFF .helper a{ color:#000000} 152 | .colorset-FF0033{ background:#FF0033;color:#FFFFFF} .colorset-FF0033 .helper{ background:rgba(0,0,0,0.4);color:#FFFFFF} .colorset-FF0033 .helper a{ color:#FFFFFF} 153 | .colorset-FF0066{ background:#FF0066;color:#FFFFFF} .colorset-FF0066 .helper{ background:rgba(0,0,0,0.4);color:#FFFFFF} .colorset-FF0066 .helper a{ color:#FFFFFF} 154 | .colorset-FF0099{ background:#FF0099;color:#FFFFFF} .colorset-FF0099 .helper{ background:rgba(0,0,0,0.4);color:#FFFFFF} .colorset-FF0099 .helper a{ color:#FFFFFF} 155 | .colorset-FF00CC{ background:#FF00CC;color:#FFFFFF} .colorset-FF00CC .helper{ background:rgba(0,0,0,0.4);color:#FFFFFF} .colorset-FF00CC .helper a{ color:#FFFFFF} 156 | .colorset-FF00FF{ background:#FF00FF;color:#FFFFFF} .colorset-FF00FF .helper{ background:rgba(0,0,0,0.4);color:#FFFFFF} .colorset-FF00FF .helper a{ color:#FFFFFF} 157 | .colorset-FF3333{ background:#FF3333;color:#FFFFFF} .colorset-FF3333 .helper{ background:rgba(0,0,0,0.4);color:#FFFFFF} .colorset-FF3333 .helper a{ color:#FFFFFF} 158 | .colorset-FF3366{ background:#FF3366;color:#FFFFFF} .colorset-FF3366 .helper{ background:rgba(0,0,0,0.4);color:#FFFFFF} .colorset-FF3366 .helper a{ color:#FFFFFF} 159 | .colorset-FF3399{ background:#FF3399;color:#FFFFFF} .colorset-FF3399 .helper{ background:rgba(0,0,0,0.4);color:#FFFFFF} .colorset-FF3399 .helper a{ color:#FFFFFF} 160 | .colorset-FF33CC{ background:#FF33CC;color:#000000} .colorset-FF33CC .helper{ background:rgba(255,255,255,0.4);color:#000000} .colorset-FF33CC .helper a{ color:#000000} 161 | .colorset-FF33FF{ background:#FF33FF;color:#000000} .colorset-FF33FF .helper{ background:rgba(255,255,255,0.4);color:#000000} .colorset-FF33FF .helper a{ color:#000000} 162 | .colorset-FF6633{ background:#FF6633;color:#000000} .colorset-FF6633 .helper{ background:rgba(255,255,255,0.4);color:#000000} .colorset-FF6633 .helper a{ color:#000000} 163 | .colorset-FF6666{ background:#FF6666;color:#000000} .colorset-FF6666 .helper{ background:rgba(255,255,255,0.4);color:#000000} .colorset-FF6666 .helper a{ color:#000000} 164 | .colorset-FF6699{ background:#FF6699;color:#000000} .colorset-FF6699 .helper{ background:rgba(255,255,255,0.4);color:#000000} .colorset-FF6699 .helper a{ color:#000000} 165 | .colorset-FF66CC{ background:#FF66CC;color:#000000} .colorset-FF66CC .helper{ background:rgba(255,255,255,0.4);color:#000000} .colorset-FF66CC .helper a{ color:#000000} 166 | .colorset-FF66FF{ background:#FF66FF;color:#000000} .colorset-FF66FF .helper{ background:rgba(255,255,255,0.4);color:#000000} .colorset-FF66FF .helper a{ color:#000000} 167 | .colorset-FF9933{ background:#FF9933;color:#000000} .colorset-FF9933 .helper{ background:rgba(255,255,255,0.4);color:#000000} .colorset-FF9933 .helper a{ color:#000000} 168 | .colorset-FF9966{ background:#FF9966;color:#000000} .colorset-FF9966 .helper{ background:rgba(255,255,255,0.4);color:#000000} .colorset-FF9966 .helper a{ color:#000000} 169 | .colorset-FF9999{ background:#FF9999;color:#000000} .colorset-FF9999 .helper{ background:rgba(255,255,255,0.4);color:#000000} .colorset-FF9999 .helper a{ color:#000000} 170 | .colorset-FF99CC{ background:#FF99CC;color:#000000} .colorset-FF99CC .helper{ background:rgba(255,255,255,0.4);color:#000000} .colorset-FF99CC .helper a{ color:#000000} 171 | .colorset-FF99FF{ background:#FF99FF;color:#000000} .colorset-FF99FF .helper{ background:rgba(255,255,255,0.4);color:#000000} .colorset-FF99FF .helper a{ color:#000000} 172 | .colorset-FFCC33{ background:#FFCC33;color:#000000} .colorset-FFCC33 .helper{ background:rgba(255,255,255,0.4);color:#000000} .colorset-FFCC33 .helper a{ color:#000000} 173 | .colorset-FFCC66{ background:#FFCC66;color:#000000} .colorset-FFCC66 .helper{ background:rgba(255,255,255,0.4);color:#000000} .colorset-FFCC66 .helper a{ color:#000000} 174 | .colorset-FFCC99{ background:#FFCC99;color:#000000} .colorset-FFCC99 .helper{ background:rgba(255,255,255,0.4);color:#000000} .colorset-FFCC99 .helper a{ color:#000000} 175 | .colorset-FFCCCC{ background:#FFCCCC;color:#000000} .colorset-FFCCCC .helper{ background:rgba(255,255,255,0.4);color:#000000} .colorset-FFCCCC .helper a{ color:#000000} 176 | .colorset-FFCCFF{ background:#FFCCFF;color:#000000} .colorset-FFCCFF .helper{ background:rgba(255,255,255,0.4);color:#000000} .colorset-FFCCFF .helper a{ color:#000000} 177 | .colorset-FFFF33{ background:#FFFF33;color:#000000} .colorset-FFFF33 .helper{ background:rgba(255,255,255,0.4);color:#000000} .colorset-FFFF33 .helper a{ color:#000000} 178 | .colorset-FFFF66{ background:#FFFF66;color:#000000} .colorset-FFFF66 .helper{ background:rgba(255,255,255,0.4);color:#000000} .colorset-FFFF66 .helper a{ color:#000000} 179 | .colorset-FFFF99{ background:#FFFF99;color:#000000} .colorset-FFFF99 .helper{ background:rgba(255,255,255,0.4);color:#000000} .colorset-FFFF99 .helper a{ color:#000000} 180 | .colorset-FFFFCC{ background:#FFFFCC;color:#000000} .colorset-FFFFCC .helper{ background:rgba(255,255,255,0.4);color:#000000} .colorset-FFFFCC .helper a{ color:#000000} 181 | .colorset-FFFFFF{ background:#FFFFFF;color:#000000} .colorset-FFFFFF .helper{ background:rgba(255,255,255,0.4);color:#000000} .colorset-FFFFFF .helper a{ color:#000000} -------------------------------------------------------------------------------- /lib/viewer/static/colorsets.json: -------------------------------------------------------------------------------- 1 | {"selectors":["colorset-000033","colorset-000033","colorset-000066","colorset-000066","colorset-000099","colorset-000099","colorset-0000CC","colorset-0000CC","colorset-0000FF","colorset-0000FF","colorset-003333","colorset-003333","colorset-003366","colorset-003366","colorset-003399","colorset-003399","colorset-0033CC","colorset-0033CC","colorset-0033FF","colorset-0033FF","colorset-006633","colorset-006633","colorset-006666","colorset-006666","colorset-006699","colorset-006699","colorset-0066CC","colorset-0066CC","colorset-0066FF","colorset-0066FF","colorset-009933","colorset-009933","colorset-009966","colorset-009966","colorset-009999","colorset-009999","colorset-0099CC","colorset-0099CC","colorset-0099FF","colorset-0099FF","colorset-00CC33","colorset-00CC33","colorset-00CC66","colorset-00CC66","colorset-00CC99","colorset-00CC99","colorset-00CCCC","colorset-00CCCC","colorset-00CCFF","colorset-00CCFF","colorset-00FF33","colorset-00FF33","colorset-00FF66","colorset-00FF66","colorset-00FF99","colorset-00FF99","colorset-00FFCC","colorset-00FFCC","colorset-00FFFF","colorset-00FFFF","colorset-330033","colorset-330033","colorset-330066","colorset-330066","colorset-330099","colorset-330099","colorset-3300CC","colorset-3300CC","colorset-3300FF","colorset-3300FF","colorset-333333","colorset-333366","colorset-333366","colorset-333399","colorset-333399","colorset-3333CC","colorset-3333CC","colorset-3333FF","colorset-3333FF","colorset-336633","colorset-336633","colorset-336666","colorset-336666","colorset-336699","colorset-336699","colorset-3366CC","colorset-3366CC","colorset-3366FF","colorset-3366FF","colorset-339933","colorset-339933","colorset-339966","colorset-339966","colorset-339999","colorset-339999","colorset-3399CC","colorset-3399CC","colorset-3399FF","colorset-3399FF","colorset-33CC33","colorset-33CC33","colorset-33CC66","colorset-33CC66","colorset-33CC99","colorset-33CC99","colorset-33CCCC","colorset-33CCCC","colorset-33CCFF","colorset-33CCFF","colorset-33FF33","colorset-33FF33","colorset-33FF66","colorset-33FF66","colorset-33FF99","colorset-33FF99","colorset-33FFCC","colorset-33FFCC","colorset-33FFFF","colorset-33FFFF","colorset-660033","colorset-660033","colorset-660066","colorset-660066","colorset-660099","colorset-660099","colorset-6600CC","colorset-6600CC","colorset-6600FF","colorset-6600FF","colorset-663333","colorset-663333","colorset-663366","colorset-663366","colorset-663399","colorset-663399","colorset-6633CC","colorset-6633CC","colorset-6633FF","colorset-6633FF","colorset-666633","colorset-666633","colorset-666666","colorset-666699","colorset-666699","colorset-6666CC","colorset-6666CC","colorset-6666FF","colorset-6666FF","colorset-669933","colorset-669933","colorset-669966","colorset-669966","colorset-669999","colorset-669999","colorset-6699CC","colorset-6699CC","colorset-6699FF","colorset-6699FF","colorset-66CC33","colorset-66CC33","colorset-66CC66","colorset-66CC66","colorset-66CC99","colorset-66CC99","colorset-66CCCC","colorset-66CCCC","colorset-66CCFF","colorset-66CCFF","colorset-66FF33","colorset-66FF33","colorset-66FF66","colorset-66FF66","colorset-66FF99","colorset-66FF99","colorset-66FFCC","colorset-66FFCC","colorset-66FFFF","colorset-66FFFF","colorset-990033","colorset-990033","colorset-990066","colorset-990066","colorset-990099","colorset-990099","colorset-9900CC","colorset-9900CC","colorset-9900FF","colorset-9900FF","colorset-993333","colorset-993333","colorset-993366","colorset-993366","colorset-993399","colorset-993399","colorset-9933CC","colorset-9933CC","colorset-9933FF","colorset-9933FF","colorset-996633","colorset-996633","colorset-996666","colorset-996666","colorset-996699","colorset-996699","colorset-9966CC","colorset-9966CC","colorset-9966FF","colorset-9966FF","colorset-999933","colorset-999933","colorset-999966","colorset-999966","colorset-999999","colorset-9999CC","colorset-9999CC","colorset-9999FF","colorset-9999FF","colorset-99CC33","colorset-99CC33","colorset-99CC66","colorset-99CC66","colorset-99CC99","colorset-99CC99","colorset-99CCCC","colorset-99CCCC","colorset-99CCFF","colorset-99CCFF","colorset-99FF33","colorset-99FF33","colorset-99FF66","colorset-99FF66","colorset-99FF99","colorset-99FF99","colorset-99FFCC","colorset-99FFCC","colorset-99FFFF","colorset-99FFFF","colorset-CC0033","colorset-CC0033","colorset-CC0066","colorset-CC0066","colorset-CC0099","colorset-CC0099","colorset-CC00CC","colorset-CC00CC","colorset-CC00FF","colorset-CC00FF","colorset-CC3333","colorset-CC3333","colorset-CC3366","colorset-CC3366","colorset-CC3399","colorset-CC3399","colorset-CC33CC","colorset-CC33CC","colorset-CC33FF","colorset-CC33FF","colorset-CC6633","colorset-CC6633","colorset-CC6666","colorset-CC6666","colorset-CC6699","colorset-CC6699","colorset-CC66CC","colorset-CC66CC","colorset-CC66FF","colorset-CC66FF","colorset-CC9933","colorset-CC9933","colorset-CC9966","colorset-CC9966","colorset-CC9999","colorset-CC9999","colorset-CC99CC","colorset-CC99CC","colorset-CC99FF","colorset-CC99FF","colorset-CCCC33","colorset-CCCC33","colorset-CCCC66","colorset-CCCC66","colorset-CCCC99","colorset-CCCC99","colorset-CCCCCC","colorset-CCCCFF","colorset-CCCCFF","colorset-CCFF33","colorset-CCFF33","colorset-CCFF66","colorset-CCFF66","colorset-CCFF99","colorset-CCFF99","colorset-CCFFCC","colorset-CCFFCC","colorset-CCFFFF","colorset-CCFFFF","colorset-FF0033","colorset-FF0033","colorset-FF0066","colorset-FF0066","colorset-FF0099","colorset-FF0099","colorset-FF00CC","colorset-FF00CC","colorset-FF00FF","colorset-FF00FF","colorset-FF3333","colorset-FF3333","colorset-FF3366","colorset-FF3366","colorset-FF3399","colorset-FF3399","colorset-FF33CC","colorset-FF33CC","colorset-FF33FF","colorset-FF33FF","colorset-FF6633","colorset-FF6633","colorset-FF6666","colorset-FF6666","colorset-FF6699","colorset-FF6699","colorset-FF66CC","colorset-FF66CC","colorset-FF66FF","colorset-FF66FF","colorset-FF9933","colorset-FF9933","colorset-FF9966","colorset-FF9966","colorset-FF9999","colorset-FF9999","colorset-FF99CC","colorset-FF99CC","colorset-FF99FF","colorset-FF99FF","colorset-FFCC33","colorset-FFCC33","colorset-FFCC66","colorset-FFCC66","colorset-FFCC99","colorset-FFCC99","colorset-FFCCCC","colorset-FFCCCC","colorset-FFCCFF","colorset-FFCCFF","colorset-FFFF33","colorset-FFFF33","colorset-FFFF66","colorset-FFFF66","colorset-FFFF99","colorset-FFFF99","colorset-FFFFCC","colorset-FFFFCC","colorset-FFFFFF"],"hex":["#000033","#000033","#000066","#000066","#000099","#000099","#0000CC","#0000CC","#0000FF","#0000FF","#003333","#003333","#003366","#003366","#003399","#003399","#0033CC","#0033CC","#0033FF","#0033FF","#006633","#006633","#006666","#006666","#006699","#006699","#0066CC","#0066CC","#0066FF","#0066FF","#009933","#009933","#009966","#009966","#009999","#009999","#0099CC","#0099CC","#0099FF","#0099FF","#00CC33","#00CC33","#00CC66","#00CC66","#00CC99","#00CC99","#00CCCC","#00CCCC","#00CCFF","#00CCFF","#00FF33","#00FF33","#00FF66","#00FF66","#00FF99","#00FF99","#00FFCC","#00FFCC","#00FFFF","#00FFFF","#330033","#330033","#330066","#330066","#330099","#330099","#3300CC","#3300CC","#3300FF","#3300FF","#333333","#333366","#333366","#333399","#333399","#3333CC","#3333CC","#3333FF","#3333FF","#336633","#336633","#336666","#336666","#336699","#336699","#3366CC","#3366CC","#3366FF","#3366FF","#339933","#339933","#339966","#339966","#339999","#339999","#3399CC","#3399CC","#3399FF","#3399FF","#33CC33","#33CC33","#33CC66","#33CC66","#33CC99","#33CC99","#33CCCC","#33CCCC","#33CCFF","#33CCFF","#33FF33","#33FF33","#33FF66","#33FF66","#33FF99","#33FF99","#33FFCC","#33FFCC","#33FFFF","#33FFFF","#660033","#660033","#660066","#660066","#660099","#660099","#6600CC","#6600CC","#6600FF","#6600FF","#663333","#663333","#663366","#663366","#663399","#663399","#6633CC","#6633CC","#6633FF","#6633FF","#666633","#666633","#666666","#666699","#666699","#6666CC","#6666CC","#6666FF","#6666FF","#669933","#669933","#669966","#669966","#669999","#669999","#6699CC","#6699CC","#6699FF","#6699FF","#66CC33","#66CC33","#66CC66","#66CC66","#66CC99","#66CC99","#66CCCC","#66CCCC","#66CCFF","#66CCFF","#66FF33","#66FF33","#66FF66","#66FF66","#66FF99","#66FF99","#66FFCC","#66FFCC","#66FFFF","#66FFFF","#990033","#990033","#990066","#990066","#990099","#990099","#9900CC","#9900CC","#9900FF","#9900FF","#993333","#993333","#993366","#993366","#993399","#993399","#9933CC","#9933CC","#9933FF","#9933FF","#996633","#996633","#996666","#996666","#996699","#996699","#9966CC","#9966CC","#9966FF","#9966FF","#999933","#999933","#999966","#999966","#999999","#9999CC","#9999CC","#9999FF","#9999FF","#99CC33","#99CC33","#99CC66","#99CC66","#99CC99","#99CC99","#99CCCC","#99CCCC","#99CCFF","#99CCFF","#99FF33","#99FF33","#99FF66","#99FF66","#99FF99","#99FF99","#99FFCC","#99FFCC","#99FFFF","#99FFFF","#CC0033","#CC0033","#CC0066","#CC0066","#CC0099","#CC0099","#CC00CC","#CC00CC","#CC00FF","#CC00FF","#CC3333","#CC3333","#CC3366","#CC3366","#CC3399","#CC3399","#CC33CC","#CC33CC","#CC33FF","#CC33FF","#CC6633","#CC6633","#CC6666","#CC6666","#CC6699","#CC6699","#CC66CC","#CC66CC","#CC66FF","#CC66FF","#CC9933","#CC9933","#CC9966","#CC9966","#CC9999","#CC9999","#CC99CC","#CC99CC","#CC99FF","#CC99FF","#CCCC33","#CCCC33","#CCCC66","#CCCC66","#CCCC99","#CCCC99","#CCCCCC","#CCCCFF","#CCCCFF","#CCFF33","#CCFF33","#CCFF66","#CCFF66","#CCFF99","#CCFF99","#CCFFCC","#CCFFCC","#CCFFFF","#CCFFFF","#FF0033","#FF0033","#FF0066","#FF0066","#FF0099","#FF0099","#FF00CC","#FF00CC","#FF00FF","#FF00FF","#FF3333","#FF3333","#FF3366","#FF3366","#FF3399","#FF3399","#FF33CC","#FF33CC","#FF33FF","#FF33FF","#FF6633","#FF6633","#FF6666","#FF6666","#FF6699","#FF6699","#FF66CC","#FF66CC","#FF66FF","#FF66FF","#FF9933","#FF9933","#FF9966","#FF9966","#FF9999","#FF9999","#FF99CC","#FF99CC","#FF99FF","#FF99FF","#FFCC33","#FFCC33","#FFCC66","#FFCC66","#FFCC99","#FFCC99","#FFCCCC","#FFCCCC","#FFCCFF","#FFCCFF","#FFFF33","#FFFF33","#FFFF66","#FFFF66","#FFFF99","#FFFF99","#FFFFCC","#FFFFCC","#FFFFFF"],"map":{"#000033":{"selector":"colorset-000033","color":"#000033"},"colorset-000033":{"selector":"colorset-000033","color":"#000033"},"#000066":{"selector":"colorset-000066","color":"#000066"},"colorset-000066":{"selector":"colorset-000066","color":"#000066"},"#000099":{"selector":"colorset-000099","color":"#000099"},"colorset-000099":{"selector":"colorset-000099","color":"#000099"},"#0000CC":{"selector":"colorset-0000CC","color":"#0000CC"},"colorset-0000CC":{"selector":"colorset-0000CC","color":"#0000CC"},"#0000FF":{"selector":"colorset-0000FF","color":"#0000FF"},"colorset-0000FF":{"selector":"colorset-0000FF","color":"#0000FF"},"#003333":{"selector":"colorset-003333","color":"#003333"},"colorset-003333":{"selector":"colorset-003333","color":"#003333"},"#003366":{"selector":"colorset-003366","color":"#003366"},"colorset-003366":{"selector":"colorset-003366","color":"#003366"},"#003399":{"selector":"colorset-003399","color":"#003399"},"colorset-003399":{"selector":"colorset-003399","color":"#003399"},"#0033CC":{"selector":"colorset-0033CC","color":"#0033CC"},"colorset-0033CC":{"selector":"colorset-0033CC","color":"#0033CC"},"#0033FF":{"selector":"colorset-0033FF","color":"#0033FF"},"colorset-0033FF":{"selector":"colorset-0033FF","color":"#0033FF"},"#006633":{"selector":"colorset-006633","color":"#006633"},"colorset-006633":{"selector":"colorset-006633","color":"#006633"},"#006666":{"selector":"colorset-006666","color":"#006666"},"colorset-006666":{"selector":"colorset-006666","color":"#006666"},"#006699":{"selector":"colorset-006699","color":"#006699"},"colorset-006699":{"selector":"colorset-006699","color":"#006699"},"#0066CC":{"selector":"colorset-0066CC","color":"#0066CC"},"colorset-0066CC":{"selector":"colorset-0066CC","color":"#0066CC"},"#0066FF":{"selector":"colorset-0066FF","color":"#0066FF"},"colorset-0066FF":{"selector":"colorset-0066FF","color":"#0066FF"},"#009933":{"selector":"colorset-009933","color":"#009933"},"colorset-009933":{"selector":"colorset-009933","color":"#009933"},"#009966":{"selector":"colorset-009966","color":"#009966"},"colorset-009966":{"selector":"colorset-009966","color":"#009966"},"#009999":{"selector":"colorset-009999","color":"#009999"},"colorset-009999":{"selector":"colorset-009999","color":"#009999"},"#0099CC":{"selector":"colorset-0099CC","color":"#0099CC"},"colorset-0099CC":{"selector":"colorset-0099CC","color":"#0099CC"},"#0099FF":{"selector":"colorset-0099FF","color":"#0099FF"},"colorset-0099FF":{"selector":"colorset-0099FF","color":"#0099FF"},"#00CC33":{"selector":"colorset-00CC33","color":"#00CC33"},"colorset-00CC33":{"selector":"colorset-00CC33","color":"#00CC33"},"#00CC66":{"selector":"colorset-00CC66","color":"#00CC66"},"colorset-00CC66":{"selector":"colorset-00CC66","color":"#00CC66"},"#00CC99":{"selector":"colorset-00CC99","color":"#00CC99"},"colorset-00CC99":{"selector":"colorset-00CC99","color":"#00CC99"},"#00CCCC":{"selector":"colorset-00CCCC","color":"#00CCCC"},"colorset-00CCCC":{"selector":"colorset-00CCCC","color":"#00CCCC"},"#00CCFF":{"selector":"colorset-00CCFF","color":"#00CCFF"},"colorset-00CCFF":{"selector":"colorset-00CCFF","color":"#00CCFF"},"#00FF33":{"selector":"colorset-00FF33","color":"#00FF33"},"colorset-00FF33":{"selector":"colorset-00FF33","color":"#00FF33"},"#00FF66":{"selector":"colorset-00FF66","color":"#00FF66"},"colorset-00FF66":{"selector":"colorset-00FF66","color":"#00FF66"},"#00FF99":{"selector":"colorset-00FF99","color":"#00FF99"},"colorset-00FF99":{"selector":"colorset-00FF99","color":"#00FF99"},"#00FFCC":{"selector":"colorset-00FFCC","color":"#00FFCC"},"colorset-00FFCC":{"selector":"colorset-00FFCC","color":"#00FFCC"},"#00FFFF":{"selector":"colorset-00FFFF","color":"#00FFFF"},"colorset-00FFFF":{"selector":"colorset-00FFFF","color":"#00FFFF"},"#330033":{"selector":"colorset-330033","color":"#330033"},"colorset-330033":{"selector":"colorset-330033","color":"#330033"},"#330066":{"selector":"colorset-330066","color":"#330066"},"colorset-330066":{"selector":"colorset-330066","color":"#330066"},"#330099":{"selector":"colorset-330099","color":"#330099"},"colorset-330099":{"selector":"colorset-330099","color":"#330099"},"#3300CC":{"selector":"colorset-3300CC","color":"#3300CC"},"colorset-3300CC":{"selector":"colorset-3300CC","color":"#3300CC"},"#3300FF":{"selector":"colorset-3300FF","color":"#3300FF"},"colorset-3300FF":{"selector":"colorset-3300FF","color":"#3300FF"},"#333333":{"selector":"colorset-333333","color":"#333333"},"colorset-333333":{"selector":"colorset-333333","color":"#333333"},"#333366":{"selector":"colorset-333366","color":"#333366"},"colorset-333366":{"selector":"colorset-333366","color":"#333366"},"#333399":{"selector":"colorset-333399","color":"#333399"},"colorset-333399":{"selector":"colorset-333399","color":"#333399"},"#3333CC":{"selector":"colorset-3333CC","color":"#3333CC"},"colorset-3333CC":{"selector":"colorset-3333CC","color":"#3333CC"},"#3333FF":{"selector":"colorset-3333FF","color":"#3333FF"},"colorset-3333FF":{"selector":"colorset-3333FF","color":"#3333FF"},"#336633":{"selector":"colorset-336633","color":"#336633"},"colorset-336633":{"selector":"colorset-336633","color":"#336633"},"#336666":{"selector":"colorset-336666","color":"#336666"},"colorset-336666":{"selector":"colorset-336666","color":"#336666"},"#336699":{"selector":"colorset-336699","color":"#336699"},"colorset-336699":{"selector":"colorset-336699","color":"#336699"},"#3366CC":{"selector":"colorset-3366CC","color":"#3366CC"},"colorset-3366CC":{"selector":"colorset-3366CC","color":"#3366CC"},"#3366FF":{"selector":"colorset-3366FF","color":"#3366FF"},"colorset-3366FF":{"selector":"colorset-3366FF","color":"#3366FF"},"#339933":{"selector":"colorset-339933","color":"#339933"},"colorset-339933":{"selector":"colorset-339933","color":"#339933"},"#339966":{"selector":"colorset-339966","color":"#339966"},"colorset-339966":{"selector":"colorset-339966","color":"#339966"},"#339999":{"selector":"colorset-339999","color":"#339999"},"colorset-339999":{"selector":"colorset-339999","color":"#339999"},"#3399CC":{"selector":"colorset-3399CC","color":"#3399CC"},"colorset-3399CC":{"selector":"colorset-3399CC","color":"#3399CC"},"#3399FF":{"selector":"colorset-3399FF","color":"#3399FF"},"colorset-3399FF":{"selector":"colorset-3399FF","color":"#3399FF"},"#33CC33":{"selector":"colorset-33CC33","color":"#33CC33"},"colorset-33CC33":{"selector":"colorset-33CC33","color":"#33CC33"},"#33CC66":{"selector":"colorset-33CC66","color":"#33CC66"},"colorset-33CC66":{"selector":"colorset-33CC66","color":"#33CC66"},"#33CC99":{"selector":"colorset-33CC99","color":"#33CC99"},"colorset-33CC99":{"selector":"colorset-33CC99","color":"#33CC99"},"#33CCCC":{"selector":"colorset-33CCCC","color":"#33CCCC"},"colorset-33CCCC":{"selector":"colorset-33CCCC","color":"#33CCCC"},"#33CCFF":{"selector":"colorset-33CCFF","color":"#33CCFF"},"colorset-33CCFF":{"selector":"colorset-33CCFF","color":"#33CCFF"},"#33FF33":{"selector":"colorset-33FF33","color":"#33FF33"},"colorset-33FF33":{"selector":"colorset-33FF33","color":"#33FF33"},"#33FF66":{"selector":"colorset-33FF66","color":"#33FF66"},"colorset-33FF66":{"selector":"colorset-33FF66","color":"#33FF66"},"#33FF99":{"selector":"colorset-33FF99","color":"#33FF99"},"colorset-33FF99":{"selector":"colorset-33FF99","color":"#33FF99"},"#33FFCC":{"selector":"colorset-33FFCC","color":"#33FFCC"},"colorset-33FFCC":{"selector":"colorset-33FFCC","color":"#33FFCC"},"#33FFFF":{"selector":"colorset-33FFFF","color":"#33FFFF"},"colorset-33FFFF":{"selector":"colorset-33FFFF","color":"#33FFFF"},"#660033":{"selector":"colorset-660033","color":"#660033"},"colorset-660033":{"selector":"colorset-660033","color":"#660033"},"#660066":{"selector":"colorset-660066","color":"#660066"},"colorset-660066":{"selector":"colorset-660066","color":"#660066"},"#660099":{"selector":"colorset-660099","color":"#660099"},"colorset-660099":{"selector":"colorset-660099","color":"#660099"},"#6600CC":{"selector":"colorset-6600CC","color":"#6600CC"},"colorset-6600CC":{"selector":"colorset-6600CC","color":"#6600CC"},"#6600FF":{"selector":"colorset-6600FF","color":"#6600FF"},"colorset-6600FF":{"selector":"colorset-6600FF","color":"#6600FF"},"#663333":{"selector":"colorset-663333","color":"#663333"},"colorset-663333":{"selector":"colorset-663333","color":"#663333"},"#663366":{"selector":"colorset-663366","color":"#663366"},"colorset-663366":{"selector":"colorset-663366","color":"#663366"},"#663399":{"selector":"colorset-663399","color":"#663399"},"colorset-663399":{"selector":"colorset-663399","color":"#663399"},"#6633CC":{"selector":"colorset-6633CC","color":"#6633CC"},"colorset-6633CC":{"selector":"colorset-6633CC","color":"#6633CC"},"#6633FF":{"selector":"colorset-6633FF","color":"#6633FF"},"colorset-6633FF":{"selector":"colorset-6633FF","color":"#6633FF"},"#666633":{"selector":"colorset-666633","color":"#666633"},"colorset-666633":{"selector":"colorset-666633","color":"#666633"},"#666666":{"selector":"colorset-666666","color":"#666666"},"colorset-666666":{"selector":"colorset-666666","color":"#666666"},"#666699":{"selector":"colorset-666699","color":"#666699"},"colorset-666699":{"selector":"colorset-666699","color":"#666699"},"#6666CC":{"selector":"colorset-6666CC","color":"#6666CC"},"colorset-6666CC":{"selector":"colorset-6666CC","color":"#6666CC"},"#6666FF":{"selector":"colorset-6666FF","color":"#6666FF"},"colorset-6666FF":{"selector":"colorset-6666FF","color":"#6666FF"},"#669933":{"selector":"colorset-669933","color":"#669933"},"colorset-669933":{"selector":"colorset-669933","color":"#669933"},"#669966":{"selector":"colorset-669966","color":"#669966"},"colorset-669966":{"selector":"colorset-669966","color":"#669966"},"#669999":{"selector":"colorset-669999","color":"#669999"},"colorset-669999":{"selector":"colorset-669999","color":"#669999"},"#6699CC":{"selector":"colorset-6699CC","color":"#6699CC"},"colorset-6699CC":{"selector":"colorset-6699CC","color":"#6699CC"},"#6699FF":{"selector":"colorset-6699FF","color":"#6699FF"},"colorset-6699FF":{"selector":"colorset-6699FF","color":"#6699FF"},"#66CC33":{"selector":"colorset-66CC33","color":"#66CC33"},"colorset-66CC33":{"selector":"colorset-66CC33","color":"#66CC33"},"#66CC66":{"selector":"colorset-66CC66","color":"#66CC66"},"colorset-66CC66":{"selector":"colorset-66CC66","color":"#66CC66"},"#66CC99":{"selector":"colorset-66CC99","color":"#66CC99"},"colorset-66CC99":{"selector":"colorset-66CC99","color":"#66CC99"},"#66CCCC":{"selector":"colorset-66CCCC","color":"#66CCCC"},"colorset-66CCCC":{"selector":"colorset-66CCCC","color":"#66CCCC"},"#66CCFF":{"selector":"colorset-66CCFF","color":"#66CCFF"},"colorset-66CCFF":{"selector":"colorset-66CCFF","color":"#66CCFF"},"#66FF33":{"selector":"colorset-66FF33","color":"#66FF33"},"colorset-66FF33":{"selector":"colorset-66FF33","color":"#66FF33"},"#66FF66":{"selector":"colorset-66FF66","color":"#66FF66"},"colorset-66FF66":{"selector":"colorset-66FF66","color":"#66FF66"},"#66FF99":{"selector":"colorset-66FF99","color":"#66FF99"},"colorset-66FF99":{"selector":"colorset-66FF99","color":"#66FF99"},"#66FFCC":{"selector":"colorset-66FFCC","color":"#66FFCC"},"colorset-66FFCC":{"selector":"colorset-66FFCC","color":"#66FFCC"},"#66FFFF":{"selector":"colorset-66FFFF","color":"#66FFFF"},"colorset-66FFFF":{"selector":"colorset-66FFFF","color":"#66FFFF"},"#990033":{"selector":"colorset-990033","color":"#990033"},"colorset-990033":{"selector":"colorset-990033","color":"#990033"},"#990066":{"selector":"colorset-990066","color":"#990066"},"colorset-990066":{"selector":"colorset-990066","color":"#990066"},"#990099":{"selector":"colorset-990099","color":"#990099"},"colorset-990099":{"selector":"colorset-990099","color":"#990099"},"#9900CC":{"selector":"colorset-9900CC","color":"#9900CC"},"colorset-9900CC":{"selector":"colorset-9900CC","color":"#9900CC"},"#9900FF":{"selector":"colorset-9900FF","color":"#9900FF"},"colorset-9900FF":{"selector":"colorset-9900FF","color":"#9900FF"},"#993333":{"selector":"colorset-993333","color":"#993333"},"colorset-993333":{"selector":"colorset-993333","color":"#993333"},"#993366":{"selector":"colorset-993366","color":"#993366"},"colorset-993366":{"selector":"colorset-993366","color":"#993366"},"#993399":{"selector":"colorset-993399","color":"#993399"},"colorset-993399":{"selector":"colorset-993399","color":"#993399"},"#9933CC":{"selector":"colorset-9933CC","color":"#9933CC"},"colorset-9933CC":{"selector":"colorset-9933CC","color":"#9933CC"},"#9933FF":{"selector":"colorset-9933FF","color":"#9933FF"},"colorset-9933FF":{"selector":"colorset-9933FF","color":"#9933FF"},"#996633":{"selector":"colorset-996633","color":"#996633"},"colorset-996633":{"selector":"colorset-996633","color":"#996633"},"#996666":{"selector":"colorset-996666","color":"#996666"},"colorset-996666":{"selector":"colorset-996666","color":"#996666"},"#996699":{"selector":"colorset-996699","color":"#996699"},"colorset-996699":{"selector":"colorset-996699","color":"#996699"},"#9966CC":{"selector":"colorset-9966CC","color":"#9966CC"},"colorset-9966CC":{"selector":"colorset-9966CC","color":"#9966CC"},"#9966FF":{"selector":"colorset-9966FF","color":"#9966FF"},"colorset-9966FF":{"selector":"colorset-9966FF","color":"#9966FF"},"#999933":{"selector":"colorset-999933","color":"#999933"},"colorset-999933":{"selector":"colorset-999933","color":"#999933"},"#999966":{"selector":"colorset-999966","color":"#999966"},"colorset-999966":{"selector":"colorset-999966","color":"#999966"},"#999999":{"selector":"colorset-999999","color":"#999999"},"colorset-999999":{"selector":"colorset-999999","color":"#999999"},"#9999CC":{"selector":"colorset-9999CC","color":"#9999CC"},"colorset-9999CC":{"selector":"colorset-9999CC","color":"#9999CC"},"#9999FF":{"selector":"colorset-9999FF","color":"#9999FF"},"colorset-9999FF":{"selector":"colorset-9999FF","color":"#9999FF"},"#99CC33":{"selector":"colorset-99CC33","color":"#99CC33"},"colorset-99CC33":{"selector":"colorset-99CC33","color":"#99CC33"},"#99CC66":{"selector":"colorset-99CC66","color":"#99CC66"},"colorset-99CC66":{"selector":"colorset-99CC66","color":"#99CC66"},"#99CC99":{"selector":"colorset-99CC99","color":"#99CC99"},"colorset-99CC99":{"selector":"colorset-99CC99","color":"#99CC99"},"#99CCCC":{"selector":"colorset-99CCCC","color":"#99CCCC"},"colorset-99CCCC":{"selector":"colorset-99CCCC","color":"#99CCCC"},"#99CCFF":{"selector":"colorset-99CCFF","color":"#99CCFF"},"colorset-99CCFF":{"selector":"colorset-99CCFF","color":"#99CCFF"},"#99FF33":{"selector":"colorset-99FF33","color":"#99FF33"},"colorset-99FF33":{"selector":"colorset-99FF33","color":"#99FF33"},"#99FF66":{"selector":"colorset-99FF66","color":"#99FF66"},"colorset-99FF66":{"selector":"colorset-99FF66","color":"#99FF66"},"#99FF99":{"selector":"colorset-99FF99","color":"#99FF99"},"colorset-99FF99":{"selector":"colorset-99FF99","color":"#99FF99"},"#99FFCC":{"selector":"colorset-99FFCC","color":"#99FFCC"},"colorset-99FFCC":{"selector":"colorset-99FFCC","color":"#99FFCC"},"#99FFFF":{"selector":"colorset-99FFFF","color":"#99FFFF"},"colorset-99FFFF":{"selector":"colorset-99FFFF","color":"#99FFFF"},"#CC0033":{"selector":"colorset-CC0033","color":"#CC0033"},"colorset-CC0033":{"selector":"colorset-CC0033","color":"#CC0033"},"#CC0066":{"selector":"colorset-CC0066","color":"#CC0066"},"colorset-CC0066":{"selector":"colorset-CC0066","color":"#CC0066"},"#CC0099":{"selector":"colorset-CC0099","color":"#CC0099"},"colorset-CC0099":{"selector":"colorset-CC0099","color":"#CC0099"},"#CC00CC":{"selector":"colorset-CC00CC","color":"#CC00CC"},"colorset-CC00CC":{"selector":"colorset-CC00CC","color":"#CC00CC"},"#CC00FF":{"selector":"colorset-CC00FF","color":"#CC00FF"},"colorset-CC00FF":{"selector":"colorset-CC00FF","color":"#CC00FF"},"#CC3333":{"selector":"colorset-CC3333","color":"#CC3333"},"colorset-CC3333":{"selector":"colorset-CC3333","color":"#CC3333"},"#CC3366":{"selector":"colorset-CC3366","color":"#CC3366"},"colorset-CC3366":{"selector":"colorset-CC3366","color":"#CC3366"},"#CC3399":{"selector":"colorset-CC3399","color":"#CC3399"},"colorset-CC3399":{"selector":"colorset-CC3399","color":"#CC3399"},"#CC33CC":{"selector":"colorset-CC33CC","color":"#CC33CC"},"colorset-CC33CC":{"selector":"colorset-CC33CC","color":"#CC33CC"},"#CC33FF":{"selector":"colorset-CC33FF","color":"#CC33FF"},"colorset-CC33FF":{"selector":"colorset-CC33FF","color":"#CC33FF"},"#CC6633":{"selector":"colorset-CC6633","color":"#CC6633"},"colorset-CC6633":{"selector":"colorset-CC6633","color":"#CC6633"},"#CC6666":{"selector":"colorset-CC6666","color":"#CC6666"},"colorset-CC6666":{"selector":"colorset-CC6666","color":"#CC6666"},"#CC6699":{"selector":"colorset-CC6699","color":"#CC6699"},"colorset-CC6699":{"selector":"colorset-CC6699","color":"#CC6699"},"#CC66CC":{"selector":"colorset-CC66CC","color":"#CC66CC"},"colorset-CC66CC":{"selector":"colorset-CC66CC","color":"#CC66CC"},"#CC66FF":{"selector":"colorset-CC66FF","color":"#CC66FF"},"colorset-CC66FF":{"selector":"colorset-CC66FF","color":"#CC66FF"},"#CC9933":{"selector":"colorset-CC9933","color":"#CC9933"},"colorset-CC9933":{"selector":"colorset-CC9933","color":"#CC9933"},"#CC9966":{"selector":"colorset-CC9966","color":"#CC9966"},"colorset-CC9966":{"selector":"colorset-CC9966","color":"#CC9966"},"#CC9999":{"selector":"colorset-CC9999","color":"#CC9999"},"colorset-CC9999":{"selector":"colorset-CC9999","color":"#CC9999"},"#CC99CC":{"selector":"colorset-CC99CC","color":"#CC99CC"},"colorset-CC99CC":{"selector":"colorset-CC99CC","color":"#CC99CC"},"#CC99FF":{"selector":"colorset-CC99FF","color":"#CC99FF"},"colorset-CC99FF":{"selector":"colorset-CC99FF","color":"#CC99FF"},"#CCCC33":{"selector":"colorset-CCCC33","color":"#CCCC33"},"colorset-CCCC33":{"selector":"colorset-CCCC33","color":"#CCCC33"},"#CCCC66":{"selector":"colorset-CCCC66","color":"#CCCC66"},"colorset-CCCC66":{"selector":"colorset-CCCC66","color":"#CCCC66"},"#CCCC99":{"selector":"colorset-CCCC99","color":"#CCCC99"},"colorset-CCCC99":{"selector":"colorset-CCCC99","color":"#CCCC99"},"#CCCCCC":{"selector":"colorset-CCCCCC","color":"#CCCCCC"},"colorset-CCCCCC":{"selector":"colorset-CCCCCC","color":"#CCCCCC"},"#CCCCFF":{"selector":"colorset-CCCCFF","color":"#CCCCFF"},"colorset-CCCCFF":{"selector":"colorset-CCCCFF","color":"#CCCCFF"},"#CCFF33":{"selector":"colorset-CCFF33","color":"#CCFF33"},"colorset-CCFF33":{"selector":"colorset-CCFF33","color":"#CCFF33"},"#CCFF66":{"selector":"colorset-CCFF66","color":"#CCFF66"},"colorset-CCFF66":{"selector":"colorset-CCFF66","color":"#CCFF66"},"#CCFF99":{"selector":"colorset-CCFF99","color":"#CCFF99"},"colorset-CCFF99":{"selector":"colorset-CCFF99","color":"#CCFF99"},"#CCFFCC":{"selector":"colorset-CCFFCC","color":"#CCFFCC"},"colorset-CCFFCC":{"selector":"colorset-CCFFCC","color":"#CCFFCC"},"#CCFFFF":{"selector":"colorset-CCFFFF","color":"#CCFFFF"},"colorset-CCFFFF":{"selector":"colorset-CCFFFF","color":"#CCFFFF"},"#FF0033":{"selector":"colorset-FF0033","color":"#FF0033"},"colorset-FF0033":{"selector":"colorset-FF0033","color":"#FF0033"},"#FF0066":{"selector":"colorset-FF0066","color":"#FF0066"},"colorset-FF0066":{"selector":"colorset-FF0066","color":"#FF0066"},"#FF0099":{"selector":"colorset-FF0099","color":"#FF0099"},"colorset-FF0099":{"selector":"colorset-FF0099","color":"#FF0099"},"#FF00CC":{"selector":"colorset-FF00CC","color":"#FF00CC"},"colorset-FF00CC":{"selector":"colorset-FF00CC","color":"#FF00CC"},"#FF00FF":{"selector":"colorset-FF00FF","color":"#FF00FF"},"colorset-FF00FF":{"selector":"colorset-FF00FF","color":"#FF00FF"},"#FF3333":{"selector":"colorset-FF3333","color":"#FF3333"},"colorset-FF3333":{"selector":"colorset-FF3333","color":"#FF3333"},"#FF3366":{"selector":"colorset-FF3366","color":"#FF3366"},"colorset-FF3366":{"selector":"colorset-FF3366","color":"#FF3366"},"#FF3399":{"selector":"colorset-FF3399","color":"#FF3399"},"colorset-FF3399":{"selector":"colorset-FF3399","color":"#FF3399"},"#FF33CC":{"selector":"colorset-FF33CC","color":"#FF33CC"},"colorset-FF33CC":{"selector":"colorset-FF33CC","color":"#FF33CC"},"#FF33FF":{"selector":"colorset-FF33FF","color":"#FF33FF"},"colorset-FF33FF":{"selector":"colorset-FF33FF","color":"#FF33FF"},"#FF6633":{"selector":"colorset-FF6633","color":"#FF6633"},"colorset-FF6633":{"selector":"colorset-FF6633","color":"#FF6633"},"#FF6666":{"selector":"colorset-FF6666","color":"#FF6666"},"colorset-FF6666":{"selector":"colorset-FF6666","color":"#FF6666"},"#FF6699":{"selector":"colorset-FF6699","color":"#FF6699"},"colorset-FF6699":{"selector":"colorset-FF6699","color":"#FF6699"},"#FF66CC":{"selector":"colorset-FF66CC","color":"#FF66CC"},"colorset-FF66CC":{"selector":"colorset-FF66CC","color":"#FF66CC"},"#FF66FF":{"selector":"colorset-FF66FF","color":"#FF66FF"},"colorset-FF66FF":{"selector":"colorset-FF66FF","color":"#FF66FF"},"#FF9933":{"selector":"colorset-FF9933","color":"#FF9933"},"colorset-FF9933":{"selector":"colorset-FF9933","color":"#FF9933"},"#FF9966":{"selector":"colorset-FF9966","color":"#FF9966"},"colorset-FF9966":{"selector":"colorset-FF9966","color":"#FF9966"},"#FF9999":{"selector":"colorset-FF9999","color":"#FF9999"},"colorset-FF9999":{"selector":"colorset-FF9999","color":"#FF9999"},"#FF99CC":{"selector":"colorset-FF99CC","color":"#FF99CC"},"colorset-FF99CC":{"selector":"colorset-FF99CC","color":"#FF99CC"},"#FF99FF":{"selector":"colorset-FF99FF","color":"#FF99FF"},"colorset-FF99FF":{"selector":"colorset-FF99FF","color":"#FF99FF"},"#FFCC33":{"selector":"colorset-FFCC33","color":"#FFCC33"},"colorset-FFCC33":{"selector":"colorset-FFCC33","color":"#FFCC33"},"#FFCC66":{"selector":"colorset-FFCC66","color":"#FFCC66"},"colorset-FFCC66":{"selector":"colorset-FFCC66","color":"#FFCC66"},"#FFCC99":{"selector":"colorset-FFCC99","color":"#FFCC99"},"colorset-FFCC99":{"selector":"colorset-FFCC99","color":"#FFCC99"},"#FFCCCC":{"selector":"colorset-FFCCCC","color":"#FFCCCC"},"colorset-FFCCCC":{"selector":"colorset-FFCCCC","color":"#FFCCCC"},"#FFCCFF":{"selector":"colorset-FFCCFF","color":"#FFCCFF"},"colorset-FFCCFF":{"selector":"colorset-FFCCFF","color":"#FFCCFF"},"#FFFF33":{"selector":"colorset-FFFF33","color":"#FFFF33"},"colorset-FFFF33":{"selector":"colorset-FFFF33","color":"#FFFF33"},"#FFFF66":{"selector":"colorset-FFFF66","color":"#FFFF66"},"colorset-FFFF66":{"selector":"colorset-FFFF66","color":"#FFFF66"},"#FFFF99":{"selector":"colorset-FFFF99","color":"#FFFF99"},"colorset-FFFF99":{"selector":"colorset-FFFF99","color":"#FFFF99"},"#FFFFCC":{"selector":"colorset-FFFFCC","color":"#FFFFCC"},"colorset-FFFFCC":{"selector":"colorset-FFFFCC","color":"#FFFFCC"},"#FFFFFF":{"selector":"colorset-FFFFFF","color":"#FFFFFF"},"colorset-FFFFFF":{"selector":"colorset-FFFFFF","color":"#FFFFFF"}}} --------------------------------------------------------------------------------