update with changes from Production branch
2
.ruby-gemset
Normal file
@ -0,0 +1,2 @@
|
||||
thingspeak
|
||||
|
2
.ruby-version
Normal file
@ -0,0 +1,2 @@
|
||||
ruby-2.1.0
|
||||
|
65
Gemfile
@ -1,18 +1,61 @@
|
||||
source 'http://rubygems.org'
|
||||
|
||||
gem 'rails', '3.2.1'
|
||||
gem 'rails', '4.0.2'
|
||||
gem 'jquery-rails', '3.0.4'
|
||||
gem 'rails_autolink'
|
||||
gem 'mysql2'
|
||||
gem 'authlogic'
|
||||
gem 'jquery-rails', '2.0.1'
|
||||
gem 'twitter_oauth', git: 'git://github.com/moomerman/twitter_oauth.git'
|
||||
gem 'therubyracer'
|
||||
gem 'exception_notification'
|
||||
gem 'nested_form'
|
||||
gem 'dalli'
|
||||
gem 'kgio'
|
||||
gem 'will_paginate', '~> 3.0.pre2'
|
||||
gem 'nokogiri'
|
||||
gem 'acts_as_tree'
|
||||
gem 'acts_as_list'
|
||||
gem 'gravatarify'
|
||||
gem 'dynamic_form'
|
||||
gem 'geokit'
|
||||
gem 'redis'
|
||||
gem 'resque-scheduler', '2.3.1', :require => 'resque_scheduler'
|
||||
gem 'resque-timeout'
|
||||
gem 'daemons'
|
||||
#gem 'db2fog' (not compatible with Rails 4, see https://github.com/hakanensari/db2fog for Rails 4 version)
|
||||
gem 'simplificator-rwebthumb', :git => "git://github.com/simplificator/rwebthumb.git"
|
||||
gem 'tweetstream'
|
||||
gem 'capistrano', '~> 2.15.4'
|
||||
gem 'rack-utf8_sanitizer'
|
||||
gem 'newrelic_rpm'
|
||||
gem 'actionpack-xml_parser'
|
||||
|
||||
# To use debugger
|
||||
# gem 'ruby-debug'
|
||||
|
||||
# Bundle gems for the local environment. Make sure to
|
||||
# put test-only gems in this group so their generators
|
||||
# and rake tasks are available in development mode:
|
||||
group :development, :test do
|
||||
gem 'rspec', '>= 2.0.0.beta.20'
|
||||
gem 'rspec-rails', '>= 2.0.0.beta.20'
|
||||
gem 'autotest'
|
||||
gem 'webrat'
|
||||
gem 'annotate'
|
||||
# assets
|
||||
gem 'sass-rails', " ~> 4.0"
|
||||
gem 'coffee-rails', " ~> 4.0"
|
||||
gem 'uglifier'
|
||||
|
||||
group :development do
|
||||
gem 'annotate', '~> 2.6.1'
|
||||
gem 'quiet_assets'
|
||||
gem 'thin'
|
||||
gem 'i18n-tasks', '~> 0.2.10'
|
||||
end
|
||||
|
||||
group :test do
|
||||
gem 'sqlite3-ruby', '1.3.3', :require => 'sqlite3'
|
||||
gem 'rspec-rails', '~> 2.14.1'
|
||||
gem 'spork'
|
||||
gem 'factory_girl_rails'
|
||||
gem 'webrat'
|
||||
gem 'faker'
|
||||
gem 'json_spec'
|
||||
gem 'autotest'
|
||||
gem 'autotest-rails'
|
||||
gem 'ZenTest'
|
||||
gem 'database_cleaner', '~> 1.2.0'
|
||||
end
|
||||
|
||||
|
388
Gemfile.lock
@ -1,123 +1,333 @@
|
||||
GIT
|
||||
remote: git://github.com/moomerman/twitter_oauth.git
|
||||
revision: 04e6bbfe635a376cae342d234214cdab864fe797
|
||||
specs:
|
||||
twitter_oauth (0.4.94)
|
||||
json (>= 1.8.0)
|
||||
mime-types (>= 1.16)
|
||||
oauth (>= 0.4.7)
|
||||
|
||||
GIT
|
||||
remote: git://github.com/simplificator/rwebthumb.git
|
||||
revision: dbd96a62787201f7bf901c39c8df003a22b45ec9
|
||||
specs:
|
||||
simplificator-rwebthumb (0.3.4)
|
||||
|
||||
GEM
|
||||
remote: http://rubygems.org/
|
||||
specs:
|
||||
ZenTest (4.6.2)
|
||||
actionmailer (3.2.1)
|
||||
actionpack (= 3.2.1)
|
||||
mail (~> 2.4.0)
|
||||
actionpack (3.2.1)
|
||||
activemodel (= 3.2.1)
|
||||
activesupport (= 3.2.1)
|
||||
builder (~> 3.0.0)
|
||||
ZenTest (4.9.5)
|
||||
actionmailer (4.0.2)
|
||||
actionpack (= 4.0.2)
|
||||
mail (~> 2.5.4)
|
||||
actionpack (4.0.2)
|
||||
activesupport (= 4.0.2)
|
||||
builder (~> 3.1.0)
|
||||
erubis (~> 2.7.0)
|
||||
journey (~> 1.0.1)
|
||||
rack (~> 1.4.0)
|
||||
rack-cache (~> 1.1)
|
||||
rack-test (~> 0.6.1)
|
||||
sprockets (~> 2.1.2)
|
||||
activemodel (3.2.1)
|
||||
activesupport (= 3.2.1)
|
||||
builder (~> 3.0.0)
|
||||
activerecord (3.2.1)
|
||||
activemodel (= 3.2.1)
|
||||
activesupport (= 3.2.1)
|
||||
arel (~> 3.0.0)
|
||||
tzinfo (~> 0.3.29)
|
||||
activeresource (3.2.1)
|
||||
activemodel (= 3.2.1)
|
||||
activesupport (= 3.2.1)
|
||||
activesupport (3.2.1)
|
||||
i18n (~> 0.6)
|
||||
multi_json (~> 1.0)
|
||||
annotate (2.4.0)
|
||||
arel (3.0.0)
|
||||
authlogic (3.1.0)
|
||||
activerecord (>= 3.0.7)
|
||||
activerecord (>= 3.0.7)
|
||||
rack (~> 1.5.2)
|
||||
rack-test (~> 0.6.2)
|
||||
actionpack-xml_parser (1.0.1)
|
||||
actionpack (>= 4.0.0.rc1)
|
||||
activemodel (4.0.2)
|
||||
activesupport (= 4.0.2)
|
||||
builder (~> 3.1.0)
|
||||
activerecord (4.0.2)
|
||||
activemodel (= 4.0.2)
|
||||
activerecord-deprecated_finders (~> 1.0.2)
|
||||
activesupport (= 4.0.2)
|
||||
arel (~> 4.0.0)
|
||||
activerecord-deprecated_finders (1.0.3)
|
||||
activesupport (4.0.2)
|
||||
i18n (~> 0.6, >= 0.6.4)
|
||||
minitest (~> 4.2)
|
||||
multi_json (~> 1.3)
|
||||
thread_safe (~> 0.1)
|
||||
tzinfo (~> 0.3.37)
|
||||
acts_as_list (0.3.0)
|
||||
activerecord (>= 3.0)
|
||||
acts_as_tree (1.5.0)
|
||||
activerecord (>= 3.0.0)
|
||||
addressable (2.3.5)
|
||||
annotate (2.6.1)
|
||||
activerecord (>= 2.3.0)
|
||||
rake (>= 0.8.7)
|
||||
arel (4.0.1)
|
||||
atomic (1.1.14)
|
||||
authlogic (3.3.0)
|
||||
activerecord (>= 3.2)
|
||||
activesupport (>= 3.2)
|
||||
autotest (4.4.6)
|
||||
ZenTest (>= 4.4.1)
|
||||
builder (3.0.0)
|
||||
diff-lcs (1.1.3)
|
||||
autotest-rails (4.2.1)
|
||||
ZenTest (~> 4.5)
|
||||
builder (3.1.4)
|
||||
capistrano (2.15.4)
|
||||
highline
|
||||
net-scp (>= 1.0.0)
|
||||
net-sftp (>= 2.0.0)
|
||||
net-ssh (>= 2.0.14)
|
||||
net-ssh-gateway (>= 1.1.0)
|
||||
coffee-rails (4.0.1)
|
||||
coffee-script (>= 2.2.0)
|
||||
railties (>= 4.0.0, < 5.0)
|
||||
coffee-script (2.2.0)
|
||||
coffee-script-source
|
||||
execjs
|
||||
coffee-script-source (1.6.3)
|
||||
cookiejar (0.3.0)
|
||||
daemons (1.1.9)
|
||||
dalli (2.7.0)
|
||||
database_cleaner (1.2.0)
|
||||
diff-lcs (1.2.5)
|
||||
dynamic_form (1.1.4)
|
||||
easy_translate (0.4.0)
|
||||
json
|
||||
thread
|
||||
thread_safe
|
||||
em-http-request (1.1.2)
|
||||
addressable (>= 2.3.4)
|
||||
cookiejar
|
||||
em-socksify (>= 0.3)
|
||||
eventmachine (>= 1.0.3)
|
||||
http_parser.rb (>= 0.6.0)
|
||||
em-socksify (0.3.0)
|
||||
eventmachine (>= 1.0.0.beta.4)
|
||||
em-twitter (0.3.2)
|
||||
eventmachine (~> 1.0)
|
||||
http_parser.rb (~> 0.6)
|
||||
simple_oauth (~> 0.2)
|
||||
erubis (2.7.0)
|
||||
hike (1.2.1)
|
||||
i18n (0.6.0)
|
||||
journey (1.0.1)
|
||||
jquery-rails (2.0.1)
|
||||
railties (>= 3.2.0, < 5.0)
|
||||
thor (~> 0.14)
|
||||
json (1.6.5)
|
||||
mail (2.4.1)
|
||||
i18n (>= 0.4.0)
|
||||
eventmachine (1.0.3)
|
||||
exception_notification (4.0.1)
|
||||
actionmailer (>= 3.0.4)
|
||||
activesupport (>= 3.0.4)
|
||||
execjs (2.0.2)
|
||||
factory_girl (4.3.0)
|
||||
activesupport (>= 3.0.0)
|
||||
factory_girl_rails (4.3.0)
|
||||
factory_girl (~> 4.3.0)
|
||||
railties (>= 3.0.0)
|
||||
faker (1.2.0)
|
||||
i18n (~> 0.5)
|
||||
faraday (0.8.9)
|
||||
multipart-post (~> 1.2.0)
|
||||
geokit (1.8.4)
|
||||
multi_json (>= 1.3.2)
|
||||
gravatarify (3.1.0)
|
||||
highline (1.6.20)
|
||||
hike (1.2.3)
|
||||
http_parser.rb (0.6.0)
|
||||
i18n (0.6.9)
|
||||
i18n-tasks (0.2.18)
|
||||
activesupport
|
||||
easy_translate (>= 0.4.0)
|
||||
erubis
|
||||
highline
|
||||
rake
|
||||
term-ansicolor
|
||||
terminal-table
|
||||
jquery-rails (3.0.4)
|
||||
railties (>= 3.0, < 5.0)
|
||||
thor (>= 0.14, < 2.0)
|
||||
json (1.8.1)
|
||||
json_spec (1.1.1)
|
||||
multi_json (~> 1.0)
|
||||
rspec (~> 2.0)
|
||||
kgio (2.8.1)
|
||||
libv8 (3.16.14.3)
|
||||
mail (2.5.4)
|
||||
mime-types (~> 1.16)
|
||||
treetop (~> 1.4.8)
|
||||
mime-types (1.17.2)
|
||||
multi_json (1.0.4)
|
||||
mysql2 (0.3.11)
|
||||
nokogiri (1.5.0)
|
||||
mime-types (1.25.1)
|
||||
mini_portile (0.5.2)
|
||||
minitest (4.7.5)
|
||||
mono_logger (1.1.0)
|
||||
multi_json (1.8.4)
|
||||
multipart-post (1.2.0)
|
||||
mysql2 (0.3.14)
|
||||
nested_form (0.3.2)
|
||||
net-scp (1.1.2)
|
||||
net-ssh (>= 2.6.5)
|
||||
net-sftp (2.1.2)
|
||||
net-ssh (>= 2.6.5)
|
||||
net-ssh (2.7.0)
|
||||
net-ssh-gateway (1.2.0)
|
||||
net-ssh (>= 2.6.5)
|
||||
newrelic_rpm (3.7.1.182)
|
||||
nokogiri (1.6.1)
|
||||
mini_portile (~> 0.5.0)
|
||||
oauth (0.4.7)
|
||||
polyglot (0.3.3)
|
||||
rack (1.4.1)
|
||||
rack-cache (1.1)
|
||||
rack (>= 0.4)
|
||||
rack-ssl (1.3.2)
|
||||
quiet_assets (1.0.2)
|
||||
railties (>= 3.1, < 5.0)
|
||||
rack (1.5.2)
|
||||
rack-protection (1.5.2)
|
||||
rack
|
||||
rack-test (0.6.1)
|
||||
rack-test (0.6.2)
|
||||
rack (>= 1.0)
|
||||
rails (3.2.1)
|
||||
actionmailer (= 3.2.1)
|
||||
actionpack (= 3.2.1)
|
||||
activerecord (= 3.2.1)
|
||||
activeresource (= 3.2.1)
|
||||
activesupport (= 3.2.1)
|
||||
bundler (~> 1.0)
|
||||
railties (= 3.2.1)
|
||||
railties (3.2.1)
|
||||
actionpack (= 3.2.1)
|
||||
activesupport (= 3.2.1)
|
||||
rack-ssl (~> 1.3.2)
|
||||
rack-utf8_sanitizer (1.1.0)
|
||||
rack (~> 1.0)
|
||||
rails (4.0.2)
|
||||
actionmailer (= 4.0.2)
|
||||
actionpack (= 4.0.2)
|
||||
activerecord (= 4.0.2)
|
||||
activesupport (= 4.0.2)
|
||||
bundler (>= 1.3.0, < 2.0)
|
||||
railties (= 4.0.2)
|
||||
sprockets-rails (~> 2.0.0)
|
||||
rails_autolink (1.1.5)
|
||||
rails (> 3.1)
|
||||
railties (4.0.2)
|
||||
actionpack (= 4.0.2)
|
||||
activesupport (= 4.0.2)
|
||||
rake (>= 0.8.7)
|
||||
rdoc (~> 3.4)
|
||||
thor (~> 0.14.6)
|
||||
rake (0.9.2.2)
|
||||
rdoc (3.12)
|
||||
json (~> 1.4)
|
||||
rspec (2.8.0)
|
||||
rspec-core (~> 2.8.0)
|
||||
rspec-expectations (~> 2.8.0)
|
||||
rspec-mocks (~> 2.8.0)
|
||||
rspec-core (2.8.0)
|
||||
rspec-expectations (2.8.0)
|
||||
diff-lcs (~> 1.1.2)
|
||||
rspec-mocks (2.8.0)
|
||||
rspec-rails (2.8.1)
|
||||
thor (>= 0.18.1, < 2.0)
|
||||
rake (10.1.1)
|
||||
redis (3.0.7)
|
||||
redis-namespace (1.4.1)
|
||||
redis (~> 3.0.4)
|
||||
ref (1.0.5)
|
||||
resque (1.25.1)
|
||||
mono_logger (~> 1.0)
|
||||
multi_json (~> 1.0)
|
||||
redis-namespace (~> 1.2)
|
||||
sinatra (>= 0.9.2)
|
||||
vegas (~> 0.1.2)
|
||||
resque-scheduler (2.3.1)
|
||||
redis (>= 3.0.0)
|
||||
resque (~> 1.25)
|
||||
rufus-scheduler (~> 2.0)
|
||||
resque-timeout (1.0.0)
|
||||
resque (~> 1.0)
|
||||
rspec (2.14.1)
|
||||
rspec-core (~> 2.14.0)
|
||||
rspec-expectations (~> 2.14.0)
|
||||
rspec-mocks (~> 2.14.0)
|
||||
rspec-core (2.14.7)
|
||||
rspec-expectations (2.14.4)
|
||||
diff-lcs (>= 1.1.3, < 2.0)
|
||||
rspec-mocks (2.14.4)
|
||||
rspec-rails (2.14.1)
|
||||
actionpack (>= 3.0)
|
||||
activemodel (>= 3.0)
|
||||
activesupport (>= 3.0)
|
||||
railties (>= 3.0)
|
||||
rspec (~> 2.8.0)
|
||||
sprockets (2.1.2)
|
||||
rspec-core (~> 2.14.0)
|
||||
rspec-expectations (~> 2.14.0)
|
||||
rspec-mocks (~> 2.14.0)
|
||||
rufus-scheduler (2.0.24)
|
||||
tzinfo (>= 0.3.22)
|
||||
sass (3.2.13)
|
||||
sass-rails (4.0.1)
|
||||
railties (>= 4.0.0, < 5.0)
|
||||
sass (>= 3.1.10)
|
||||
sprockets-rails (~> 2.0.0)
|
||||
simple_oauth (0.2.0)
|
||||
sinatra (1.4.4)
|
||||
rack (~> 1.4)
|
||||
rack-protection (~> 1.4)
|
||||
tilt (~> 1.3, >= 1.3.4)
|
||||
spork (0.9.2)
|
||||
sprockets (2.10.1)
|
||||
hike (~> 1.2)
|
||||
multi_json (~> 1.0)
|
||||
rack (~> 1.0)
|
||||
tilt (~> 1.1, != 1.3.0)
|
||||
thor (0.14.6)
|
||||
tilt (1.3.3)
|
||||
treetop (1.4.10)
|
||||
sprockets-rails (2.0.1)
|
||||
actionpack (>= 3.0)
|
||||
activesupport (>= 3.0)
|
||||
sprockets (~> 2.8)
|
||||
sqlite3 (1.3.8)
|
||||
sqlite3-ruby (1.3.3)
|
||||
sqlite3 (>= 1.3.3)
|
||||
term-ansicolor (1.2.2)
|
||||
tins (~> 0.8)
|
||||
terminal-table (1.4.5)
|
||||
therubyracer (0.12.0)
|
||||
libv8 (~> 3.16.14.0)
|
||||
ref
|
||||
thin (1.6.1)
|
||||
daemons (>= 1.0.9)
|
||||
eventmachine (>= 1.0.0)
|
||||
rack (>= 1.0.0)
|
||||
thor (0.18.1)
|
||||
thread (0.1.3)
|
||||
thread_safe (0.1.3)
|
||||
atomic
|
||||
tilt (1.4.1)
|
||||
tins (0.13.1)
|
||||
treetop (1.4.15)
|
||||
polyglot
|
||||
polyglot (>= 0.3.1)
|
||||
tzinfo (0.3.31)
|
||||
tweetstream (2.6.0)
|
||||
daemons (~> 1.1)
|
||||
em-http-request (>= 1.1.1)
|
||||
em-twitter (~> 0.3)
|
||||
multi_json (~> 1.3)
|
||||
twitter (~> 4.8)
|
||||
twitter (4.8.1)
|
||||
faraday (~> 0.8, < 0.10)
|
||||
multi_json (~> 1.0)
|
||||
simple_oauth (~> 0.2)
|
||||
tzinfo (0.3.38)
|
||||
uglifier (2.4.0)
|
||||
execjs (>= 0.3.0)
|
||||
json (>= 1.8.0)
|
||||
vegas (0.1.11)
|
||||
rack (>= 1.0.0)
|
||||
webrat (0.7.3)
|
||||
nokogiri (>= 1.2.0)
|
||||
rack (>= 1.0)
|
||||
rack-test (>= 0.5.3)
|
||||
will_paginate (3.0.5)
|
||||
|
||||
PLATFORMS
|
||||
ruby
|
||||
|
||||
DEPENDENCIES
|
||||
annotate
|
||||
ZenTest
|
||||
actionpack-xml_parser
|
||||
acts_as_list
|
||||
acts_as_tree
|
||||
annotate (~> 2.6.1)
|
||||
authlogic
|
||||
autotest
|
||||
jquery-rails (= 2.0.1)
|
||||
autotest-rails
|
||||
capistrano (~> 2.15.4)
|
||||
coffee-rails (~> 4.0)
|
||||
daemons
|
||||
dalli
|
||||
database_cleaner (~> 1.2.0)
|
||||
dynamic_form
|
||||
exception_notification
|
||||
factory_girl_rails
|
||||
faker
|
||||
geokit
|
||||
gravatarify
|
||||
i18n-tasks (~> 0.2.10)
|
||||
jquery-rails (= 3.0.4)
|
||||
json_spec
|
||||
kgio
|
||||
mysql2
|
||||
rails (= 3.2.1)
|
||||
rspec (>= 2.0.0.beta.20)
|
||||
rspec-rails (>= 2.0.0.beta.20)
|
||||
nested_form
|
||||
newrelic_rpm
|
||||
nokogiri
|
||||
quiet_assets
|
||||
rack-utf8_sanitizer
|
||||
rails (= 4.0.2)
|
||||
rails_autolink
|
||||
redis
|
||||
resque-scheduler (= 2.3.1)
|
||||
resque-timeout
|
||||
rspec-rails (~> 2.14.1)
|
||||
sass-rails (~> 4.0)
|
||||
simplificator-rwebthumb!
|
||||
spork
|
||||
sqlite3-ruby (= 1.3.3)
|
||||
therubyracer
|
||||
thin
|
||||
tweetstream
|
||||
twitter_oauth!
|
||||
uglifier
|
||||
webrat
|
||||
will_paginate (~> 3.0.pre2)
|
||||
|
@ -24,7 +24,7 @@ h3. Run The Application
|
||||
|
||||
1. Start the server: rails server
|
||||
|
||||
Your application will now be running at http://localhost:3000/
|
||||
Your application will now be running at http://localhost:3000/
|
||||
|
||||
h2. Changing Application Text
|
||||
|
||||
@ -36,14 +36,9 @@ h2. (Optional) Memcached Support and Rate Limiting
|
||||
|
||||
Please see our "official memcached fork":https://github.com/llawlor/thingspeak
|
||||
|
||||
h2. (Optional) HTTP GET Support
|
||||
|
||||
By default the application will accept channel updates via GET or POST. To only allow POSTs, change the value of GET_SUPPORT found in config/initializers/constants.rb
|
||||
|
||||
h2. (Optional) Email Setup
|
||||
|
||||
Set your domain, user_name, and password in config/environment.rb
|
||||
Also set your password reset link in app/controllers/mailer.controller.rb line 14 (and uncomment the line)
|
||||
|
||||
h1. Installation on clean install of Ubuntu 10.10 and 11.10
|
||||
|
||||
@ -57,8 +52,8 @@ Start Terminal
|
||||
|
||||
<code>bash < <(curl -s https://raw.github.com/wayneeseguin/rvm/master/binscripts/rvm-installer )</code>
|
||||
<code>echo '[[ -s "$HOME/.rvm/scripts/rvm" ]] && . "$HOME/.rvm/scripts/rvm" # Load RVM function' >> ~/.bash_profile</code>
|
||||
|
||||
<code>source ~/.bash_profile</code>
|
||||
|
||||
<code>source .bash_profile</code>
|
||||
|
||||
<code>sudo aptitude install mysql-server libmysqlclient-dev libmysql-ruby</code>
|
||||
Note: Enter and confirm a new MySQL password
|
||||
@ -83,3 +78,4 @@ Start Terminal
|
||||
<code>rake db:schema:load</code>
|
||||
|
||||
<code>rails server</code>
|
||||
|
||||
|
BIN
app/assets/images/GitHub_ThingSpeak_API.jpg
Normal file
After Width: | Height: | Size: 26 KiB |
BIN
app/assets/images/eye.png
Normal file
After Width: | Height: | Size: 3.2 KiB |
BIN
app/assets/images/flag_gray.gif
Normal file
After Width: | Height: | Size: 983 B |
BIN
app/assets/images/flag_red.gif
Normal file
After Width: | Height: | Size: 987 B |
BIN
app/assets/images/front.png
Normal file
After Width: | Height: | Size: 17 KiB |
BIN
app/assets/images/icon_location_24.png
Normal file
After Width: | Height: | Size: 618 B |
BIN
app/assets/images/icon_rss.gif
Normal file
After Width: | Height: | Size: 1008 B |
BIN
app/assets/images/icons/InfoBox.png
Executable file
After Width: | Height: | Size: 837 B |
BIN
app/assets/images/icons/Locked.png
Normal file
After Width: | Height: | Size: 1.8 KiB |
BIN
app/assets/images/icons/Public_32.png
Normal file
After Width: | Height: | Size: 4.7 KiB |
BIN
app/assets/images/icons/Unlocked.png
Normal file
After Width: | Height: | Size: 1.6 KiB |
BIN
app/assets/images/icons/delete.png
Executable file
After Width: | Height: | Size: 715 B |
BIN
app/assets/images/my_house_status_update.png
Normal file
After Width: | Height: | Size: 13 KiB |
BIN
app/assets/images/rails.png
Normal file
After Width: | Height: | Size: 6.5 KiB |
BIN
app/assets/images/shading.png
Normal file
After Width: | Height: | Size: 565 B |
BIN
app/assets/images/social_sensor_network_logo.png
Normal file
After Width: | Height: | Size: 15 KiB |
BIN
app/assets/images/social_sensor_network_main.png
Normal file
After Width: | Height: | Size: 130 KiB |
BIN
app/assets/images/thingspeak_logo.png
Normal file
After Width: | Height: | Size: 1.6 KiB |
@ -1,9 +1,21 @@
|
||||
// This is a manifest file that'll be compiled into including all the files listed below.
|
||||
// Add new JavaScript/Coffee code in separate files in this directory and they'll automatically
|
||||
// be included in the compiled file accessible from http://example.com/assets/application.js
|
||||
// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
|
||||
// the compiled file.
|
||||
//
|
||||
//= require jquery
|
||||
// Place your application-specific JavaScript functions and classes here
|
||||
// This file is automatically included by javascript_include_tag :defaults
|
||||
|
||||
//= require jquery_ujs
|
||||
//= require_tree .
|
||||
//= require ./jquery.cookie.js
|
||||
//= require ./highcharts.js
|
||||
//= require ./exporting.js
|
||||
//= require ./updateChart.js
|
||||
//= require ./timeago.min.js
|
||||
//= require ./jquery.shorten.min.js
|
||||
//= require ./nested_form.js
|
||||
//= require ./noapi.js
|
||||
//= require ./rest.js
|
||||
//= require ./tabby.js
|
||||
//= require ./validate.min.js
|
||||
//= require ./channels.js
|
||||
//= require ./sidebar.js
|
||||
//= require ./prettify.js
|
||||
//= require ./docs.js
|
||||
//= require ./custom.js
|
||||
|
||||
|
21
app/assets/javascripts/channels.js
Normal file
@ -0,0 +1,21 @@
|
||||
$(function () {
|
||||
$("[id^=showsite]").each (
|
||||
function() {
|
||||
var element = this;
|
||||
$(element).shorten(
|
||||
{
|
||||
width:235,
|
||||
tooltip:true,
|
||||
tail: '...'
|
||||
|
||||
});
|
||||
});
|
||||
$("div.progressbar").each (
|
||||
function () {
|
||||
var element = this;
|
||||
$(element).progressbar(
|
||||
{
|
||||
value: parseInt($(element).attr("rel"))
|
||||
});
|
||||
});
|
||||
});
|
11
app/assets/javascripts/custom.js
Normal file
@ -0,0 +1,11 @@
|
||||
// when the document is ready
|
||||
$(document).ready(function() {
|
||||
|
||||
// show form to add a talkback command
|
||||
$('#talkback_command_add').click(function() {
|
||||
$(this).hide();
|
||||
$('#talkback_command_add_form').removeClass('hide');
|
||||
});
|
||||
|
||||
});
|
||||
|
23
app/assets/javascripts/docs.js
Normal file
@ -0,0 +1,23 @@
|
||||
$(document).ready(function() {
|
||||
|
||||
// when a response is clicked
|
||||
$('.response').click(function() {
|
||||
// get the response type
|
||||
var response_type = $(this).data('response_type');
|
||||
|
||||
// remove active responses
|
||||
$('.response').removeClass('active');
|
||||
|
||||
// add active response
|
||||
$('.response-' + response_type).addClass('active');
|
||||
|
||||
// hide other formats
|
||||
$('.format').hide();
|
||||
|
||||
// show this format
|
||||
$('.format-' + response_type).show();
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
21
app/assets/javascripts/exporting.js
Normal file
@ -0,0 +1,21 @@
|
||||
/*
|
||||
Highcharts JS v2.1.4 (2011-03-02)
|
||||
Exporting module
|
||||
|
||||
(c) 2010 Torstein H?nsi
|
||||
|
||||
License: www.highcharts.com/license
|
||||
*/
|
||||
(function(){var k=Highcharts,y=k.Chart,C=k.addEvent,r=k.createElement,z=k.discardElement,u=k.css,w=k.merge,s=k.each,p=k.extend,D=Math.max,q=document,E=window,A="ontouchstart"in q.documentElement,B=k.setOptions({lang:{downloadPNG:"Download PNG image",downloadJPEG:"Download JPEG image",downloadPDF:"Download PDF document",downloadSVG:"Download SVG vector image",exportButtonTitle:"Export to raster or vector image",printButtonTitle:"Print the chart"}});B.navigation={menuStyle:{border:"1px solid #A0A0A0",
|
||||
background:"#FFFFFF"},menuItemStyle:{padding:"0 5px",background:"none",color:"#303030",fontSize:A?"14px":"11px"},menuItemHoverStyle:{background:"#4572A5",color:"#FFFFFF"},buttonOptions:{align:"right",backgroundColor:{linearGradient:[0,0,0,20],stops:[[0.4,"#F7F7F7"],[0.6,"#E3E3E3"]]},borderColor:"#B0B0B0",borderRadius:3,borderWidth:1,height:20,hoverBorderColor:"#909090",hoverSymbolFill:"#81A7CF",hoverSymbolStroke:"#4572A5",symbolFill:"#E0E0E0",symbolStroke:"#A0A0A0",symbolX:11.5,symbolY:10.5,verticalAlign:"top",
|
||||
width:24,y:10}};B.exporting={type:"image/png",url:"http://export.highcharts.com/",width:800,buttons:{exportButton:{symbol:"exportIcon",x:-10,symbolFill:"#A8BF77",hoverSymbolFill:"#768F3E",_titleKey:"exportButtonTitle",menuItems:[{textKey:"downloadPNG",onclick:function(){this.exportChart()}},{textKey:"downloadJPEG",onclick:function(){this.exportChart({type:"image/jpeg"})}},{textKey:"downloadPDF",onclick:function(){this.exportChart({type:"application/pdf"})}},{textKey:"downloadSVG",onclick:function(){this.exportChart({type:"image/svg+xml"})}}]},
|
||||
printButton:{symbol:"printIcon",x:-36,symbolFill:"#B5C9DF",hoverSymbolFill:"#779ABF",_titleKey:"printButtonTitle",onclick:function(){this.print()}}}};p(y.prototype,{getSVG:function(b){var c=this,a,f,d,i,e,h,j=w(c.options,b);if(!q.createElementNS)q.createElementNS=function(l,g){var n=q.createElement(g);n.getBBox=function(){return c.renderer.Element.prototype.getBBox.apply({element:n})};return n};a=r("div",null,{position:"absolute",top:"-9999em",width:c.chartWidth+"px",height:c.chartHeight+"px"},q.body);
|
||||
p(j.chart,{renderTo:a,forExport:true});j.exporting.enabled=false;j.chart.plotBackgroundImage=null;j.series=[];s(c.series,function(l){d=l.options;d.animation=false;d.showCheckbox=false;if(d&&d.marker&&/^url\(/.test(d.marker.symbol))d.marker.symbol="circle";d.data=[];s(l.data,function(g){i=g.config;e={x:g.x,y:g.y,name:g.name};typeof i=="object"&&g.config&&i.constructor!=Array&&p(e,i);d.data.push(e);(h=g.config&&g.config.marker)&&/^url\(/.test(h.symbol)&&delete h.symbol});j.series.push(d)});b=new Highcharts.Chart(j);
|
||||
f=b.container.innerHTML;j=null;b.destroy();z(a);f=f.replace(/zIndex="[^"]+"/g,"").replace(/isShadow="[^"]+"/g,"").replace(/symbolName="[^"]+"/g,"").replace(/jQuery[0-9]+="[^"]+"/g,"").replace(/isTracker="[^"]+"/g,"").replace(/url\([^#]+#/g,"url(#").replace(/id=([^" >]+)/g,'id="$1"').replace(/class=([^" ]+)/g,'class="$1"').replace(/ transform /g," ").replace(/:(path|rect)/g,"$1").replace(/style="([^"]+)"/g,function(l){return l.toLowerCase()});f=f.replace(/(url\(#highcharts-[0-9]+)"/g,"$1").replace(/"/g,
|
||||
"'");if(f.match(/ xmlns="/g).length==2)f=f.replace(/xmlns="[^"]+"/,"");return f},exportChart:function(b,c){var a,f=this.getSVG(c);b=w(this.options.exporting,b);a=r("form",{method:"post",action:b.url},{display:"none"},q.body);s(["filename","type","width","svg"],function(d){r("input",{type:"hidden",name:d,value:{filename:b.filename||"chart",type:b.type,width:b.width,svg:f}[d]},null,a)});a.submit();z(a)},print:function(){var b=this,c=b.container,a=[],f=c.parentNode,d=q.body,i=d.childNodes;if(!b.isPrinting){b.isPrinting=
|
||||
true;s(i,function(e,h){if(e.nodeType==1){a[h]=e.style.display;e.style.display="none"}});d.appendChild(c);E.print();setTimeout(function(){f.appendChild(c);s(i,function(e,h){if(e.nodeType==1)e.style.display=a[h]});b.isPrinting=false},1E3)}},contextMenu:function(b,c,a,f,d,i){var e=this,h=e.options.navigation,j=h.menuItemStyle,l=e.chartWidth,g=e.chartHeight,n="cache-"+b,m=e[n],o=D(d,i),t,x;if(!m){e[n]=m=r("div",{className:"highcharts-"+b},{position:"absolute",zIndex:1E3,padding:o+"px"},e.container);t=
|
||||
r("div",null,p({MozBoxShadow:"3px 3px 10px #888",WebkitBoxShadow:"3px 3px 10px #888",boxShadow:"3px 3px 10px #888"},h.menuStyle),m);x=function(){u(m,{display:"none"})};C(m,"mouseleave",x);s(c,function(v){if(v)r("div",{onmouseover:function(){u(this,h.menuItemHoverStyle)},onmouseout:function(){u(this,j)},innerHTML:v.text||k.getOptions().lang[v.textKey]},p({cursor:"pointer"},j),t)[A?"ontouchstart":"onclick"]=function(){x();v.onclick.apply(e,arguments)}});e.exportMenuWidth=m.offsetWidth;e.exportMenuHeight=
|
||||
m.offsetHeight}b={display:"block"};if(a+e.exportMenuWidth>l)b.right=l-a-d-o+"px";else b.left=a-o+"px";if(f+i+e.exportMenuHeight>g)b.bottom=g-f-o+"px";else b.top=f+i-o+"px";u(m,b)},addButton:function(b){function c(){g.attr(o);l.attr(m)}var a=this,f=a.renderer,d=w(a.options.navigation.buttonOptions,b),i=d.onclick,e=d.menuItems,h=d.width,j=d.height,l,g,n;b=d.borderWidth;var m={stroke:d.borderColor},o={stroke:d.symbolStroke,fill:d.symbolFill};if(d.enabled!==false){l=f.rect(0,0,h,j,d.borderRadius,b).align(d,
|
||||
true).attr(p({fill:d.backgroundColor,"stroke-width":b,zIndex:19},m)).add();n=f.rect(0,0,h,j,0).align(d).attr({fill:"rgba(255, 255, 255, 0.001)",title:k.getOptions().lang[d._titleKey],zIndex:21}).css({cursor:"pointer"}).on("mouseover",function(){g.attr({stroke:d.hoverSymbolStroke,fill:d.hoverSymbolFill});l.attr({stroke:d.hoverBorderColor})}).on("mouseout",c).on("click",c).add();if(e)i=function(){c();var t=n.getBBox();a.contextMenu("export-menu",e,t.x,t.y,h,j)};n.on("click",function(){i.apply(a,arguments)});
|
||||
g=f.symbol(d.symbol,d.symbolX,d.symbolY,(d.symbolSize||12)/2).align(d,true).attr(p(o,{"stroke-width":d.symbolStrokeWidth||1,zIndex:20})).add()}}});k.Renderer.prototype.symbols.exportIcon=function(b,c,a){return["M",b-a,c+a,"L",b+a,c+a,b+a,c+a*0.5,b-a,c+a*0.5,"Z","M",b,c+a*0.5,"L",b-a*0.5,c-a/3,b-a/6,c-a/3,b-a/6,c-a,b+a/6,c-a,b+a/6,c-a/3,b+a*0.5,c-a/3,"Z"]};k.Renderer.prototype.symbols.printIcon=function(b,c,a){return["M",b-a,c+a*0.5,"L",b+a,c+a*0.5,b+a,c-a/3,b-a,c-a/3,"Z","M",b-a*0.5,c-a/3,"L",b-a*
|
||||
0.5,c-a,b+a*0.5,c-a,b+a*0.5,c-a/3,"Z","M",b-a*0.5,c+a*0.5,"L",b-a*0.75,c+a,b+a*0.75,c+a,b+a*0.5,c+a*0.5,"Z"]};y.prototype.callbacks.push(function(b){var c,a=b.options.exporting,f=a.buttons;if(a.enabled!==false)for(c in f)b.addButton(f[c])})})();
|
295
app/assets/javascripts/highcharts.js
Normal file
@ -0,0 +1,295 @@
|
||||
/*
|
||||
Highcharts JS v3.0.8 (2014-01-09)
|
||||
|
||||
(c) 2009-2014 Torstein Honsi
|
||||
|
||||
License: www.highcharts.com/license
|
||||
*/
|
||||
(function(){function r(a,b){var c;a||(a={});for(c in b)a[c]=b[c];return a}function x(){var a,b=arguments,c,d={},e=function(a,b){var c,d;typeof a!=="object"&&(a={});for(d in b)b.hasOwnProperty(d)&&(c=b[d],a[d]=c&&typeof c==="object"&&Object.prototype.toString.call(c)!=="[object Array]"&&typeof c.nodeType!=="number"?e(a[d]||{},c):b[d]);return a};b[0]===!0&&(d=b[1],b=Array.prototype.slice.call(b,2));c=b.length;for(a=0;a<c;a++)d=e(d,b[a]);return d}function z(a,b){return parseInt(a,b||10)}function da(a){return typeof a===
|
||||
"string"}function S(a){return typeof a==="object"}function La(a){return Object.prototype.toString.call(a)==="[object Array]"}function xa(a){return typeof a==="number"}function ya(a){return P.log(a)/P.LN10}function ea(a){return P.pow(10,a)}function fa(a,b){for(var c=a.length;c--;)if(a[c]===b){a.splice(c,1);break}}function s(a){return a!==u&&a!==null}function y(a,b,c){var d,e;if(da(b))s(c)?a.setAttribute(b,c):a&&a.getAttribute&&(e=a.getAttribute(b));else if(s(b)&&S(b))for(d in b)a.setAttribute(d,b[d]);
|
||||
return e}function ka(a){return La(a)?a:[a]}function o(){var a=arguments,b,c,d=a.length;for(b=0;b<d;b++)if(c=a[b],typeof c!=="undefined"&&c!==null)return c}function D(a,b){if(za&&b&&b.opacity!==u)b.filter="alpha(opacity="+b.opacity*100+")";r(a.style,b)}function T(a,b,c,d,e){a=v.createElement(a);b&&r(a,b);e&&D(a,{padding:0,border:Q,margin:0});c&&D(a,c);d&&d.appendChild(a);return a}function ga(a,b){var c=function(){};c.prototype=new a;r(c.prototype,b);return c}function Ea(a,b,c,d){var e=G.lang,a=+a||
|
||||
0,f=b===-1?(a.toString().split(".")[1]||"").length:isNaN(b=M(b))?2:b,b=c===void 0?e.decimalPoint:c,d=d===void 0?e.thousandsSep:d,e=a<0?"-":"",c=String(z(a=M(a).toFixed(f))),g=c.length>3?c.length%3:0;return e+(g?c.substr(0,g)+d:"")+c.substr(g).replace(/(\d{3})(?=\d)/g,"$1"+d)+(f?b+M(a-c).toFixed(f).slice(2):"")}function Fa(a,b){return Array((b||2)+1-String(a).length).join(0)+a}function Ua(a,b,c){var d=a[b];a[b]=function(){var a=Array.prototype.slice.call(arguments);a.unshift(d);return c.apply(this,
|
||||
a)}}function Ga(a,b){for(var c="{",d=!1,e,f,g,h,i,j=[];(c=a.indexOf(c))!==-1;){e=a.slice(0,c);if(d){f=e.split(":");g=f.shift().split(".");i=g.length;e=b;for(h=0;h<i;h++)e=e[g[h]];if(f.length)f=f.join(":"),g=/\.([0-9])/,h=G.lang,i=void 0,/f$/.test(f)?(i=(i=f.match(g))?i[1]:-1,e=Ea(e,i,h.decimalPoint,f.indexOf(",")>-1?h.thousandsSep:"")):e=$a(f,e)}j.push(e);a=a.slice(c+1);c=(d=!d)?"}":"{"}j.push(a);return j.join("")}function lb(a){return P.pow(10,N(P.log(a)/P.LN10))}function mb(a,b,c,d){var e,c=o(c,
|
||||
1);e=a/c;b||(b=[1,2,2.5,5,10],d&&d.allowDecimals===!1&&(c===1?b=[1,2,5,10]:c<=0.1&&(b=[1/c])));for(d=0;d<b.length;d++)if(a=b[d],e<=(b[d]+(b[d+1]||b[d]))/2)break;a*=c;return a}function Ab(){this.symbol=this.color=0}function nb(a,b){var c=a.length,d,e;for(e=0;e<c;e++)a[e].ss_i=e;a.sort(function(a,c){d=b(a,c);return d===0?a.ss_i-c.ss_i:d});for(e=0;e<c;e++)delete a[e].ss_i}function Ma(a){for(var b=a.length,c=a[0];b--;)a[b]<c&&(c=a[b]);return c}function Aa(a){for(var b=a.length,c=a[0];b--;)a[b]>c&&(c=
|
||||
a[b]);return c}function Na(a,b){for(var c in a)a[c]&&a[c]!==b&&a[c].destroy&&a[c].destroy(),delete a[c]}function Oa(a){ab||(ab=T(Ha));a&&ab.appendChild(a);ab.innerHTML=""}function la(a,b){var c="Highcharts error #"+a+": www.highcharts.com/errors/"+a;if(b)throw c;else C.console&&console.log(c)}function ha(a){return parseFloat(a.toPrecision(14))}function Pa(a,b){pa=o(a,b.animation)}function Bb(){var a=G.global.useUTC,b=a?"getUTC":"get",c=a?"setUTC":"set";Qa=(a&&G.global.timezoneOffset||0)*6E4;bb=a?
|
||||
Date.UTC:function(a,b,c,g,h,i){return(new Date(a,b,o(c,1),o(g,0),o(h,0),o(i,0))).getTime()};ob=b+"Minutes";pb=b+"Hours";qb=b+"Day";Va=b+"Date";cb=b+"Month";db=b+"FullYear";Cb=c+"Minutes";Db=c+"Hours";rb=c+"Date";Eb=c+"Month";Fb=c+"FullYear"}function qa(){}function Ra(a,b,c,d){this.axis=a;this.pos=b;this.type=c||"";this.isNew=!0;!c&&!d&&this.addLabel()}function ra(){this.init.apply(this,arguments)}function Gb(a,b,c,d,e,f){var g=a.chart.inverted;this.axis=a;this.isNegative=c;this.options=b;this.x=d;
|
||||
this.total=null;this.points={};this.stack=e;this.percent=f==="percent";this.alignOptions={align:b.align||(g?c?"left":"right":"center"),verticalAlign:b.verticalAlign||(g?"middle":c?"bottom":"top"),y:o(b.y,g?4:c?14:-6),x:o(b.x,g?c?-6:6:0)};this.textAlign=b.textAlign||(g?c?"right":"left":"center")}function sb(){this.init.apply(this,arguments)}function eb(){this.init.apply(this,arguments)}var u,v=document,C=window,P=Math,w=P.round,N=P.floor,Ia=P.ceil,t=P.max,I=P.min,M=P.abs,U=P.cos,$=P.sin,Ba=P.PI,Ca=
|
||||
Ba*2/360,sa=navigator.userAgent,Hb=C.opera,za=/msie/i.test(sa)&&!Hb,fb=v.documentMode===8,gb=/AppleWebKit/.test(sa),Wa=/Firefox/.test(sa),Ib=/(Mobile|Android|Windows Phone)/.test(sa),Da="http://www.w3.org/2000/svg",V=!!v.createElementNS&&!!v.createElementNS(Da,"svg").createSVGRect,Nb=Wa&&parseInt(sa.split("Firefox/")[1],10)<4,ba=!V&&!za&&!!v.createElement("canvas").getContext,Xa,hb=v.documentElement.ontouchstart!==u,Jb={},tb=0,ab,G,$a,pa,ub,E,ma=function(){},Ja=[],Ha="div",Q="none",Ob=/^[0-9]+$/,
|
||||
Kb="rgba(192,192,192,"+(V?1.0E-4:0.002)+")",Lb="stroke-width",bb,Qa,ob,pb,qb,Va,cb,db,Cb,Db,rb,Eb,Fb,L={};C.Highcharts=C.Highcharts?la(16,!0):{};$a=function(a,b,c){if(!s(b)||isNaN(b))return"Invalid date";var a=o(a,"%Y-%m-%d %H:%M:%S"),d=new Date(b-Qa),e,f=d[pb](),g=d[qb](),h=d[Va](),i=d[cb](),j=d[db](),k=G.lang,l=k.weekdays,d=r({a:l[g].substr(0,3),A:l[g],d:Fa(h),e:h,b:k.shortMonths[i],B:k.months[i],m:Fa(i+1),y:j.toString().substr(2,2),Y:j,H:Fa(f),I:Fa(f%12||12),l:f%12||12,M:Fa(d[ob]()),p:f<12?"AM":
|
||||
"PM",P:f<12?"am":"pm",S:Fa(d.getSeconds()),L:Fa(w(b%1E3),3)},Highcharts.dateFormats);for(e in d)for(;a.indexOf("%"+e)!==-1;)a=a.replace("%"+e,typeof d[e]==="function"?d[e](b):d[e]);return c?a.substr(0,1).toUpperCase()+a.substr(1):a};Ab.prototype={wrapColor:function(a){if(this.color>=a)this.color=0},wrapSymbol:function(a){if(this.symbol>=a)this.symbol=0}};E=function(){for(var a=0,b=arguments,c=b.length,d={};a<c;a++)d[b[a++]]=b[a];return d}("millisecond",1,"second",1E3,"minute",6E4,"hour",36E5,"day",
|
||||
864E5,"week",6048E5,"month",26784E5,"year",31556952E3);ub={init:function(a,b,c){var b=b||"",d=a.shift,e=b.indexOf("C")>-1,f=e?7:3,g,b=b.split(" "),c=[].concat(c),h,i,j=function(a){for(g=a.length;g--;)a[g]==="M"&&a.splice(g+1,0,a[g+1],a[g+2],a[g+1],a[g+2])};e&&(j(b),j(c));a.isArea&&(h=b.splice(b.length-6,6),i=c.splice(c.length-6,6));if(d<=c.length/f&&b.length===c.length)for(;d--;)c=[].concat(c).splice(0,f).concat(c);a.shift=0;if(b.length)for(a=c.length;b.length<a;)d=[].concat(b).splice(b.length-f,
|
||||
f),e&&(d[f-6]=d[f-2],d[f-5]=d[f-1]),b=b.concat(d);h&&(b=b.concat(h),c=c.concat(i));return[b,c]},step:function(a,b,c,d){var e=[],f=a.length;if(c===1)e=d;else if(f===b.length&&c<1)for(;f--;)d=parseFloat(a[f]),e[f]=isNaN(d)?a[f]:c*parseFloat(b[f]-d)+d;else e=b;return e}};(function(a){C.HighchartsAdapter=C.HighchartsAdapter||a&&{init:function(b){var c=a.fx,d=c.step,e,f=a.Tween,g=f&&f.propHooks;e=a.cssHooks.opacity;a.extend(a.easing,{easeOutQuad:function(a,b,c,d,e){return-d*(b/=e)*(b-2)+c}});a.each(["cur",
|
||||
"_default","width","height","opacity"],function(a,b){var e=d,k;b==="cur"?e=c.prototype:b==="_default"&&f&&(e=g[b],b="set");(k=e[b])&&(e[b]=function(c){var d,c=a?c:this;if(c.prop!=="align")return d=c.elem,d.attr?d.attr(c.prop,b==="cur"?u:c.now):k.apply(this,arguments)})});Ua(e,"get",function(a,b,c){return b.attr?b.opacity||0:a.call(this,b,c)});e=function(a){var c=a.elem,d;if(!a.started)d=b.init(c,c.d,c.toD),a.start=d[0],a.end=d[1],a.started=!0;c.attr("d",b.step(a.start,a.end,a.pos,c.toD))};f?g.d={set:e}:
|
||||
d.d=e;this.each=Array.prototype.forEach?function(a,b){return Array.prototype.forEach.call(a,b)}:function(a,b){for(var c=0,d=a.length;c<d;c++)if(b.call(a[c],a[c],c,a)===!1)return c};a.fn.highcharts=function(){var a="Chart",b=arguments,c,d;da(b[0])&&(a=b[0],b=Array.prototype.slice.call(b,1));c=b[0];if(c!==u)c.chart=c.chart||{},c.chart.renderTo=this[0],new Highcharts[a](c,b[1]),d=this;c===u&&(d=Ja[y(this[0],"data-highcharts-chart")]);return d}},getScript:a.getScript,inArray:a.inArray,adapterRun:function(b,
|
||||
c){return a(b)[c]()},grep:a.grep,map:function(a,c){for(var d=[],e=0,f=a.length;e<f;e++)d[e]=c.call(a[e],a[e],e,a);return d},offset:function(b){return a(b).offset()},addEvent:function(b,c,d){a(b).bind(c,d)},removeEvent:function(b,c,d){var e=v.removeEventListener?"removeEventListener":"detachEvent";v[e]&&b&&!b[e]&&(b[e]=function(){});a(b).unbind(c,d)},fireEvent:function(b,c,d,e){var f=a.Event(c),g="detached"+c,h;!za&&d&&(delete d.layerX,delete d.layerY);r(f,d);b[c]&&(b[g]=b[c],b[c]=null);a.each(["preventDefault",
|
||||
"stopPropagation"],function(a,b){var c=f[b];f[b]=function(){try{c.call(f)}catch(a){b==="preventDefault"&&(h=!0)}}});a(b).trigger(f);b[g]&&(b[c]=b[g],b[g]=null);e&&!f.isDefaultPrevented()&&!h&&e(f)},washMouseEvent:function(a){var c=a.originalEvent||a;if(c.pageX===u)c.pageX=a.pageX,c.pageY=a.pageY;return c},animate:function(b,c,d){var e=a(b);if(!b.style)b.style={};if(c.d)b.toD=c.d,c.d=1;e.stop();c.opacity!==u&&b.attr&&(c.opacity+="px");e.animate(c,d)},stop:function(b){a(b).stop()}}})(C.jQuery);var W=
|
||||
C.HighchartsAdapter,J=W||{};W&&W.init.call(W,ub);var ib=J.adapterRun,Pb=J.getScript,ta=J.inArray,n=J.each,vb=J.grep,Qb=J.offset,Sa=J.map,F=J.addEvent,X=J.removeEvent,A=J.fireEvent,Rb=J.washMouseEvent,jb=J.animate,Ya=J.stop,J={enabled:!0,x:0,y:15,style:{color:"#666",cursor:"default",fontSize:"11px",lineHeight:"14px"}};G={colors:"#2f7ed8,#0d233a,#8bbc21,#910000,#1aadce,#492970,#f28f43,#77a1e5,#c42525,#a6c96a".split(","),symbols:["circle","diamond","square","triangle","triangle-down"],lang:{loading:"Loading...",
|
||||
months:"January,February,March,April,May,June,July,August,September,October,November,December".split(","),shortMonths:"Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec".split(","),weekdays:"Sunday,Monday,Tuesday,Wednesday,Thursday,Friday,Saturday".split(","),decimalPoint:".",numericSymbols:"k,M,G,T,P,E".split(","),resetZoom:"Reset zoom",resetZoomTitle:"Reset zoom level 1:1",thousandsSep:","},global:{useUTC:!0,canvasToolsURL:"http://code.highcharts.com/3.0.8/modules/canvas-tools.js",VMLRadialGradientURL:"http://code.highcharts.com/3.0.8/gfx/vml-radial-gradient.png"},
|
||||
chart:{borderColor:"#4572A7",borderRadius:5,defaultSeriesType:"line",ignoreHiddenSeries:!0,spacing:[10,10,15,10],style:{fontFamily:'"Lucida Grande", "Lucida Sans Unicode", Verdana, Arial, Helvetica, sans-serif',fontSize:"12px"},backgroundColor:"#FFFFFF",plotBorderColor:"#C0C0C0",resetZoomButton:{theme:{zIndex:20},position:{align:"right",x:-10,y:10}}},title:{text:"Chart title",align:"center",margin:15,style:{color:"#274b6d",fontSize:"16px"}},subtitle:{text:"",align:"center",style:{color:"#4d759e"}},
|
||||
plotOptions:{line:{allowPointSelect:!1,showCheckbox:!1,animation:{duration:1E3},events:{},lineWidth:2,marker:{enabled:!0,lineWidth:0,radius:4,lineColor:"#FFFFFF",states:{hover:{enabled:!0},select:{fillColor:"#FFFFFF",lineColor:"#000000",lineWidth:2}}},point:{events:{}},dataLabels:x(J,{align:"center",enabled:!1,formatter:function(){return this.y===null?"":Ea(this.y,-1)},verticalAlign:"bottom",y:0}),cropThreshold:300,pointRange:0,states:{hover:{marker:{}},select:{marker:{}}},stickyTracking:!0,turboThreshold:1E3}},
|
||||
labels:{style:{position:"absolute",color:"#3E576F"}},legend:{enabled:!0,align:"center",layout:"horizontal",labelFormatter:function(){return this.name},borderWidth:1,borderColor:"#909090",borderRadius:5,navigation:{activeColor:"#274b6d",inactiveColor:"#CCC"},shadow:!1,itemStyle:{cursor:"pointer",color:"#274b6d",fontSize:"12px"},itemHoverStyle:{color:"#000"},itemHiddenStyle:{color:"#CCC"},itemCheckboxStyle:{position:"absolute",width:"13px",height:"13px"},symbolPadding:5,verticalAlign:"bottom",x:0,y:0,
|
||||
title:{style:{fontWeight:"bold"}}},loading:{labelStyle:{fontWeight:"bold",position:"relative",top:"1em"},style:{position:"absolute",backgroundColor:"white",opacity:0.5,textAlign:"center"}},tooltip:{enabled:!0,animation:V,backgroundColor:"rgba(255, 255, 255, .85)",borderWidth:1,borderRadius:3,dateTimeLabelFormats:{millisecond:"%A, %b %e, %H:%M:%S.%L",second:"%A, %b %e, %H:%M:%S",minute:"%A, %b %e, %H:%M",hour:"%A, %b %e, %H:%M",day:"%A, %b %e, %Y",week:"Week from %A, %b %e, %Y",month:"%B %Y",year:"%Y"},
|
||||
headerFormat:'<span style="font-size: 10px">{point.key}</span><br/>',pointFormat:'<span style="color:{series.color}">{series.name}</span>: <b>{point.y}</b><br/>',shadow:!0,snap:Ib?25:10,style:{color:"#333333",cursor:"default",fontSize:"12px",padding:"8px",whiteSpace:"nowrap"}},credits:{enabled:!0,text:"ThingSpeak.com",href:"https://thingspeak.com",position:{align:"right",x:-10,verticalAlign:"bottom",y:-5},style:{cursor:"pointer",color:"#D62020",fontSize:"9px"}}};var Y=G.plotOptions,W=Y.line;Bb();
|
||||
var Sb=/rgba\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]?(?:\.[0-9]+)?)\s*\)/,Tb=/#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/,Ub=/rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/,ua=function(a){var b=[],c,d;(function(a){a&&a.stops?d=Sa(a.stops,function(a){return ua(a[1])}):(c=Sb.exec(a))?b=[z(c[1]),z(c[2]),z(c[3]),parseFloat(c[4],10)]:(c=Tb.exec(a))?b=[z(c[1],16),z(c[2],16),z(c[3],16),1]:(c=Ub.exec(a))&&(b=[z(c[1]),z(c[2]),z(c[3]),1])})(a);return{get:function(c){var f;
|
||||
d?(f=x(a),f.stops=[].concat(f.stops),n(d,function(a,b){f.stops[b]=[f.stops[b][0],a.get(c)]})):f=b&&!isNaN(b[0])?c==="rgb"?"rgb("+b[0]+","+b[1]+","+b[2]+")":c==="a"?b[3]:"rgba("+b.join(",")+")":a;return f},brighten:function(a){if(d)n(d,function(b){b.brighten(a)});else if(xa(a)&&a!==0){var c;for(c=0;c<3;c++)b[c]+=z(a*255),b[c]<0&&(b[c]=0),b[c]>255&&(b[c]=255)}return this},rgba:b,setOpacity:function(a){b[3]=a;return this}}};qa.prototype={init:function(a,b){this.element=b==="span"?T(b):v.createElementNS(Da,
|
||||
b);this.renderer=a;this.attrSetters={}},opacity:1,animate:function(a,b,c){b=o(b,pa,!0);Ya(this);if(b){b=x(b);if(c)b.complete=c;jb(this,a,b)}else this.attr(a),c&&c()},attr:function(a,b){var c,d,e,f,g=this.element,h=g.nodeName.toLowerCase(),i=this.renderer,j,k=this.attrSetters,l=this.shadows,m,p,q=this;da(a)&&s(b)&&(c=a,a={},a[c]=b);if(da(a))c=a,h==="circle"?c={x:"cx",y:"cy"}[c]||c:c==="strokeWidth"&&(c="stroke-width"),q=y(g,c)||this[c]||0,c!=="d"&&c!=="visibility"&&c!=="fill"&&(q=parseFloat(q));else{for(c in a)if(j=
|
||||
!1,d=a[c],e=k[c]&&k[c].call(this,d,c),e!==!1){e!==u&&(d=e);if(c==="d")d&&d.join&&(d=d.join(" ")),/(NaN| {2}|^$)/.test(d)&&(d="M 0 0");else if(c==="x"&&h==="text")for(e=0;e<g.childNodes.length;e++)f=g.childNodes[e],y(f,"x")===y(g,"x")&&y(f,"x",d);else if(this.rotation&&(c==="x"||c==="y"))p=!0;else if(c==="fill")d=i.color(d,g,c);else if(h==="circle"&&(c==="x"||c==="y"))c={x:"cx",y:"cy"}[c]||c;else if(h==="rect"&&c==="r")y(g,{rx:d,ry:d}),j=!0;else if(c==="translateX"||c==="translateY"||c==="rotation"||
|
||||
c==="verticalAlign"||c==="scaleX"||c==="scaleY")j=p=!0;else if(c==="stroke")d=i.color(d,g,c);else if(c==="dashstyle")if(c="stroke-dasharray",d=d&&d.toLowerCase(),d==="solid")d=Q;else{if(d){d=d.replace("shortdashdotdot","3,1,1,1,1,1,").replace("shortdashdot","3,1,1,1").replace("shortdot","1,1,").replace("shortdash","3,1,").replace("longdash","8,3,").replace(/dot/g,"1,3,").replace("dash","4,3,").replace(/,$/,"").split(",");for(e=d.length;e--;)d[e]=z(d[e])*o(a["stroke-width"],this["stroke-width"]);d=
|
||||
d.join(",")}}else if(c==="width")d=z(d);else if(c==="align")c="text-anchor",d={left:"start",center:"middle",right:"end"}[d];else if(c==="title")e=g.getElementsByTagName("title")[0],e||(e=v.createElementNS(Da,"title"),g.appendChild(e)),e.textContent=d;c==="strokeWidth"&&(c="stroke-width");if(c==="stroke-width"||c==="stroke"){this[c]=d;if(this.stroke&&this["stroke-width"])y(g,"stroke",this.stroke),y(g,"stroke-width",this["stroke-width"]),this.hasStroke=!0;else if(c==="stroke-width"&&d===0&&this.hasStroke)g.removeAttribute("stroke"),
|
||||
this.hasStroke=!1;j=!0}this.symbolName&&/^(x|y|width|height|r|start|end|innerR|anchorX|anchorY)/.test(c)&&(m||(this.symbolAttr(a),m=!0),j=!0);if(l&&/^(width|height|visibility|x|y|d|transform|cx|cy|r)$/.test(c))for(e=l.length;e--;)y(l[e],c,c==="height"?t(d-(l[e].cutHeight||0),0):d);if((c==="width"||c==="height")&&h==="rect"&&d<0)d=0;this[c]=d;c==="text"?(d!==this.textStr&&delete this.bBox,this.textStr=d,this.added&&i.buildText(this)):j||y(g,c,d)}p&&this.updateTransform()}return q},addClass:function(a){var b=
|
||||
this.element,c=y(b,"class")||"";c.indexOf(a)===-1&&y(b,"class",c+" "+a);return this},symbolAttr:function(a){var b=this;n("x,y,r,start,end,width,height,innerR,anchorX,anchorY".split(","),function(c){b[c]=o(a[c],b[c])});b.attr({d:b.renderer.symbols[b.symbolName](b.x,b.y,b.width,b.height,b)})},clip:function(a){return this.attr("clip-path",a?"url("+this.renderer.url+"#"+a.id+")":Q)},crisp:function(a,b,c,d,e){var f,g={},h={},i,a=a||this.strokeWidth||this.attr&&this.attr("stroke-width")||0;i=w(a)%2/2;h.x=
|
||||
N(b||this.x||0)+i;h.y=N(c||this.y||0)+i;h.width=N((d||this.width||0)-2*i);h.height=N((e||this.height||0)-2*i);h.strokeWidth=a;for(f in h)this[f]!==h[f]&&(this[f]=g[f]=h[f]);return g},css:function(a){var b=this.element,c=this.textWidth=a&&a.width&&b.nodeName.toLowerCase()==="text"&&z(a.width),d,e="",f=function(a,b){return"-"+b.toLowerCase()};if(a&&a.color)a.fill=a.color;this.styles=a=r(this.styles,a);c&&delete a.width;if(za&&!V)D(this.element,a);else{for(d in a)e+=d.replace(/([A-Z])/g,f)+":"+a[d]+
|
||||
";";y(b,"style",e)}c&&this.added&&this.renderer.buildText(this);return this},on:function(a,b){var c=this,d=c.element;hb&&a==="click"?(d.ontouchstart=function(a){c.touchEventFired=Date.now();a.preventDefault();b.call(d,a)},d.onclick=function(a){(sa.indexOf("Android")===-1||Date.now()-(c.touchEventFired||0)>1100)&&b.call(d,a)}):d["on"+a]=b;return this},setRadialReference:function(a){this.element.radialReference=a;return this},translate:function(a,b){return this.attr({translateX:a,translateY:b})},invert:function(){this.inverted=
|
||||
!0;this.updateTransform();return this},updateTransform:function(){var a=this.translateX||0,b=this.translateY||0,c=this.scaleX,d=this.scaleY,e=this.inverted,f=this.rotation;e&&(a+=this.attr("width"),b+=this.attr("height"));a=["translate("+a+","+b+")"];e?a.push("rotate(90) scale(-1,1)"):f&&a.push("rotate("+f+" "+(this.x||0)+" "+(this.y||0)+")");(s(c)||s(d))&&a.push("scale("+o(c,1)+" "+o(d,1)+")");a.length&&y(this.element,"transform",a.join(" "))},toFront:function(){var a=this.element;a.parentNode.appendChild(a);
|
||||
return this},align:function(a,b,c){var d,e,f,g,h={};e=this.renderer;f=e.alignedObjects;if(a){if(this.alignOptions=a,this.alignByTranslate=b,!c||da(c))this.alignTo=d=c||"renderer",fa(f,this),f.push(this),c=null}else a=this.alignOptions,b=this.alignByTranslate,d=this.alignTo;c=o(c,e[d],e);d=a.align;e=a.verticalAlign;f=(c.x||0)+(a.x||0);g=(c.y||0)+(a.y||0);if(d==="right"||d==="center")f+=(c.width-(a.width||0))/{right:1,center:2}[d];h[b?"translateX":"x"]=w(f);if(e==="bottom"||e==="middle")g+=(c.height-
|
||||
(a.height||0))/({bottom:1,middle:2}[e]||1);h[b?"translateY":"y"]=w(g);this[this.placed?"animate":"attr"](h);this.placed=!0;this.alignAttr=h;return this},getBBox:function(){var a=this.bBox,b=this.renderer,c,d,e=this.rotation;c=this.element;var f=this.styles,g=e*Ca;d=this.textStr;var h;if(d===""||Ob.test(d))h=d.length+"|"+f.fontSize+"|"+f.fontFamily,a=b.cache[h];if(!a){if(c.namespaceURI===Da||b.forExport){try{a=c.getBBox?r({},c.getBBox()):{width:c.offsetWidth,height:c.offsetHeight}}catch(i){}if(!a||
|
||||
a.width<0)a={width:0,height:0}}else a=this.htmlGetBBox();if(b.isSVG){c=a.width;d=a.height;if(za&&f&&f.fontSize==="11px"&&d.toPrecision(3)==="22.7")a.height=d=14;if(e)a.width=M(d*$(g))+M(c*U(g)),a.height=M(d*U(g))+M(c*$(g))}this.bBox=a;h&&(b.cache[h]=a)}return a},show:function(){return this.attr({visibility:"visible"})},hide:function(){return this.attr({visibility:"hidden"})},fadeOut:function(a){var b=this;b.animate({opacity:0},{duration:a||150,complete:function(){b.hide()}})},add:function(a){var b=
|
||||
this.renderer,c=a||b,d=c.element||b.box,e=d.childNodes,f=this.element,g=y(f,"zIndex"),h;if(a)this.parentGroup=a;this.parentInverted=a&&a.inverted;this.textStr!==void 0&&b.buildText(this);if(g)c.handleZ=!0,g=z(g);if(c.handleZ)for(c=0;c<e.length;c++)if(a=e[c],b=y(a,"zIndex"),a!==f&&(z(b)>g||!s(g)&&s(b))){d.insertBefore(f,a);h=!0;break}h||d.appendChild(f);this.added=!0;A(this,"add");return this},safeRemoveChild:function(a){var b=a.parentNode;b&&b.removeChild(a)},destroy:function(){var a=this,b=a.element||
|
||||
{},c=a.shadows,d=a.renderer.isSVG&&b.nodeName==="SPAN"&&a.parentGroup,e,f;b.onclick=b.onmouseout=b.onmouseover=b.onmousemove=b.point=null;Ya(a);if(a.clipPath)a.clipPath=a.clipPath.destroy();if(a.stops){for(f=0;f<a.stops.length;f++)a.stops[f]=a.stops[f].destroy();a.stops=null}a.safeRemoveChild(b);for(c&&n(c,function(b){a.safeRemoveChild(b)});d&&d.div.childNodes.length===0;)b=d.parentGroup,a.safeRemoveChild(d.div),delete d.div,d=b;a.alignTo&&fa(a.renderer.alignedObjects,a);for(e in a)delete a[e];return null},
|
||||
shadow:function(a,b,c){var d=[],e,f,g=this.element,h,i,j,k;if(a){i=o(a.width,3);j=(a.opacity||0.15)/i;k=this.parentInverted?"(-1,-1)":"("+o(a.offsetX,1)+", "+o(a.offsetY,1)+")";for(e=1;e<=i;e++){f=g.cloneNode(0);h=i*2+1-2*e;y(f,{isShadow:"true",stroke:a.color||"black","stroke-opacity":j*e,"stroke-width":h,transform:"translate"+k,fill:Q});if(c)y(f,"height",t(y(f,"height")-h,0)),f.cutHeight=h;b?b.element.appendChild(f):g.parentNode.insertBefore(f,g);d.push(f)}this.shadows=d}return this}};var va=function(){this.init.apply(this,
|
||||
arguments)};va.prototype={Element:qa,init:function(a,b,c,d){var e=location,f,g;f=this.createElement("svg").attr({version:"1.1"});g=f.element;a.appendChild(g);a.innerHTML.indexOf("xmlns")===-1&&y(g,"xmlns",Da);this.isSVG=!0;this.box=g;this.boxWrapper=f;this.alignedObjects=[];this.url=(Wa||gb)&&v.getElementsByTagName("base").length?e.href.replace(/#.*?$/,"").replace(/([\('\)])/g,"\\$1").replace(/ /g,"%20"):"";this.createElement("desc").add().element.appendChild(v.createTextNode("Created with Highcharts 3.0.8"));
|
||||
this.defs=this.createElement("defs").add();this.forExport=d;this.gradients={};this.cache={};this.setSize(b,c,!1);var h;if(Wa&&a.getBoundingClientRect)this.subPixelFix=b=function(){D(a,{left:0,top:0});h=a.getBoundingClientRect();D(a,{left:Ia(h.left)-h.left+"px",top:Ia(h.top)-h.top+"px"})},b(),F(C,"resize",b)},isHidden:function(){return!this.boxWrapper.getBBox().width},destroy:function(){var a=this.defs;this.box=null;this.boxWrapper=this.boxWrapper.destroy();Na(this.gradients||{});this.gradients=null;
|
||||
if(a)this.defs=a.destroy();this.subPixelFix&&X(C,"resize",this.subPixelFix);return this.alignedObjects=null},createElement:function(a){var b=new this.Element;b.init(this,a);return b},draw:function(){},buildText:function(a){for(var b=a.element,c=this,d=c.forExport,e=o(a.textStr,"").toString().replace(/<(b|strong)>/g,'<span style="font-weight:bold">').replace(/<(i|em)>/g,'<span style="font-style:italic">').replace(/<a/g,"<span").replace(/<\/(b|strong|i|em|a)>/g,"</span>").split(/<br.*?>/g),f=b.childNodes,
|
||||
g=/style="([^"]+)"/,h=/href="(http[^"]+)"/,i=y(b,"x"),j=a.styles,k=a.textWidth,l=j&&j.lineHeight,m=f.length;m--;)b.removeChild(f[m]);k&&!a.added&&this.box.appendChild(b);e[e.length-1]===""&&e.pop();n(e,function(e,f){var m,o=0,e=e.replace(/<span/g,"|||<span").replace(/<\/span>/g,"</span>|||");m=e.split("|||");n(m,function(e){if(e!==""||m.length===1){var p={},n=v.createElementNS(Da,"tspan"),s;g.test(e)&&(s=e.match(g)[1].replace(/(;| |^)color([ :])/,"$1fill$2"),y(n,"style",s));h.test(e)&&!d&&(y(n,"onclick",
|
||||
'location.href="'+e.match(h)[1]+'"'),D(n,{cursor:"pointer"}));e=(e.replace(/<(.|\n)*?>/g,"")||" ").replace(/</g,"<").replace(/>/g,">");if(e!==" "&&(n.appendChild(v.createTextNode(e)),o?p.dx=0:p.x=i,y(n,p),!o&&f&&(!V&&d&&D(n,{display:"block"}),y(n,"dy",l||c.fontMetrics(/px$/.test(n.style.fontSize)?n.style.fontSize:j.fontSize).h,gb&&n.offsetHeight)),b.appendChild(n),o++,k))for(var e=e.replace(/([^\^])-/g,"$1- ").split(" "),p=e.length>1&&j.whiteSpace!=="nowrap",t,w,u=a._clipHeight,r=[],B=z(l||
|
||||
16),x=1;p&&(e.length||r.length);)delete a.bBox,t=a.getBBox(),w=t.width,!V&&c.forExport&&(w=c.measureSpanWidth(n.firstChild.data,a.styles)),t=w>k,!t||e.length===1?(e=r,r=[],e.length&&(x++,u&&x*B>u?(e=["..."],a.attr("title",a.textStr)):(n=v.createElementNS(Da,"tspan"),y(n,{dy:B,x:i}),s&&y(n,"style",s),b.appendChild(n),w>k&&(k=w)))):(n.removeChild(n.firstChild),r.unshift(e.pop())),e.length&&n.appendChild(v.createTextNode(e.join(" ").replace(/- /g,"-")))}})})},button:function(a,b,c,d,e,f,g,h,i){var j=
|
||||
this.label(a,b,c,i,null,null,null,null,"button"),k=0,l,m,p,q,n,o,a={x1:0,y1:0,x2:0,y2:1},e=x({"stroke-width":1,stroke:"#CCCCCC",fill:{linearGradient:a,stops:[[0,"#FEFEFE"],[1,"#F6F6F6"]]},r:2,padding:5,style:{color:"black"}},e);p=e.style;delete e.style;f=x(e,{stroke:"#68A",fill:{linearGradient:a,stops:[[0,"#FFF"],[1,"#ACF"]]}},f);q=f.style;delete f.style;g=x(e,{stroke:"#68A",fill:{linearGradient:a,stops:[[0,"#9BD"],[1,"#CDF"]]}},g);n=g.style;delete g.style;h=x(e,{style:{color:"#CCC"}},h);o=h.style;
|
||||
delete h.style;F(j.element,za?"mouseover":"mouseenter",function(){k!==3&&j.attr(f).css(q)});F(j.element,za?"mouseout":"mouseleave",function(){k!==3&&(l=[e,f,g][k],m=[p,q,n][k],j.attr(l).css(m))});j.setState=function(a){(j.state=k=a)?a===2?j.attr(g).css(n):a===3&&j.attr(h).css(o):j.attr(e).css(p)};return j.on("click",function(){k!==3&&d.call(j)}).attr(e).css(r({cursor:"default"},p))},crispLine:function(a,b){a[1]===a[4]&&(a[1]=a[4]=w(a[1])-b%2/2);a[2]===a[5]&&(a[2]=a[5]=w(a[2])+b%2/2);return a},path:function(a){var b=
|
||||
{fill:Q};La(a)?b.d=a:S(a)&&r(b,a);return this.createElement("path").attr(b)},circle:function(a,b,c){a=S(a)?a:{x:a,y:b,r:c};return this.createElement("circle").attr(a)},arc:function(a,b,c,d,e,f){if(S(a))b=a.y,c=a.r,d=a.innerR,e=a.start,f=a.end,a=a.x;a=this.symbol("arc",a||0,b||0,c||0,c||0,{innerR:d||0,start:e||0,end:f||0});a.r=c;return a},rect:function(a,b,c,d,e,f){e=S(a)?a.r:e;e=this.createElement("rect").attr({rx:e,ry:e,fill:Q});return e.attr(S(a)?a:e.crisp(f,a,b,t(c,0),t(d,0)))},setSize:function(a,
|
||||
b,c){var d=this.alignedObjects,e=d.length;this.width=a;this.height=b;for(this.boxWrapper[o(c,!0)?"animate":"attr"]({width:a,height:b});e--;)d[e].align()},g:function(a){var b=this.createElement("g");return s(a)?b.attr({"class":"highcharts-"+a}):b},image:function(a,b,c,d,e){var f={preserveAspectRatio:Q};arguments.length>1&&r(f,{x:b,y:c,width:d,height:e});f=this.createElement("image").attr(f);f.element.setAttributeNS?f.element.setAttributeNS("http://www.w3.org/1999/xlink","href",a):f.element.setAttribute("hc-svg-href",
|
||||
a);return f},symbol:function(a,b,c,d,e,f){var g,h=this.symbols[a],h=h&&h(w(b),w(c),d,e,f),i=/^url\((.*?)\)$/,j,k;if(h)g=this.path(h),r(g,{symbolName:a,x:b,y:c,width:d,height:e}),f&&r(g,f);else if(i.test(a))k=function(a,b){a.element&&(a.attr({width:b[0],height:b[1]}),a.alignByTranslate||a.translate(w((d-b[0])/2),w((e-b[1])/2)))},j=a.match(i)[1],a=Jb[j],g=this.image(j).attr({x:b,y:c}),g.isImg=!0,a?k(g,a):(g.attr({width:0,height:0}),T("img",{onload:function(){k(g,Jb[j]=[this.width,this.height])},src:j}));
|
||||
return g},symbols:{circle:function(a,b,c,d){var e=0.166*c;return["M",a+c/2,b,"C",a+c+e,b,a+c+e,b+d,a+c/2,b+d,"C",a-e,b+d,a-e,b,a+c/2,b,"Z"]},square:function(a,b,c,d){return["M",a,b,"L",a+c,b,a+c,b+d,a,b+d,"Z"]},triangle:function(a,b,c,d){return["M",a+c/2,b,"L",a+c,b+d,a,b+d,"Z"]},"triangle-down":function(a,b,c,d){return["M",a,b,"L",a+c,b,a+c/2,b+d,"Z"]},diamond:function(a,b,c,d){return["M",a+c/2,b,"L",a+c,b+d/2,a+c/2,b+d,a,b+d/2,"Z"]},arc:function(a,b,c,d,e){var f=e.start,c=e.r||c||d,g=e.end-0.001,
|
||||
d=e.innerR,h=e.open,i=U(f),j=$(f),k=U(g),g=$(g),e=e.end-f<Ba?0:1;return["M",a+c*i,b+c*j,"A",c,c,0,e,1,a+c*k,b+c*g,h?"M":"L",a+d*k,b+d*g,"A",d,d,0,e,0,a+d*i,b+d*j,h?"":"Z"]}},clipRect:function(a,b,c,d){var e="highcharts-"+tb++,f=this.createElement("clipPath").attr({id:e}).add(this.defs),a=this.rect(a,b,c,d,0).add(f);a.id=e;a.clipPath=f;return a},color:function(a,b,c){var d=this,e,f=/^rgba/,g,h,i,j,k,l,m,p=[];a&&a.linearGradient?g="linearGradient":a&&a.radialGradient&&(g="radialGradient");if(g){c=a[g];
|
||||
h=d.gradients;j=a.stops;b=b.radialReference;La(c)&&(a[g]=c={x1:c[0],y1:c[1],x2:c[2],y2:c[3],gradientUnits:"userSpaceOnUse"});g==="radialGradient"&&b&&!s(c.gradientUnits)&&(c=x(c,{cx:b[0]-b[2]/2+c.cx*b[2],cy:b[1]-b[2]/2+c.cy*b[2],r:c.r*b[2],gradientUnits:"userSpaceOnUse"}));for(m in c)m!=="id"&&p.push(m,c[m]);for(m in j)p.push(j[m]);p=p.join(",");h[p]?a=h[p].id:(c.id=a="highcharts-"+tb++,h[p]=i=d.createElement(g).attr(c).add(d.defs),i.stops=[],n(j,function(a){f.test(a[1])?(e=ua(a[1]),k=e.get("rgb"),
|
||||
l=e.get("a")):(k=a[1],l=1);a=d.createElement("stop").attr({offset:a[0],"stop-color":k,"stop-opacity":l}).add(i);i.stops.push(a)}));return"url("+d.url+"#"+a+")"}else return f.test(a)?(e=ua(a),y(b,c+"-opacity",e.get("a")),e.get("rgb")):(b.removeAttribute(c+"-opacity"),a)},text:function(a,b,c,d){var e=G.chart.style,f=ba||!V&&this.forExport;if(d&&!this.forExport)return this.html(a,b,c);b=w(o(b,0));c=w(o(c,0));a=this.createElement("text").attr({x:b,y:c,text:a}).css({fontFamily:e.fontFamily,fontSize:e.fontSize});
|
||||
f&&a.css({position:"absolute"});a.x=b;a.y=c;return a},fontMetrics:function(a){var a=z(a||11),a=a<24?a+4:w(a*1.2),b=w(a*0.8);return{h:a,b:b}},label:function(a,b,c,d,e,f,g,h,i){function j(){var a,b;a=o.element.style;wa=(ia===void 0||wb===void 0||q.styles.textAlign)&&o.getBBox();q.width=(ia||wa.width||0)+2*ca+kb;q.height=(wb||wa.height||0)+2*ca;ja=ca+p.fontMetrics(a&&a.fontSize).b;if(v){if(!H)a=w(-t*ca),b=h?-ja:0,q.box=H=d?p.symbol(d,a,b,q.width,q.height,y):p.rect(a,b,q.width,q.height,0,y[Lb]),H.add(q);
|
||||
H.isImg||H.attr(x({width:q.width,height:q.height},y));y=null}}function k(){var a=q.styles,a=a&&a.textAlign,b=kb+ca*(1-t),c;c=h?0:ja;if(s(ia)&&(a==="center"||a==="right"))b+={center:0.5,right:1}[a]*(ia-wa.width);(b!==o.x||c!==o.y)&&o.attr({x:b,y:c});o.x=b;o.y=c}function l(a,b){H?H.attr(a,b):y[a]=b}function m(){o.add(q);q.attr({text:a,x:b,y:c});H&&s(e)&&q.attr({anchorX:e,anchorY:f})}var p=this,q=p.g(i),o=p.text("",0,0,g).attr({zIndex:1}),H,wa,t=0,ca=3,kb=0,ia,wb,Z,K,B=0,y={},ja,g=q.attrSetters,v;F(q,
|
||||
"add",m);g.width=function(a){ia=a;return!1};g.height=function(a){wb=a;return!1};g.padding=function(a){s(a)&&a!==ca&&(ca=a,k());return!1};g.paddingLeft=function(a){s(a)&&a!==kb&&(kb=a,k());return!1};g.align=function(a){t={left:0,center:0.5,right:1}[a];return!1};g.text=function(a,b){o.attr(b,a);j();k();return!1};g[Lb]=function(a,b){v=!0;B=a%2/2;l(b,a);return!1};g.stroke=g.fill=g.r=function(a,b){b==="fill"&&(v=!0);l(b,a);return!1};g.anchorX=function(a,b){e=a;l(b,a+B-Z);return!1};g.anchorY=function(a,
|
||||
b){f=a;l(b,a-K);return!1};g.x=function(a){q.x=a;a-=t*((ia||wa.width)+ca);Z=w(a);q.attr("translateX",Z);return!1};g.y=function(a){K=q.y=w(a);q.attr("translateY",K);return!1};var z=q.css;return r(q,{css:function(a){if(a){var b={},a=x(a);n("fontSize,fontWeight,fontFamily,color,lineHeight,width,textDecoration,textShadow".split(","),function(c){a[c]!==u&&(b[c]=a[c],delete a[c])});o.css(b)}return z.call(q,a)},getBBox:function(){return{width:wa.width+2*ca,height:wa.height+2*ca,x:wa.x-ca,y:wa.y-ca}},shadow:function(a){H&&
|
||||
H.shadow(a);return q},destroy:function(){X(q,"add",m);X(q.element,"mouseenter");X(q.element,"mouseleave");o&&(o=o.destroy());H&&(H=H.destroy());qa.prototype.destroy.call(q);q=p=j=k=l=m=null}})}};Xa=va;r(qa.prototype,{htmlCss:function(a){var b=this.element;if(b=a&&b.tagName==="SPAN"&&a.width)delete a.width,this.textWidth=b,this.updateTransform();this.styles=r(this.styles,a);D(this.element,a);return this},htmlGetBBox:function(){var a=this.element,b=this.bBox;if(!b){if(a.nodeName==="text")a.style.position=
|
||||
"absolute";b=this.bBox={x:a.offsetLeft,y:a.offsetTop,width:a.offsetWidth,height:a.offsetHeight}}return b},htmlUpdateTransform:function(){if(this.added){var a=this.renderer,b=this.element,c=this.translateX||0,d=this.translateY||0,e=this.x||0,f=this.y||0,g=this.textAlign||"left",h={left:0,center:0.5,right:1}[g],i=this.shadows;D(b,{marginLeft:c,marginTop:d});i&&n(i,function(a){D(a,{marginLeft:c+1,marginTop:d+1})});this.inverted&&n(b.childNodes,function(c){a.invertChild(c,b)});if(b.tagName==="SPAN"){var j=
|
||||
this.rotation,k,l=z(this.textWidth),m=[j,g,b.innerHTML,this.textWidth].join(",");if(m!==this.cTT){k=a.fontMetrics(b.style.fontSize).b;s(j)&&this.setSpanRotation(j,h,k);i=o(this.elemWidth,b.offsetWidth);if(i>l&&/[ \-]/.test(b.textContent||b.innerText))D(b,{width:l+"px",display:"block",whiteSpace:"normal"}),i=l;this.getSpanCorrection(i,k,h,j,g)}D(b,{left:e+(this.xCorr||0)+"px",top:f+(this.yCorr||0)+"px"});if(gb)k=b.offsetHeight;this.cTT=m}}else this.alignOnAdd=!0},setSpanRotation:function(a,b,c){var d=
|
||||
{},e=za?"-ms-transform":gb?"-webkit-transform":Wa?"MozTransform":Hb?"-o-transform":"";d[e]=d.transform="rotate("+a+"deg)";d[e+(Wa?"Origin":"-origin")]=b*100+"% "+c+"px";D(this.element,d)},getSpanCorrection:function(a,b,c){this.xCorr=-a*c;this.yCorr=-b}});r(va.prototype,{html:function(a,b,c){var d=G.chart.style,e=this.createElement("span"),f=e.attrSetters,g=e.element,h=e.renderer;f.text=function(a){a!==g.innerHTML&&delete this.bBox;g.innerHTML=a;return!1};f.x=f.y=f.align=f.rotation=function(a,b){b===
|
||||
"align"&&(b="textAlign");e[b]=a;e.htmlUpdateTransform();return!1};e.attr({text:a,x:w(b),y:w(c)}).css({position:"absolute",whiteSpace:"nowrap",fontFamily:d.fontFamily,fontSize:d.fontSize});e.css=e.htmlCss;if(h.isSVG)e.add=function(a){var b,c=h.box.parentNode,d=[];if(this.parentGroup=a){if(b=a.div,!b){for(;a;)d.push(a),a=a.parentGroup;n(d.reverse(),function(a){var d;b=a.div=a.div||T(Ha,{className:y(a.element,"class")},{position:"absolute",left:(a.translateX||0)+"px",top:(a.translateY||0)+"px"},b||c);
|
||||
d=b.style;r(a.attrSetters,{translateX:function(a){d.left=a+"px"},translateY:function(a){d.top=a+"px"},visibility:function(a,b){d[b]=a}})})}}else b=c;b.appendChild(g);e.added=!0;e.alignOnAdd&&e.htmlUpdateTransform();return e};return e}});var R;if(!V&&!ba){Highcharts.VMLElement=R={init:function(a,b){var c=["<",b,' filled="f" stroked="f"'],d=["position: ","absolute",";"],e=b===Ha;(b==="shape"||e)&&d.push("left:0;top:0;width:1px;height:1px;");d.push("visibility: ",e?"hidden":"visible");c.push(' style="',
|
||||
d.join(""),'"/>');if(b)c=e||b==="span"||b==="img"?c.join(""):a.prepVML(c),this.element=T(c);this.renderer=a;this.attrSetters={}},add:function(a){var b=this.renderer,c=this.element,d=b.box,d=a?a.element||a:d;a&&a.inverted&&b.invertChild(c,d);d.appendChild(c);this.added=!0;this.alignOnAdd&&!this.deferUpdateTransform&&this.updateTransform();A(this,"add");return this},updateTransform:qa.prototype.htmlUpdateTransform,setSpanRotation:function(){var a=this.rotation,b=U(a*Ca),c=$(a*Ca);D(this.element,{filter:a?
|
||||
["progid:DXImageTransform.Microsoft.Matrix(M11=",b,", M12=",-c,", M21=",c,", M22=",b,", sizingMethod='auto expand')"].join(""):Q})},getSpanCorrection:function(a,b,c,d,e){var f=d?U(d*Ca):1,g=d?$(d*Ca):0,h=o(this.elemHeight,this.element.offsetHeight),i;this.xCorr=f<0&&-a;this.yCorr=g<0&&-h;i=f*g<0;this.xCorr+=g*b*(i?1-c:c);this.yCorr-=f*b*(d?i?c:1-c:1);e&&e!=="left"&&(this.xCorr-=a*c*(f<0?-1:1),d&&(this.yCorr-=h*c*(g<0?-1:1)),D(this.element,{textAlign:e}))},pathToVML:function(a){for(var b=a.length,
|
||||
c=[];b--;)if(xa(a[b]))c[b]=w(a[b]*10)-5;else if(a[b]==="Z")c[b]="x";else if(c[b]=a[b],a.isArc&&(a[b]==="wa"||a[b]==="at"))c[b+5]===c[b+7]&&(c[b+7]+=a[b+7]>a[b+5]?1:-1),c[b+6]===c[b+8]&&(c[b+8]+=a[b+8]>a[b+6]?1:-1);return c.join(" ")||"x"},attr:function(a,b){var c,d,e,f=this.element||{},g=f.style,h=f.nodeName,i=this.renderer,j=this.symbolName,k,l=this.shadows,m,p=this.attrSetters,q=this;da(a)&&s(b)&&(c=a,a={},a[c]=b);if(da(a))c=a,q=c==="strokeWidth"||c==="stroke-width"?this.strokeweight:this[c];else for(c in a)if(d=
|
||||
a[c],m=!1,e=p[c]&&p[c].call(this,d,c),e!==!1&&d!==null){e!==u&&(d=e);if(j&&/^(x|y|r|start|end|width|height|innerR|anchorX|anchorY)/.test(c))k||(this.symbolAttr(a),k=!0),m=!0;else if(c==="d"){d=d||[];this.d=d.join(" ");f.path=d=this.pathToVML(d);if(l)for(e=l.length;e--;)l[e].path=l[e].cutOff?this.cutOffPath(d,l[e].cutOff):d;m=!0}else if(c==="visibility"){if(l)for(e=l.length;e--;)l[e].style[c]=d;h==="DIV"&&(d=d==="hidden"?"-999em":0,fb||(g[c]=d?"visible":"hidden"),c="top");g[c]=d;m=!0}else if(c==="zIndex")d&&
|
||||
(g[c]=d),m=!0;else if(ta(c,["x","y","width","height"])!==-1)this[c]=d,c==="x"||c==="y"?c={x:"left",y:"top"}[c]:d=t(0,d),this.updateClipping?(this[c]=d,this.updateClipping()):g[c]=d,m=!0;else if(c==="class"&&h==="DIV")f.className=d;else if(c==="stroke")d=i.color(d,f,c),c="strokecolor";else if(c==="stroke-width"||c==="strokeWidth")f.stroked=d?!0:!1,c="strokeweight",this[c]=d,xa(d)&&(d+="px");else if(c==="dashstyle")(f.getElementsByTagName("stroke")[0]||T(i.prepVML(["<stroke/>"]),null,null,f))[c]=d||
|
||||
"solid",this.dashstyle=d,m=!0;else if(c==="fill")if(h==="SPAN")g.color=d;else{if(h!=="IMG")f.filled=d!==Q?!0:!1,d=i.color(d,f,c,this),c="fillcolor"}else if(c==="opacity")m=!0;else if(h==="shape"&&c==="rotation")this[c]=f.style[c]=d,f.style.left=-w($(d*Ca)+1)+"px",f.style.top=w(U(d*Ca))+"px";else if(c==="translateX"||c==="translateY"||c==="rotation")this[c]=d,this.updateTransform(),m=!0;m||(fb?f[c]=d:y(f,c,d))}return q},clip:function(a){var b=this,c;a?(c=a.members,fa(c,b),c.push(b),b.destroyClip=function(){fa(c,
|
||||
b)},a=a.getCSS(b)):(b.destroyClip&&b.destroyClip(),a={clip:fb?"inherit":"rect(auto)"});return b.css(a)},css:qa.prototype.htmlCss,safeRemoveChild:function(a){a.parentNode&&Oa(a)},destroy:function(){this.destroyClip&&this.destroyClip();return qa.prototype.destroy.apply(this)},on:function(a,b){this.element["on"+a]=function(){var a=C.event;a.target=a.srcElement;b(a)};return this},cutOffPath:function(a,b){var c,a=a.split(/[ ,]/);c=a.length;if(c===9||c===11)a[c-4]=a[c-2]=z(a[c-2])-10*b;return a.join(" ")},
|
||||
shadow:function(a,b,c){var d=[],e,f=this.element,g=this.renderer,h,i=f.style,j,k=f.path,l,m,p,q;k&&typeof k.value!=="string"&&(k="x");m=k;if(a){p=o(a.width,3);q=(a.opacity||0.15)/p;for(e=1;e<=3;e++){l=p*2+1-2*e;c&&(m=this.cutOffPath(k.value,l+0.5));j=['<shape isShadow="true" strokeweight="',l,'" filled="false" path="',m,'" coordsize="10 10" style="',f.style.cssText,'" />'];h=T(g.prepVML(j),null,{left:z(i.left)+o(a.offsetX,1),top:z(i.top)+o(a.offsetY,1)});if(c)h.cutOff=l+1;j=['<stroke color="',a.color||
|
||||
"black",'" opacity="',q*e,'"/>'];T(g.prepVML(j),null,null,h);b?b.element.appendChild(h):f.parentNode.insertBefore(h,f);d.push(h)}this.shadows=d}return this}};R=ga(qa,R);var xb={Element:R,isIE8:sa.indexOf("MSIE 8.0")>-1,init:function(a,b,c){var d,e;this.alignedObjects=[];d=this.createElement(Ha);e=d.element;e.style.position="relative";a.appendChild(d.element);this.isVML=!0;this.box=e;this.boxWrapper=d;this.cache={};this.setSize(b,c,!1);if(!v.namespaces.hcv){v.namespaces.add("hcv","urn:schemas-microsoft-com:vml");
|
||||
try{v.createStyleSheet().cssText="hcv\\:fill, hcv\\:path, hcv\\:shape, hcv\\:stroke{ behavior:url(#default#VML); display: inline-block; } "}catch(f){v.styleSheets[0].cssText+="hcv\\:fill, hcv\\:path, hcv\\:shape, hcv\\:stroke{ behavior:url(#default#VML); display: inline-block; } "}}},isHidden:function(){return!this.box.offsetWidth},clipRect:function(a,b,c,d){var e=this.createElement(),f=S(a);return r(e,{members:[],left:(f?a.x:a)+1,top:(f?a.y:b)+1,width:(f?a.width:c)-1,height:(f?a.height:d)-1,getCSS:function(a){var b=
|
||||
a.element,c=b.nodeName,a=a.inverted,d=this.top-(c==="shape"?b.offsetTop:0),e=this.left,b=e+this.width,f=d+this.height,d={clip:"rect("+w(a?e:d)+"px,"+w(a?f:b)+"px,"+w(a?b:f)+"px,"+w(a?d:e)+"px)"};!a&&fb&&c==="DIV"&&r(d,{width:b+"px",height:f+"px"});return d},updateClipping:function(){n(e.members,function(a){a.css(e.getCSS(a))})}})},color:function(a,b,c,d){var e=this,f,g=/^rgba/,h,i,j=Q;a&&a.linearGradient?i="gradient":a&&a.radialGradient&&(i="pattern");if(i){var k,l,m=a.linearGradient||a.radialGradient,
|
||||
p,q,o,H,t,s="",a=a.stops,w,r=[],u=function(){h=['<fill colors="'+r.join(",")+'" opacity="',o,'" o:opacity2="',q,'" type="',i,'" ',s,'focus="100%" method="any" />'];T(e.prepVML(h),null,null,b)};p=a[0];w=a[a.length-1];p[0]>0&&a.unshift([0,p[1]]);w[0]<1&&a.push([1,w[1]]);n(a,function(a,b){g.test(a[1])?(f=ua(a[1]),k=f.get("rgb"),l=f.get("a")):(k=a[1],l=1);r.push(a[0]*100+"% "+k);b?(o=l,H=k):(q=l,t=k)});if(c==="fill")if(i==="gradient")c=m.x1||m[0]||0,a=m.y1||m[1]||0,p=m.x2||m[2]||0,m=m.y2||m[3]||0,s='angle="'+
|
||||
(90-P.atan((m-a)/(p-c))*180/Ba)+'"',u();else{var j=m.r,x=j*2,Z=j*2,y=m.cx,B=m.cy,v=b.radialReference,ja,j=function(){v&&(ja=d.getBBox(),y+=(v[0]-ja.x)/ja.width-0.5,B+=(v[1]-ja.y)/ja.height-0.5,x*=v[2]/ja.width,Z*=v[2]/ja.height);s='src="'+G.global.VMLRadialGradientURL+'" size="'+x+","+Z+'" origin="0.5,0.5" position="'+y+","+B+'" color2="'+t+'" ';u()};d.added?j():F(d,"add",j);j=H}else j=k}else if(g.test(a)&&b.tagName!=="IMG")f=ua(a),h=["<",c,' opacity="',f.get("a"),'"/>'],T(this.prepVML(h),null,null,
|
||||
b),j=f.get("rgb");else{j=b.getElementsByTagName(c);if(j.length)j[0].opacity=1,j[0].type="solid";j=a}return j},prepVML:function(a){var b=this.isIE8,a=a.join("");b?(a=a.replace("/>",' xmlns="urn:schemas-microsoft-com:vml" />'),a=a.indexOf('style="')===-1?a.replace("/>",' style="display:inline-block;behavior:url(#default#VML);" />'):a.replace('style="','style="display:inline-block;behavior:url(#default#VML);')):a=a.replace("<","<hcv:");return a},text:va.prototype.html,path:function(a){var b={coordsize:"10 10"};
|
||||
La(a)?b.d=a:S(a)&&r(b,a);return this.createElement("shape").attr(b)},circle:function(a,b,c){var d=this.symbol("circle");if(S(a))c=a.r,b=a.y,a=a.x;d.isCircle=!0;d.r=c;return d.attr({x:a,y:b})},g:function(a){var b;a&&(b={className:"highcharts-"+a,"class":"highcharts-"+a});return this.createElement(Ha).attr(b)},image:function(a,b,c,d,e){var f=this.createElement("img").attr({src:a});arguments.length>1&&f.attr({x:b,y:c,width:d,height:e});return f},rect:function(a,b,c,d,e,f){var g=this.symbol("rect");g.r=
|
||||
S(a)?a.r:e;return g.attr(S(a)?a:g.crisp(f,a,b,t(c,0),t(d,0)))},invertChild:function(a,b){var c=b.style;D(a,{flip:"x",left:z(c.width)-1,top:z(c.height)-1,rotation:-90})},symbols:{arc:function(a,b,c,d,e){var f=e.start,g=e.end,h=e.r||c||d,c=e.innerR,d=U(f),i=$(f),j=U(g),k=$(g);if(g-f===0)return["x"];f=["wa",a-h,b-h,a+h,b+h,a+h*d,b+h*i,a+h*j,b+h*k];e.open&&!c&&f.push("e","M",a,b);f.push("at",a-c,b-c,a+c,b+c,a+c*j,b+c*k,a+c*d,b+c*i,"x","e");f.isArc=!0;return f},circle:function(a,b,c,d,e){e&&(c=d=2*e.r);
|
||||
e&&e.isCircle&&(a-=c/2,b-=d/2);return["wa",a,b,a+c,b+d,a+c,b+d/2,a+c,b+d/2,"e"]},rect:function(a,b,c,d,e){var f=a+c,g=b+d,h;!s(e)||!e.r?f=va.prototype.symbols.square.apply(0,arguments):(h=I(e.r,c,d),f=["M",a+h,b,"L",f-h,b,"wa",f-2*h,b,f,b+2*h,f-h,b,f,b+h,"L",f,g-h,"wa",f-2*h,g-2*h,f,g,f,g-h,f-h,g,"L",a+h,g,"wa",a,g-2*h,a+2*h,g,a+h,g,a,g-h,"L",a,b+h,"wa",a,b,a+2*h,b+2*h,a,b+h,a+h,b,"x","e"]);return f}}};Highcharts.VMLRenderer=R=function(){this.init.apply(this,arguments)};R.prototype=x(va.prototype,
|
||||
xb);Xa=R}va.prototype.measureSpanWidth=function(a,b){var c=v.createElement("span"),d;d=v.createTextNode(a);c.appendChild(d);D(c,b);this.box.appendChild(c);d=c.offsetWidth;Oa(c);return d};var Mb;if(ba)Highcharts.CanVGRenderer=R=function(){Da="http://www.w3.org/1999/xhtml"},R.prototype.symbols={},Mb=function(){function a(){var a=b.length,d;for(d=0;d<a;d++)b[d]();b=[]}var b=[];return{push:function(c,d){b.length===0&&Pb(d,a);b.push(c)}}}(),Xa=R;Ra.prototype={addLabel:function(){var a=this.axis,b=a.options,
|
||||
c=a.chart,d=a.horiz,e=a.categories,f=a.names,g=this.pos,h=b.labels,i=a.tickPositions,d=d&&e&&!h.step&&!h.staggerLines&&!h.rotation&&c.plotWidth/i.length||!d&&(c.margin[3]||c.chartWidth*0.33),j=g===i[0],k=g===i[i.length-1],l,f=e?o(e[g],f[g],g):g,e=this.label,m=i.info;a.isDatetimeAxis&&m&&(l=b.dateTimeLabelFormats[m.higherRanks[g]||m.unitName]);this.isFirst=j;this.isLast=k;b=a.labelFormatter.call({axis:a,chart:c,isFirst:j,isLast:k,dateTimeLabelFormat:l,value:a.isLog?ha(ea(f)):f});g=d&&{width:t(1,w(d-
|
||||
2*(h.padding||10)))+"px"};g=r(g,h.style);if(s(e))e&&e.attr({text:b}).css(g);else{l={align:a.labelAlign};if(xa(h.rotation))l.rotation=h.rotation;if(d&&h.ellipsis)l._clipHeight=a.len/i.length;this.label=s(b)&&h.enabled?c.renderer.text(b,0,0,h.useHTML).attr(l).css(g).add(a.labelGroup):null}},getLabelSize:function(){var a=this.label,b=this.axis;return a?a.getBBox()[b.horiz?"height":"width"]:0},getLabelSides:function(){var a=this.label.getBBox(),b=this.axis,c=b.horiz,d=b.options.labels,a=c?a.width:a.height,
|
||||
b=c?a*{left:0,center:0.5,right:1}[b.labelAlign]-d.x:a;return[-b,a-b]},handleOverflow:function(a,b){var B;var c=!0,d=this.axis,e=this.isFirst,f=this.isLast,g=d.horiz?b.x:b.y,h=d.reversed,i=d.tickPositions,j=this.getLabelSides(),k=j[0],j=j[1],l=d.pos,m=l+d.len,p=this.label.line||0,q=d.labelEdge,o=d.justifyLabels&&(e||f);q[p]===u||g+k>q[p]?q[p]=g+j:o||(c=!1);if(o)B=(d=d.ticks[i[a+(e?1:-1)]])&&d.label.xy&&d.label.xy.x+d.getLabelSides()[e?0:1],i=B,e&&!h||f&&h?g+k<l&&(g=l-k,d&&g+j>i&&(c=!1)):g+j>m&&(g=
|
||||
m-j,d&&g+k<i&&(c=!1)),b.x=g;return c},getPosition:function(a,b,c,d){var e=this.axis,f=e.chart,g=d&&f.oldChartHeight||f.chartHeight;return{x:a?e.translate(b+c,null,null,d)+e.transB:e.left+e.offset+(e.opposite?(d&&f.oldChartWidth||f.chartWidth)-e.right-e.left:0),y:a?g-e.bottom+e.offset-(e.opposite?e.height:0):g-e.translate(b+c,null,null,d)-e.transB}},getLabelPosition:function(a,b,c,d,e,f,g,h){var i=this.axis,j=i.transA,k=i.reversed,l=i.staggerLines,m=i.chart.renderer.fontMetrics(e.style.fontSize).b,
|
||||
p=e.rotation,a=a+e.x-(f&&d?f*j*(k?-1:1):0),b=b+e.y-(f&&!d?f*j*(k?1:-1):0);p&&i.side===2&&(b-=m-m*U(p*Ca));!s(e.y)&&!p&&(b+=m-c.getBBox().height/2);if(l)c.line=g/(h||1)%l,b+=c.line*(i.labelOffset/l);return{x:a,y:b}},getMarkPath:function(a,b,c,d,e,f){return f.crispLine(["M",a,b,"L",a+(e?0:-c),b+(e?c:0)],d)},render:function(a,b,c){var d=this.axis,e=d.options,f=d.chart.renderer,g=d.horiz,h=this.type,i=this.label,j=this.pos,k=e.labels,l=this.gridLine,m=h?h+"Grid":"grid",p=h?h+"Tick":"tick",q=e[m+"LineWidth"],
|
||||
n=e[m+"LineColor"],H=e[m+"LineDashStyle"],t=e[p+"Length"],m=e[p+"Width"]||0,s=e[p+"Color"],w=e[p+"Position"],p=this.mark,r=k.step,ia=!0,x=d.tickmarkOffset,v=this.getPosition(g,j,x,b),y=v.x,v=v.y,B=g&&y===d.pos+d.len||!g&&v===d.pos?-1:1;this.isActive=!0;if(q){j=d.getPlotLinePath(j+x,q*B,b,!0);if(l===u){l={stroke:n,"stroke-width":q};if(H)l.dashstyle=H;if(!h)l.zIndex=1;if(b)l.opacity=0;this.gridLine=l=q?f.path(j).attr(l).add(d.gridGroup):null}if(!b&&l&&j)l[this.isNew?"attr":"animate"]({d:j,opacity:c})}if(m&&
|
||||
t)w==="inside"&&(t=-t),d.opposite&&(t=-t),h=this.getMarkPath(y,v,t,m*B,g,f),p?p.animate({d:h,opacity:c}):this.mark=f.path(h).attr({stroke:s,"stroke-width":m,opacity:c}).add(d.axisGroup);if(i&&!isNaN(y))i.xy=v=this.getLabelPosition(y,v,i,g,k,x,a,r),this.isFirst&&!this.isLast&&!o(e.showFirstLabel,1)||this.isLast&&!this.isFirst&&!o(e.showLastLabel,1)?ia=!1:!d.isRadial&&!k.step&&!k.rotation&&!b&&c!==0&&(ia=this.handleOverflow(a,v)),r&&a%r&&(ia=!1),ia&&!isNaN(v.y)?(v.opacity=c,i[this.isNew?"attr":"animate"](v),
|
||||
this.isNew=!1):i.attr("y",-9999)},destroy:function(){Na(this,this.axis)}};var yb=function(a,b){this.axis=a;if(b)this.options=b,this.id=b.id};yb.prototype={render:function(){var a=this,b=a.axis,c=b.horiz,d=(b.pointRange||0)/2,e=a.options,f=e.label,g=a.label,h=e.width,i=e.to,j=e.from,k=s(j)&&s(i),l=e.value,m=e.dashStyle,p=a.svgElem,q=[],n,H=e.color,w=e.zIndex,r=e.events,u=b.chart.renderer;b.isLog&&(j=ya(j),i=ya(i),l=ya(l));if(h){if(q=b.getPlotLinePath(l,h),d={stroke:H,"stroke-width":h},m)d.dashstyle=
|
||||
m}else if(k){if(j=t(j,b.min-d),i=I(i,b.max+d),q=b.getPlotBandPath(j,i,e),d={fill:H},e.borderWidth)d.stroke=e.borderColor,d["stroke-width"]=e.borderWidth}else return;if(s(w))d.zIndex=w;if(p)if(q)p.animate({d:q},null,p.onGetPath);else{if(p.hide(),p.onGetPath=function(){p.show()},g)a.label=g=g.destroy()}else if(q&&q.length&&(a.svgElem=p=u.path(q).attr(d).add(),r))for(n in e=function(b){p.on(b,function(c){r[b].apply(a,[c])})},r)e(n);if(f&&s(f.text)&&q&&q.length&&b.width>0&&b.height>0){f=x({align:c&&k&&
|
||||
"center",x:c?!k&&4:10,verticalAlign:!c&&k&&"middle",y:c?k?16:10:k?6:-4,rotation:c&&!k&&90},f);if(!g)a.label=g=u.text(f.text,0,0,f.useHTML).attr({align:f.textAlign||f.align,rotation:f.rotation,zIndex:w}).css(f.style).add();b=[q[1],q[4],o(q[6],q[1])];q=[q[2],q[5],o(q[7],q[2])];c=Ma(b);k=Ma(q);g.align(f,!1,{x:c,y:k,width:Aa(b)-c,height:Aa(q)-k});g.show()}else g&&g.hide();return a},destroy:function(){fa(this.axis.plotLinesAndBands,this);delete this.axis;Na(this)}};ra.prototype={defaultOptions:{dateTimeLabelFormats:{millisecond:"%H:%M:%S.%L",
|
||||
second:"%H:%M:%S",minute:"%H:%M",hour:"%H:%M",day:"%e. %b",week:"%e. %b",month:"%b '%y",year:"%Y"},endOnTick:!1,gridLineColor:"#C0C0C0",labels:J,lineColor:"#C0D0E0",lineWidth:1,minPadding:0.01,maxPadding:0.01,minorGridLineColor:"#E0E0E0",minorGridLineWidth:1,minorTickColor:"#A0A0A0",minorTickLength:2,minorTickPosition:"outside",startOfWeek:1,startOnTick:!1,tickColor:"#C0D0E0",tickLength:5,tickmarkPlacement:"between",tickPixelInterval:100,tickPosition:"outside",tickWidth:1,title:{align:"middle",style:{color:"#4d759e",
|
||||
fontWeight:"bold"}},type:"linear"},defaultYAxisOptions:{endOnTick:!0,gridLineWidth:1,tickPixelInterval:72,showLastLabel:!0,labels:{x:-8,y:3},lineWidth:0,maxPadding:0.05,minPadding:0.05,startOnTick:!0,tickWidth:0,title:{rotation:270,text:"Values"},stackLabels:{enabled:!1,formatter:function(){return Ea(this.total,-1)},style:J.style}},defaultLeftAxisOptions:{labels:{x:-8,y:null},title:{rotation:270}},defaultRightAxisOptions:{labels:{x:8,y:null},title:{rotation:90}},defaultBottomAxisOptions:{labels:{x:0,
|
||||
y:14},title:{rotation:0}},defaultTopAxisOptions:{labels:{x:0,y:-5},title:{rotation:0}},init:function(a,b){var c=b.isX;this.horiz=a.inverted?!c:c;this.coll=(this.isXAxis=c)?"xAxis":"yAxis";this.opposite=b.opposite;this.side=b.side||(this.horiz?this.opposite?0:2:this.opposite?1:3);this.setOptions(b);var d=this.options,e=d.type;this.labelFormatter=d.labels.formatter||this.defaultLabelFormatter;this.userOptions=b;this.minPixelPadding=0;this.chart=a;this.reversed=d.reversed;this.zoomEnabled=d.zoomEnabled!==
|
||||
!1;this.categories=d.categories||e==="category";this.names=[];this.isLog=e==="logarithmic";this.isDatetimeAxis=e==="datetime";this.isLinked=s(d.linkedTo);this.tickmarkOffset=this.categories&&d.tickmarkPlacement==="between"?0.5:0;this.ticks={};this.labelEdge=[];this.minorTicks={};this.plotLinesAndBands=[];this.alternateBands={};this.len=0;this.minRange=this.userMinRange=d.minRange||d.maxZoom;this.range=d.range;this.offset=d.offset||0;this.stacks={};this.oldStacks={};this.stackExtremes={};this.min=
|
||||
this.max=null;this.crosshair=o(d.crosshair,ka(a.options.tooltip.crosshairs)[c?0:1],!1);var f,d=this.options.events;ta(this,a.axes)===-1&&(a.axes.push(this),a[this.coll].push(this));this.series=this.series||[];if(a.inverted&&c&&this.reversed===u)this.reversed=!0;this.removePlotLine=this.removePlotBand=this.removePlotBandOrLine;for(f in d)F(this,f,d[f]);if(this.isLog)this.val2lin=ya,this.lin2val=ea},setOptions:function(a){this.options=x(this.defaultOptions,this.isXAxis?{}:this.defaultYAxisOptions,[this.defaultTopAxisOptions,
|
||||
this.defaultRightAxisOptions,this.defaultBottomAxisOptions,this.defaultLeftAxisOptions][this.side],x(G[this.coll],a))},defaultLabelFormatter:function(){var a=this.axis,b=this.value,c=a.categories,d=this.dateTimeLabelFormat,e=G.lang.numericSymbols,f=e&&e.length,g,h=a.options.labels.format,a=a.isLog?b:a.tickInterval;if(h)g=Ga(h,this);else if(c)g=b;else if(d)g=$a(d,b);else if(f&&a>=1E3)for(;f--&&g===u;)c=Math.pow(1E3,f+1),a>=c&&e[f]!==null&&(g=Ea(b/c,-1)+e[f]);g===u&&(g=b>=1E4?Ea(b,0):Ea(b,-1,u,""));
|
||||
return g},getSeriesExtremes:function(){var a=this,b=a.chart;a.hasVisibleSeries=!1;a.dataMin=a.dataMax=null;a.stackExtremes={};a.buildStacks();n(a.series,function(c){if(c.visible||!b.options.chart.ignoreHiddenSeries){var d;d=c.options.threshold;var e;a.hasVisibleSeries=!0;a.isLog&&d<=0&&(d=null);if(a.isXAxis){if(d=c.xData,d.length)a.dataMin=I(o(a.dataMin,d[0]),Ma(d)),a.dataMax=t(o(a.dataMax,d[0]),Aa(d))}else{c.getExtremes();e=c.dataMax;c=c.dataMin;if(s(c)&&s(e))a.dataMin=I(o(a.dataMin,c),c),a.dataMax=
|
||||
t(o(a.dataMax,e),e);if(s(d))if(a.dataMin>=d)a.dataMin=d,a.ignoreMinPadding=!0;else if(a.dataMax<d)a.dataMax=d,a.ignoreMaxPadding=!0}}})},translate:function(a,b,c,d,e,f){var g=this.len,h=1,i=0,j=d?this.oldTransA:this.transA,d=d?this.oldMin:this.min,k=this.minPixelPadding,e=(this.options.ordinal||this.isLog&&e)&&this.lin2val;if(!j)j=this.transA;c&&(h*=-1,i=g);this.reversed&&(h*=-1,i-=h*g);b?(a=a*h+i,a-=k,a=a/j+d,e&&(a=this.lin2val(a))):(e&&(a=this.val2lin(a)),f==="between"&&(f=0.5),a=h*(a-d)*j+i+h*
|
||||
k+(xa(f)?j*f*this.pointRange:0));return a},toPixels:function(a,b){return this.translate(a,!1,!this.horiz,null,!0)+(b?0:this.pos)},toValue:function(a,b){return this.translate(a-(b?0:this.pos),!0,!this.horiz,null,!0)},getPlotLinePath:function(a,b,c,d,e){var f=this.chart,g=this.left,h=this.top,i,j,k=c&&f.oldChartHeight||f.chartHeight,l=c&&f.oldChartWidth||f.chartWidth,m;i=this.transB;e=o(e,this.translate(a,null,null,c));a=c=w(e+i);i=j=w(k-e-i);if(isNaN(e))m=!0;else if(this.horiz){if(i=h,j=k-this.bottom,
|
||||
a<g||a>g+this.width)m=!0}else if(a=g,c=l-this.right,i<h||i>h+this.height)m=!0;return m&&!d?null:f.renderer.crispLine(["M",a,i,"L",c,j],b||1)},getLinearTickPositions:function(a,b,c){for(var d,b=ha(N(b/a)*a),c=ha(Ia(c/a)*a),e=[];b<=c;){e.push(b);b=ha(b+a);if(b===d)break;d=b}return e},getMinorTickPositions:function(){var a=this.options,b=this.tickPositions,c=this.minorTickInterval,d=[],e;if(this.isLog){e=b.length;for(a=1;a<e;a++)d=d.concat(this.getLogTickPositions(c,b[a-1],b[a],!0))}else if(this.isDatetimeAxis&&
|
||||
a.minorTickInterval==="auto")d=d.concat(this.getTimeTicks(this.normalizeTimeTickInterval(c),this.min,this.max,a.startOfWeek)),d[0]<this.min&&d.shift();else for(b=this.min+(b[0]-this.min)%c;b<=this.max;b+=c)d.push(b);return d},adjustForMinRange:function(){var a=this.options,b=this.min,c=this.max,d,e=this.dataMax-this.dataMin>=this.minRange,f,g,h,i,j;if(this.isXAxis&&this.minRange===u&&!this.isLog)s(a.min)||s(a.max)?this.minRange=null:(n(this.series,function(a){i=a.xData;for(g=j=a.xIncrement?1:i.length-
|
||||
1;g>0;g--)if(h=i[g]-i[g-1],f===u||h<f)f=h}),this.minRange=I(f*5,this.dataMax-this.dataMin));if(c-b<this.minRange){var k=this.minRange;d=(k-c+b)/2;d=[b-d,o(a.min,b-d)];if(e)d[2]=this.dataMin;b=Aa(d);c=[b+k,o(a.max,b+k)];if(e)c[2]=this.dataMax;c=Ma(c);c-b<k&&(d[0]=c-k,d[1]=o(a.min,c-k),b=Aa(d))}this.min=b;this.max=c},setAxisTranslation:function(a){var b=this.max-this.min,c=0,d,e=0,f=0,g=this.linkedParent,h=!!this.categories,i=this.transA;if(this.isXAxis||h)g?(e=g.minPointOffset,f=g.pointRangePadding):
|
||||
n(this.series,function(a){var g=t(a.pointRange,+h),i=a.options.pointPlacement,m=a.closestPointRange;g>b&&(g=0);c=t(c,g);e=t(e,da(i)?0:g/2);f=t(f,i==="on"?0:g);!a.noSharedTooltip&&s(m)&&(d=s(d)?I(d,m):m)}),g=this.ordinalSlope&&d?this.ordinalSlope/d:1,this.minPointOffset=e*=g,this.pointRangePadding=f*=g,this.pointRange=I(c,b),this.closestPointRange=d;if(a)this.oldTransA=i;this.translationSlope=this.transA=i=this.len/(b+f||1);this.transB=this.horiz?this.left:this.bottom;this.minPixelPadding=i*e},setTickPositions:function(a){var b=
|
||||
this,c=b.chart,d=b.options,e=b.isLog,f=b.isDatetimeAxis,g=b.isXAxis,h=b.isLinked,i=b.options.tickPositioner,j=d.maxPadding,k=d.minPadding,l=d.tickInterval,m=d.minTickInterval,p=d.tickPixelInterval,q,na=b.categories;h?(b.linkedParent=c[b.coll][d.linkedTo],c=b.linkedParent.getExtremes(),b.min=o(c.min,c.dataMin),b.max=o(c.max,c.dataMax),d.type!==b.linkedParent.options.type&&la(11,1)):(b.min=o(b.userMin,d.min,b.dataMin),b.max=o(b.userMax,d.max,b.dataMax));if(e)!a&&I(b.min,o(b.dataMin,b.min))<=0&&la(10,
|
||||
1),b.min=ha(ya(b.min)),b.max=ha(ya(b.max));if(b.range&&s(b.max))b.userMin=b.min=t(b.min,b.max-b.range),b.userMax=b.max,b.range=null;b.beforePadding&&b.beforePadding();b.adjustForMinRange();if(!na&&!b.usePercentage&&!h&&s(b.min)&&s(b.max)&&(c=b.max-b.min)){if(!s(d.min)&&!s(b.userMin)&&k&&(b.dataMin<0||!b.ignoreMinPadding))b.min-=c*k;if(!s(d.max)&&!s(b.userMax)&&j&&(b.dataMax>0||!b.ignoreMaxPadding))b.max+=c*j}b.min===b.max||b.min===void 0||b.max===void 0?b.tickInterval=1:h&&!l&&p===b.linkedParent.options.tickPixelInterval?
|
||||
b.tickInterval=b.linkedParent.tickInterval:(b.tickInterval=o(l,na?1:(b.max-b.min)*p/t(b.len,p)),!s(l)&&b.len<p&&!this.isRadial&&!na&&d.startOnTick&&d.endOnTick&&(q=!0,b.tickInterval/=4));g&&!a&&n(b.series,function(a){a.processData(b.min!==b.oldMin||b.max!==b.oldMax)});b.setAxisTranslation(!0);b.beforeSetTickPositions&&b.beforeSetTickPositions();if(b.postProcessTickInterval)b.tickInterval=b.postProcessTickInterval(b.tickInterval);if(b.pointRange)b.tickInterval=t(b.pointRange,b.tickInterval);if(!l&&
|
||||
b.tickInterval<m)b.tickInterval=m;if(!f&&!e&&!l)b.tickInterval=mb(b.tickInterval,null,lb(b.tickInterval),d);b.minorTickInterval=d.minorTickInterval==="auto"&&b.tickInterval?b.tickInterval/5:d.minorTickInterval;b.tickPositions=a=d.tickPositions?[].concat(d.tickPositions):i&&i.apply(b,[b.min,b.max]);if(!a)!b.ordinalPositions&&(b.max-b.min)/b.tickInterval>t(2*b.len,200)&&la(19,!0),a=f?b.getTimeTicks(b.normalizeTimeTickInterval(b.tickInterval,d.units),b.min,b.max,d.startOfWeek,b.ordinalPositions,b.closestPointRange,
|
||||
!0):e?b.getLogTickPositions(b.tickInterval,b.min,b.max):b.getLinearTickPositions(b.tickInterval,b.min,b.max),q&&a.splice(1,a.length-2),b.tickPositions=a;if(!h)e=a[0],f=a[a.length-1],h=b.minPointOffset||0,d.startOnTick?b.min=e:b.min-h>e&&a.shift(),d.endOnTick?b.max=f:b.max+h<f&&a.pop(),a.length===1&&(b.min-=0.001,b.max+=0.001)},setMaxTicks:function(){var a=this.chart,b=a.maxTicks||{},c=this.tickPositions,d=this._maxTicksKey=[this.coll,this.pos,this.len].join("-");if(!this.isLinked&&!this.isDatetimeAxis&&
|
||||
c&&c.length>(b[d]||0)&&this.options.alignTicks!==!1)b[d]=c.length;a.maxTicks=b},adjustTickAmount:function(){var a=this._maxTicksKey,b=this.tickPositions,c=this.chart.maxTicks;if(c&&c[a]&&!this.isDatetimeAxis&&!this.categories&&!this.isLinked&&this.options.alignTicks!==!1&&this.min!==u){var d=this.tickAmount,e=b.length;this.tickAmount=a=c[a];if(e<a){for(;b.length<a;)b.push(ha(b[b.length-1]+this.tickInterval));this.transA*=(e-1)/(a-1);this.max=b[b.length-1]}if(s(d)&&a!==d)this.isDirty=!0}},setScale:function(){var a=
|
||||
this.stacks,b,c,d,e;this.oldMin=this.min;this.oldMax=this.max;this.oldAxisLength=this.len;this.setAxisSize();e=this.len!==this.oldAxisLength;n(this.series,function(a){if(a.isDirtyData||a.isDirty||a.xAxis.isDirty)d=!0});if(e||d||this.isLinked||this.forceRedraw||this.userMin!==this.oldUserMin||this.userMax!==this.oldUserMax){if(!this.isXAxis)for(b in a)for(c in a[b])a[b][c].total=null,a[b][c].cum=0;this.forceRedraw=!1;this.getSeriesExtremes();this.setTickPositions();this.oldUserMin=this.userMin;this.oldUserMax=
|
||||
this.userMax;if(!this.isDirty)this.isDirty=e||this.min!==this.oldMin||this.max!==this.oldMax}else if(!this.isXAxis){if(this.oldStacks)a=this.stacks=this.oldStacks;for(b in a)for(c in a[b])a[b][c].cum=a[b][c].total}this.setMaxTicks()},setExtremes:function(a,b,c,d,e){var f=this,g=f.chart,c=o(c,!0),e=r(e,{min:a,max:b});A(f,"setExtremes",e,function(){f.userMin=a;f.userMax=b;f.eventArgs=e;f.isDirtyExtremes=!0;c&&g.redraw(d)})},zoom:function(a,b){this.allowZoomOutside||(s(this.dataMin)&&a<=this.dataMin&&
|
||||
(a=u),s(this.dataMax)&&b>=this.dataMax&&(b=u));this.displayBtn=a!==u||b!==u;this.setExtremes(a,b,!1,u,{trigger:"zoom"});return!0},setAxisSize:function(){var a=this.chart,b=this.options,c=b.offsetLeft||0,d=b.offsetRight||0,e=this.horiz,f,g;this.left=g=o(b.left,a.plotLeft+c);this.top=f=o(b.top,a.plotTop);this.width=c=o(b.width,a.plotWidth-c+d);this.height=b=o(b.height,a.plotHeight);this.bottom=a.chartHeight-b-f;this.right=a.chartWidth-c-g;this.len=t(e?c:b,0);this.pos=e?g:f},getExtremes:function(){var a=
|
||||
this.isLog;return{min:a?ha(ea(this.min)):this.min,max:a?ha(ea(this.max)):this.max,dataMin:this.dataMin,dataMax:this.dataMax,userMin:this.userMin,userMax:this.userMax}},getThreshold:function(a){var b=this.isLog,c=b?ea(this.min):this.min,b=b?ea(this.max):this.max;c>a||a===null?a=c:b<a&&(a=b);return this.translate(a,0,1,0,1)},autoLabelAlign:function(a){a=(o(a,0)-this.side*90+720)%360;return a>15&&a<165?"right":a>195&&a<345?"left":"center"},getOffset:function(){var a=this,b=a.chart,c=b.renderer,d=a.options,
|
||||
e=a.tickPositions,f=a.ticks,g=a.horiz,h=a.side,i=b.inverted?[1,0,3,2][h]:h,j,k=0,l,m=0,p=d.title,q=d.labels,na=0,H=b.axisOffset,w=b.clipOffset,r=[-1,1,1,-1][h],v,x=1,y=o(q.maxStaggerLines,5),z,Z,K,B;a.hasData=j=a.hasVisibleSeries||s(a.min)&&s(a.max)&&!!e;a.showAxis=b=j||o(d.showEmpty,!0);a.staggerLines=a.horiz&&q.staggerLines;if(!a.axisGroup)a.gridGroup=c.g("grid").attr({zIndex:d.gridZIndex||1}).add(),a.axisGroup=c.g("axis").attr({zIndex:d.zIndex||2}).add(),a.labelGroup=c.g("axis-labels").attr({zIndex:q.zIndex||
|
||||
7}).add();if(j||a.isLinked){a.labelAlign=o(q.align||a.autoLabelAlign(q.rotation));n(e,function(b){f[b]?f[b].addLabel():f[b]=new Ra(a,b)});if(a.horiz&&!a.staggerLines&&y&&!q.rotation){for(v=a.reversed?[].concat(e).reverse():e;x<y;){j=[];z=!1;for(q=0;q<v.length;q++)Z=v[q],K=(K=f[Z].label&&f[Z].label.getBBox())?K.width:0,B=q%x,K&&(Z=a.translate(Z),j[B]!==u&&Z<j[B]&&(z=!0),j[B]=Z+K);if(z)x++;else break}if(x>1)a.staggerLines=x}n(e,function(b){if(h===0||h===2||{1:"left",3:"right"}[h]===a.labelAlign)na=
|
||||
t(f[b].getLabelSize(),na)});if(a.staggerLines)na*=a.staggerLines,a.labelOffset=na}else for(v in f)f[v].destroy(),delete f[v];if(p&&p.text&&p.enabled!==!1){if(!a.axisTitle)a.axisTitle=c.text(p.text,0,0,p.useHTML).attr({zIndex:7,rotation:p.rotation||0,align:p.textAlign||{low:"left",middle:"center",high:"right"}[p.align]}).css(p.style).add(a.axisGroup),a.axisTitle.isNew=!0;if(b)k=a.axisTitle.getBBox()[g?"height":"width"],m=o(p.margin,g?5:10),l=p.offset;a.axisTitle[b?"show":"hide"]()}a.offset=r*o(d.offset,
|
||||
H[h]);a.axisTitleMargin=o(l,na+m+(h!==2&&na&&r*d.labels[g?"y":"x"]));H[h]=t(H[h],a.axisTitleMargin+k+r*a.offset);w[i]=t(w[i],N(d.lineWidth/2)*2)},getLinePath:function(a){var b=this.chart,c=this.opposite,d=this.offset,e=this.horiz,f=this.left+(c?this.width:0)+d,d=b.chartHeight-this.bottom-(c?this.height:0)+d;c&&(a*=-1);return b.renderer.crispLine(["M",e?this.left:f,e?d:this.top,"L",e?b.chartWidth-this.right:f,e?d:b.chartHeight-this.bottom],a)},getTitlePosition:function(){var a=this.horiz,b=this.left,
|
||||
c=this.top,d=this.len,e=this.options.title,f=a?b:c,g=this.opposite,h=this.offset,i=z(e.style.fontSize||12),d={low:f+(a?0:d),middle:f+d/2,high:f+(a?d:0)}[e.align],b=(a?c+this.height:b)+(a?1:-1)*(g?-1:1)*this.axisTitleMargin+(this.side===2?i:0);return{x:a?d:b+(g?this.width:0)+h+(e.x||0),y:a?b-(g?this.height:0)+h:d+(e.y||0)}},render:function(){var a=this,b=a.horiz,c=a.reversed,d=a.chart,e=d.renderer,f=a.options,g=a.isLog,h=a.isLinked,i=a.tickPositions,j,k=a.axisTitle,l=a.stacks,m=a.ticks,p=a.minorTicks,
|
||||
q=a.alternateBands,o=f.stackLabels,H=f.alternateGridColor,t=a.tickmarkOffset,w=f.lineWidth,r=d.hasRendered&&s(a.oldMin)&&!isNaN(a.oldMin),v=a.hasData,x=a.showAxis,y,z=a.justifyLabels=!a.staggerLines&&b&&f.labels.overflow==="justify",K;a.labelEdge.length=0;n([m,p,q],function(a){for(var b in a)a[b].isActive=!1});if(v||h)if(a.minorTickInterval&&!a.categories&&n(a.getMinorTickPositions(),function(b){p[b]||(p[b]=new Ra(a,b,"minor"));r&&p[b].isNew&&p[b].render(null,!0);p[b].render(null,!1,1)}),i.length&&
|
||||
(j=i.slice(),(b&&c||!b&&!c)&&j.reverse(),z&&(j=j.slice(1).concat([j[0]])),n(j,function(b,c){z&&(c=c===j.length-1?0:c+1);if(!h||b>=a.min&&b<=a.max)m[b]||(m[b]=new Ra(a,b)),r&&m[b].isNew&&m[b].render(c,!0,0.1),m[b].render(c,!1,1)}),t&&a.min===0&&(m[-1]||(m[-1]=new Ra(a,-1,null,!0)),m[-1].render(-1))),H&&n(i,function(b,c){if(c%2===0&&b<a.max)q[b]||(q[b]=new yb(a)),y=b+t,K=i[c+1]!==u?i[c+1]+t:a.max,q[b].options={from:g?ea(y):y,to:g?ea(K):K,color:H},q[b].render(),q[b].isActive=!0}),!a._addedPlotLB)n((f.plotLines||
|
||||
[]).concat(f.plotBands||[]),function(b){a.addPlotBandOrLine(b)}),a._addedPlotLB=!0;n([m,p,q],function(a){var b,c,e=[],f=pa?pa.duration||500:0,g=function(){for(c=e.length;c--;)a[e[c]]&&!a[e[c]].isActive&&(a[e[c]].destroy(),delete a[e[c]])};for(b in a)if(!a[b].isActive)a[b].render(b,!1,0),a[b].isActive=!1,e.push(b);a===q||!d.hasRendered||!f?g():f&&setTimeout(g,f)});if(w)b=a.getLinePath(w),a.axisLine?a.axisLine.animate({d:b}):a.axisLine=e.path(b).attr({stroke:f.lineColor,"stroke-width":w,zIndex:7}).add(a.axisGroup),
|
||||
a.axisLine[x?"show":"hide"]();if(k&&x)k[k.isNew?"attr":"animate"](a.getTitlePosition()),k.isNew=!1;if(o&&o.enabled){var B,A,f=a.stackTotalGroup;if(!f)a.stackTotalGroup=f=e.g("stack-labels").attr({visibility:"visible",zIndex:6}).add();f.translate(d.plotLeft,d.plotTop);for(B in l)for(A in e=l[B],e)e[A].render(f)}a.isDirty=!1},redraw:function(){var a=this.chart.pointer;a.reset&&a.reset(!0);this.render();n(this.plotLinesAndBands,function(a){a.render()});n(this.series,function(a){a.isDirty=!0})},buildStacks:function(){var a=
|
||||
this.series,b=a.length;if(!this.isXAxis){for(;b--;)a[b].setStackedPoints();if(this.usePercentage)for(b=0;b<a.length;b++)a[b].setPercentStacks()}},destroy:function(a){var b=this,c=b.stacks,d,e=b.plotLinesAndBands;a||X(b);for(d in c)Na(c[d]),c[d]=null;n([b.ticks,b.minorTicks,b.alternateBands],function(a){Na(a)});for(a=e.length;a--;)e[a].destroy();n("stackTotalGroup,axisLine,axisTitle,axisGroup,cross,gridGroup,labelGroup".split(","),function(a){b[a]&&(b[a]=b[a].destroy())});this.cross&&this.cross.destroy()},
|
||||
drawCrosshair:function(a,b){if(this.crosshair)if((s(b)||!o(this.crosshair.snap,!0))===!1)this.hideCrosshair();else{var c,d=this.crosshair,e=d.animation;o(d.snap,!0)?s(b)&&(c=this.chart.inverted!=this.horiz?b.plotX:this.len-b.plotY):c=this.horiz?a.chartX-this.pos:this.len-a.chartY+this.pos;c=this.isRadial?this.getPlotLinePath(this.isXAxis?b.x:o(b.stackY,b.y)):this.getPlotLinePath(null,null,null,null,c);if(c===null)this.hideCrosshair();else if(this.cross)this.cross.attr({visibility:"visible"})[e?"animate":
|
||||
"attr"]({d:c},e);else{e={"stroke-width":d.width||1,stroke:d.color||"#C0C0C0",zIndex:d.zIndex||2};if(d.dashStyle)e.dashstyle=d.dashStyle;this.cross=this.chart.renderer.path(c).attr(e).add()}}},hideCrosshair:function(){this.cross&&this.cross.hide()}};r(ra.prototype,{getPlotBandPath:function(a,b){var c=this.getPlotLinePath(b),d=this.getPlotLinePath(a);d&&c?d.push(c[4],c[5],c[1],c[2]):d=null;return d},addPlotBand:function(a){this.addPlotBandOrLine(a,"plotBands")},addPlotLine:function(a){this.addPlotBandOrLine(a,
|
||||
"plotLines")},addPlotBandOrLine:function(a,b){var c=(new yb(this,a)).render(),d=this.userOptions;c&&(b&&(d[b]=d[b]||[],d[b].push(a)),this.plotLinesAndBands.push(c));return c},removePlotBandOrLine:function(a){for(var b=this.plotLinesAndBands,c=this.options,d=this.userOptions,e=b.length;e--;)b[e].id===a&&b[e].destroy();n([c.plotLines||[],d.plotLines||[],c.plotBands||[],d.plotBands||[]],function(b){for(e=b.length;e--;)b[e].id===a&&fa(b,b[e])})}});ra.prototype.getLogTickPositions=function(a,b,c,d){var e=
|
||||
this.options,f=this.len,g=[];if(!d)this._minorAutoInterval=null;if(a>=0.5)a=w(a),g=this.getLinearTickPositions(a,b,c);else if(a>=0.08)for(var f=N(b),h,i,j,k,l,e=a>0.3?[1,2,4]:a>0.15?[1,2,4,6,8]:[1,2,3,4,5,6,7,8,9];f<c+1&&!l;f++){i=e.length;for(h=0;h<i&&!l;h++)j=ya(ea(f)*e[h]),j>b&&(!d||k<=c)&&g.push(k),k>c&&(l=!0),k=j}else if(b=ea(b),c=ea(c),a=e[d?"minorTickInterval":"tickInterval"],a=o(a==="auto"?null:a,this._minorAutoInterval,(c-b)*(e.tickPixelInterval/(d?5:1))/((d?f/this.tickPositions.length:f)||
|
||||
1)),a=mb(a,null,lb(a)),g=Sa(this.getLinearTickPositions(a,b,c),ya),!d)this._minorAutoInterval=a/5;if(!d)this.tickInterval=a;return g};ra.prototype.getTimeTicks=function(a,b,c,d){var e=[],f={},g=G.global.useUTC,h,i=new Date(b-Qa),j=a.unitRange,k=a.count;if(s(b)){j>=E.second&&(i.setMilliseconds(0),i.setSeconds(j>=E.minute?0:k*N(i.getSeconds()/k)));if(j>=E.minute)i[Cb](j>=E.hour?0:k*N(i[ob]()/k));if(j>=E.hour)i[Db](j>=E.day?0:k*N(i[pb]()/k));if(j>=E.day)i[rb](j>=E.month?1:k*N(i[Va]()/k));j>=E.month&&
|
||||
(i[Eb](j>=E.year?0:k*N(i[cb]()/k)),h=i[db]());j>=E.year&&(h-=h%k,i[Fb](h));if(j===E.week)i[rb](i[Va]()-i[qb]()+o(d,1));b=1;Qa&&(i=new Date(i.getTime()+Qa));h=i[db]();for(var d=i.getTime(),l=i[cb](),m=i[Va](),p=g?Qa:(864E5+i.getTimezoneOffset()*6E4)%864E5;d<c;)e.push(d),j===E.year?d=bb(h+b*k,0):j===E.month?d=bb(h,l+b*k):!g&&(j===E.day||j===E.week)?d=bb(h,l,m+b*k*(j===E.day?1:7)):d+=j*k,b++;e.push(d);n(vb(e,function(a){return j<=E.hour&&a%E.day===p}),function(a){f[a]="day"})}e.info=r(a,{higherRanks:f,
|
||||
totalRange:j*k});return e};ra.prototype.normalizeTimeTickInterval=function(a,b){var c=b||[["millisecond",[1,2,5,10,20,25,50,100,200,500]],["second",[1,2,5,10,15,30]],["minute",[1,2,5,10,15,30]],["hour",[1,2,3,4,6,8,12]],["day",[1,2]],["week",[1,2]],["month",[1,2,3,4,6]],["year",null]],d=c[c.length-1],e=E[d[0]],f=d[1],g;for(g=0;g<c.length;g++)if(d=c[g],e=E[d[0]],f=d[1],c[g+1]&&a<=(e*f[f.length-1]+E[c[g+1][0]])/2)break;e===E.year&&a<5*e&&(f=[1,2,5]);c=mb(a/e,f,d[0]==="year"?t(lb(a/e),1):1);return{unitRange:e,
|
||||
count:c,unitName:d[0]}};Gb.prototype={destroy:function(){Na(this,this.axis)},render:function(a){var b=this.options,c=b.format,c=c?Ga(c,this):b.formatter.call(this);this.label?this.label.attr({text:c,visibility:"hidden"}):this.label=this.axis.chart.renderer.text(c,0,0,b.useHTML).css(b.style).attr({align:this.textAlign,rotation:b.rotation,visibility:"hidden"}).add(a)},setOffset:function(a,b){var c=this.axis,d=c.chart,e=d.inverted,f=this.isNegative,g=c.translate(this.percent?100:this.total,0,0,0,1),
|
||||
c=c.translate(0),c=M(g-c),h=d.xAxis[0].translate(this.x)+a,i=d.plotHeight,f={x:e?f?g:g-c:h,y:e?i-h-b:f?i-g-c:i-g,width:e?c:b,height:e?b:c};if(e=this.label)e.align(this.alignOptions,null,f),f=e.alignAttr,e.attr({visibility:this.options.crop===!1||d.isInsidePlot(f.x,f.y)?V?"inherit":"visible":"hidden"})}};sb.prototype={init:function(a,b){var c=b.borderWidth,d=b.style,e=z(d.padding);this.chart=a;this.options=b;this.crosshairs=[];this.now={x:0,y:0};this.isHidden=!0;this.label=a.renderer.label("",0,0,
|
||||
b.shape,null,null,b.useHTML,null,"tooltip").attr({padding:e,fill:b.backgroundColor,"stroke-width":c,r:b.borderRadius,zIndex:8}).css(d).css({padding:0}).add().attr({y:-999});ba||this.label.shadow(b.shadow);this.shared=b.shared},destroy:function(){if(this.label)this.label=this.label.destroy();clearTimeout(this.hideTimer);clearTimeout(this.tooltipTimeout)},move:function(a,b,c,d){var e=this,f=e.now,g=e.options.animation!==!1&&!e.isHidden;r(f,{x:g?(2*f.x+a)/3:a,y:g?(f.y+b)/2:b,anchorX:g?(2*f.anchorX+c)/
|
||||
3:c,anchorY:g?(f.anchorY+d)/2:d});e.label.attr(f);if(g&&(M(a-f.x)>1||M(b-f.y)>1))clearTimeout(this.tooltipTimeout),this.tooltipTimeout=setTimeout(function(){e&&e.move(a,b,c,d)},32)},hide:function(){var a=this,b;clearTimeout(this.hideTimer);if(!this.isHidden)b=this.chart.hoverPoints,this.hideTimer=setTimeout(function(){a.label.fadeOut();a.isHidden=!0},o(this.options.hideDelay,500)),b&&n(b,function(a){a.setState()}),this.chart.hoverPoints=null},getAnchor:function(a,b){var c,d=this.chart,e=d.inverted,
|
||||
f=d.plotTop,g=0,h=0,i,a=ka(a);c=a[0].tooltipPos;this.followPointer&&b&&(b.chartX===u&&(b=d.pointer.normalize(b)),c=[b.chartX-d.plotLeft,b.chartY-f]);c||(n(a,function(a){i=a.series.yAxis;g+=a.plotX;h+=(a.plotLow?(a.plotLow+a.plotHigh)/2:a.plotY)+(!e&&i?i.top-f:0)}),g/=a.length,h/=a.length,c=[e?d.plotWidth-h:g,this.shared&&!e&&a.length>1&&b?b.chartY-f:e?d.plotHeight-g:h]);return Sa(c,w)},getPosition:function(a,b,c){var d=this.chart,e=d.plotLeft,f=d.plotTop,g=d.plotWidth,h=d.plotHeight,i=o(this.options.distance,
|
||||
12),j=c.plotX,c=c.plotY,d=j+e+(d.inverted?i:-a-i),k=c-b+f+15,l;d<7&&(d=e+t(j,0)+i);d+a>e+g&&(d-=d+a-(e+g),k=c-b+f-i,l=!0);k<f+5&&(k=f+5,l&&c>=k&&c<=k+b&&(k=c+f+i));k+b>f+h&&(k=t(f,f+h-b-i));return{x:d,y:k}},defaultFormatter:function(a){var b=this.points||ka(this),c=b[0].series,d;d=[c.tooltipHeaderFormatter(b[0])];n(b,function(a){c=a.series;d.push(c.tooltipFormatter&&c.tooltipFormatter(a)||a.point.tooltipFormatter(c.tooltipOptions.pointFormat))});d.push(a.options.footerFormat||"");return d.join("")},
|
||||
refresh:function(a,b){var c=this.chart,d=this.label,e=this.options,f,g,h={},i,j=[];i=e.formatter||this.defaultFormatter;var h=c.hoverPoints,k,l=this.shared;clearTimeout(this.hideTimer);this.followPointer=ka(a)[0].series.tooltipOptions.followPointer;g=this.getAnchor(a,b);f=g[0];g=g[1];l&&(!a.series||!a.series.noSharedTooltip)?(c.hoverPoints=a,h&&n(h,function(a){a.setState()}),n(a,function(a){a.setState("hover");j.push(a.getLabelConfig())}),h={x:a[0].category,y:a[0].y},h.points=j,a=a[0]):h=a.getLabelConfig();
|
||||
i=i.call(h,this);h=a.series;i===!1?this.hide():(this.isHidden&&(Ya(d),d.attr("opacity",1).show()),d.attr({text:i}),k=e.borderColor||a.color||h.color||"#606060",d.attr({stroke:k}),this.updatePosition({plotX:f,plotY:g}),this.isHidden=!1);A(c,"tooltipRefresh",{text:i,x:f+c.plotLeft,y:g+c.plotTop,borderColor:k})},updatePosition:function(a){var b=this.chart,c=this.label,c=(this.options.positioner||this.getPosition).call(this,c.width,c.height,a);this.move(w(c.x),w(c.y),a.plotX+b.plotLeft,a.plotY+b.plotTop)}};
|
||||
var Za=Highcharts.Pointer=function(a,b){this.init(a,b)};Za.prototype={init:function(a,b){var c=b.chart,d=c.events,e=ba?"":c.zoomType,c=a.inverted,f;this.options=b;this.chart=a;this.zoomX=f=/x/.test(e);this.zoomY=e=/y/.test(e);this.zoomHor=f&&!c||e&&c;this.zoomVert=e&&!c||f&&c;this.runChartClick=d&&!!d.click;this.pinchDown=[];this.lastValidTouch={};if(b.tooltip.enabled)a.tooltip=new sb(a,b.tooltip);this.setDOMEvents()},normalize:function(a,b){var c,d,a=a||C.event;if(!a.target)a.target=a.srcElement;
|
||||
a=Rb(a);d=a.touches?a.touches.item(0):a;if(!b)this.chartPosition=b=Qb(this.chart.container);d.pageX===u?(c=t(a.x,a.clientX-b.left),d=a.y):(c=d.pageX-b.left,d=d.pageY-b.top);return r(a,{chartX:w(c),chartY:w(d)})},getCoordinates:function(a){var b={xAxis:[],yAxis:[]};n(this.chart.axes,function(c){b[c.isXAxis?"xAxis":"yAxis"].push({axis:c,value:c.toValue(a[c.horiz?"chartX":"chartY"])})});return b},getIndex:function(a){var b=this.chart;return b.inverted?b.plotHeight+b.plotTop-a.chartY:a.chartX-b.plotLeft},
|
||||
runPointActions:function(a){var b=this,c=b.chart,d=c.series,e=c.tooltip,f,g,h=c.hoverPoint,i=c.hoverSeries,j,k,l=c.chartWidth,m=b.getIndex(a);if(e&&b.options.tooltip.shared&&(!i||!i.noSharedTooltip)){g=[];j=d.length;for(k=0;k<j;k++)if(d[k].visible&&d[k].options.enableMouseTracking!==!1&&!d[k].noSharedTooltip&&d[k].tooltipPoints.length&&(f=d[k].tooltipPoints[m])&&f.series)f._dist=M(m-f.clientX),l=I(l,f._dist),g.push(f);for(j=g.length;j--;)g[j]._dist>l&&g.splice(j,1);if(g.length&&g[0].clientX!==b.hoverX)e.refresh(g,
|
||||
a),b.hoverX=g[0].clientX}if(i&&i.tracker){if((f=i.tooltipPoints[m])&&f!==h)f.onMouseOver(a)}else e&&e.followPointer&&!e.isHidden&&(d=e.getAnchor([{}],a),e.updatePosition({plotX:d[0],plotY:d[1]}));if(e&&!b._onDocumentMouseMove)b._onDocumentMouseMove=function(a){b.onDocumentMouseMove(a)},F(v,"mousemove",b._onDocumentMouseMove);n(c.axes,function(b){b.drawCrosshair(a,o(h,f))})},reset:function(a){var b=this.chart,c=b.hoverSeries,d=b.hoverPoint,e=b.tooltip,f=e&&e.shared?b.hoverPoints:d;(a=a&&e&&f)&&ka(f)[0].plotX===
|
||||
u&&(a=!1);if(a)e.refresh(f),d&&d.setState(d.state,!0);else{if(d)d.onMouseOut();if(c)c.onMouseOut();e&&e.hide();if(this._onDocumentMouseMove)X(v,"mousemove",this._onDocumentMouseMove),this._onDocumentMouseMove=null;n(b.axes,function(a){a.hideCrosshair()});this.hoverX=null}},scaleGroups:function(a,b){var c=this.chart,d;n(c.series,function(e){d=a||e.getPlotBox();e.xAxis&&e.xAxis.zoomEnabled&&(e.group.attr(d),e.markerGroup&&(e.markerGroup.attr(d),e.markerGroup.clip(b?c.clipRect:null)),e.dataLabelsGroup&&
|
||||
e.dataLabelsGroup.attr(d))});c.clipRect.attr(b||c.clipBox)},pinchTranslate:function(a,b,c,d,e,f,g,h){a&&this.pinchTranslateDirection(!0,c,d,e,f,g,h);b&&this.pinchTranslateDirection(!1,c,d,e,f,g,h)},pinchTranslateDirection:function(a,b,c,d,e,f,g,h){var i=this.chart,j=a?"x":"y",k=a?"X":"Y",l="chart"+k,m=a?"width":"height",p=i["plot"+(a?"Left":"Top")],q,o,n=h||1,t=i.inverted,w=i.bounds[a?"h":"v"],r=b.length===1,s=b[0][l],u=c[0][l],v=!r&&b[1][l],x=!r&&c[1][l],y,c=function(){!r&&M(s-v)>20&&(n=h||M(u-x)/
|
||||
M(s-v));o=(p-u)/n+s;q=i["plot"+(a?"Width":"Height")]/n};c();b=o;b<w.min?(b=w.min,y=!0):b+q>w.max&&(b=w.max-q,y=!0);y?(u-=0.8*(u-g[j][0]),r||(x-=0.8*(x-g[j][1])),c()):g[j]=[u,x];t||(f[j]=o-p,f[m]=q);f=t?1/n:n;e[m]=q;e[j]=b;d[t?a?"scaleY":"scaleX":"scale"+k]=n;d["translate"+k]=f*p+(u-f*s)},pinch:function(a){var b=this,c=b.chart,d=b.pinchDown,e=c.tooltip&&c.tooltip.options.followTouchMove,f=a.touches,g=f.length,h=b.lastValidTouch,i=b.zoomHor||b.pinchHor,j=b.zoomVert||b.pinchVert,k=i||j,l=b.selectionMarker,
|
||||
m={},p=g===1&&(b.inClass(a.target,"highcharts-tracker")&&c.runTrackerClick||c.runChartClick),q={};(k||e)&&!p&&a.preventDefault();Sa(f,function(a){return b.normalize(a)});if(a.type==="touchstart")n(f,function(a,b){d[b]={chartX:a.chartX,chartY:a.chartY}}),h.x=[d[0].chartX,d[1]&&d[1].chartX],h.y=[d[0].chartY,d[1]&&d[1].chartY],n(c.axes,function(a){if(a.zoomEnabled){var b=c.bounds[a.horiz?"h":"v"],d=a.minPixelPadding,e=a.toPixels(a.dataMin),f=a.toPixels(a.dataMax),g=I(e,f),e=t(e,f);b.min=I(a.pos,g-d);
|
||||
b.max=t(a.pos+a.len,e+d)}});else if(d.length){if(!l)b.selectionMarker=l=r({destroy:ma},c.plotBox);b.pinchTranslate(i,j,d,f,m,l,q,h);b.hasPinched=k;b.scaleGroups(m,q);!k&&e&&g===1&&this.runPointActions(b.normalize(a))}},dragStart:function(a){var b=this.chart;b.mouseIsDown=a.type;b.cancelClick=!1;b.mouseDownX=this.mouseDownX=a.chartX;b.mouseDownY=this.mouseDownY=a.chartY},drag:function(a){var b=this.chart,c=b.options.chart,d=a.chartX,e=a.chartY,f=this.zoomHor,g=this.zoomVert,h=b.plotLeft,i=b.plotTop,
|
||||
j=b.plotWidth,k=b.plotHeight,l,m=this.mouseDownX,p=this.mouseDownY;d<h?d=h:d>h+j&&(d=h+j);e<i?e=i:e>i+k&&(e=i+k);this.hasDragged=Math.sqrt(Math.pow(m-d,2)+Math.pow(p-e,2));if(this.hasDragged>10){l=b.isInsidePlot(m-h,p-i);if(b.hasCartesianSeries&&(this.zoomX||this.zoomY)&&l&&!this.selectionMarker)this.selectionMarker=b.renderer.rect(h,i,f?1:j,g?1:k,0).attr({fill:c.selectionMarkerFill||"rgba(69,114,167,0.25)",zIndex:7}).add();this.selectionMarker&&f&&(d-=m,this.selectionMarker.attr({width:M(d),x:(d>
|
||||
0?0:d)+m}));this.selectionMarker&&g&&(d=e-p,this.selectionMarker.attr({height:M(d),y:(d>0?0:d)+p}));l&&!this.selectionMarker&&c.panning&&b.pan(a,c.panning)}},drop:function(a){var b=this.chart,c=this.hasPinched;if(this.selectionMarker){var d={xAxis:[],yAxis:[],originalEvent:a.originalEvent||a},e=this.selectionMarker,f=e.x,g=e.y,h;if(this.hasDragged||c)n(b.axes,function(a){if(a.zoomEnabled){var b=a.horiz,c=a.toValue(b?f:g),b=a.toValue(b?f+e.width:g+e.height);!isNaN(c)&&!isNaN(b)&&(d[a.coll].push({axis:a,
|
||||
min:I(c,b),max:t(c,b)}),h=!0)}}),h&&A(b,"selection",d,function(a){b.zoom(r(a,c?{animation:!1}:null))});this.selectionMarker=this.selectionMarker.destroy();c&&this.scaleGroups()}if(b)D(b.container,{cursor:b._cursor}),b.cancelClick=this.hasDragged>10,b.mouseIsDown=this.hasDragged=this.hasPinched=!1,this.pinchDown=[]},onContainerMouseDown:function(a){a=this.normalize(a);a.preventDefault&&a.preventDefault();this.dragStart(a)},onDocumentMouseUp:function(a){this.drop(a)},onDocumentMouseMove:function(a){var b=
|
||||
this.chart,c=this.chartPosition,d=b.hoverSeries,a=this.normalize(a,c);c&&d&&!this.inClass(a.target,"highcharts-tracker")&&!b.isInsidePlot(a.chartX-b.plotLeft,a.chartY-b.plotTop)&&this.reset()},onContainerMouseLeave:function(){this.reset();this.chartPosition=null},onContainerMouseMove:function(a){var b=this.chart,a=this.normalize(a);b.mouseIsDown==="mousedown"&&this.drag(a);(this.inClass(a.target,"highcharts-tracker")||b.isInsidePlot(a.chartX-b.plotLeft,a.chartY-b.plotTop))&&!b.openMenu&&this.runPointActions(a)},
|
||||
inClass:function(a,b){for(var c;a;){if(c=y(a,"class"))if(c.indexOf(b)!==-1)return!0;else if(c.indexOf("highcharts-container")!==-1)return!1;a=a.parentNode}},onTrackerMouseOut:function(a){var b=this.chart.hoverSeries,a=a.relatedTarget||a.toElement,c=a.point&&a.point.series;if(b&&!b.options.stickyTracking&&!this.inClass(a,"highcharts-tooltip")&&c!==b)b.onMouseOut()},onContainerClick:function(a){var b=this.chart,c=b.hoverPoint,d=b.plotLeft,e=b.plotTop,f=b.inverted,g,h,i,a=this.normalize(a);a.cancelBubble=
|
||||
!0;if(!b.cancelClick)c&&this.inClass(a.target,"highcharts-tracker")?(g=this.chartPosition,h=c.plotX,i=c.plotY,r(c,{pageX:g.left+d+(f?b.plotWidth-i:h),pageY:g.top+e+(f?b.plotHeight-h:i)}),A(c.series,"click",r(a,{point:c})),b.hoverPoint&&c.firePointEvent("click",a)):(r(a,this.getCoordinates(a)),b.isInsidePlot(a.chartX-d,a.chartY-e)&&A(b,"click",a))},onContainerTouchStart:function(a){var b=this.chart;a.touches.length===1?(a=this.normalize(a),b.isInsidePlot(a.chartX-b.plotLeft,a.chartY-b.plotTop)?(this.runPointActions(a),
|
||||
this.pinch(a)):this.reset()):a.touches.length===2&&this.pinch(a)},onContainerTouchMove:function(a){(a.touches.length===1||a.touches.length===2)&&this.pinch(a)},onDocumentTouchEnd:function(a){this.drop(a)},setDOMEvents:function(){var a=this,b=a.chart.container,c;this._events=c=[[b,"onmousedown","onContainerMouseDown"],[b,"onmousemove","onContainerMouseMove"],[b,"onclick","onContainerClick"],[b,"mouseleave","onContainerMouseLeave"],[v,"mouseup","onDocumentMouseUp"]];hb&&c.push([b,"ontouchstart","onContainerTouchStart"],
|
||||
[b,"ontouchmove","onContainerTouchMove"],[v,"touchend","onDocumentTouchEnd"]);n(c,function(b){a["_"+b[2]]=function(c){a[b[2]](c)};b[1].indexOf("on")===0?b[0][b[1]]=a["_"+b[2]]:F(b[0],b[1],a["_"+b[2]])})},destroy:function(){var a=this;n(a._events,function(b){b[1].indexOf("on")===0?b[0][b[1]]=null:X(b[0],b[1],a["_"+b[2]])});delete a._events;clearInterval(a.tooltipTimeout)}};J=Highcharts.TrackerMixin={drawTrackerPoint:function(){var a=this,b=a.chart,c=b.pointer,d=a.options.cursor,e=d&&{cursor:d},f=function(c){var d=
|
||||
c.target,e;if(b.hoverSeries!==a)a.onMouseOver();for(;d&&!e;)e=d.point,d=d.parentNode;if(e!==u&&e!==b.hoverPoint)e.onMouseOver(c)};n(a.points,function(a){if(a.graphic)a.graphic.element.point=a;if(a.dataLabel)a.dataLabel.element.point=a});if(!a._hasTracking)n(a.trackerGroups,function(b){if(a[b]&&(a[b].addClass("highcharts-tracker").on("mouseover",f).on("mouseout",function(a){c.onTrackerMouseOut(a)}).css(e),hb))a[b].on("touchstart",f)}),a._hasTracking=!0},drawTrackerGraph:function(){var a=this,b=a.options,
|
||||
c=b.trackByArea,d=[].concat(c?a.areaPath:a.graphPath),e=d.length,f=a.chart,g=f.pointer,h=f.renderer,i=f.options.tooltip.snap,j=a.tracker,k=b.cursor,l=k&&{cursor:k},k=a.singlePoints,m,p=function(){if(f.hoverSeries!==a)a.onMouseOver()};if(e&&!c)for(m=e+1;m--;)d[m]==="M"&&d.splice(m+1,0,d[m+1]-i,d[m+2],"L"),(m&&d[m]==="M"||m===e)&&d.splice(m,0,"L",d[m-2]+i,d[m-1]);for(m=0;m<k.length;m++)e=k[m],d.push("M",e.plotX-i,e.plotY,"L",e.plotX+i,e.plotY);j?j.attr({d:d}):(a.tracker=h.path(d).attr({"stroke-linejoin":"round",
|
||||
visibility:a.visible?"visible":"hidden",stroke:Kb,fill:c?Kb:Q,"stroke-width":b.lineWidth+(c?0:2*i),zIndex:2}).add(a.group),n([a.tracker,a.markerGroup],function(a){a.addClass("highcharts-tracker").on("mouseover",p).on("mouseout",function(a){g.onTrackerMouseOut(a)}).css(l);if(hb)a.on("touchstart",p)}))}};if(C.PointerEvent||C.MSPointerEvent){var oa={};Za.prototype.getWebkitTouches=function(){var a,b=[];b.item=function(a){return this[a]};for(a in oa)oa.hasOwnProperty(a)&&b.push({pageX:oa[a].pageX,pageY:oa[a].pageY,
|
||||
target:oa[a].target});return b};Ua(Za.prototype,"init",function(a,b,c){b.container.style["-ms-touch-action"]=b.container.style["touch-action"]="none";a.call(this,b,c)});Ua(Za.prototype,"setDOMEvents",function(a){var b=this;a.apply(this,Array.prototype.slice.call(arguments,1));n([[this.chart.container,"PointerDown","touchstart","onContainerTouchStart",function(a){oa[a.pointerId]={pageX:a.pageX,pageY:a.pageY,target:a.currentTarget}}],[this.chart.container,"PointerMove","touchmove","onContainerTouchMove",
|
||||
function(a){oa[a.pointerId]={pageX:a.pageX,pageY:a.pageY};if(!oa[a.pointerId].target)oa[a.pointerId].target=a.currentTarget}],[document,"PointerUp","touchend","onDocumentTouchEnd",function(a){delete oa[a.pointerId]}]],function(a){F(a[0],window.PointerEvent?a[1].toLowerCase():"MS"+a[1],function(d){d=d.originalEvent;if(d.pointerType==="touch"||d.pointerType===d.MSPOINTER_TYPE_TOUCH)a[4](d),b[a[3]]({type:a[2],target:d.currentTarget,preventDefault:ma,touches:b.getWebkitTouches()})})})})}var zb=Highcharts.Legend=
|
||||
function(a,b){this.init(a,b)};zb.prototype={init:function(a,b){var c=this,d=b.itemStyle,e=o(b.padding,8),f=b.itemMarginTop||0;this.options=b;if(b.enabled)c.baseline=z(d.fontSize)+3+f,c.itemStyle=d,c.itemHiddenStyle=x(d,b.itemHiddenStyle),c.itemMarginTop=f,c.padding=e,c.initialItemX=e,c.initialItemY=e-5,c.maxItemWidth=0,c.chart=a,c.itemHeight=0,c.lastLineHeight=0,c.symbolWidth=o(b.symbolWidth,16),c.pages=[],c.render(),F(c.chart,"endResize",function(){c.positionCheckboxes()})},colorizeItem:function(a,
|
||||
b){var c=this.options,d=a.legendItem,e=a.legendLine,f=a.legendSymbol,g=this.itemHiddenStyle.color,c=b?c.itemStyle.color:g,h=b?a.legendColor||a.color:g,g=a.options&&a.options.marker,i={stroke:h,fill:h},j;d&&d.css({fill:c,color:c});e&&e.attr({stroke:h});if(f){if(g&&f.isMarker)for(j in g=a.convertAttribs(g),g)d=g[j],d!==u&&(i[j]=d);f.attr(i)}},positionItem:function(a){var b=this.options,c=b.symbolPadding,b=!b.rtl,d=a._legendItemPos,e=d[0],d=d[1],f=a.checkbox;a.legendGroup&&a.legendGroup.translate(b?
|
||||
e:this.legendWidth-e-2*c-4,d);if(f)f.x=e,f.y=d},destroyItem:function(a){var b=a.checkbox;n(["legendItem","legendLine","legendSymbol","legendGroup"],function(b){a[b]&&(a[b]=a[b].destroy())});b&&Oa(a.checkbox)},destroy:function(){var a=this.group,b=this.box;if(b)this.box=b.destroy();if(a)this.group=a.destroy()},positionCheckboxes:function(a){var b=this.group.alignAttr,c,d=this.clipHeight||this.legendHeight;if(b)c=b.translateY,n(this.allItems,function(e){var f=e.checkbox,g;f&&(g=c+f.y+(a||0)+3,D(f,{left:b.translateX+
|
||||
e.legendItemWidth+f.x-20+"px",top:g+"px",display:g>c-6&&g<c+d-6?"":Q}))})},renderTitle:function(){var a=this.padding,b=this.options.title,c=0;if(b.text){if(!this.title)this.title=this.chart.renderer.label(b.text,a-3,a-4,null,null,null,null,null,"legend-title").attr({zIndex:1}).css(b.style).add(this.group);a=this.title.getBBox();c=a.height;this.offsetWidth=a.width;this.contentGroup.attr({translateY:c})}this.titleHeight=c},renderItem:function(a){var B;var b=this,c=b.chart,d=c.renderer,e=b.options,f=
|
||||
e.layout==="horizontal",g=b.symbolWidth,h=e.symbolPadding,i=b.itemStyle,j=b.itemHiddenStyle,k=b.padding,l=f?o(e.itemDistance,8):0,m=!e.rtl,p=e.width,q=e.itemMarginBottom||0,n=b.itemMarginTop,r=b.initialItemX,s=a.legendItem,u=a.series&&a.series.drawLegendSymbol?a.series:a,v=u.options,v=v&&v.showCheckbox,y=e.useHTML;if(!s&&(a.legendGroup=d.g("legend-item").attr({zIndex:1}).add(b.scrollGroup),u.drawLegendSymbol(b,a),a.legendItem=s=d.text(e.labelFormat?Ga(e.labelFormat,a):e.labelFormatter.call(a),m?g+
|
||||
h:-h,b.baseline,y).css(x(a.visible?i:j)).attr({align:m?"left":"right",zIndex:2}).add(a.legendGroup),(y?s:a.legendGroup).on("mouseover",function(){a.setState("hover");s.css(b.options.itemHoverStyle)}).on("mouseout",function(){s.css(a.visible?i:j);a.setState()}).on("click",function(b){var c=function(){a.setVisible()},b={browserEvent:b};a.firePointEvent?a.firePointEvent("legendItemClick",b,c):A(a,"legendItemClick",b,c)}),b.colorizeItem(a,a.visible),v))a.checkbox=T("input",{type:"checkbox",checked:a.selected,
|
||||
defaultChecked:a.selected},e.itemCheckboxStyle,c.container),F(a.checkbox,"click",function(b){A(a,"checkboxClick",{checked:b.target.checked},function(){a.select()})});d=s.getBBox();B=a.legendItemWidth=e.itemWidth||a.legendItemWidth||g+h+d.width+l+(v?20:0),e=B;b.itemHeight=g=w(a.legendItemHeight||d.height);if(f&&b.itemX-r+e>(p||c.chartWidth-2*k-r))b.itemX=r,b.itemY+=n+b.lastLineHeight+q,b.lastLineHeight=0;b.maxItemWidth=t(b.maxItemWidth,e);b.lastItemY=n+b.itemY+q;b.lastLineHeight=t(g,b.lastLineHeight);
|
||||
a._legendItemPos=[b.itemX,b.itemY];f?b.itemX+=e:(b.itemY+=n+g+q,b.lastLineHeight=g);b.offsetWidth=p||t((f?b.itemX-r-l:e)+k,b.offsetWidth)},getAllItems:function(){var a=[];n(this.chart.series,function(b){var c=b.options;if(o(c.showInLegend,!s(c.linkedTo)?u:!1,!0))a=a.concat(b.legendItems||(c.legendType==="point"?b.data:b))});return a},render:function(){var a=this,b=a.chart,c=b.renderer,d=a.group,e,f,g,h,i=a.box,j=a.options,k=a.padding,l=j.borderWidth,m=j.backgroundColor;a.itemX=a.initialItemX;a.itemY=
|
||||
a.initialItemY;a.offsetWidth=0;a.lastItemY=0;if(!d)a.group=d=c.g("legend").attr({zIndex:7}).add(),a.contentGroup=c.g().attr({zIndex:1}).add(d),a.scrollGroup=c.g().add(a.contentGroup);a.renderTitle();e=a.getAllItems();nb(e,function(a,b){return(a.options&&a.options.legendIndex||0)-(b.options&&b.options.legendIndex||0)});j.reversed&&e.reverse();a.allItems=e;a.display=f=!!e.length;n(e,function(b){a.renderItem(b)});g=j.width||a.offsetWidth;h=a.lastItemY+a.lastLineHeight+a.titleHeight;h=a.handleOverflow(h);
|
||||
if(l||m){g+=k;h+=k;if(i){if(g>0&&h>0)i[i.isNew?"attr":"animate"](i.crisp(null,null,null,g,h)),i.isNew=!1}else a.box=i=c.rect(0,0,g,h,j.borderRadius,l||0).attr({stroke:j.borderColor,"stroke-width":l||0,fill:m||Q}).add(d).shadow(j.shadow),i.isNew=!0;i[f?"show":"hide"]()}a.legendWidth=g;a.legendHeight=h;n(e,function(b){a.positionItem(b)});f&&d.align(r({width:g,height:h},j),!0,"spacingBox");b.isResizing||this.positionCheckboxes()},handleOverflow:function(a){var b=this,c=this.chart,d=c.renderer,e=this.options,
|
||||
f=e.y,f=c.spacingBox.height+(e.verticalAlign==="top"?-f:f)-this.padding,g=e.maxHeight,h,i=this.clipRect,j=e.navigation,k=o(j.animation,!0),l=j.arrowSize||12,m=this.nav,p=this.pages,q,t=this.allItems;e.layout==="horizontal"&&(f/=2);g&&(f=I(f,g));p.length=0;if(a>f&&!e.useHTML){this.clipHeight=h=f-20-this.titleHeight-this.padding;this.currentPage=o(this.currentPage,1);this.fullHeight=a;n(t,function(a,b){var c=a._legendItemPos[1],d=w(a.legendItem.bBox.height),e=p.length;if(!e||c-p[e-1]>h)p.push(q||c);
|
||||
b===t.length-1&&c+d-p[e-1]>h&&p.push(c);c!==q&&(q=c)});if(!i)i=b.clipRect=d.clipRect(0,this.padding,9999,0),b.contentGroup.clip(i);i.attr({height:h});if(!m)this.nav=m=d.g().attr({zIndex:1}).add(this.group),this.up=d.symbol("triangle",0,0,l,l).on("click",function(){b.scroll(-1,k)}).add(m),this.pager=d.text("",15,10).css(j.style).add(m),this.down=d.symbol("triangle-down",0,0,l,l).on("click",function(){b.scroll(1,k)}).add(m);b.scroll(0);a=f}else if(m)i.attr({height:c.chartHeight}),m.hide(),this.scrollGroup.attr({translateY:1}),
|
||||
this.clipHeight=0;return a},scroll:function(a,b){var c=this.pages,d=c.length,e=this.currentPage+a,f=this.clipHeight,g=this.options.navigation,h=g.activeColor,g=g.inactiveColor,i=this.pager,j=this.padding;e>d&&(e=d);if(e>0)b!==u&&Pa(b,this.chart),this.nav.attr({translateX:j,translateY:f+this.padding+7+this.titleHeight,visibility:"visible"}),this.up.attr({fill:e===1?g:h}).css({cursor:e===1?"default":"pointer"}),i.attr({text:e+"/"+d}),this.down.attr({x:18+this.pager.getBBox().width,fill:e===d?g:h}).css({cursor:e===
|
||||
d?"default":"pointer"}),c=-c[e-1]+this.initialItemY,this.scrollGroup.animate({translateY:c}),this.currentPage=e,this.positionCheckboxes(c)}};R=Highcharts.LegendSymbolMixin={drawRectangle:function(a,b){var c=a.options.symbolHeight||12;b.legendSymbol=this.chart.renderer.rect(0,a.baseline-5-c/2,a.symbolWidth,c,o(a.options.symbolRadius,2)).attr({zIndex:3}).add(b.legendGroup)},drawLineMarker:function(a){var b=this.options,c=b.marker,d;d=a.symbolWidth;var e=this.chart.renderer,f=this.legendGroup,a=a.baseline-
|
||||
w(e.fontMetrics(a.options.itemStyle.fontSize).b*0.3),g;if(b.lineWidth){g={"stroke-width":b.lineWidth};if(b.dashStyle)g.dashstyle=b.dashStyle;this.legendLine=e.path(["M",0,a,"L",d,a]).attr(g).add(f)}if(c&&c.enabled)b=c.radius,this.legendSymbol=d=e.symbol(this.symbol,d/2-b,a-b,2*b,2*b).add(f),d.isMarker=!0}};/Trident\/7\.0/.test(sa)&&Ua(zb.prototype,"positionItem",function(a,b){var c=this,d=function(){b._legendItemPos&&a.call(c,b)};c.chart.renderer.forExport?d():setTimeout(d)});eb.prototype={init:function(a,
|
||||
b){var c,d=a.series;a.series=null;c=x(G,a);c.series=a.series=d;this.userOptions=a;d=c.chart;this.margin=this.splashArray("margin",d);this.spacing=this.splashArray("spacing",d);var e=d.events;this.bounds={h:{},v:{}};this.callback=b;this.isResizing=0;this.options=c;this.axes=[];this.series=[];this.hasCartesianSeries=d.showAxes;var f=this,g;f.index=Ja.length;Ja.push(f);d.reflow!==!1&&F(f,"load",function(){f.initReflow()});if(e)for(g in e)F(f,g,e[g]);f.xAxis=[];f.yAxis=[];f.animation=ba?!1:o(d.animation,
|
||||
!0);f.pointCount=0;f.counters=new Ab;f.firstRender()},initSeries:function(a){var b=this.options.chart;(b=L[a.type||b.type||b.defaultSeriesType])||la(17,!0);b=new b;b.init(this,a);return b},isInsidePlot:function(a,b,c){var d=c?b:a,a=c?a:b;return d>=0&&d<=this.plotWidth&&a>=0&&a<=this.plotHeight},adjustTickAmounts:function(){this.options.chart.alignTicks!==!1&&n(this.axes,function(a){a.adjustTickAmount()});this.maxTicks=null},redraw:function(a){var b=this.axes,c=this.series,d=this.pointer,e=this.legend,
|
||||
f=this.isDirtyLegend,g,h,i=this.isDirtyBox,j=c.length,k=j,l=this.renderer,m=l.isHidden(),p=[];Pa(a,this);m&&this.cloneRenderTo();for(this.layOutTitles();k--;)if(a=c[k],a.options.stacking&&(g=!0,a.isDirty)){h=!0;break}if(h)for(k=j;k--;)if(a=c[k],a.options.stacking)a.isDirty=!0;n(c,function(a){a.isDirty&&a.options.legendType==="point"&&(f=!0)});if(f&&e.options.enabled)e.render(),this.isDirtyLegend=!1;g&&this.getStacks();if(this.hasCartesianSeries){if(!this.isResizing)this.maxTicks=null,n(b,function(a){a.setScale()});
|
||||
this.adjustTickAmounts();this.getMargins();n(b,function(a){a.isDirty&&(i=!0)});n(b,function(a){if(a.isDirtyExtremes)a.isDirtyExtremes=!1,p.push(function(){A(a,"afterSetExtremes",r(a.eventArgs,a.getExtremes()));delete a.eventArgs});(i||g)&&a.redraw()})}i&&this.drawChartBox();n(c,function(a){a.isDirty&&a.visible&&(!a.isCartesian||a.xAxis)&&a.redraw()});d&&d.reset&&d.reset(!0);l.draw();A(this,"redraw");m&&this.cloneRenderTo(!0);n(p,function(a){a.call()})},get:function(a){var b=this.axes,c=this.series,
|
||||
d,e;for(d=0;d<b.length;d++)if(b[d].options.id===a)return b[d];for(d=0;d<c.length;d++)if(c[d].options.id===a)return c[d];for(d=0;d<c.length;d++){e=c[d].points||[];for(b=0;b<e.length;b++)if(e[b].id===a)return e[b]}return null},getAxes:function(){var a=this,b=this.options,c=b.xAxis=ka(b.xAxis||{}),b=b.yAxis=ka(b.yAxis||{});n(c,function(a,b){a.index=b;a.isX=!0});n(b,function(a,b){a.index=b});c=c.concat(b);n(c,function(b){new ra(a,b)});a.adjustTickAmounts()},getSelectedPoints:function(){var a=[];n(this.series,
|
||||
function(b){a=a.concat(vb(b.points||[],function(a){return a.selected}))});return a},getSelectedSeries:function(){return vb(this.series,function(a){return a.selected})},getStacks:function(){var a=this;n(a.yAxis,function(a){if(a.stacks&&a.hasVisibleSeries)a.oldStacks=a.stacks});n(a.series,function(b){if(b.options.stacking&&(b.visible===!0||a.options.chart.ignoreHiddenSeries===!1))b.stackKey=b.type+o(b.options.stack,"")})},showResetZoom:function(){var a=this,b=G.lang,c=a.options.chart.resetZoomButton,
|
||||
d=c.theme,e=d.states,f=c.relativeTo==="chart"?null:"plotBox";this.resetZoomButton=a.renderer.button(b.resetZoom,null,null,function(){a.zoomOut()},d,e&&e.hover).attr({align:c.position.align,title:b.resetZoomTitle}).add().align(c.position,!1,f)},zoomOut:function(){var a=this;A(a,"selection",{resetSelection:!0},function(){a.zoom()})},zoom:function(a){var b,c=this.pointer,d=!1,e;!a||a.resetSelection?n(this.axes,function(a){b=a.zoom()}):n(a.xAxis.concat(a.yAxis),function(a){var e=a.axis,h=e.isXAxis;if(c[h?
|
||||
"zoomX":"zoomY"]||c[h?"pinchX":"pinchY"])b=e.zoom(a.min,a.max),e.displayBtn&&(d=!0)});e=this.resetZoomButton;if(d&&!e)this.showResetZoom();else if(!d&&S(e))this.resetZoomButton=e.destroy();b&&this.redraw(o(this.options.chart.animation,a&&a.animation,this.pointCount<100))},pan:function(a,b){var c=this,d=c.hoverPoints,e;d&&n(d,function(a){a.setState()});n(b==="xy"?[1,0]:[1],function(b){var d=a[b?"chartX":"chartY"],h=c[b?"xAxis":"yAxis"][0],i=c[b?"mouseDownX":"mouseDownY"],j=(h.pointRange||0)/2,k=h.getExtremes(),
|
||||
l=h.toValue(i-d,!0)+j,i=h.toValue(i+c[b?"plotWidth":"plotHeight"]-d,!0)-j;h.series.length&&l>I(k.dataMin,k.min)&&i<t(k.dataMax,k.max)&&(h.setExtremes(l,i,!1,!1,{trigger:"pan"}),e=!0);c[b?"mouseDownX":"mouseDownY"]=d});e&&c.redraw(!1);D(c.container,{cursor:"move"})},setTitle:function(a,b){var f;var c=this,d=c.options,e;e=d.title=x(d.title,a);f=d.subtitle=x(d.subtitle,b),d=f;n([["title",a,e],["subtitle",b,d]],function(a){var b=a[0],d=c[b],e=a[1],a=a[2];d&&e&&(c[b]=d=d.destroy());a&&a.text&&!d&&(c[b]=
|
||||
c.renderer.text(a.text,0,0,a.useHTML).attr({align:a.align,"class":"highcharts-"+b,zIndex:a.zIndex||4}).css(a.style).add())});c.layOutTitles()},layOutTitles:function(){var a=0,b=this.title,c=this.subtitle,d=this.options,e=d.title,d=d.subtitle,f=this.spacingBox.width-44;if(b&&(b.css({width:(e.width||f)+"px"}).align(r({y:15},e),!1,"spacingBox"),!e.floating&&!e.verticalAlign))a=b.getBBox().height,a>=18&&a<=25&&(a=15);c&&(c.css({width:(d.width||f)+"px"}).align(r({y:a+e.margin},d),!1,"spacingBox"),!d.floating&&
|
||||
!d.verticalAlign&&(a=Ia(a+c.getBBox().height)));this.titleOffset=a},getChartSize:function(){var a=this.options.chart,b=this.renderToClone||this.renderTo;this.containerWidth=ib(b,"width");this.containerHeight=ib(b,"height");this.chartWidth=t(0,a.width||this.containerWidth||600);this.chartHeight=t(0,o(a.height,this.containerHeight>19?this.containerHeight:400))},cloneRenderTo:function(a){var b=this.renderToClone,c=this.container;a?b&&(this.renderTo.appendChild(c),Oa(b),delete this.renderToClone):(c&&
|
||||
c.parentNode===this.renderTo&&this.renderTo.removeChild(c),this.renderToClone=b=this.renderTo.cloneNode(0),D(b,{position:"absolute",top:"-9999px",display:"block"}),v.body.appendChild(b),c&&b.appendChild(c))},getContainer:function(){var a,b=this.options.chart,c,d,e;this.renderTo=a=b.renderTo;e="highcharts-"+tb++;if(da(a))this.renderTo=a=v.getElementById(a);a||la(13,!0);c=z(y(a,"data-highcharts-chart"));!isNaN(c)&&Ja[c]&&Ja[c].destroy();y(a,"data-highcharts-chart",this.index);a.innerHTML="";a.offsetWidth||
|
||||
this.cloneRenderTo();this.getChartSize();c=this.chartWidth;d=this.chartHeight;this.container=a=T(Ha,{className:"highcharts-container"+(b.className?" "+b.className:""),id:e},r({position:"relative",overflow:"hidden",width:c+"px",height:d+"px",textAlign:"left",lineHeight:"normal",zIndex:0,"-webkit-tap-highlight-color":"rgba(0,0,0,0)"},b.style),this.renderToClone||a);this._cursor=a.style.cursor;this.renderer=b.forExport?new va(a,c,d,!0):new Xa(a,c,d);ba&&this.renderer.create(this,a,c,d)},getMargins:function(){var a=
|
||||
this.spacing,b,c=this.legend,d=this.margin,e=this.options.legend,f=o(e.margin,10),g=e.x,h=e.y,i=e.align,j=e.verticalAlign,k=this.titleOffset;this.resetMargins();b=this.axisOffset;if(k&&!s(d[0]))this.plotTop=t(this.plotTop,k+this.options.title.margin+a[0]);if(c.display&&!e.floating)if(i==="right"){if(!s(d[1]))this.marginRight=t(this.marginRight,c.legendWidth-g+f+a[1])}else if(i==="left"){if(!s(d[3]))this.plotLeft=t(this.plotLeft,c.legendWidth+g+f+a[3])}else if(j==="top"){if(!s(d[0]))this.plotTop=t(this.plotTop,
|
||||
c.legendHeight+h+f+a[0])}else if(j==="bottom"&&!s(d[2]))this.marginBottom=t(this.marginBottom,c.legendHeight-h+f+a[2]);this.extraBottomMargin&&(this.marginBottom+=this.extraBottomMargin);this.extraTopMargin&&(this.plotTop+=this.extraTopMargin);this.hasCartesianSeries&&n(this.axes,function(a){a.getOffset()});s(d[3])||(this.plotLeft+=b[3]);s(d[0])||(this.plotTop+=b[0]);s(d[2])||(this.marginBottom+=b[2]);s(d[1])||(this.marginRight+=b[1]);this.setChartSize()},reflow:function(a){var b=this,c=b.options.chart,
|
||||
d=b.renderTo,e=c.width||ib(d,"width"),f=c.height||ib(d,"height"),c=a?a.target:C,d=function(){if(b.container)b.setSize(e,f,!1),b.hasUserSize=null};if(!b.hasUserSize&&e&&f&&(c===C||c===v)){if(e!==b.containerWidth||f!==b.containerHeight)clearTimeout(b.reflowTimeout),a?b.reflowTimeout=setTimeout(d,100):d();b.containerWidth=e;b.containerHeight=f}},initReflow:function(){var a=this,b=function(b){a.reflow(b)};F(C,"resize",b);F(a,"destroy",function(){X(C,"resize",b)})},setSize:function(a,b,c){var d=this,e,
|
||||
f,g;d.isResizing+=1;g=function(){d&&A(d,"endResize",null,function(){d.isResizing-=1})};Pa(c,d);d.oldChartHeight=d.chartHeight;d.oldChartWidth=d.chartWidth;if(s(a))d.chartWidth=e=t(0,w(a)),d.hasUserSize=!!e;if(s(b))d.chartHeight=f=t(0,w(b));(pa?jb:D)(d.container,{width:e+"px",height:f+"px"},pa);d.setChartSize(!0);d.renderer.setSize(e,f,c);d.maxTicks=null;n(d.axes,function(a){a.isDirty=!0;a.setScale()});n(d.series,function(a){a.isDirty=!0});d.isDirtyLegend=!0;d.isDirtyBox=!0;d.getMargins();d.redraw(c);
|
||||
d.oldChartHeight=null;A(d,"resize");pa===!1?g():setTimeout(g,pa&&pa.duration||500)},setChartSize:function(a){var b=this.inverted,c=this.renderer,d=this.chartWidth,e=this.chartHeight,f=this.options.chart,g=this.spacing,h=this.clipOffset,i,j,k,l;this.plotLeft=i=w(this.plotLeft);this.plotTop=j=w(this.plotTop);this.plotWidth=k=t(0,w(d-i-this.marginRight));this.plotHeight=l=t(0,w(e-j-this.marginBottom));this.plotSizeX=b?l:k;this.plotSizeY=b?k:l;this.plotBorderWidth=f.plotBorderWidth||0;this.spacingBox=
|
||||
c.spacingBox={x:g[3],y:g[0],width:d-g[3]-g[1],height:e-g[0]-g[2]};this.plotBox=c.plotBox={x:i,y:j,width:k,height:l};d=2*N(this.plotBorderWidth/2);b=Ia(t(d,h[3])/2);c=Ia(t(d,h[0])/2);this.clipBox={x:b,y:c,width:N(this.plotSizeX-t(d,h[1])/2-b),height:N(this.plotSizeY-t(d,h[2])/2-c)};a||n(this.axes,function(a){a.setAxisSize();a.setAxisTranslation()})},resetMargins:function(){var a=this.spacing,b=this.margin;this.plotTop=o(b[0],a[0]);this.marginRight=o(b[1],a[1]);this.marginBottom=o(b[2],a[2]);this.plotLeft=
|
||||
o(b[3],a[3]);this.axisOffset=[0,0,0,0];this.clipOffset=[0,0,0,0]},drawChartBox:function(){var a=this.options.chart,b=this.renderer,c=this.chartWidth,d=this.chartHeight,e=this.chartBackground,f=this.plotBackground,g=this.plotBorder,h=this.plotBGImage,i=a.borderWidth||0,j=a.backgroundColor,k=a.plotBackgroundColor,l=a.plotBackgroundImage,m=a.plotBorderWidth||0,p,q=this.plotLeft,o=this.plotTop,n=this.plotWidth,t=this.plotHeight,s=this.plotBox,r=this.clipRect,w=this.clipBox;p=i+(a.shadow?8:0);if(i||j)if(e)e.animate(e.crisp(null,
|
||||
null,null,c-p,d-p));else{e={fill:j||Q};if(i)e.stroke=a.borderColor,e["stroke-width"]=i;this.chartBackground=b.rect(p/2,p/2,c-p,d-p,a.borderRadius,i).attr(e).add().shadow(a.shadow)}if(k)f?f.animate(s):this.plotBackground=b.rect(q,o,n,t,0).attr({fill:k}).add().shadow(a.plotShadow);if(l)h?h.animate(s):this.plotBGImage=b.image(l,q,o,n,t).add();r?r.animate({width:w.width,height:w.height}):this.clipRect=b.clipRect(w);if(m)g?g.animate(g.crisp(null,q,o,n,t)):this.plotBorder=b.rect(q,o,n,t,0,-m).attr({stroke:a.plotBorderColor,
|
||||
"stroke-width":m,zIndex:1}).add();this.isDirtyBox=!1},propFromSeries:function(){var a=this,b=a.options.chart,c,d=a.options.series,e,f;n(["inverted","angular","polar"],function(g){c=L[b.type||b.defaultSeriesType];f=a[g]||b[g]||c&&c.prototype[g];for(e=d&&d.length;!f&&e--;)(c=L[d[e].type])&&c.prototype[g]&&(f=!0);a[g]=f})},linkSeries:function(){var a=this,b=a.series;n(b,function(a){a.linkedSeries.length=0});n(b,function(b){var d=b.options.linkedTo;if(da(d)&&(d=d===":previous"?a.series[b.index-1]:a.get(d)))d.linkedSeries.push(b),
|
||||
b.linkedParent=d})},render:function(){var a=this,b=a.axes,c=a.renderer,d=a.options,e=d.labels,f=d.credits,g;a.setTitle();a.legend=new zb(a,d.legend);a.getStacks();n(b,function(a){a.setScale()});a.getMargins();a.maxTicks=null;n(b,function(a){a.setTickPositions(!0);a.setMaxTicks()});a.adjustTickAmounts();a.getMargins();a.drawChartBox();a.hasCartesianSeries&&n(b,function(a){a.render()});if(!a.seriesGroup)a.seriesGroup=c.g("series-group").attr({zIndex:3}).add();n(a.series,function(a){a.translate();a.setTooltipPoints();
|
||||
a.render()});e.items&&n(e.items,function(b){var d=r(e.style,b.style),f=z(d.left)+a.plotLeft,g=z(d.top)+a.plotTop+12;delete d.left;delete d.top;c.text(b.html,f,g).attr({zIndex:2}).css(d).add()});if(f.enabled&&!a.credits)g=f.href,a.credits=c.text(f.text,0,0).on("click",function(){if(g)location.href=g}).attr({align:f.position.align,zIndex:8}).css(f.style).add().align(f.position);a.hasRendered=!0},destroy:function(){var a=this,b=a.axes,c=a.series,d=a.container,e,f=d&&d.parentNode;A(a,"destroy");Ja[a.index]=
|
||||
u;a.renderTo.removeAttribute("data-highcharts-chart");X(a);for(e=b.length;e--;)b[e]=b[e].destroy();for(e=c.length;e--;)c[e]=c[e].destroy();n("title,subtitle,chartBackground,plotBackground,plotBGImage,plotBorder,seriesGroup,clipRect,credits,pointer,scroller,rangeSelector,legend,resetZoomButton,tooltip,renderer".split(","),function(b){var c=a[b];c&&c.destroy&&(a[b]=c.destroy())});if(d)d.innerHTML="",X(d),f&&Oa(d);for(e in a)delete a[e]},isReadyToRender:function(){var a=this;return!V&&C==C.top&&v.readyState!==
|
||||
"complete"||ba&&!C.canvg?(ba?Mb.push(function(){a.firstRender()},a.options.global.canvasToolsURL):v.attachEvent("onreadystatechange",function(){v.detachEvent("onreadystatechange",a.firstRender);v.readyState==="complete"&&a.firstRender()}),!1):!0},firstRender:function(){var a=this,b=a.options,c=a.callback;if(a.isReadyToRender())a.getContainer(),A(a,"init"),a.resetMargins(),a.setChartSize(),a.propFromSeries(),a.getAxes(),n(b.series||[],function(b){a.initSeries(b)}),a.linkSeries(),A(a,"beforeRender"),
|
||||
a.pointer=new Za(a,b),a.render(),a.renderer.draw(),c&&c.apply(a,[a]),n(a.callbacks,function(b){b.apply(a,[a])}),a.cloneRenderTo(!0),A(a,"load")},splashArray:function(a,b){var c=b[a],c=S(c)?c:[c,c,c,c];return[o(b[a+"Top"],c[0]),o(b[a+"Right"],c[1]),o(b[a+"Bottom"],c[2]),o(b[a+"Left"],c[3])]}};eb.prototype.callbacks=[];var xb=Highcharts.CenteredSeriesMixin={getCenter:function(){var a=this.options,b=this.chart,c=2*(a.slicedOffset||0),d,e=b.plotWidth-2*c,f=b.plotHeight-2*c,b=a.center,a=[o(b[0],"50%"),
|
||||
o(b[1],"50%"),a.size||"100%",a.innerSize||0],g=I(e,f),h;return Sa(a,function(a,b){h=/%$/.test(a);d=b<2||b===2&&h;return(h?[e,f,g,g][b]*z(a)/100:a)+(d?c:0)})}},Ka=function(){};Ka.prototype={init:function(a,b,c){this.series=a;this.applyOptions(b,c);this.pointAttr={};if(a.options.colorByPoint&&(b=a.options.colors||a.chart.options.colors,this.color=this.color||b[a.colorCounter++],a.colorCounter===b.length))a.colorCounter=0;a.chart.pointCount++;return this},applyOptions:function(a,b){var c=this.series,
|
||||
d=c.pointValKey,a=Ka.prototype.optionsToObject.call(this,a);r(this,a);this.options=this.options?r(this.options,a):a;if(d)this.y=this[d];if(this.x===u&&c)this.x=b===u?c.autoIncrement():b;return this},optionsToObject:function(a){var b={},c=this.series,d=c.pointArrayMap||["y"],e=d.length,f=0,g=0;if(typeof a==="number"||a===null)b[d[0]]=a;else if(La(a)){if(a.length>e){c=typeof a[0];if(c==="string")b.name=a[0];else if(c==="number")b.x=a[0];f++}for(;g<e;)b[d[g++]]=a[f++]}else if(typeof a==="object"){b=
|
||||
a;if(a.dataLabels)c._hasPointLabels=!0;if(a.marker)c._hasPointMarkers=!0}return b},destroy:function(){var a=this.series.chart,b=a.hoverPoints,c;a.pointCount--;if(b&&(this.setState(),fa(b,this),!b.length))a.hoverPoints=null;if(this===a.hoverPoint)this.onMouseOut();if(this.graphic||this.dataLabel)X(this),this.destroyElements();this.legendItem&&a.legend.destroyItem(this);for(c in this)this[c]=null},destroyElements:function(){for(var a="graphic,dataLabel,dataLabelUpper,group,connector,shadowGroup".split(","),
|
||||
b,c=6;c--;)b=a[c],this[b]&&(this[b]=this[b].destroy())},getLabelConfig:function(){return{x:this.category,y:this.y,key:this.name||this.category,series:this.series,point:this,percentage:this.percentage,total:this.total||this.stackTotal}},select:function(a,b){var c=this,d=c.series,e=d.chart,a=o(a,!c.selected);c.firePointEvent(a?"select":"unselect",{accumulate:b},function(){c.selected=c.options.selected=a;d.options.data[ta(c,d.data)]=c.options;c.setState(a&&"select");b||n(e.getSelectedPoints(),function(a){if(a.selected&&
|
||||
a!==c)a.selected=a.options.selected=!1,d.options.data[ta(a,d.data)]=a.options,a.setState(""),a.firePointEvent("unselect")})})},onMouseOver:function(a){var b=this.series,c=b.chart,d=c.tooltip,e=c.hoverPoint;if(e&&e!==this)e.onMouseOut();this.firePointEvent("mouseOver");d&&(!d.shared||b.noSharedTooltip)&&d.refresh(this,a);this.setState("hover");c.hoverPoint=this},onMouseOut:function(){var a=this.series.chart,b=a.hoverPoints;if(!b||ta(this,b)===-1)this.firePointEvent("mouseOut"),this.setState(),a.hoverPoint=
|
||||
null},tooltipFormatter:function(a){var b=this.series,c=b.tooltipOptions,d=o(c.valueDecimals,""),e=c.valuePrefix||"",f=c.valueSuffix||"";n(b.pointArrayMap||["y"],function(b){b="{point."+b;if(e||f)a=a.replace(b+"}",e+b+"}"+f);a=a.replace(b+"}",b+":,."+d+"f}")});return Ga(a,{point:this,series:this.series})},firePointEvent:function(a,b,c){var d=this,e=this.series.options;(e.point.events[a]||d.options&&d.options.events&&d.options.events[a])&&this.importEvents();a==="click"&&e.allowPointSelect&&(c=function(a){d.select(null,
|
||||
a.ctrlKey||a.metaKey||a.shiftKey)});A(this,a,b,c)},importEvents:function(){if(!this.hasImportedEvents){var a=x(this.series.options.point,this.options).events,b;this.events=a;for(b in a)F(this,b,a[b]);this.hasImportedEvents=!0}},setState:function(a,b){var c=this.plotX,d=this.plotY,e=this.series,f=e.options.states,g=Y[e.type].marker&&e.options.marker,h=g&&!g.enabled,i=g&&g.states[a],j=i&&i.enabled===!1,k=e.stateMarkerGraphic,l=this.marker||{},m=e.chart,p=this.pointAttr,a=a||"",b=b&&k;if(!(a===this.state&&
|
||||
!b||this.selected&&a!=="select"||f[a]&&f[a].enabled===!1||a&&(j||h&&!i.enabled)||a&&l.states&&l.states[a]&&l.states[a].enabled===!1)){if(this.graphic)f=g&&this.graphic.symbolName&&p[a].r,this.graphic.attr(x(p[a],f?{x:c-f,y:d-f,width:2*f,height:2*f}:{}));else{if(a&&i)if(f=i.radius,l=l.symbol||e.symbol,k&&k.currentSymbol!==l&&(k=k.destroy()),k)k[b?"animate":"attr"]({x:c-f,y:d-f});else e.stateMarkerGraphic=k=m.renderer.symbol(l,c-f,d-f,2*f,2*f).attr(p[a]).add(e.markerGroup),k.currentSymbol=l;if(k)k[a&&
|
||||
m.isInsidePlot(c,d,m.inverted)?"show":"hide"]()}this.state=a}}};var O=function(){};O.prototype={isCartesian:!0,type:"line",pointClass:Ka,sorted:!0,requireSorting:!0,pointAttrToOptions:{stroke:"lineColor","stroke-width":"lineWidth",fill:"fillColor",r:"radius"},axisTypes:["xAxis","yAxis"],colorCounter:0,parallelArrays:["x","y"],init:function(a,b){var c=this,d,e,f=a.series,g=function(a,b){return o(a.options.index,a._i)-o(b.options.index,b._i)};c.chart=a;c.options=b=c.setOptions(b);c.linkedSeries=[];
|
||||
c.bindAxes();r(c,{name:b.name,state:"",pointAttr:{},visible:b.visible!==!1,selected:b.selected===!0});if(ba)b.animation=!1;e=b.events;for(d in e)F(c,d,e[d]);if(e&&e.click||b.point&&b.point.events&&b.point.events.click||b.allowPointSelect)a.runTrackerClick=!0;c.getColor();c.getSymbol();n(c.parallelArrays,function(a){c[a+"Data"]=[]});c.setData(b.data,!1);if(c.isCartesian)a.hasCartesianSeries=!0;f.push(c);c._i=f.length-1;nb(f,g);this.yAxis&&nb(this.yAxis.series,g);n(f,function(a,b){a.index=b;a.name=
|
||||
a.name||"Series "+(b+1)})},bindAxes:function(){var a=this,b=a.options,c=a.chart,d;n(a.axisTypes||[],function(e){n(c[e],function(c){d=c.options;if(b[e]===d.index||b[e]!==u&&b[e]===d.id||b[e]===u&&d.index===0)c.series.push(a),a[e]=c,c.isDirty=!0});!a[e]&&a.optionalAxis!==e&&la(18,!0)})},updateParallelArrays:function(a,b){var c=a.series,d=arguments;n(c.parallelArrays,typeof b==="number"?function(d){var f=d==="y"&&c.toYData?c.toYData(a):a[d];c[d+"Data"][b]=f}:function(a){Array.prototype[b].apply(c[a+
|
||||
"Data"],Array.prototype.slice.call(d,2))})},autoIncrement:function(){var a=this.options,b=this.xIncrement,b=o(b,a.pointStart,0);this.pointInterval=o(this.pointInterval,a.pointInterval,1);this.xIncrement=b+this.pointInterval;return b},getSegments:function(){var a=-1,b=[],c,d=this.points,e=d.length;if(e)if(this.options.connectNulls){for(c=e;c--;)d[c].y===null&&d.splice(c,1);d.length&&(b=[d])}else n(d,function(c,g){c.y===null?(g>a+1&&b.push(d.slice(a+1,g)),a=g):g===e-1&&b.push(d.slice(a+1,g+1))});this.segments=
|
||||
b},setOptions:function(a){var b=this.chart,c=b.options.plotOptions,b=b.userOptions||{},d=b.plotOptions||{},e=c[this.type];this.userOptions=a;c=x(e,c.series,a);this.tooltipOptions=x(G.tooltip,G.plotOptions[this.type].tooltip,b.tooltip,d.series&&d.series.tooltip,d[this.type]&&d[this.type].tooltip,a.tooltip);e.marker===null&&delete c.marker;return c},getColor:function(){var a=this.options,b=this.userOptions,c=this.chart.options.colors,d=this.chart.counters,e;e=a.color||Y[this.type].color;if(!e&&!a.colorByPoint)s(b._colorIndex)?
|
||||
a=b._colorIndex:(b._colorIndex=d.color,a=d.color++),e=c[a];this.color=e;d.wrapColor(c.length)},getSymbol:function(){var a=this.userOptions,b=this.options.marker,c=this.chart,d=c.options.symbols,c=c.counters;this.symbol=b.symbol;if(!this.symbol)s(a._symbolIndex)?a=a._symbolIndex:(a._symbolIndex=c.symbol,a=c.symbol++),this.symbol=d[a];if(/^url/.test(this.symbol))b.radius=0;c.wrapSymbol(d.length)},drawLegendSymbol:R.drawLineMarker,setData:function(a,b){var c=this,d=c.points,e=c.options,f=c.chart,g=null,
|
||||
h=c.xAxis,i=h&&!!h.categories,j;c.xIncrement=null;c.pointRange=i?1:e.pointRange;c.colorCounter=0;var a=a||[],k=a.length;j=e.turboThreshold;var l=this.xData,m=this.yData,p=c.pointArrayMap,p=p&&p.length;n(this.parallelArrays,function(a){c[a+"Data"].length=0});if(j&&k>j){for(j=0;g===null&&j<k;)g=a[j],j++;if(xa(g)){i=o(e.pointStart,0);e=o(e.pointInterval,1);for(j=0;j<k;j++)l[j]=i,m[j]=a[j],i+=e;c.xIncrement=i}else if(La(g))if(p)for(j=0;j<k;j++)e=a[j],l[j]=e[0],m[j]=e.slice(1,p+1);else for(j=0;j<k;j++)e=
|
||||
a[j],l[j]=e[0],m[j]=e[1];else la(12)}else for(j=0;j<k;j++)if(a[j]!==u&&(e={series:c},c.pointClass.prototype.applyOptions.apply(e,[a[j]]),c.updateParallelArrays(e,j),i&&e.name))h.names[e.x]=e.name;da(m[0])&&la(14,!0);c.data=[];c.options.data=a;for(j=d&&d.length||0;j--;)d[j]&&d[j].destroy&&d[j].destroy();if(h)h.minRange=h.userMinRange;c.isDirty=c.isDirtyData=f.isDirtyBox=!0;o(b,!0)&&f.redraw(!1)},processData:function(a){var b=this.xData,c=this.yData,d=b.length,e;e=0;var f,g,h=this.xAxis,i=this.options,
|
||||
j=i.cropThreshold,k=this.isCartesian;if(k&&!this.isDirty&&!h.isDirty&&!this.yAxis.isDirty&&!a)return!1;if(k&&this.sorted&&(!j||d>j||this.forceCrop))if(a=h.min,h=h.max,b[d-1]<a||b[0]>h)b=[],c=[];else if(b[0]<a||b[d-1]>h)e=this.cropData(this.xData,this.yData,a,h),b=e.xData,c=e.yData,e=e.start,f=!0;for(h=b.length-1;h>=0;h--)d=b[h]-b[h-1],d>0&&(g===u||d<g)?g=d:d<0&&this.requireSorting&&la(15);this.cropped=f;this.cropStart=e;this.processedXData=b;this.processedYData=c;if(i.pointRange===null)this.pointRange=
|
||||
g||1;this.closestPointRange=g},cropData:function(a,b,c,d){var e=a.length,f=0,g=e,h=o(this.cropShoulder,1),i;for(i=0;i<e;i++)if(a[i]>=c){f=t(0,i-h);break}for(;i<e;i++)if(a[i]>d){g=i+h;break}return{xData:a.slice(f,g),yData:b.slice(f,g),start:f,end:g}},generatePoints:function(){var a=this.options.data,b=this.data,c,d=this.processedXData,e=this.processedYData,f=this.pointClass,g=d.length,h=this.cropStart||0,i,j=this.hasGroupedData,k,l=[],m;if(!b&&!j)b=[],b.length=a.length,b=this.data=b;for(m=0;m<g;m++)i=
|
||||
h+m,j?l[m]=(new f).init(this,[d[m]].concat(ka(e[m]))):(b[i]?k=b[i]:a[i]!==u&&(b[i]=k=(new f).init(this,a[i],d[m])),l[m]=k);if(b&&(g!==(c=b.length)||j))for(m=0;m<c;m++)if(m===h&&!j&&(m+=g),b[m])b[m].destroyElements(),b[m].plotX=u;this.data=b;this.points=l},setStackedPoints:function(){if(this.options.stacking&&!(this.visible!==!0&&this.chart.options.chart.ignoreHiddenSeries!==!1)){var a=this.processedXData,b=this.processedYData,c=[],d=b.length,e=this.options,f=e.threshold,g=e.stack,e=e.stacking,h=this.stackKey,
|
||||
i="-"+h,j=this.negStacks,k=this.yAxis,l=k.stacks,m=k.oldStacks,p,q,o,n,s;for(o=0;o<d;o++){n=a[o];s=b[o];q=(p=j&&s<f)?i:h;l[q]||(l[q]={});if(!l[q][n])m[q]&&m[q][n]?(l[q][n]=m[q][n],l[q][n].total=null):l[q][n]=new Gb(k,k.options.stackLabels,p,n,g,e);q=l[q][n];q.points[this.index]=[q.cum||0];e==="percent"?(p=p?h:i,j&&l[p]&&l[p][n]?(p=l[p][n],q.total=p.total=t(p.total,q.total)+M(s)||0):q.total+=M(s)||0):q.total+=s||0;q.cum=(q.cum||0)+(s||0);q.points[this.index].push(q.cum);c[o]=q.cum}if(e==="percent")k.usePercentage=
|
||||
!0;this.stackedYData=c;k.oldStacks={}}},setPercentStacks:function(){var a=this,b=a.stackKey,c=a.yAxis.stacks;n([b,"-"+b],function(b){var d;for(var e=a.xData.length,f,g;e--;)if(f=a.xData[e],d=(g=c[b]&&c[b][f])&&g.points[a.index],f=d)g=g.total?100/g.total:0,f[0]=ha(f[0]*g),f[1]=ha(f[1]*g),a.stackedYData[e]=f[1]})},getExtremes:function(a){var b=this.yAxis,c=this.processedXData,d,e=[],f=0;d=this.xAxis.getExtremes();var g=d.min,h=d.max,i,j,k,l,a=a||this.stackedYData||this.processedYData;d=a.length;for(l=
|
||||
0;l<d;l++)if(j=c[l],k=a[l],i=k!==null&&k!==u&&(!b.isLog||k.length||k>0),j=this.getExtremesFromAll||this.cropped||(c[l+1]||j)>=g&&(c[l-1]||j)<=h,i&&j)if(i=k.length)for(;i--;)k[i]!==null&&(e[f++]=k[i]);else e[f++]=k;this.dataMin=o(void 0,Ma(e));this.dataMax=o(void 0,Aa(e))},translate:function(){this.processedXData||this.processData();this.generatePoints();for(var a=this.options,b=a.stacking,c=this.xAxis,d=c.categories,e=this.yAxis,f=this.points,g=f.length,h=!!this.modifyValue,i=a.pointPlacement,j=i===
|
||||
"between"||xa(i),k=a.threshold,a=0;a<g;a++){var l=f[a],m=l.x,p=l.y,q=l.low,n=b&&e.stacks[(this.negStacks&&p<k?"-":"")+this.stackKey];if(e.isLog&&p<=0)l.y=p=null;l.plotX=c.translate(m,0,0,0,1,i,this.type==="flags");if(b&&this.visible&&n&&n[m])n=n[m],p=n.points[this.index],q=p[0],p=p[1],q===0&&(q=o(k,e.min)),e.isLog&&q<=0&&(q=null),l.total=l.stackTotal=n.total,l.percentage=b==="percent"&&l.y/n.total*100,l.stackY=p,n.setOffset(this.pointXOffset||0,this.barW||0);l.yBottom=s(q)?e.translate(q,0,1,0,1):
|
||||
null;h&&(p=this.modifyValue(p,l));l.plotY=typeof p==="number"&&p!==Infinity?e.translate(p,0,1,0,1):u;l.clientX=j?c.translate(m,0,0,0,1):l.plotX;l.negative=l.y<(k||0);l.category=d&&d[l.x]!==u?d[l.x]:l.x}this.getSegments()},setTooltipPoints:function(a){var b=[],c,d,e=this.xAxis,f=e&&e.getExtremes(),g=e?e.tooltipLen||e.len:this.chart.plotSizeX,h,i,j=[];if(this.options.enableMouseTracking!==!1){if(a)this.tooltipPoints=null;n(this.segments||this.points,function(a){b=b.concat(a)});e&&e.reversed&&(b=b.reverse());
|
||||
this.orderTooltipPoints&&this.orderTooltipPoints(b);a=b.length;for(i=0;i<a;i++)if(e=b[i],c=e.x,c>=f.min&&c<=f.max){h=b[i+1];c=d===u?0:d+1;for(d=b[i+1]?I(t(0,N((e.clientX+(h?h.wrappedClientX||h.clientX:g))/2)),g):g;c>=0&&c<=d;)j[c++]=e}this.tooltipPoints=j}},tooltipHeaderFormatter:function(a){var b=this.tooltipOptions,c=b.dateTimeLabelFormats,d=b.xDateFormat||c.year,e=this.xAxis,f=e&&e.options.type==="datetime",b=b.headerFormat,e=e&&e.closestPointRange,g;if(f&&!d)if(e)for(g in E){if(E[g]>=e){d=c[g];
|
||||
break}}else d=c.day;f&&d&&xa(a.key)&&(b=b.replace("{point.key}","{point.key:"+d+"}"));return Ga(b,{point:a,series:this})},onMouseOver:function(){var a=this.chart,b=a.hoverSeries;if(b&&b!==this)b.onMouseOut();this.options.events.mouseOver&&A(this,"mouseOver");this.setState("hover");a.hoverSeries=this},onMouseOut:function(){var a=this.options,b=this.chart,c=b.tooltip,d=b.hoverPoint;if(d)d.onMouseOut();this&&a.events.mouseOut&&A(this,"mouseOut");c&&!a.stickyTracking&&(!c.shared||this.noSharedTooltip)&&
|
||||
c.hide();this.setState();b.hoverSeries=null},animate:function(a){var b=this,c=b.chart,d=c.renderer,e;e=b.options.animation;var f=c.clipBox,g=c.inverted,h;if(e&&!S(e))e=Y[b.type].animation;h="_sharedClip"+e.duration+e.easing;if(a)a=c[h],e=c[h+"m"],a||(c[h]=a=d.clipRect(r(f,{width:0})),c[h+"m"]=e=d.clipRect(-99,g?-c.plotLeft:-c.plotTop,99,g?c.chartWidth:c.chartHeight)),b.group.clip(a),b.markerGroup.clip(e),b.sharedClipKey=h;else{if(a=c[h])a.animate({width:c.plotSizeX},e),c[h+"m"].animate({width:c.plotSizeX+
|
||||
99},e);b.animate=null;b.animationTimeout=setTimeout(function(){b.afterAnimate()},e.duration)}},afterAnimate:function(){var a=this.chart,b=this.sharedClipKey,c=this.group;c&&this.options.clip!==!1&&(c.clip(a.clipRect),this.markerGroup.clip());setTimeout(function(){b&&a[b]&&(a[b]=a[b].destroy(),a[b+"m"]=a[b+"m"].destroy())},100)},drawPoints:function(){var a,b=this.points,c=this.chart,d,e,f,g,h,i,j,k,l=this.options.marker,m=this.pointAttr[""],p,q=this.markerGroup;if(l.enabled||this._hasPointMarkers)for(f=
|
||||
b.length;f--;)if(g=b[f],d=N(g.plotX),e=g.plotY,k=g.graphic,i=g.marker||{},a=l.enabled&&i.enabled===u||i.enabled,p=c.isInsidePlot(w(d),e,c.inverted),a&&e!==u&&!isNaN(e)&&g.y!==null)if(a=g.pointAttr[g.selected?"select":""]||m,h=a.r,i=o(i.symbol,this.symbol),j=i.indexOf("url")===0,k)k.attr({visibility:p?V?"inherit":"visible":"hidden"}).animate(r({x:d-h,y:e-h},k.symbolName?{width:2*h,height:2*h}:{}));else{if(p&&(h>0||j))g.graphic=c.renderer.symbol(i,d-h,e-h,2*h,2*h).attr(a).add(q)}else if(k)g.graphic=
|
||||
k.destroy()},convertAttribs:function(a,b,c,d){var e=this.pointAttrToOptions,f,g,h={},a=a||{},b=b||{},c=c||{},d=d||{};for(f in e)g=e[f],h[f]=o(a[g],b[f],c[f],d[f]);return h},getAttribs:function(){var a=this,b=a.options,c=Y[a.type].marker?b.marker:b,d=c.states,e=d.hover,f,g=a.color;f={stroke:g,fill:g};var h=a.points||[],i,j=[],k,l=a.pointAttrToOptions;i=b.turboThreshold;var m=b.negativeColor,p=c.lineColor,q;b.marker?(e.radius=e.radius||c.radius+2,e.lineWidth=e.lineWidth||c.lineWidth+1):e.color=e.color||
|
||||
ua(e.color||g).brighten(e.brightness).get();j[""]=a.convertAttribs(c,f);n(["hover","select"],function(b){j[b]=a.convertAttribs(d[b],j[""])});a.pointAttr=j;g=h.length;if(!i||g<i)for(;g--;){i=h[g];if((c=i.options&&i.options.marker||i.options)&&c.enabled===!1)c.radius=0;if(i.negative&&m)i.color=i.fillColor=m;f=b.colorByPoint||i.color;if(i.options)for(q in l)s(c[l[q]])&&(f=!0);if(f){c=c||{};k=[];d=c.states||{};f=d.hover=d.hover||{};if(!b.marker)f.color=f.color||e.color||ua(i.color).brighten(f.brightness||
|
||||
e.brightness).get();k[""]=a.convertAttribs(r({color:i.color,fillColor:i.color,lineColor:p===null?i.color:u},c),j[""]);k.hover=a.convertAttribs(d.hover,j.hover,k[""]);k.select=a.convertAttribs(d.select,j.select,k[""])}else k=j;i.pointAttr=k}},destroy:function(){var a=this,b=a.chart,c=/AppleWebKit\/533/.test(sa),d,e,f=a.data||[],g,h,i;A(a,"destroy");X(a);n(a.axisTypes||[],function(b){if(i=a[b])fa(i.series,a),i.isDirty=i.forceRedraw=!0});a.legendItem&&a.chart.legend.destroyItem(a);for(e=f.length;e--;)(g=
|
||||
f[e])&&g.destroy&&g.destroy();a.points=null;clearTimeout(a.animationTimeout);n("area,graph,dataLabelsGroup,group,markerGroup,tracker,graphNeg,areaNeg,posClip,negClip".split(","),function(b){a[b]&&(d=c&&b==="group"?"hide":"destroy",a[b][d]())});if(b.hoverSeries===a)b.hoverSeries=null;fa(b.series,a);for(h in a)delete a[h]},getSegmentPath:function(a){var b=this,c=[],d=b.options.step;n(a,function(e,f){var g=e.plotX,h=e.plotY,i;b.getPointSpline?c.push.apply(c,b.getPointSpline(a,e,f)):(c.push(f?"L":"M"),
|
||||
d&&f&&(i=a[f-1],d==="right"?c.push(i.plotX,h):d==="center"?c.push((i.plotX+g)/2,i.plotY,(i.plotX+g)/2,h):c.push(g,i.plotY)),c.push(e.plotX,e.plotY))});return c},getGraphPath:function(){var a=this,b=[],c,d=[];n(a.segments,function(e){c=a.getSegmentPath(e);e.length>1?b=b.concat(c):d.push(e[0])});a.singlePoints=d;return a.graphPath=b},drawGraph:function(){var a=this,b=this.options,c=[["graph",b.lineColor||this.color]],d=b.lineWidth,e=b.dashStyle,f=b.linecap!=="square",g=this.getGraphPath(),h=b.negativeColor;
|
||||
h&&c.push(["graphNeg",h]);n(c,function(c,h){var k=c[0],l=a[k];if(l)Ya(l),l.animate({d:g});else if(d&&g.length)l={stroke:c[1],"stroke-width":d,zIndex:1},e?l.dashstyle=e:f&&(l["stroke-linecap"]=l["stroke-linejoin"]="round"),a[k]=a.chart.renderer.path(g).attr(l).add(a.group).shadow(!h&&b.shadow)})},clipNeg:function(){var a=this.options,b=this.chart,c=b.renderer,d=a.negativeColor||a.negativeFillColor,e,f=this.graph,g=this.area,h=this.posClip,i=this.negClip;e=b.chartWidth;var j=b.chartHeight,k=t(e,j),
|
||||
l=this.yAxis;if(d&&(f||g)){d=w(l.toPixels(a.threshold||0,!0));d<0&&(k-=d);a={x:0,y:0,width:k,height:d};k={x:0,y:d,width:k,height:k};if(b.inverted)a.height=k.y=b.plotWidth-d,c.isVML&&(a={x:b.plotWidth-d-b.plotLeft,y:0,width:e,height:j},k={x:d+b.plotLeft-e,y:0,width:b.plotLeft+d,height:e});l.reversed?(b=k,e=a):(b=a,e=k);h?(h.animate(b),i.animate(e)):(this.posClip=h=c.clipRect(b),this.negClip=i=c.clipRect(e),f&&this.graphNeg&&(f.clip(h),this.graphNeg.clip(i)),g&&(g.clip(h),this.areaNeg.clip(i)))}},invertGroups:function(){function a(){var a=
|
||||
{width:b.yAxis.len,height:b.xAxis.len};n(["group","markerGroup"],function(c){b[c]&&b[c].attr(a).invert()})}var b=this,c=b.chart;if(b.xAxis)F(c,"resize",a),F(b,"destroy",function(){X(c,"resize",a)}),a(),b.invertGroups=a},plotGroup:function(a,b,c,d,e){var f=this[a],g=!f;g&&(this[a]=f=this.chart.renderer.g(b).attr({visibility:c,zIndex:d||0.1}).add(e));f[g?"attr":"animate"](this.getPlotBox());return f},getPlotBox:function(){return{translateX:this.xAxis?this.xAxis.left:this.chart.plotLeft,translateY:this.yAxis?
|
||||
this.yAxis.top:this.chart.plotTop,scaleX:1,scaleY:1}},render:function(){var a=this.chart,b,c=this.options,d=c.animation&&!!this.animate&&a.renderer.isSVG,e=this.visible?"visible":"hidden",f=c.zIndex,g=this.hasRendered,h=a.seriesGroup;b=this.plotGroup("group","series",e,f,h);this.markerGroup=this.plotGroup("markerGroup","markers",e,f,h);d&&this.animate(!0);this.getAttribs();b.inverted=this.isCartesian?a.inverted:!1;this.drawGraph&&(this.drawGraph(),this.clipNeg());this.drawDataLabels&&this.drawDataLabels();
|
||||
this.visible&&this.drawPoints();this.options.enableMouseTracking!==!1&&this.drawTracker();a.inverted&&this.invertGroups();c.clip!==!1&&!this.sharedClipKey&&!g&&b.clip(a.clipRect);d?this.animate():g||this.afterAnimate();this.isDirty=this.isDirtyData=!1;this.hasRendered=!0},redraw:function(){var a=this.chart,b=this.isDirtyData,c=this.group,d=this.xAxis,e=this.yAxis;c&&(a.inverted&&c.attr({width:a.plotWidth,height:a.plotHeight}),c.animate({translateX:o(d&&d.left,a.plotLeft),translateY:o(e&&e.top,a.plotTop)}));
|
||||
this.translate();this.setTooltipPoints(!0);this.render();b&&A(this,"updatedData")},setState:function(a){var b=this.options,c=this.graph,d=this.graphNeg,e=b.states,b=b.lineWidth,a=a||"";if(this.state!==a)this.state=a,e[a]&&e[a].enabled===!1||(a&&(b=e[a].lineWidth||b+1),c&&!c.dashstyle&&(a={"stroke-width":b},c.attr(a),d&&d.attr(a)))},setVisible:function(a,b){var c=this,d=c.chart,e=c.legendItem,f,g=d.options.chart.ignoreHiddenSeries,h=c.visible;f=(c.visible=a=c.userOptions.visible=a===u?!h:a)?"show":
|
||||
"hide";n(["group","dataLabelsGroup","markerGroup","tracker"],function(a){if(c[a])c[a][f]()});if(d.hoverSeries===c)c.onMouseOut();e&&d.legend.colorizeItem(c,a);c.isDirty=!0;c.options.stacking&&n(d.series,function(a){if(a.options.stacking&&a.visible)a.isDirty=!0});n(c.linkedSeries,function(b){b.setVisible(a,!1)});if(g)d.isDirtyBox=!0;b!==!1&&d.redraw();A(c,f)},show:function(){this.setVisible(!0)},hide:function(){this.setVisible(!1)},select:function(a){this.selected=a=a===u?!this.selected:a;if(this.checkbox)this.checkbox.checked=
|
||||
a;A(this,a?"select":"unselect")},drawTracker:J.drawTrackerGraph};r(eb.prototype,{addSeries:function(a,b,c){var d,e=this;a&&(b=o(b,!0),A(e,"addSeries",{options:a},function(){d=e.initSeries(a);e.isDirtyLegend=!0;e.linkSeries();b&&e.redraw(c)}));return d},addAxis:function(a,b,c,d){var e=b?"xAxis":"yAxis",f=this.options;new ra(this,x(a,{index:this[e].length,isX:b}));f[e]=ka(f[e]||{});f[e].push(a);o(c,!0)&&this.redraw(d)},showLoading:function(a){var b=this.options,c=this.loadingDiv,d=b.loading;if(!c)this.loadingDiv=
|
||||
c=T(Ha,{className:"highcharts-loading"},r(d.style,{zIndex:10,display:Q}),this.container),this.loadingSpan=T("span",null,d.labelStyle,c);this.loadingSpan.innerHTML=a||b.lang.loading;if(!this.loadingShown)D(c,{opacity:0,display:"",left:this.plotLeft+"px",top:this.plotTop+"px",width:this.plotWidth+"px",height:this.plotHeight+"px"}),jb(c,{opacity:d.style.opacity},{duration:d.showDuration||0}),this.loadingShown=!0},hideLoading:function(){var a=this.options,b=this.loadingDiv;b&&jb(b,{opacity:0},{duration:a.loading.hideDuration||
|
||||
100,complete:function(){D(b,{display:Q})}});this.loadingShown=!1}});r(Ka.prototype,{update:function(a,b,c){var d=this,e=d.series,f=d.graphic,g,h=e.data,i=e.chart,j=e.options,b=o(b,!0);d.firePointEvent("update",{options:a},function(){d.applyOptions(a);if(S(a)){e.getAttribs();if(f)a&&a.marker&&a.marker.symbol?d.graphic=f.destroy():f.attr(d.pointAttr[d.state||""]);if(a&&a.dataLabels&&d.dataLabel)d.dataLabel=d.dataLabel.destroy()}g=ta(d,h);e.updateParallelArrays(d,g);j.data[g]=d.options;e.isDirty=e.isDirtyData=
|
||||
!0;if(!e.fixedBox&&e.hasCartesianSeries)i.isDirtyBox=!0;j.legendType==="point"&&i.legend.destroyItem(d);b&&i.redraw(c)})},remove:function(a,b){var c=this,d=c.series,e=d.points,f=d.chart,g,h=d.data;Pa(b,f);a=o(a,!0);c.firePointEvent("remove",null,function(){g=ta(c,h);h.length===e.length&&e.splice(g,1);h.splice(g,1);d.options.data.splice(g,1);d.updateParallelArrays(c,"splice",g,1);c.destroy();d.isDirty=!0;d.isDirtyData=!0;a&&f.redraw()})}});r(O.prototype,{addPoint:function(a,b,c,d){var e=this.options,
|
||||
f=this.data,g=this.graph,h=this.area,i=this.chart,j=this.xAxis&&this.xAxis.names,k=g&&g.shift||0,l=e.data,m,p=this.xData;Pa(d,i);c&&n([g,h,this.graphNeg,this.areaNeg],function(a){if(a)a.shift=k+1});if(h)h.isArea=!0;b=o(b,!0);d={series:this};this.pointClass.prototype.applyOptions.apply(d,[a]);g=d.x;h=p.length;if(this.requireSorting&&g<p[h-1])for(m=!0;h&&p[h-1]>g;)h--;this.updateParallelArrays(d,"splice",h);this.updateParallelArrays(d,h);if(j)j[g]=d.name;l.splice(h,0,a);m&&(this.data.splice(h,0,null),
|
||||
this.processData());e.legendType==="point"&&this.generatePoints();c&&(f[0]&&f[0].remove?f[0].remove(!1):(f.shift(),this.updateParallelArrays(d,"shift"),l.shift()));this.isDirtyData=this.isDirty=!0;b&&(this.getAttribs(),i.redraw())},remove:function(a,b){var c=this,d=c.chart,a=o(a,!0);if(!c.isRemoving)c.isRemoving=!0,A(c,"remove",null,function(){c.destroy();d.isDirtyLegend=d.isDirtyBox=!0;d.linkSeries();a&&d.redraw(b)});c.isRemoving=!1},update:function(a,b){var c=this.chart,d=this.type,e=L[d].prototype,
|
||||
f,a=x(this.userOptions,{animation:!1,index:this.index,pointStart:this.xData[0]},{data:this.options.data},a);this.remove(!1);for(f in e)e.hasOwnProperty(f)&&(this[f]=u);r(this,L[a.type||d].prototype);this.init(c,a);o(b,!0)&&c.redraw(!1)}});r(ra.prototype,{update:function(a,b){var c=this.chart,a=c.options[this.coll][this.options.index]=x(this.userOptions,a);this.destroy(!0);this._addedPlotLB=this.userMin=this.userMax=u;this.init(c,r(a,{events:u}));c.isDirtyBox=!0;o(b,!0)&&c.redraw()},remove:function(a){var b=
|
||||
this.chart,c=this.coll;n(this.series,function(a){a.remove(!1)});fa(b.axes,this);fa(b[c],this);b.options[c].splice(this.options.index,1);n(b[c],function(a,b){a.options.index=b});this.destroy();b.isDirtyBox=!0;o(a,!0)&&b.redraw()},setTitle:function(a,b){this.update({title:a},b)},setCategories:function(a,b){this.update({categories:a},b)}});var aa=ga(O);L.line=aa;Y.area=x(W,{threshold:0});var Ta=ga(O,{type:"area",getSegments:function(){var a=[],b=[],c=[],d=this.xAxis,e=this.yAxis,f=e.stacks[this.stackKey],
|
||||
g={},h,i,j=this.points,k=this.options.connectNulls,l,m,p;if(this.options.stacking&&!this.cropped){for(m=0;m<j.length;m++)g[j[m].x]=j[m];for(p in f)f[p].total!==null&&c.push(+p);c.sort(function(a,b){return a-b});n(c,function(a){if(!k||g[a]&&g[a].y!==null)g[a]?b.push(g[a]):(h=d.translate(a),l=f[a].percent?f[a].total?f[a].cum*100/f[a].total:0:f[a].cum,i=e.toPixels(l,!0),b.push({y:null,plotX:h,clientX:h,plotY:i,yBottom:i,onMouseOver:ma}))});b.length&&a.push(b)}else O.prototype.getSegments.call(this),
|
||||
a=this.segments;this.segments=a},getSegmentPath:function(a){var b=O.prototype.getSegmentPath.call(this,a),c=[].concat(b),d,e=this.options;d=b.length;var f=this.yAxis.getThreshold(e.threshold),g;d===3&&c.push("L",b[1],b[2]);if(e.stacking&&!this.closedStacks)for(d=a.length-1;d>=0;d--)g=o(a[d].yBottom,f),d<a.length-1&&e.step&&c.push(a[d+1].plotX,g),c.push(a[d].plotX,g);else this.closeSegment(c,a,f);this.areaPath=this.areaPath.concat(c);return b},closeSegment:function(a,b,c){a.push("L",b[b.length-1].plotX,
|
||||
c,"L",b[0].plotX,c)},drawGraph:function(){this.areaPath=[];O.prototype.drawGraph.apply(this);var a=this,b=this.areaPath,c=this.options,d=c.negativeColor,e=c.negativeFillColor,f=[["area",this.color,c.fillColor]];(d||e)&&f.push(["areaNeg",d,e]);n(f,function(d){var e=d[0],f=a[e];f?f.animate({d:b}):a[e]=a.chart.renderer.path(b).attr({fill:o(d[2],ua(d[1]).setOpacity(o(c.fillOpacity,0.75)).get()),zIndex:0}).add(a.group)})},drawLegendSymbol:R.drawRectangle});L.area=Ta;Y.spline=x(W);aa=ga(O,{type:"spline",
|
||||
getPointSpline:function(a,b,c){var d=b.plotX,e=b.plotY,f=a[c-1],g=a[c+1],h,i,j,k;if(f&&g){a=f.plotY;j=g.plotX;var g=g.plotY,l;h=(1.5*d+f.plotX)/2.5;i=(1.5*e+a)/2.5;j=(1.5*d+j)/2.5;k=(1.5*e+g)/2.5;l=(k-i)*(j-d)/(j-h)+e-k;i+=l;k+=l;i>a&&i>e?(i=t(a,e),k=2*e-i):i<a&&i<e&&(i=I(a,e),k=2*e-i);k>g&&k>e?(k=t(g,e),i=2*e-k):k<g&&k<e&&(k=I(g,e),i=2*e-k);b.rightContX=j;b.rightContY=k}c?(b=["C",f.rightContX||f.plotX,f.rightContY||f.plotY,h||d,i||e,d,e],f.rightContX=f.rightContY=null):b=["M",d,e];return b}});L.spline=
|
||||
aa;Y.areaspline=x(Y.area);Ta=Ta.prototype;aa=ga(aa,{type:"areaspline",closedStacks:!0,getSegmentPath:Ta.getSegmentPath,closeSegment:Ta.closeSegment,drawGraph:Ta.drawGraph,drawLegendSymbol:R.drawRectangle});L.areaspline=aa;Y.column=x(W,{borderColor:"#FFFFFF",borderWidth:1,borderRadius:0,groupPadding:0.2,marker:null,pointPadding:0.1,minPointLength:0,cropThreshold:50,pointRange:null,states:{hover:{brightness:0.1,shadow:!1},select:{color:"#C0C0C0",borderColor:"#000000",shadow:!1}},dataLabels:{align:null,
|
||||
verticalAlign:null,y:null},stickyTracking:!1,threshold:0});aa=ga(O,{type:"column",pointAttrToOptions:{stroke:"borderColor","stroke-width":"borderWidth",fill:"color",r:"borderRadius"},cropShoulder:0,trackerGroups:["group","dataLabelsGroup"],negStacks:!0,init:function(){O.prototype.init.apply(this,arguments);var a=this,b=a.chart;b.hasRendered&&n(b.series,function(b){if(b.type===a.type)b.isDirty=!0})},getColumnMetrics:function(){var a=this,b=a.options,c=a.xAxis,d=a.yAxis,e=c.reversed,f,g={},h,i=0;b.grouping===
|
||||
!1?i=1:n(a.chart.series,function(b){var c=b.options,e=b.yAxis;if(b.type===a.type&&b.visible&&d.len===e.len&&d.pos===e.pos)c.stacking?(f=b.stackKey,g[f]===u&&(g[f]=i++),h=g[f]):c.grouping!==!1&&(h=i++),b.columnIndex=h});var c=I(M(c.transA)*(c.ordinalSlope||b.pointRange||c.closestPointRange||1),c.len),j=c*b.groupPadding,k=(c-2*j)/i,l=b.pointWidth,b=s(l)?(k-l)/2:k*b.pointPadding,l=o(l,k-2*b);return a.columnMetrics={width:l,offset:b+(j+((e?i-(a.columnIndex||0):a.columnIndex)||0)*k-c/2)*(e?-1:1)}},translate:function(){var a=
|
||||
this.chart,b=this.options,c=b.borderWidth,d=this.yAxis,e=this.translatedThreshold=d.getThreshold(b.threshold),f=o(b.minPointLength,5),b=this.getColumnMetrics(),g=b.width,h=this.barW=Ia(t(g,1+2*c)),i=this.pointXOffset=b.offset,j=-(c%2?0.5:0),k=c%2?0.5:1;a.renderer.isVML&&a.inverted&&(k+=1);O.prototype.translate.apply(this);n(this.points,function(a){var b=o(a.yBottom,e),c=I(t(-999-b,a.plotY),d.len+999+b),n=a.plotX+i,s=h,r=I(c,b),u,c=t(c,b)-r;M(c)<f&&f&&(c=f,r=w(M(r-e)>f?b-f:e-(d.translate(a.y,0,1,0,
|
||||
1)<=e?f:0)));a.barX=n;a.pointWidth=g;b=M(n)<0.5;s=w(n+s)+j;n=w(n)+j;s-=n;u=M(r)<0.5;c=w(r+c)+k;r=w(r)+k;c-=r;b&&(n+=1,s-=1);u&&(r-=1,c+=1);a.shapeType="rect";a.shapeArgs={x:n,y:r,width:s,height:c}})},getSymbol:ma,drawLegendSymbol:R.drawRectangle,drawGraph:ma,drawPoints:function(){var a=this,b=this.chart,c=a.options,d=b.renderer,e=b.options.animationLimit||250,f;n(a.points,function(g){var h=g.plotY,i=g.graphic;if(h!==u&&!isNaN(h)&&g.y!==null)f=g.shapeArgs,i?(Ya(i),i[b.pointCount<e?"animate":"attr"](x(f))):
|
||||
g.graphic=d[g.shapeType](f).attr(g.pointAttr[g.selected?"select":""]).add(a.group).shadow(c.shadow,null,c.stacking&&!c.borderRadius);else if(i)g.graphic=i.destroy()})},drawTracker:J.drawTrackerPoint,animate:function(a){var b=this.yAxis,c=this.options,d=this.chart.inverted,e={};if(V)a?(e.scaleY=0.001,a=I(b.pos+b.len,t(b.pos,b.toPixels(c.threshold))),d?e.translateX=a-b.len:e.translateY=a,this.group.attr(e)):(e.scaleY=1,e[d?"translateX":"translateY"]=b.pos,this.group.animate(e,this.options.animation),
|
||||
this.animate=null)},remove:function(){var a=this,b=a.chart;b.hasRendered&&n(b.series,function(b){if(b.type===a.type)b.isDirty=!0});O.prototype.remove.apply(a,arguments)}});L.column=aa;Y.bar=x(Y.column);aa=ga(aa,{type:"bar",inverted:!0});L.bar=aa;Y.scatter=x(W,{lineWidth:0,tooltip:{headerFormat:'<span style="font-size: 10px; color:{series.color}">{series.name}</span><br/>',pointFormat:"x: <b>{point.x}</b><br/>y: <b>{point.y}</b><br/>",followPointer:!0},stickyTracking:!1});aa=ga(O,{type:"scatter",sorted:!1,
|
||||
requireSorting:!1,noSharedTooltip:!0,trackerGroups:["markerGroup"],takeOrdinalPosition:!1,drawTracker:J.drawTrackerPoint,drawGraph:function(){this.options.lineWidth&&O.prototype.drawGraph.call(this)},setTooltipPoints:ma});L.scatter=aa;Y.pie=x(W,{borderColor:"#FFFFFF",borderWidth:1,center:[null,null],clip:!1,colorByPoint:!0,dataLabels:{distance:30,enabled:!0,formatter:function(){return this.point.name}},ignoreHiddenPoint:!0,legendType:"point",marker:null,size:null,showInLegend:!1,slicedOffset:10,states:{hover:{brightness:0.1,
|
||||
shadow:!1}},stickyTracking:!1,tooltip:{followPointer:!0}});W={type:"pie",isCartesian:!1,pointClass:ga(Ka,{init:function(){Ka.prototype.init.apply(this,arguments);var a=this,b;if(a.y<0)a.y=null;r(a,{visible:a.visible!==!1,name:o(a.name,"Slice")});b=function(b){a.slice(b.type==="select")};F(a,"select",b);F(a,"unselect",b);return a},setVisible:function(a){var b=this,c=b.series,d=c.chart,e;b.visible=b.options.visible=a=a===u?!b.visible:a;c.options.data[ta(b,c.data)]=b.options;e=a?"show":"hide";n(["graphic",
|
||||
"dataLabel","connector","shadowGroup"],function(a){if(b[a])b[a][e]()});b.legendItem&&d.legend.colorizeItem(b,a);if(!c.isDirty&&c.options.ignoreHiddenPoint)c.isDirty=!0,d.redraw()},slice:function(a,b,c){var d=this.series;Pa(c,d.chart);o(b,!0);this.sliced=this.options.sliced=a=s(a)?a:!this.sliced;d.options.data[ta(this,d.data)]=this.options;a=a?this.slicedTranslation:{translateX:0,translateY:0};this.graphic.animate(a);this.shadowGroup&&this.shadowGroup.animate(a)}}),requireSorting:!1,noSharedTooltip:!0,
|
||||
trackerGroups:["group","dataLabelsGroup"],axisTypes:[],pointAttrToOptions:{stroke:"borderColor","stroke-width":"borderWidth",fill:"color"},getColor:ma,animate:function(a){var b=this,c=b.points,d=b.startAngleRad;if(!a)n(c,function(a){var c=a.graphic,a=a.shapeArgs;c&&(c.attr({r:b.center[3]/2,start:d,end:d}),c.animate({r:a.r,start:a.start,end:a.end},b.options.animation))}),b.animate=null},setData:function(a,b){O.prototype.setData.call(this,a,!1);this.processData();this.generatePoints();o(b,!0)&&this.chart.redraw()},
|
||||
generatePoints:function(){var a,b=0,c,d,e,f=this.options.ignoreHiddenPoint;O.prototype.generatePoints.call(this);c=this.points;d=c.length;for(a=0;a<d;a++)e=c[a],b+=f&&!e.visible?0:e.y;this.total=b;for(a=0;a<d;a++)e=c[a],e.percentage=b>0?e.y/b*100:0,e.total=b},translate:function(a){this.generatePoints();var b=0,c=this.options,d=c.slicedOffset,e=d+c.borderWidth,f,g,h,i=c.startAngle||0,j=this.startAngleRad=Ba/180*(i-90),i=(this.endAngleRad=Ba/180*((c.endAngle||i+360)-90))-j,k=this.points,l=c.dataLabels.distance,
|
||||
c=c.ignoreHiddenPoint,m,n=k.length,o;if(!a)this.center=a=this.getCenter();this.getX=function(b,c){h=P.asin((b-a[1])/(a[2]/2+l));return a[0]+(c?-1:1)*U(h)*(a[2]/2+l)};for(m=0;m<n;m++){o=k[m];f=j+b*i;if(!c||o.visible)b+=o.percentage/100;g=j+b*i;o.shapeType="arc";o.shapeArgs={x:a[0],y:a[1],r:a[2]/2,innerR:a[3]/2,start:w(f*1E3)/1E3,end:w(g*1E3)/1E3};h=(g+f)/2;h>0.75*i&&(h-=2*Ba);o.slicedTranslation={translateX:w(U(h)*d),translateY:w($(h)*d)};f=U(h)*a[2]/2;g=$(h)*a[2]/2;o.tooltipPos=[a[0]+f*0.7,a[1]+g*
|
||||
0.7];o.half=h<-Ba/2||h>Ba/2?1:0;o.angle=h;e=I(e,l/2);o.labelPos=[a[0]+f+U(h)*l,a[1]+g+$(h)*l,a[0]+f+U(h)*e,a[1]+g+$(h)*e,a[0]+f,a[1]+g,l<0?"center":o.half?"right":"left",h]}},setTooltipPoints:ma,drawGraph:null,drawPoints:function(){var a=this,b=a.chart.renderer,c,d,e=a.options.shadow,f,g;if(e&&!a.shadowGroup)a.shadowGroup=b.g("shadow").add(a.group);n(a.points,function(h){d=h.graphic;g=h.shapeArgs;f=h.shadowGroup;if(e&&!f)f=h.shadowGroup=b.g("shadow").add(a.shadowGroup);c=h.sliced?h.slicedTranslation:
|
||||
{translateX:0,translateY:0};f&&f.attr(c);d?d.animate(r(g,c)):h.graphic=d=b.arc(g).setRadialReference(a.center).attr(h.pointAttr[h.selected?"select":""]).attr({"stroke-linejoin":"round"}).attr(c).add(a.group).shadow(e,f);h.visible!==void 0&&h.setVisible(h.visible)})},sortByAngle:function(a,b){a.sort(function(a,d){return a.angle!==void 0&&(d.angle-a.angle)*b})},drawTracker:J.drawTrackerPoint,drawLegendSymbol:R.drawRectangle,getCenter:xb.getCenter,getSymbol:ma};W=ga(O,W);L.pie=W;O.prototype.drawDataLabels=
|
||||
function(){var a=this,b=a.options,c=b.cursor,d=b.dataLabels,b=a.points,e,f,g,h;if(d.enabled||a._hasPointLabels)a.dlProcessOptions&&a.dlProcessOptions(d),h=a.plotGroup("dataLabelsGroup","data-labels",a.visible?"visible":"hidden",d.zIndex||6),f=d,n(b,function(b){var j,k=b.dataLabel,l,m,n=b.connector,q=!0;e=b.options&&b.options.dataLabels;j=o(e&&e.enabled,f.enabled);if(k&&!j)b.dataLabel=k.destroy();else if(j){d=x(f,e);j=d.rotation;l=b.getLabelConfig();g=d.format?Ga(d.format,l):d.formatter.call(l,d);
|
||||
d.style.color=o(d.color,d.style.color,a.color,"black");if(k)if(s(g))k.attr({text:g}),q=!1;else{if(b.dataLabel=k=k.destroy(),n)b.connector=n.destroy()}else if(s(g)){k={fill:d.backgroundColor,stroke:d.borderColor,"stroke-width":d.borderWidth,r:d.borderRadius||0,rotation:j,padding:d.padding,zIndex:1};for(m in k)k[m]===u&&delete k[m];k=b.dataLabel=a.chart.renderer[j?"text":"label"](g,0,-999,null,null,null,d.useHTML).attr(k).css(r(d.style,c&&{cursor:c})).add(h).shadow(d.shadow)}k&&a.alignDataLabel(b,k,
|
||||
d,null,q)}})};O.prototype.alignDataLabel=function(a,b,c,d,e){var f=this.chart,g=f.inverted,h=o(a.plotX,-999),i=o(a.plotY,-999),j=b.getBBox();if(a=this.visible&&(a.series.forceDL||f.isInsidePlot(a.plotX,a.plotY,g)))d=r({x:g?f.plotWidth-i:h,y:w(g?f.plotHeight-h:i),width:0,height:0},d),r(c,{width:j.width,height:j.height}),c.rotation?(g={align:c.align,x:d.x+c.x+d.width/2,y:d.y+c.y+d.height/2},b[e?"attr":"animate"](g)):(b.align(c,null,d),g=b.alignAttr,o(c.overflow,"justify")==="justify"?this.justifyDataLabel(b,
|
||||
c,g,j,d,e):o(c.crop,!0)&&(a=f.isInsidePlot(g.x,g.y)&&f.isInsidePlot(g.x+j.width,g.y+j.height)));if(!a)b.attr({y:-999}),b.placed=!1};O.prototype.justifyDataLabel=function(a,b,c,d,e,f){var g=this.chart,h=b.align,i=b.verticalAlign,j,k;j=c.x;if(j<0)h==="right"?b.align="left":b.x=-j,k=!0;j=c.x+d.width;if(j>g.plotWidth)h==="left"?b.align="right":b.x=g.plotWidth-j,k=!0;j=c.y;if(j<0)i==="bottom"?b.verticalAlign="top":b.y=-j,k=!0;j=c.y+d.height;if(j>g.plotHeight)i==="top"?b.verticalAlign="bottom":b.y=g.plotHeight-
|
||||
j,k=!0;if(k)a.placed=!f,a.align(b,null,e)};if(L.pie)L.pie.prototype.drawDataLabels=function(){var a=this,b=a.data,c,d=a.chart,e=a.options.dataLabels,f=o(e.connectorPadding,10),g=o(e.connectorWidth,1),h=d.plotWidth,d=d.plotHeight,i,j,k=o(e.softConnector,!0),l=e.distance,m=a.center,p=m[2]/2,q=m[1],s=l>0,r,u,v,x,y=[[],[]],z,A,E,K,B,D=[0,0,0,0],I=function(a,b){return b.y-a.y};if(a.visible&&(e.enabled||a._hasPointLabels)){O.prototype.drawDataLabels.apply(a);n(b,function(a){a.dataLabel&&a.visible&&y[a.half].push(a)});
|
||||
for(K=0;!x&&b[K];)x=b[K]&&b[K].dataLabel&&(b[K].dataLabel.getBBox().height||21),K++;for(K=2;K--;){var b=[],J=[],F=y[K],G=F.length,C;a.sortByAngle(F,K-0.5);if(l>0){for(B=q-p-l;B<=q+p+l;B+=x)b.push(B);u=b.length;if(G>u){c=[].concat(F);c.sort(I);for(B=G;B--;)c[B].rank=B;for(B=G;B--;)F[B].rank>=u&&F.splice(B,1);G=F.length}for(B=0;B<G;B++){c=F[B];v=c.labelPos;c=9999;var L,N;for(N=0;N<u;N++)L=M(b[N]-v[1]),L<c&&(c=L,C=N);if(C<B&&b[B]!==null)C=B;else for(u<G-B+C&&b[B]!==null&&(C=u-G+B);b[C]===null;)C++;J.push({i:C,
|
||||
y:b[C]});b[C]=null}J.sort(I)}for(B=0;B<G;B++){c=F[B];v=c.labelPos;r=c.dataLabel;E=c.visible===!1?"hidden":"visible";c=v[1];if(l>0){if(u=J.pop(),C=u.i,A=u.y,c>A&&b[C+1]!==null||c<A&&b[C-1]!==null)A=c}else A=c;z=e.justify?m[0]+(K?-1:1)*(p+l):a.getX(C===0||C===b.length-1?c:A,K);r._attr={visibility:E,align:v[6]};r._pos={x:z+e.x+({left:f,right:-f}[v[6]]||0),y:A+e.y-10};r.connX=z;r.connY=A;if(this.options.size===null)u=r.width,z-u<f?D[3]=t(w(u-z+f),D[3]):z+u>h-f&&(D[1]=t(w(z+u-h+f),D[1])),A-x/2<0?D[0]=
|
||||
t(w(-A+x/2),D[0]):A+x/2>d&&(D[2]=t(w(A+x/2-d),D[2]))}}if(Aa(D)===0||this.verifyDataLabelOverflow(D))this.placeDataLabels(),s&&g&&n(this.points,function(b){i=b.connector;v=b.labelPos;if((r=b.dataLabel)&&r._pos)E=r._attr.visibility,z=r.connX,A=r.connY,j=k?["M",z+(v[6]==="left"?5:-5),A,"C",z,A,2*v[2]-v[4],2*v[3]-v[5],v[2],v[3],"L",v[4],v[5]]:["M",z+(v[6]==="left"?5:-5),A,"L",v[2],v[3],"L",v[4],v[5]],i?(i.animate({d:j}),i.attr("visibility",E)):b.connector=i=a.chart.renderer.path(j).attr({"stroke-width":g,
|
||||
stroke:e.connectorColor||b.color||"#606060",visibility:E}).add(a.group);else if(i)b.connector=i.destroy()})}},L.pie.prototype.placeDataLabels=function(){n(this.points,function(a){var a=a.dataLabel,b;if(a)(b=a._pos)?(a.attr(a._attr),a[a.moved?"animate":"attr"](b),a.moved=!0):a&&a.attr({y:-999})})},L.pie.prototype.alignDataLabel=ma,L.pie.prototype.verifyDataLabelOverflow=function(a){var b=this.center,c=this.options,d=c.center,e=c=c.minSize||80,f;d[0]!==null?e=t(b[2]-t(a[1],a[3]),c):(e=t(b[2]-a[1]-a[3],
|
||||
c),b[0]+=(a[3]-a[1])/2);d[1]!==null?e=t(I(e,b[2]-t(a[0],a[2])),c):(e=t(I(e,b[2]-a[0]-a[2]),c),b[1]+=(a[0]-a[2])/2);e<b[2]?(b[2]=e,this.translate(b),n(this.points,function(a){if(a.dataLabel)a.dataLabel._pos=null}),this.drawDataLabels&&this.drawDataLabels()):f=!0;return f};if(L.column)L.column.prototype.alignDataLabel=function(a,b,c,d,e){var f=this.chart,g=f.inverted,h=a.dlBox||a.shapeArgs,i=a.below||a.plotY>o(this.translatedThreshold,f.plotSizeY),j=o(c.inside,!!this.options.stacking);if(h&&(d=x(h),
|
||||
g&&(d={x:f.plotWidth-d.y-d.height,y:f.plotHeight-d.x-d.width,width:d.height,height:d.width}),!j))g?(d.x+=i?0:d.width,d.width=0):(d.y+=i?d.height:0,d.height=0);c.align=o(c.align,!g||j?"center":i?"right":"left");c.verticalAlign=o(c.verticalAlign,g||j?"middle":i?"top":"bottom");O.prototype.alignDataLabel.call(this,a,b,c,d,e)};r(Highcharts,{Axis:ra,Chart:eb,Color:ua,Point:Ka,Tick:Ra,Tooltip:sb,Renderer:Xa,Series:O,SVGElement:qa,SVGRenderer:va,arrayMin:Ma,arrayMax:Aa,charts:Ja,dateFormat:$a,format:Ga,
|
||||
pathAnim:ub,getOptions:function(){return G},hasBidiBug:Nb,isTouchDevice:Ib,numberFormat:Ea,seriesTypes:L,setOptions:function(a){G=x(!0,G,a);Bb();return G},addEvent:F,removeEvent:X,createElement:T,discardElement:Oa,css:D,each:n,extend:r,map:Sa,merge:x,pick:o,splat:ka,extendClass:ga,pInt:z,wrap:Ua,svg:V,canvas:ba,vml:!V&&!ba,product:"Highcharts",version:"3.0.8"})})();
|
||||
|
47
app/assets/javascripts/jquery.cookie.js
Normal file
@ -0,0 +1,47 @@
|
||||
/*!
|
||||
* jQuery Cookie Plugin
|
||||
* https://github.com/carhartl/jquery-cookie
|
||||
*
|
||||
* Copyright 2011, Klaus Hartl
|
||||
* Dual licensed under the MIT or GPL Version 2 licenses.
|
||||
* http://www.opensource.org/licenses/mit-license.php
|
||||
* http://www.opensource.org/licenses/GPL-2.0
|
||||
*/
|
||||
(function($) {
|
||||
$.cookie = function(key, value, options) {
|
||||
|
||||
// key and at least value given, set cookie...
|
||||
if (arguments.length > 1 && (!/Object/.test(Object.prototype.toString.call(value)) || value === null || value === undefined)) {
|
||||
options = $.extend({}, options);
|
||||
|
||||
if (value === null || value === undefined) {
|
||||
options.expires = -1;
|
||||
}
|
||||
|
||||
if (typeof options.expires === 'number') {
|
||||
var days = options.expires, t = options.expires = new Date();
|
||||
t.setDate(t.getDate() + days);
|
||||
}
|
||||
|
||||
value = String(value);
|
||||
|
||||
return (document.cookie = [
|
||||
encodeURIComponent(key), '=', options.raw ? value : encodeURIComponent(value),
|
||||
options.expires ? '; expires=' + options.expires.toUTCString() : '', // use expires attribute, max-age is not supported by IE
|
||||
options.path ? '; path=' + options.path : '',
|
||||
options.domain ? '; domain=' + options.domain : '',
|
||||
options.secure ? '; secure' : ''
|
||||
].join(''));
|
||||
}
|
||||
|
||||
// key and possibly options given, get cookie...
|
||||
options = value || {};
|
||||
var decode = options.raw ? function(s) { return s; } : decodeURIComponent;
|
||||
|
||||
var pairs = document.cookie.split('; ');
|
||||
for (var i = 0, pair; pair = pairs[i] && pairs[i].split('='); i++) {
|
||||
if (decode(pair[0]) === key) return decode(pair[1] || ''); // IE saves cookies with empty string as "c; ", e.g. without "=" as opposed to EOMB, thus pair[1] may be undefined
|
||||
}
|
||||
return null;
|
||||
};
|
||||
})(jQuery);
|
7
app/assets/javascripts/jquery.shorten.min.js
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
/*
|
||||
* Shorten, a jQuery plugin to automatically shorten text to fit in a block or a pre-set width and configure how the text ends.
|
||||
* Copyright (C) 2009-2011 Marc Diethelm
|
||||
* License: (GPL 3, http://www.gnu.org/licenses/gpl-3.0.txt) see license.txt
|
||||
*/
|
||||
|
||||
(function(a){function s(g,c){return c.measureText(g).width}function t(g,c){c.text(g);return c.width()}var q=false,o,j,k;a.fn.shorten=function(){var g={},c=arguments,r=c.callee;if(c.length)if(c[0].constructor==Object)g=c[0];else if(c[0]=="options")return a(this).eq(0).data("shorten-options");else g={width:parseInt(c[0]),tail:c[1]};this.css("visibility","hidden");var h=a.extend({},r.defaults,g);return this.each(function(){var e=a(this),d=e.text(),p=d.length,i,f=a("<span/>").html(h.tail).text(),l={shortened:false, textOverflow:false};i=e.css("float")!="none"?h.width||e.width():h.width||e.parent().width();if(i<0)return true;e.data("shorten-options",h);this.style.display="inline-block";this.style.whiteSpace="nowrap";if(o){var b=a(this),n=document.createElement("canvas");ctx=n.getContext("2d");b.html(n);ctx.font=b.css("font-style")+" "+b.css("font-variant")+" "+b.css("font-weight")+" "+Math.ceil(parseFloat(b.css("font-size")))+"px "+b.css("font-family");j=ctx;k=s}else{b=a('<table style="padding:0; margin:0; border:none; font:inherit;width:auto;zoom:1;position:absolute;"><tr style="padding:0; margin:0; border:none; font:inherit;"><td style="padding:0; margin:0; border:none; font:inherit;white-space:nowrap;"></td></tr></table>'); $td=a("td",b);a(this).html(b);j=$td;k=t}b=k.call(this,d,j);if(b<i){e.text(d);this.style.visibility="visible";e.data("shorten-info",l);return true}h.tooltip&&this.setAttribute("title",d);if(r._native&&!g.width){n=a("<span>"+h.tail+"</span>").text();if(n.length==1&&n.charCodeAt(0)==8230){e.text(d);this.style.overflow="hidden";this.style[r._native]="ellipsis";this.style.visibility="visible";l.shortened=true;l.textOverflow="ellipsis";e.data("shorten-info",l);return true}}f=k.call(this,f,j);i-=f;f=i*1.15; if(b-f>0){f=d.substring(0,Math.ceil(p*(f/b)));if(k.call(this,f,j)>i){d=f;p=d.length}}do{p--;d=d.substring(0,p)}while(k.call(this,d,j)>=i);e.html(a.trim(a("<span/>").text(d).html())+h.tail);this.style.visibility="visible";l.shortened=true;e.data("shorten-info",l);return true})};var m=document.documentElement.style;if("textOverflow"in m)q="textOverflow";else if("OTextOverflow"in m)q="OTextOverflow";if(typeof Modernizr!="undefined"&&Modernizr.canvastext)o=Modernizr.canvastext;else{m=document.createElement("canvas"); o=!!(m.getContext&&m.getContext("2d")&&typeof m.getContext("2d").fillText==="function")}a.fn.shorten._is_canvasTextSupported=o;a.fn.shorten._native=q;a.fn.shorten.defaults={tail:"…",tooltip:true}})(jQuery);
|
114
app/assets/javascripts/nested_form.js
Normal file
@ -0,0 +1,114 @@
|
||||
(function($) {
|
||||
window.NestedFormEvents = function() {
|
||||
this.addFields = $.proxy(this.addFields, this);
|
||||
this.removeFields = $.proxy(this.removeFields, this);
|
||||
};
|
||||
|
||||
NestedFormEvents.prototype = {
|
||||
addFields: function(e) {
|
||||
// Setup
|
||||
var link = e.currentTarget;
|
||||
var assoc = $(link).data('association'); // Name of child
|
||||
var blueprint = $('#' + $(link).data('blueprint-id'));
|
||||
var content = blueprint.data('blueprint'); // Fields template
|
||||
|
||||
// Make the context correct by replacing <parents> with the generated ID
|
||||
// of each of the parent objects
|
||||
var context = ($(link).closest('.fields').closestChild('input, textarea, select').eq(0).attr('name') || '').replace(new RegExp('\[[a-z_]+\]$'), '');
|
||||
|
||||
// context will be something like this for a brand new form:
|
||||
// project[tasks_attributes][1255929127459][assignments_attributes][1255929128105]
|
||||
// or for an edit form:
|
||||
// project[tasks_attributes][0][assignments_attributes][1]
|
||||
if (context) {
|
||||
var parentNames = context.match(/[a-z_]+_attributes(?=\]\[(new_)?\d+\])/g) || [];
|
||||
var parentIds = context.match(/[0-9]+/g) || [];
|
||||
|
||||
for(var i = 0; i < parentNames.length; i++) {
|
||||
if(parentIds[i]) {
|
||||
content = content.replace(
|
||||
new RegExp('(_' + parentNames[i] + ')_.+?_', 'g'),
|
||||
'$1_' + parentIds[i] + '_');
|
||||
|
||||
content = content.replace(
|
||||
new RegExp('(\\[' + parentNames[i] + '\\])\\[.+?\\]', 'g'),
|
||||
'$1[' + parentIds[i] + ']');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Make a unique ID for the new child
|
||||
var regexp = new RegExp('new_' + assoc, 'g');
|
||||
var new_id = this.newId();
|
||||
content = $.trim(content.replace(regexp, new_id));
|
||||
|
||||
var field = this.insertFields(content, assoc, link);
|
||||
// bubble up event upto document (through form)
|
||||
field
|
||||
.trigger({ type: 'nested:fieldAdded', field: field })
|
||||
.trigger({ type: 'nested:fieldAdded:' + assoc, field: field });
|
||||
return false;
|
||||
},
|
||||
newId: function() {
|
||||
return new Date().getTime();
|
||||
},
|
||||
insertFields: function(content, assoc, link) {
|
||||
var target = $(link).data('target');
|
||||
if (target) {
|
||||
return $(content).appendTo($(target));
|
||||
} else {
|
||||
return $(content).insertBefore(link);
|
||||
}
|
||||
},
|
||||
removeFields: function(e) {
|
||||
var $link = $(e.currentTarget),
|
||||
assoc = $link.data('association'); // Name of child to be removed
|
||||
|
||||
var hiddenField = $link.prev('input[type=hidden]');
|
||||
hiddenField.val('1');
|
||||
|
||||
var field = $link.closest('.fields');
|
||||
field.hide();
|
||||
|
||||
field
|
||||
.trigger({ type: 'nested:fieldRemoved', field: field })
|
||||
.trigger({ type: 'nested:fieldRemoved:' + assoc, field: field });
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
window.nestedFormEvents = new NestedFormEvents();
|
||||
$(document)
|
||||
.delegate('form a.add_nested_fields', 'click', nestedFormEvents.addFields)
|
||||
.delegate('form a.remove_nested_fields', 'click', nestedFormEvents.removeFields);
|
||||
})(jQuery);
|
||||
|
||||
// http://plugins.jquery.com/project/closestChild
|
||||
/*
|
||||
* Copyright 2011, Tobias Lindig
|
||||
*
|
||||
* Dual licensed under the MIT (http://www.opensource.org/licenses/mit-license.php)
|
||||
* and GPL (http://www.opensource.org/licenses/gpl-license.php) licenses.
|
||||
*
|
||||
*/
|
||||
(function($) {
|
||||
$.fn.closestChild = function(selector) {
|
||||
// breadth first search for the first matched node
|
||||
if (selector && selector != '') {
|
||||
var queue = [];
|
||||
queue.push(this);
|
||||
while(queue.length > 0) {
|
||||
var node = queue.shift();
|
||||
var children = node.children();
|
||||
for(var i = 0; i < children.length; ++i) {
|
||||
var child = $(children[i]);
|
||||
if (child.is(selector)) {
|
||||
return child; //well, we found one
|
||||
}
|
||||
queue.push(child);
|
||||
}
|
||||
}
|
||||
}
|
||||
return $();//nothing found
|
||||
};
|
||||
})(jQuery);
|
5
app/assets/javascripts/noapi.js
Normal file
@ -0,0 +1,5 @@
|
||||
// if on api subdomain, redirect
|
||||
var wloc = window.location.toString();
|
||||
if (wloc.indexOf('api') != -1 && wloc.indexOf('api') < 10) {
|
||||
window.location = wloc.replace('api', 'www');
|
||||
}
|
35
app/assets/javascripts/prettify.js
Normal file
@ -0,0 +1,35 @@
|
||||
var q=null;window.PR_SHOULD_USE_CONTINUATION=!0;
|
||||
(function(){function L(a){function m(a){var f=a.charCodeAt(0);if(f!==92)return f;var b=a.charAt(1);return(f=r[b])?f:"0"<=b&&b<="7"?parseInt(a.substring(1),8):b==="u"||b==="x"?parseInt(a.substring(2),16):a.charCodeAt(1)}function e(a){if(a<32)return(a<16?"\\x0":"\\x")+a.toString(16);a=String.fromCharCode(a);if(a==="\\"||a==="-"||a==="["||a==="]")a="\\"+a;return a}function h(a){for(var f=a.substring(1,a.length-1).match(/\\u[\dA-Fa-f]{4}|\\x[\dA-Fa-f]{2}|\\[0-3][0-7]{0,2}|\\[0-7]{1,2}|\\[\S\s]|[^\\]/g),a=
|
||||
[],b=[],o=f[0]==="^",c=o?1:0,i=f.length;c<i;++c){var j=f[c];if(/\\[bdsw]/i.test(j))a.push(j);else{var j=m(j),d;c+2<i&&"-"===f[c+1]?(d=m(f[c+2]),c+=2):d=j;b.push([j,d]);d<65||j>122||(d<65||j>90||b.push([Math.max(65,j)|32,Math.min(d,90)|32]),d<97||j>122||b.push([Math.max(97,j)&-33,Math.min(d,122)&-33]))}}b.sort(function(a,f){return a[0]-f[0]||f[1]-a[1]});f=[];j=[NaN,NaN];for(c=0;c<b.length;++c)i=b[c],i[0]<=j[1]+1?j[1]=Math.max(j[1],i[1]):f.push(j=i);b=["["];o&&b.push("^");b.push.apply(b,a);for(c=0;c<
|
||||
f.length;++c)i=f[c],b.push(e(i[0])),i[1]>i[0]&&(i[1]+1>i[0]&&b.push("-"),b.push(e(i[1])));b.push("]");return b.join("")}function y(a){for(var f=a.source.match(/\[(?:[^\\\]]|\\[\S\s])*]|\\u[\dA-Fa-f]{4}|\\x[\dA-Fa-f]{2}|\\\d+|\\[^\dux]|\(\?[!:=]|[()^]|[^()[\\^]+/g),b=f.length,d=[],c=0,i=0;c<b;++c){var j=f[c];j==="("?++i:"\\"===j.charAt(0)&&(j=+j.substring(1))&&j<=i&&(d[j]=-1)}for(c=1;c<d.length;++c)-1===d[c]&&(d[c]=++t);for(i=c=0;c<b;++c)j=f[c],j==="("?(++i,d[i]===void 0&&(f[c]="(?:")):"\\"===j.charAt(0)&&
|
||||
(j=+j.substring(1))&&j<=i&&(f[c]="\\"+d[i]);for(i=c=0;c<b;++c)"^"===f[c]&&"^"!==f[c+1]&&(f[c]="");if(a.ignoreCase&&s)for(c=0;c<b;++c)j=f[c],a=j.charAt(0),j.length>=2&&a==="["?f[c]=h(j):a!=="\\"&&(f[c]=j.replace(/[A-Za-z]/g,function(a){a=a.charCodeAt(0);return"["+String.fromCharCode(a&-33,a|32)+"]"}));return f.join("")}for(var t=0,s=!1,l=!1,p=0,d=a.length;p<d;++p){var g=a[p];if(g.ignoreCase)l=!0;else if(/[a-z]/i.test(g.source.replace(/\\u[\da-f]{4}|\\x[\da-f]{2}|\\[^UXux]/gi,""))){s=!0;l=!1;break}}for(var r=
|
||||
{b:8,t:9,n:10,v:11,f:12,r:13},n=[],p=0,d=a.length;p<d;++p){g=a[p];if(g.global||g.multiline)throw Error(""+g);n.push("(?:"+y(g)+")")}return RegExp(n.join("|"),l?"gi":"g")}function M(a){function m(a){switch(a.nodeType){case 1:if(e.test(a.className))break;for(var g=a.firstChild;g;g=g.nextSibling)m(g);g=a.nodeName;if("BR"===g||"LI"===g)h[s]="\n",t[s<<1]=y++,t[s++<<1|1]=a;break;case 3:case 4:g=a.nodeValue,g.length&&(g=p?g.replace(/\r\n?/g,"\n"):g.replace(/[\t\n\r ]+/g," "),h[s]=g,t[s<<1]=y,y+=g.length,
|
||||
t[s++<<1|1]=a)}}var e=/(?:^|\s)nocode(?:\s|$)/,h=[],y=0,t=[],s=0,l;a.currentStyle?l=a.currentStyle.whiteSpace:window.getComputedStyle&&(l=document.defaultView.getComputedStyle(a,q).getPropertyValue("white-space"));var p=l&&"pre"===l.substring(0,3);m(a);return{a:h.join("").replace(/\n$/,""),c:t}}function B(a,m,e,h){m&&(a={a:m,d:a},e(a),h.push.apply(h,a.e))}function x(a,m){function e(a){for(var l=a.d,p=[l,"pln"],d=0,g=a.a.match(y)||[],r={},n=0,z=g.length;n<z;++n){var f=g[n],b=r[f],o=void 0,c;if(typeof b===
|
||||
"string")c=!1;else{var i=h[f.charAt(0)];if(i)o=f.match(i[1]),b=i[0];else{for(c=0;c<t;++c)if(i=m[c],o=f.match(i[1])){b=i[0];break}o||(b="pln")}if((c=b.length>=5&&"lang-"===b.substring(0,5))&&!(o&&typeof o[1]==="string"))c=!1,b="src";c||(r[f]=b)}i=d;d+=f.length;if(c){c=o[1];var j=f.indexOf(c),k=j+c.length;o[2]&&(k=f.length-o[2].length,j=k-c.length);b=b.substring(5);B(l+i,f.substring(0,j),e,p);B(l+i+j,c,C(b,c),p);B(l+i+k,f.substring(k),e,p)}else p.push(l+i,b)}a.e=p}var h={},y;(function(){for(var e=a.concat(m),
|
||||
l=[],p={},d=0,g=e.length;d<g;++d){var r=e[d],n=r[3];if(n)for(var k=n.length;--k>=0;)h[n.charAt(k)]=r;r=r[1];n=""+r;p.hasOwnProperty(n)||(l.push(r),p[n]=q)}l.push(/[\S\s]/);y=L(l)})();var t=m.length;return e}function u(a){var m=[],e=[];a.tripleQuotedStrings?m.push(["str",/^(?:'''(?:[^'\\]|\\[\S\s]|''?(?=[^']))*(?:'''|$)|"""(?:[^"\\]|\\[\S\s]|""?(?=[^"]))*(?:"""|$)|'(?:[^'\\]|\\[\S\s])*(?:'|$)|"(?:[^"\\]|\\[\S\s])*(?:"|$))/,q,"'\""]):a.multiLineStrings?m.push(["str",/^(?:'(?:[^'\\]|\\[\S\s])*(?:'|$)|"(?:[^"\\]|\\[\S\s])*(?:"|$)|`(?:[^\\`]|\\[\S\s])*(?:`|$))/,
|
||||
q,"'\"`"]):m.push(["str",/^(?:'(?:[^\n\r'\\]|\\.)*(?:'|$)|"(?:[^\n\r"\\]|\\.)*(?:"|$))/,q,"\"'"]);a.verbatimStrings&&e.push(["str",/^@"(?:[^"]|"")*(?:"|$)/,q]);var h=a.hashComments;h&&(a.cStyleComments?(h>1?m.push(["com",/^#(?:##(?:[^#]|#(?!##))*(?:###|$)|.*)/,q,"#"]):m.push(["com",/^#(?:(?:define|elif|else|endif|error|ifdef|include|ifndef|line|pragma|undef|warning)\b|[^\n\r]*)/,q,"#"]),e.push(["str",/^<(?:(?:(?:\.\.\/)*|\/?)(?:[\w-]+(?:\/[\w-]+)+)?[\w-]+\.h|[a-z]\w*)>/,q])):m.push(["com",/^#[^\n\r]*/,
|
||||
q,"#"]));a.cStyleComments&&(e.push(["com",/^\/\/[^\n\r]*/,q]),e.push(["com",/^\/\*[\S\s]*?(?:\*\/|$)/,q]));a.regexLiterals&&e.push(["lang-regex",/^(?:^^\.?|[!+-]|!=|!==|#|%|%=|&|&&|&&=|&=|\(|\*|\*=|\+=|,|-=|->|\/|\/=|:|::|;|<|<<|<<=|<=|=|==|===|>|>=|>>|>>=|>>>|>>>=|[?@[^]|\^=|\^\^|\^\^=|{|\||\|=|\|\||\|\|=|~|break|case|continue|delete|do|else|finally|instanceof|return|throw|try|typeof)\s*(\/(?=[^*/])(?:[^/[\\]|\\[\S\s]|\[(?:[^\\\]]|\\[\S\s])*(?:]|$))+\/)/]);(h=a.types)&&e.push(["typ",h]);a=(""+a.keywords).replace(/^ | $/g,
|
||||
"");a.length&&e.push(["kwd",RegExp("^(?:"+a.replace(/[\s,]+/g,"|")+")\\b"),q]);m.push(["pln",/^\s+/,q," \r\n\t\xa0"]);e.push(["lit",/^@[$_a-z][\w$@]*/i,q],["typ",/^(?:[@_]?[A-Z]+[a-z][\w$@]*|\w+_t\b)/,q],["pln",/^[$_a-z][\w$@]*/i,q],["lit",/^(?:0x[\da-f]+|(?:\d(?:_\d+)*\d*(?:\.\d*)?|\.\d\+)(?:e[+-]?\d+)?)[a-z]*/i,q,"0123456789"],["pln",/^\\[\S\s]?/,q],["pun",/^.[^\s\w"-$'./@\\`]*/,q]);return x(m,e)}function D(a,m){function e(a){switch(a.nodeType){case 1:if(k.test(a.className))break;if("BR"===a.nodeName)h(a),
|
||||
a.parentNode&&a.parentNode.removeChild(a);else for(a=a.firstChild;a;a=a.nextSibling)e(a);break;case 3:case 4:if(p){var b=a.nodeValue,d=b.match(t);if(d){var c=b.substring(0,d.index);a.nodeValue=c;(b=b.substring(d.index+d[0].length))&&a.parentNode.insertBefore(s.createTextNode(b),a.nextSibling);h(a);c||a.parentNode.removeChild(a)}}}}function h(a){function b(a,d){var e=d?a.cloneNode(!1):a,f=a.parentNode;if(f){var f=b(f,1),g=a.nextSibling;f.appendChild(e);for(var h=g;h;h=g)g=h.nextSibling,f.appendChild(h)}return e}
|
||||
for(;!a.nextSibling;)if(a=a.parentNode,!a)return;for(var a=b(a.nextSibling,0),e;(e=a.parentNode)&&e.nodeType===1;)a=e;d.push(a)}var k=/(?:^|\s)nocode(?:\s|$)/,t=/\r\n?|\n/,s=a.ownerDocument,l;a.currentStyle?l=a.currentStyle.whiteSpace:window.getComputedStyle&&(l=s.defaultView.getComputedStyle(a,q).getPropertyValue("white-space"));var p=l&&"pre"===l.substring(0,3);for(l=s.createElement("LI");a.firstChild;)l.appendChild(a.firstChild);for(var d=[l],g=0;g<d.length;++g)e(d[g]);m===(m|0)&&d[0].setAttribute("value",
|
||||
m);var r=s.createElement("OL");r.className="linenums";for(var n=Math.max(0,m-1|0)||0,g=0,z=d.length;g<z;++g)l=d[g],l.className="L"+(g+n)%10,l.firstChild||l.appendChild(s.createTextNode("\xa0")),r.appendChild(l);a.appendChild(r)}function k(a,m){for(var e=m.length;--e>=0;){var h=m[e];A.hasOwnProperty(h)?window.console&&console.warn("cannot override language handler %s",h):A[h]=a}}function C(a,m){if(!a||!A.hasOwnProperty(a))a=/^\s*</.test(m)?"default-markup":"default-code";return A[a]}function E(a){var m=
|
||||
a.g;try{var e=M(a.h),h=e.a;a.a=h;a.c=e.c;a.d=0;C(m,h)(a);var k=/\bMSIE\b/.test(navigator.userAgent),m=/\n/g,t=a.a,s=t.length,e=0,l=a.c,p=l.length,h=0,d=a.e,g=d.length,a=0;d[g]=s;var r,n;for(n=r=0;n<g;)d[n]!==d[n+2]?(d[r++]=d[n++],d[r++]=d[n++]):n+=2;g=r;for(n=r=0;n<g;){for(var z=d[n],f=d[n+1],b=n+2;b+2<=g&&d[b+1]===f;)b+=2;d[r++]=z;d[r++]=f;n=b}for(d.length=r;h<p;){var o=l[h+2]||s,c=d[a+2]||s,b=Math.min(o,c),i=l[h+1],j;if(i.nodeType!==1&&(j=t.substring(e,b))){k&&(j=j.replace(m,"\r"));i.nodeValue=
|
||||
j;var u=i.ownerDocument,v=u.createElement("SPAN");v.className=d[a+1];var x=i.parentNode;x.replaceChild(v,i);v.appendChild(i);e<o&&(l[h+1]=i=u.createTextNode(t.substring(b,o)),x.insertBefore(i,v.nextSibling))}e=b;e>=o&&(h+=2);e>=c&&(a+=2)}}catch(w){"console"in window&&console.log(w&&w.stack?w.stack:w)}}var v=["break,continue,do,else,for,if,return,while"],w=[[v,"auto,case,char,const,default,double,enum,extern,float,goto,int,long,register,short,signed,sizeof,static,struct,switch,typedef,union,unsigned,void,volatile"],
|
||||
"catch,class,delete,false,import,new,operator,private,protected,public,this,throw,true,try,typeof"],F=[w,"alignof,align_union,asm,axiom,bool,concept,concept_map,const_cast,constexpr,decltype,dynamic_cast,explicit,export,friend,inline,late_check,mutable,namespace,nullptr,reinterpret_cast,static_assert,static_cast,template,typeid,typename,using,virtual,where"],G=[w,"abstract,boolean,byte,extends,final,finally,implements,import,instanceof,null,native,package,strictfp,super,synchronized,throws,transient"],
|
||||
H=[G,"as,base,by,checked,decimal,delegate,descending,dynamic,event,fixed,foreach,from,group,implicit,in,interface,internal,into,is,lock,object,out,override,orderby,params,partial,readonly,ref,sbyte,sealed,stackalloc,string,select,uint,ulong,unchecked,unsafe,ushort,var"],w=[w,"debugger,eval,export,function,get,null,set,undefined,var,with,Infinity,NaN"],I=[v,"and,as,assert,class,def,del,elif,except,exec,finally,from,global,import,in,is,lambda,nonlocal,not,or,pass,print,raise,try,with,yield,False,True,None"],
|
||||
J=[v,"alias,and,begin,case,class,def,defined,elsif,end,ensure,false,in,module,next,nil,not,or,redo,rescue,retry,self,super,then,true,undef,unless,until,when,yield,BEGIN,END"],v=[v,"case,done,elif,esac,eval,fi,function,in,local,set,then,until"],K=/^(DIR|FILE|vector|(de|priority_)?queue|list|stack|(const_)?iterator|(multi)?(set|map)|bitset|u?(int|float)\d*)/,N=/\S/,O=u({keywords:[F,H,w,"caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END"+
|
||||
I,J,v],hashComments:!0,cStyleComments:!0,multiLineStrings:!0,regexLiterals:!0}),A={};k(O,["default-code"]);k(x([],[["pln",/^[^<?]+/],["dec",/^<!\w[^>]*(?:>|$)/],["com",/^<\!--[\S\s]*?(?:--\>|$)/],["lang-",/^<\?([\S\s]+?)(?:\?>|$)/],["lang-",/^<%([\S\s]+?)(?:%>|$)/],["pun",/^(?:<[%?]|[%?]>)/],["lang-",/^<xmp\b[^>]*>([\S\s]+?)<\/xmp\b[^>]*>/i],["lang-js",/^<script\b[^>]*>([\S\s]*?)(<\/script\b[^>]*>)/i],["lang-css",/^<style\b[^>]*>([\S\s]*?)(<\/style\b[^>]*>)/i],["lang-in.tag",/^(<\/?[a-z][^<>]*>)/i]]),
|
||||
["default-markup","htm","html","mxml","xhtml","xml","xsl"]);k(x([["pln",/^\s+/,q," \t\r\n"],["atv",/^(?:"[^"]*"?|'[^']*'?)/,q,"\"'"]],[["tag",/^^<\/?[a-z](?:[\w-.:]*\w)?|\/?>$/i],["atn",/^(?!style[\s=]|on)[a-z](?:[\w:-]*\w)?/i],["lang-uq.val",/^=\s*([^\s"'>]*(?:[^\s"'/>]|\/(?=\s)))/],["pun",/^[/<->]+/],["lang-js",/^on\w+\s*=\s*"([^"]+)"/i],["lang-js",/^on\w+\s*=\s*'([^']+)'/i],["lang-js",/^on\w+\s*=\s*([^\s"'>]+)/i],["lang-css",/^style\s*=\s*"([^"]+)"/i],["lang-css",/^style\s*=\s*'([^']+)'/i],["lang-css",
|
||||
/^style\s*=\s*([^\s"'>]+)/i]]),["in.tag"]);k(x([],[["atv",/^[\S\s]+/]]),["uq.val"]);k(u({keywords:F,hashComments:!0,cStyleComments:!0,types:K}),["c","cc","cpp","cxx","cyc","m"]);k(u({keywords:"null,true,false"}),["json"]);k(u({keywords:H,hashComments:!0,cStyleComments:!0,verbatimStrings:!0,types:K}),["cs"]);k(u({keywords:G,cStyleComments:!0}),["java"]);k(u({keywords:v,hashComments:!0,multiLineStrings:!0}),["bsh","csh","sh"]);k(u({keywords:I,hashComments:!0,multiLineStrings:!0,tripleQuotedStrings:!0}),
|
||||
["cv","py"]);k(u({keywords:"caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END",hashComments:!0,multiLineStrings:!0,regexLiterals:!0}),["perl","pl","pm"]);k(u({keywords:J,hashComments:!0,multiLineStrings:!0,regexLiterals:!0}),["rb"]);k(u({keywords:w,cStyleComments:!0,regexLiterals:!0}),["js"]);k(u({keywords:"all,and,by,catch,class,else,extends,false,finally,for,if,in,is,isnt,loop,new,no,not,null,of,off,on,or,return,super,then,true,try,unless,until,when,while,yes",
|
||||
hashComments:3,cStyleComments:!0,multilineStrings:!0,tripleQuotedStrings:!0,regexLiterals:!0}),["coffee"]);k(x([],[["str",/^[\S\s]+/]]),["regex"]);window.prettyPrintOne=function(a,m,e){var h=document.createElement("PRE");h.innerHTML=a;e&&D(h,e);E({g:m,i:e,h:h});return h.innerHTML};window.prettyPrint=function(a){function m(){for(var e=window.PR_SHOULD_USE_CONTINUATION?l.now()+250:Infinity;p<h.length&&l.now()<e;p++){var n=h[p],k=n.className;if(k.indexOf("prettyprint")>=0){var k=k.match(g),f,b;if(b=
|
||||
!k){b=n;for(var o=void 0,c=b.firstChild;c;c=c.nextSibling)var i=c.nodeType,o=i===1?o?b:c:i===3?N.test(c.nodeValue)?b:o:o;b=(f=o===b?void 0:o)&&"CODE"===f.tagName}b&&(k=f.className.match(g));k&&(k=k[1]);b=!1;for(o=n.parentNode;o;o=o.parentNode)if((o.tagName==="pre"||o.tagName==="code"||o.tagName==="xmp")&&o.className&&o.className.indexOf("prettyprint")>=0){b=!0;break}b||((b=(b=n.className.match(/\blinenums\b(?::(\d+))?/))?b[1]&&b[1].length?+b[1]:!0:!1)&&D(n,b),d={g:k,h:n,i:b},E(d))}}p<h.length?setTimeout(m,
|
||||
250):a&&a()}for(var e=[document.getElementsByTagName("pre"),document.getElementsByTagName("code"),document.getElementsByTagName("xmp")],h=[],k=0;k<e.length;++k)for(var t=0,s=e[k].length;t<s;++t)h.push(e[k][t]);var e=q,l=Date;l.now||(l={now:function(){return+new Date}});var p=0,d,g=/\blang(?:uage)?-([\w.]+)(?!\S)/;m()};window.PR={createSimpleLexer:x,registerLangHandler:k,sourceDecorator:u,PR_ATTRIB_NAME:"atn",PR_ATTRIB_VALUE:"atv",PR_COMMENT:"com",PR_DECLARATION:"dec",PR_KEYWORD:"kwd",PR_LITERAL:"lit",
|
||||
PR_NOCODE:"nocode",PR_PLAIN:"pln",PR_PUNCTUATION:"pun",PR_SOURCE:"src",PR_STRING:"str",PR_TAG:"tag",PR_TYPE:"typ"}})();
|
||||
|
||||
!function ($) {
|
||||
$(function(){
|
||||
window.prettyPrint && prettyPrint()
|
||||
})
|
||||
}(window.jQuery)
|
||||
|
137
app/assets/javascripts/rest.js
Normal file
@ -0,0 +1,137 @@
|
||||
/*
|
||||
* Copyright (c) 2010 Lyconic, LLC.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
(function($){
|
||||
|
||||
// Change the values of this global object if your method parameter is different.
|
||||
$.restSetup = { methodParam: '_method' };
|
||||
|
||||
// collects the csrf-param and csrf-token from meta tags
|
||||
$(document).ready(function(){
|
||||
$.extend($.restSetup, {
|
||||
csrfParam: $('meta[name=csrf-param]').attr('content'),
|
||||
csrfToken: $('meta[name=csrf-token]').attr('content')
|
||||
});
|
||||
});
|
||||
|
||||
// jQuery doesn't provide a better way of intercepting the ajax settings object
|
||||
var _ajax = $.ajax, options;
|
||||
|
||||
function collect_options (url, data, success, error) {
|
||||
options = { dataType: 'json' };
|
||||
if (arguments.length === 1 && typeof arguments[0] !== "string") {
|
||||
options = $.extend(options, url);
|
||||
if ("url" in options)
|
||||
if ("data" in options) {
|
||||
fill_url(options.url, options.data);
|
||||
}
|
||||
} else {
|
||||
// shift arguments if data argument was omitted
|
||||
if ($.isFunction(data)) {
|
||||
error = success;
|
||||
success = data;
|
||||
data = null;
|
||||
}
|
||||
|
||||
url = fill_url(url, data);
|
||||
|
||||
options = $.extend(options, {
|
||||
url: url,
|
||||
data: data,
|
||||
success: success,
|
||||
error: error
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function fill_url (url, data) {
|
||||
var key, u, val;
|
||||
for (key in data) {
|
||||
val = data[key];
|
||||
u = url.replace('{'+key+'}', val);
|
||||
if (u != url) {
|
||||
url = u;
|
||||
delete data[key];
|
||||
}
|
||||
}
|
||||
return url;
|
||||
}
|
||||
|
||||
// public functions
|
||||
|
||||
function ajax (settings) {
|
||||
settings.type = settings.type || "GET";
|
||||
|
||||
if (typeof settings.data !== "string")
|
||||
if (settings.data != null) {
|
||||
settings.data = $.param(settings.data);
|
||||
}
|
||||
|
||||
settings.data = settings.data || "";
|
||||
|
||||
if ($.restSetup.csrf && !$.isEmptyObject($.restSetup.csrf))
|
||||
if (!/^(get)$/i.test(settings.type))
|
||||
if (!/(authenticity_token=)/i.test(settings.data)) {
|
||||
settings.data += (settings.data ? "&" : "") + $.restSetup.csrfParam + '=' + $restSetup.csrfToken;
|
||||
}
|
||||
|
||||
if (!/^(get|post)$/i.test(settings.type)) {
|
||||
settings.data += (settings.data ? "&" : "") + $.restSetup.methodParam + '=' + settings.type.toLowerCase();
|
||||
settings.type = "POST";
|
||||
}
|
||||
|
||||
return _ajax.call(this, settings);
|
||||
}
|
||||
|
||||
function read () {
|
||||
collect_options.apply(this, arguments);
|
||||
$.extend(options, { type: 'GET' })
|
||||
return $.ajax(options);
|
||||
}
|
||||
|
||||
function create () {
|
||||
collect_options.apply(this, arguments);
|
||||
$.extend(options, { type: 'POST' });
|
||||
return $.ajax(options);
|
||||
}
|
||||
|
||||
function update () {
|
||||
collect_options.apply(this, arguments);
|
||||
$.extend(options, { type: 'PUT' });
|
||||
return $.ajax(options);
|
||||
}
|
||||
|
||||
function destroy () {
|
||||
collect_options.apply(this, arguments);
|
||||
$.extend(options, { type: 'DELETE' });
|
||||
return $.ajax(options);
|
||||
}
|
||||
|
||||
$.extend({
|
||||
ajax: ajax,
|
||||
read: read,
|
||||
create: create,
|
||||
update: update,
|
||||
destroy: destroy
|
||||
});
|
||||
|
||||
})(jQuery);
|
32
app/assets/javascripts/sidebar.js
Normal file
@ -0,0 +1,32 @@
|
||||
$(document).ready(function() {
|
||||
// if affix function exists
|
||||
if ($.fn.affix) {
|
||||
|
||||
// add sidebar affix
|
||||
$('#bootstrap-sidebar').affix();
|
||||
|
||||
// add sidebar scrollspy
|
||||
$(document.body).scrollspy({ target: '#leftcol', offset: 300 });
|
||||
|
||||
// add smooth scrolling
|
||||
$("#bootstrap-sidebar li a[href^='#']").on('click', function(e) {
|
||||
// prevent default anchor click behavior
|
||||
e.preventDefault();
|
||||
|
||||
// store hash
|
||||
var hash = this.hash;
|
||||
|
||||
// animate
|
||||
$('html, body').animate({
|
||||
scrollTop: $(this.hash).offset().top - 90
|
||||
}, 300, function(){
|
||||
// when done, add hash to url
|
||||
// (default click behaviour)
|
||||
window.location.hash = hash;
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
});
|
||||
|
267
app/assets/javascripts/tabby.js
Normal file
@ -0,0 +1,267 @@
|
||||
/*
|
||||
* Tabby jQuery plugin version 0.12
|
||||
*
|
||||
* Ted Devito - http://teddevito.com/demos/textarea.html
|
||||
*
|
||||
* Copyright (c) 2009 Ted Devito
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following
|
||||
* conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer
|
||||
* in the documentation and/or other materials provided with the distribution.
|
||||
* 3. The name of the author may not be used to endorse or promote products derived from this software without specific prior written
|
||||
* permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
|
||||
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
*/
|
||||
|
||||
// create closure
|
||||
|
||||
(function($) {
|
||||
|
||||
// plugin definition
|
||||
|
||||
$.fn.tabby = function(options) {
|
||||
//debug(this);
|
||||
// build main options before element iteration
|
||||
var opts = $.extend({}, $.fn.tabby.defaults, options);
|
||||
var pressed = $.fn.tabby.pressed;
|
||||
|
||||
// iterate and reformat each matched element
|
||||
return this.each(function() {
|
||||
$this = $(this);
|
||||
|
||||
// build element specific options
|
||||
var options = $.meta ? $.extend({}, opts, $this.data()) : opts;
|
||||
|
||||
$this.bind('keydown',function (e) {
|
||||
var kc = $.fn.tabby.catch_kc(e);
|
||||
if (16 == kc) pressed.shft = true;
|
||||
/*
|
||||
because both CTRL+TAB and ALT+TAB default to an event (changing tab/window) that
|
||||
will prevent js from capturing the keyup event, we'll set a timer on releasing them.
|
||||
*/
|
||||
if (17 == kc) {pressed.ctrl = true; setTimeout("$.fn.tabby.pressed.ctrl = false;",1000);}
|
||||
if (18 == kc) {pressed.alt = true; setTimeout("$.fn.tabby.pressed.alt = false;",1000);}
|
||||
|
||||
if (9 == kc && !pressed.ctrl && !pressed.alt) {
|
||||
e.preventDefault; // does not work in O9.63 ??
|
||||
pressed.last = kc; setTimeout("$.fn.tabby.pressed.last = null;",0);
|
||||
process_keypress ($(e.target).get(0), pressed.shft, options);
|
||||
return false;
|
||||
}
|
||||
|
||||
}).bind('keyup',function (e) {
|
||||
if (16 == $.fn.tabby.catch_kc(e)) pressed.shft = false;
|
||||
}).bind('blur',function (e) { // workaround for Opera -- http://www.webdeveloper.com/forum/showthread.php?p=806588
|
||||
if (9 == pressed.last) $(e.target).one('focus',function (e) {pressed.last = null;}).get(0).focus();
|
||||
});
|
||||
|
||||
});
|
||||
};
|
||||
|
||||
// define and expose any extra methods
|
||||
$.fn.tabby.catch_kc = function(e) { return e.keyCode ? e.keyCode : e.charCode ? e.charCode : e.which; };
|
||||
$.fn.tabby.pressed = {shft : false, ctrl : false, alt : false, last: null};
|
||||
|
||||
// private function for debugging
|
||||
function debug($obj) {
|
||||
if (window.console && window.console.log)
|
||||
window.console.log('textarea count: ' + $obj.size());
|
||||
};
|
||||
|
||||
function process_keypress (o,shft,options) {
|
||||
var scrollTo = o.scrollTop;
|
||||
//var tabString = String.fromCharCode(9);
|
||||
|
||||
// gecko; o.setSelectionRange is only available when the text box has focus
|
||||
if (o.setSelectionRange) gecko_tab (o, shft, options);
|
||||
|
||||
// ie; document.selection is always available
|
||||
else if (document.selection) ie_tab (o, shft, options);
|
||||
|
||||
o.scrollTop = scrollTo;
|
||||
}
|
||||
|
||||
// plugin defaults
|
||||
$.fn.tabby.defaults = {tabString : String.fromCharCode(9)};
|
||||
|
||||
function gecko_tab (o, shft, options) {
|
||||
var ss = o.selectionStart;
|
||||
var es = o.selectionEnd;
|
||||
|
||||
// when there's no selection and we're just working with the caret, we'll add/remove the tabs at the caret, providing more control
|
||||
if(ss == es) {
|
||||
// SHIFT+TAB
|
||||
if (shft) {
|
||||
// check to the left of the caret first
|
||||
if ("\t" == o.value.substring(ss-options.tabString.length, ss)) {
|
||||
o.value = o.value.substring(0, ss-options.tabString.length) + o.value.substring(ss); // put it back together omitting one character to the left
|
||||
o.focus();
|
||||
o.setSelectionRange(ss - options.tabString.length, ss - options.tabString.length);
|
||||
}
|
||||
// then check to the right of the caret
|
||||
else if ("\t" == o.value.substring(ss, ss + options.tabString.length)) {
|
||||
o.value = o.value.substring(0, ss) + o.value.substring(ss + options.tabString.length); // put it back together omitting one character to the right
|
||||
o.focus();
|
||||
o.setSelectionRange(ss,ss);
|
||||
}
|
||||
}
|
||||
// TAB
|
||||
else {
|
||||
o.value = o.value.substring(0, ss) + options.tabString + o.value.substring(ss);
|
||||
o.focus();
|
||||
o.setSelectionRange(ss + options.tabString.length, ss + options.tabString.length);
|
||||
}
|
||||
}
|
||||
// selections will always add/remove tabs from the start of the line
|
||||
else {
|
||||
// split the textarea up into lines and figure out which lines are included in the selection
|
||||
var lines = o.value.split("\n");
|
||||
var indices = new Array();
|
||||
var sl = 0; // start of the line
|
||||
var el = 0; // end of the line
|
||||
var sel = false;
|
||||
for (var i in lines) {
|
||||
el = sl + lines[i].length;
|
||||
indices.push({start: sl, end: el, selected: (sl <= ss && el > ss) || (el >= es && sl < es) || (sl > ss && el < es)});
|
||||
sl = el + 1;// for "\n"
|
||||
}
|
||||
|
||||
// walk through the array of lines (indices) and add tabs where appropriate
|
||||
var modifier = 0;
|
||||
for (var i in indices) {
|
||||
if (indices[i].selected) {
|
||||
var pos = indices[i].start + modifier; // adjust for tabs already inserted/removed
|
||||
// SHIFT+TAB
|
||||
if (shft && options.tabString == o.value.substring(pos,pos+options.tabString.length)) { // only SHIFT+TAB if there's a tab at the start of the line
|
||||
o.value = o.value.substring(0,pos) + o.value.substring(pos + options.tabString.length); // omit the tabstring to the right
|
||||
modifier -= options.tabString.length;
|
||||
}
|
||||
// TAB
|
||||
else if (!shft) {
|
||||
o.value = o.value.substring(0,pos) + options.tabString + o.value.substring(pos); // insert the tabstring
|
||||
modifier += options.tabString.length;
|
||||
}
|
||||
}
|
||||
}
|
||||
o.focus();
|
||||
var ns = ss + ((modifier > 0) ? options.tabString.length : (modifier < 0) ? -options.tabString.length : 0);
|
||||
var ne = es + modifier;
|
||||
o.setSelectionRange(ns,ne);
|
||||
}
|
||||
}
|
||||
|
||||
function ie_tab (o, shft, options) {
|
||||
var range = document.selection.createRange();
|
||||
|
||||
if (o == range.parentElement()) {
|
||||
// when there's no selection and we're just working with the caret, we'll add/remove the tabs at the caret, providing more control
|
||||
if ('' == range.text) {
|
||||
// SHIFT+TAB
|
||||
if (shft) {
|
||||
var bookmark = range.getBookmark();
|
||||
//first try to the left by moving opening up our empty range to the left
|
||||
range.moveStart('character', -options.tabString.length);
|
||||
if (options.tabString == range.text) {
|
||||
range.text = '';
|
||||
} else {
|
||||
// if that didn't work then reset the range and try opening it to the right
|
||||
range.moveToBookmark(bookmark);
|
||||
range.moveEnd('character', options.tabString.length);
|
||||
if (options.tabString == range.text)
|
||||
range.text = '';
|
||||
}
|
||||
// move the pointer to the start of them empty range and select it
|
||||
range.collapse(true);
|
||||
range.select();
|
||||
}
|
||||
|
||||
else {
|
||||
// very simple here. just insert the tab into the range and put the pointer at the end
|
||||
range.text = options.tabString;
|
||||
range.collapse(false);
|
||||
range.select();
|
||||
}
|
||||
}
|
||||
// selections will always add/remove tabs from the start of the line
|
||||
else {
|
||||
|
||||
var selection_text = range.text;
|
||||
var selection_len = selection_text.length;
|
||||
var selection_arr = selection_text.split("\r\n");
|
||||
|
||||
var before_range = document.body.createTextRange();
|
||||
before_range.moveToElementText(o);
|
||||
before_range.setEndPoint("EndToStart", range);
|
||||
var before_text = before_range.text;
|
||||
var before_arr = before_text.split("\r\n");
|
||||
var before_len = before_text.length; // - before_arr.length + 1;
|
||||
|
||||
var after_range = document.body.createTextRange();
|
||||
after_range.moveToElementText(o);
|
||||
after_range.setEndPoint("StartToEnd", range);
|
||||
var after_text = after_range.text; // we can accurately calculate distance to the end because we're not worried about MSIE trimming a \r\n
|
||||
|
||||
var end_range = document.body.createTextRange();
|
||||
end_range.moveToElementText(o);
|
||||
end_range.setEndPoint("StartToEnd", before_range);
|
||||
var end_text = end_range.text; // we can accurately calculate distance to the end because we're not worried about MSIE trimming a \r\n
|
||||
|
||||
var check_html = $(o).html();
|
||||
$("#r3").text(before_len + " + " + selection_len + " + " + after_text.length + " = " + check_html.length);
|
||||
if((before_len + end_text.length) < check_html.length) {
|
||||
before_arr.push("");
|
||||
before_len += 2; // for the \r\n that was trimmed
|
||||
if (shft && options.tabString == selection_arr[0].substring(0,options.tabString.length))
|
||||
selection_arr[0] = selection_arr[0].substring(options.tabString.length);
|
||||
else if (!shft) selection_arr[0] = options.tabString + selection_arr[0];
|
||||
} else {
|
||||
if (shft && options.tabString == before_arr[before_arr.length-1].substring(0,options.tabString.length))
|
||||
before_arr[before_arr.length-1] = before_arr[before_arr.length-1].substring(options.tabString.length);
|
||||
else if (!shft) before_arr[before_arr.length-1] = options.tabString + before_arr[before_arr.length-1];
|
||||
}
|
||||
|
||||
for (var i = 1; i < selection_arr.length; i++) {
|
||||
if (shft && options.tabString == selection_arr[i].substring(0,options.tabString.length))
|
||||
selection_arr[i] = selection_arr[i].substring(options.tabString.length);
|
||||
else if (!shft) selection_arr[i] = options.tabString + selection_arr[i];
|
||||
}
|
||||
|
||||
if (1 == before_arr.length && 0 == before_len) {
|
||||
if (shft && options.tabString == selection_arr[0].substring(0,options.tabString.length))
|
||||
selection_arr[0] = selection_arr[0].substring(options.tabString.length);
|
||||
else if (!shft) selection_arr[0] = options.tabString + selection_arr[0];
|
||||
}
|
||||
|
||||
if ((before_len + selection_len + after_text.length) < check_html.length) {
|
||||
selection_arr.push("");
|
||||
selection_len += 2; // for the \r\n that was trimmed
|
||||
}
|
||||
|
||||
before_range.text = before_arr.join("\r\n");
|
||||
range.text = selection_arr.join("\r\n");
|
||||
|
||||
var new_range = document.body.createTextRange();
|
||||
new_range.moveToElementText(o);
|
||||
|
||||
if (0 < before_len) new_range.setEndPoint("StartToEnd", before_range);
|
||||
else new_range.setEndPoint("StartToStart", before_range);
|
||||
new_range.setEndPoint("EndToEnd", range);
|
||||
|
||||
new_range.select();
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// end of closure
|
||||
})(jQuery);
|
11
app/assets/javascripts/timeago.min.js
vendored
Normal file
@ -0,0 +1,11 @@
|
||||
(function($){$.timeago=function(timestamp){if(timestamp instanceof Date){return inWords(timestamp);}else if(typeof timestamp==="string"){return inWords($.timeago.parse(timestamp));}else{return inWords($.timeago.datetime(timestamp));}};var $t=$.timeago;$.extend($.timeago,{settings:{refreshMillis:60000,allowFuture:false,strings:{prefixAgo:null,prefixFromNow:null,suffixAgo:"ago",suffixFromNow:"from now",seconds:"less than a minute",minute:"about a minute",minutes:"%d minutes",hour:"about an hour",hours:"about %d hours",day:"a day",days:"%d days",month:"about a month",months:"%d months",year:"about a year",years:"%d years",numbers:[]}},inWords:function(distanceMillis){var $l=this.settings.strings;var prefix=$l.prefixAgo;var suffix=$l.suffixAgo;if(this.settings.allowFuture){if(distanceMillis<0){prefix=$l.prefixFromNow;suffix=$l.suffixFromNow;}
|
||||
distanceMillis=Math.abs(distanceMillis);}
|
||||
var seconds=distanceMillis/1000;var minutes=seconds/60;var hours=minutes/60;var days=hours/24;var years=days/365;function substitute(stringOrFunction,number){var string=$.isFunction(stringOrFunction)?stringOrFunction(number,distanceMillis):stringOrFunction;var value=($l.numbers&&$l.numbers[number])||number;return string.replace(/%d/i,value);}
|
||||
var words=seconds<45&&substitute($l.seconds,Math.round(seconds))||seconds<90&&substitute($l.minute,1)||minutes<45&&substitute($l.minutes,Math.round(minutes))||minutes<90&&substitute($l.hour,1)||hours<24&&substitute($l.hours,Math.round(hours))||hours<48&&substitute($l.day,1)||days<30&&substitute($l.days,Math.floor(days))||days<60&&substitute($l.month,1)||days<365&&substitute($l.months,Math.floor(days/30))||years<2&&substitute($l.year,1)||substitute($l.years,Math.floor(years));return $.trim([prefix,words,suffix].join(" "));},parse:function(iso8601){var s=$.trim(iso8601);s=s.replace(/\.\d\d\d+/,"");s=s.replace(/-/,"/").replace(/-/,"/");s=s.replace(/T/," ").replace(/Z/," UTC");s=s.replace(/([\+\-]\d\d)\:?(\d\d)/," $1$2");return new Date(s);},datetime:function(elem){var isTime=$(elem).get(0).tagName.toLowerCase()==="time";var iso8601=isTime?$(elem).attr("datetime"):$(elem).attr("title");return $t.parse(iso8601);}});$.fn.timeago=function(){var self=this;self.each(refresh);var $s=$t.settings;if($s.refreshMillis>0){setInterval(function(){self.each(refresh);},$s.refreshMillis);}
|
||||
return self;};function refresh(){var data=prepareData(this);if(!isNaN(data.datetime)){$(this).text(inWords(data.datetime));}
|
||||
return this;}
|
||||
function prepareData(element){element=$(element);if(!element.data("timeago")){element.data("timeago",{datetime:$t.datetime(element)});var text=$.trim(element.text());if(text.length>0){element.attr("title",text);}}
|
||||
return element.data("timeago");}
|
||||
function inWords(date){return $t.inWords(distance(date));}
|
||||
function distance(date){return(new Date().getTime()-date.getTime());}
|
||||
document.createElement("abbr");document.createElement("time");}(jQuery));
|
296
app/assets/javascripts/updateChart.js
Normal file
@ -0,0 +1,296 @@
|
||||
// update the chart with all the textbox values
|
||||
function openDialogCenter(element) {
|
||||
element.dialog("open");
|
||||
var sizeArr = getDimensions( element.parent() );
|
||||
element.dialog({position:[ sizeArr[0], sizeArr[1] ] });
|
||||
|
||||
}
|
||||
function getDimensions(element) {
|
||||
var sizeArr = new Array(2);
|
||||
sizeArr[0] = $(window).width()/2 - element.width()/2;
|
||||
sizeArr[1] = $(window).height()/2 - element.height()/2;
|
||||
return sizeArr;
|
||||
}
|
||||
|
||||
function updateChart(index,
|
||||
postUpdate,
|
||||
width,
|
||||
height,
|
||||
channelId,
|
||||
newOptionsSave) {
|
||||
// default width and height
|
||||
var width = width;
|
||||
var height = height;
|
||||
// get old src
|
||||
|
||||
var iframe = $('#iframe' + index).attr("default_src");
|
||||
|
||||
if (!iframe) {
|
||||
iframe = $('#iframe' + index).attr('src');
|
||||
}
|
||||
|
||||
src = iframe.split('?')[0];
|
||||
// if not a line chart, a timeslice should be present or set timescale=30
|
||||
if ($('#type_' + index).val() != 'line') {
|
||||
if ($('#timescale_' + index).val().length == 0 && $('#average_' + index).val().length == 0 && $('#median_' + index).val().length == 0 && $('#sum_' + index).val().length == 0) {
|
||||
$('#timescale_' + index).val(30);
|
||||
}
|
||||
}
|
||||
|
||||
// add inputs to array
|
||||
var inputs = [];
|
||||
$('.chart_options' + index).each(function() {
|
||||
var v = $(this).val();
|
||||
var id = $(this).attr('id');
|
||||
var tag = id.split("_")[0];
|
||||
|
||||
if (v.length > 0) { inputs.push([tag, v]); }
|
||||
});
|
||||
|
||||
// create querystring
|
||||
var qs = '';
|
||||
while (inputs.length > 0) {
|
||||
var p = inputs.pop();
|
||||
if (p[0] == 'width') { width = parseInt(p[1]); }
|
||||
if (p[0] == 'height') { height = parseInt(p[1]); }
|
||||
|
||||
// don't add type=line to querystring, it's the default value
|
||||
if (!(p[0] == 'type' && p[1] == 'line')) {
|
||||
qs += '&' + p[0] + '=' + encodeURIComponent(p[1]);
|
||||
}
|
||||
}
|
||||
// if querystring exists, add it to src
|
||||
if (qs.length > 0) { src += '?' + qs.substring(1); }
|
||||
|
||||
// save chart options to database
|
||||
if (postUpdate && index > 0 && newOptionsSave) {
|
||||
$.update("/channels/" + channelId + "/charts/" + index,
|
||||
{
|
||||
newOptions : { options: qs }
|
||||
} );
|
||||
}
|
||||
else if (postUpdate && index > 0) {
|
||||
$.update("/channels/" + channelId + "/charts/" + index,
|
||||
{ options: qs } );
|
||||
}
|
||||
|
||||
// set embed code
|
||||
$('#embed' + index).val('<iframe width="' + width + '" height="' + height + '" style="border: 1px solid #cccccc;" src="' + src + '"></iframe>');
|
||||
|
||||
// set new src
|
||||
$('#iframe' + index).attr('src', src);
|
||||
$('#iframe' + index).attr('width', width);
|
||||
$('#iframe' + index).attr('height', height);
|
||||
}
|
||||
function updateSelectValues() {
|
||||
selectedValue = $(this).val();
|
||||
$(".mutuallyexclusive"+index).each(function () { $(this).val(""); });
|
||||
$(this).val(selectedValue);
|
||||
}
|
||||
|
||||
function setupChartForm(channelIndex) {
|
||||
return function(index, value) {
|
||||
if (value.length > 0) {
|
||||
$('#' + value.split('=')[0] + "_" + channelIndex).val(decodeURIComponent(value.split('=')[1]));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
function setupColumns(current_user, channel_id)
|
||||
{
|
||||
$( sortColumnSetup(current_user, channel_id) ) ;
|
||||
$( ".column" ).disableSelection();
|
||||
}
|
||||
|
||||
function createWindowsWithData (data, current_user, channel_id, colName) {
|
||||
|
||||
for (var i in data) {
|
||||
//each array element has a single chart object as an associative array with the type as the key
|
||||
// so I need to iterate over a array with size=1 to get a string with the window type
|
||||
for (var type in data[i]) {
|
||||
var wtype = type;
|
||||
}
|
||||
if (data[i].chart_window) window = data[i].chart_window;
|
||||
if (data[i].plugin_window) window = data[i].plugin_window;
|
||||
if (data[i].portlet_window) window = data[i].portlet_window;
|
||||
|
||||
if (window == "undefined")
|
||||
var window = (data[i].portlet_window) ? data[i].portlet_window : data[i].chart_window;
|
||||
colId = window.col;
|
||||
title = window.title;
|
||||
|
||||
var content = window.html;
|
||||
if (data[i].chart_window) {
|
||||
var windowId = window.id;
|
||||
$("body").append("<div id='chartConfig"+windowId+"'></div>");
|
||||
}
|
||||
var portlet = addWindow(colName, colId, window.id, wtype, title, content);
|
||||
portlet.each ( decoratePortlet(current_user) ) ;
|
||||
|
||||
portlet.find( ".ui-toggle" ).click( uiToggleClick );
|
||||
portlet.find( ".ui-view" ).click( uiViewClick (channel_id) );
|
||||
portlet.find( ".ui-edit" ).click( uiEditClick (channel_id) );
|
||||
portlet.find( ".ui-close" ).click( uiCloseClick (channel_id) );
|
||||
}
|
||||
}
|
||||
var createWindows = function (current_user, channel_id, colName) {
|
||||
return function(data) {
|
||||
createWindowsWithData(data, current_user, channel_id, colName);
|
||||
};
|
||||
}
|
||||
|
||||
function addWindow(colName, colId, windowId, wtype, title, content) {
|
||||
$("#"+colName+"_dialog"+colId).append('<div class="portlet ui-widget ui-widget-content ui-helper-clearfix ui-corner-all" ' +
|
||||
'id="portlet_' + windowId +
|
||||
'"><div class="portlet-header wtype wtype-'+ wtype
|
||||
+ ' ui-widget-header ui-corner-all">' + title +
|
||||
'</div><div class="portlet-content">'+content+'</div>') ;
|
||||
|
||||
if ($("#portlet_"+windowId).length > 1) {
|
||||
throw "Portlet count doesn't match what's expected";
|
||||
} else {
|
||||
return $("#portlet_"+windowId);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
var updatePortletPositions = function( current_user, channel_id) {
|
||||
return function() {
|
||||
if (current_user) {
|
||||
var result = $(this).sortable('serialize');
|
||||
colId = $(this).attr('id').charAt($(this).attr('id').length - 1);
|
||||
portletArray = getPortletArray(result);
|
||||
jsonResult = {
|
||||
"col" : colId,
|
||||
"positions" : portletArray
|
||||
} ;
|
||||
|
||||
if (portletArray.length > 0) {
|
||||
$.ajax({
|
||||
type: 'PUT',
|
||||
url: '../channels/' + channel_id + '/windows',
|
||||
data: {_method:'PUT', page : JSON.stringify(jsonResult ) },
|
||||
dataType: 'json'
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function sortColumnSetup(current_user, channel_id) {
|
||||
|
||||
$( ".column" ).sortable({
|
||||
opacity: 0.6,
|
||||
helper: function( event ) {
|
||||
return $("<div class='ui-widget-header'>Drop to re-position</div>");
|
||||
},
|
||||
connectWith: ".column",
|
||||
update: updatePortletPositions(current_user, channel_id)
|
||||
});
|
||||
}
|
||||
var decoratePortlet = function (current_user) {
|
||||
return function() {
|
||||
var portletHeader = $(this).find( ".portlet-header") ;
|
||||
portletHeader.append( "<span id='commentBtn' class='ui-view ui-icon ui-icon-comment'></span>");
|
||||
|
||||
thisObject = $(this);
|
||||
if (current_user == "true") {
|
||||
// Use feature Rollout here - needs to be implemented for this user, and this channel needs to belong to this user.
|
||||
thisObject.find('.wtype').prepend( "<span id='minusBtn' class='ui-toggle ui-icon ui-icon-minusthick'></span>");
|
||||
thisObject.find(".wtype-chart_window").append("<span id='pencilBtn' class='ui-edit ui-icon ui-icon-pencil'></span>");
|
||||
thisObject.find(".wtype").append("<span id='closeBtn' class='ui-close ui-icon ui-icon-close'></span>");
|
||||
thisObject.find(".portlet-header").css("cursor","move");
|
||||
}
|
||||
else {
|
||||
$(".column").sortable({ disabled:true });
|
||||
}
|
||||
return $(this).attr("id");
|
||||
}
|
||||
}
|
||||
function getPortletArray(data) {
|
||||
|
||||
var resultArray = new Array();
|
||||
var inputArray = data.split("&");
|
||||
|
||||
for (i in inputArray) {
|
||||
|
||||
val = inputArray[i].split("=")[1] ;
|
||||
resultArray.push(val);
|
||||
}
|
||||
|
||||
return resultArray;
|
||||
}
|
||||
|
||||
|
||||
var uiEditClick = function (channel_id) {
|
||||
return function() {
|
||||
var id = $( this ).parents( ".portlet:first" ).attr("id").substring(8);
|
||||
|
||||
var options = "";
|
||||
$("#chartConfig"+id).load("/channels/"+channel_id+"/charts/"+id+"/edit",
|
||||
function() {
|
||||
options = $("#chartOptions"+id).html();
|
||||
|
||||
if (options != "undefined" && options.length >2) {
|
||||
$.each((options.split('&')), setupChartForm( id ));
|
||||
}
|
||||
$("#button"+id).click( function() {
|
||||
updateChart(id, true, 450, 250, channel_id, true);
|
||||
$("#chartConfig"+id).dialog("close");
|
||||
|
||||
});
|
||||
})
|
||||
.dialog({ title:"Chart Options", modal: true, resizable: false, width: 500, dialogClass: "dev-info-dialog" });
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
var uiViewClick = function (channel_id) {
|
||||
return function() {
|
||||
var x = $( this ).parents( ".portlet:first" ).find( ".portlet-content" ).offset().left;
|
||||
var y = $( this ).parents( ".portlet:first" ).find( ".portlet-content" ).offset().top;
|
||||
var id = $( this ).parents( ".portlet:first" ).attr("id").substring(8);
|
||||
|
||||
$("body").append('<div id="iframepopup'+id+'" style="display:none">' +
|
||||
'<div id="iframeinner'+id+'"style="font-size:1.2em;overflow:auto;height:115px;background-color:white">' +
|
||||
'</div></div>');
|
||||
|
||||
$.get("/channels/"+channel_id+"/windows/"+id+"/iframe",
|
||||
function(response) {
|
||||
var display = response.replace(/id=\"iframe[0-9]?[0-9]?[0-9]?[0-9]?[0-9]?[0-9]?[0-9]?[0-9]?[0-9]?[0-9]?\"/, "" );
|
||||
$("#iframeinner"+id).text(display);
|
||||
}
|
||||
);
|
||||
|
||||
$("#iframepopup"+id).dialog({
|
||||
resizable:false,
|
||||
width: "300px",
|
||||
position:[x+200,y-200],
|
||||
title: "Chart Iframe",
|
||||
dialogClass: "dev-info-dialog"
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
var uiCloseClick = function (channel_id) {
|
||||
return function() {
|
||||
var id = $( this ).parents( ".portlet:first" ).attr("id").substring(8);
|
||||
var portlet = $( this ).parents( ".portlet:first" ) ;
|
||||
$.update("/channels/"+channel_id+"/windows/"+id+"/hide" ,
|
||||
function(response) {
|
||||
portlet.hide("drop", function(){
|
||||
portlet.remove();});
|
||||
}) ;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function uiToggleClick() {
|
||||
$( this ).toggleClass( "ui-icon-minusthick" ).toggleClass( "ui-icon-plusthick" );
|
||||
$( this ).parents( ".portlet:first" ).find( ".portlet-content" ).toggle();
|
||||
}
|
||||
|
16
app/assets/javascripts/validate.min.js
vendored
Normal file
@ -0,0 +1,16 @@
|
||||
/*
|
||||
* validate.js 1.0.1
|
||||
* Copyright (c) 2011 Rick Harrison, http://rickharrison.me
|
||||
* validate.js is open sourced under the MIT license.
|
||||
* Portions of validate.js are inspired by CodeIgniter.
|
||||
* http://rickharrison.github.com/validate.js
|
||||
*/
|
||||
|
||||
(function(j,k,i){var l={required:"The %s field is required.",matches:"The %s field does not match the %s field.",valid_email:"The %s field must contain a valid email address.",min_length:"The %s field must be at least %s characters in length.",max_length:"The %s field must not exceed %s characters in length.",exact_length:"The %s field must be exactly %s characters in length.",greater_than:"The %s field must contain a number greater than %s.",less_than:"The %s field must contain a number less than %s.",
|
||||
alpha:"The %s field must only contain alphabetical characters.",alpha_numeric:"The %s field must only contain alpha-numeric characters.",alpha_dash:"The %s field must only contain alpha-numeric characters, underscores, and dashes.",numeric:"The %s field must contain only numbers.",integer:"The %s field must contain an integer."},m=function(){},n=/^(.+)\[(.+)\]$/,g=/^[0-9]+$/,o=/^\-?[0-9]+$/,h=/^\-?[0-9]*\.?[0-9]+$/,p=/^[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,6}$/i,q=/^[a-z]+$/i,r=/^[a-z0-9]+$/i,s=/^[a-z0-9_-]+$/i,
|
||||
e=function(a,b,d){this.callback=d||m;this.errors=[];this.fields={};this.form=k.forms[a]||{};this.messages={};this.handlers={};a=0;for(d=b.length;a<d;a++){var c=b[a];c.name&&c.rules&&(this.fields[c.name]={name:c.name,display:c.display||c.name,rules:c.rules,type:null,value:null,checked:null})}this.form.onsubmit=function(a){return function(b){try{return a._validateForm(b)}catch(c){}}}(this)};e.prototype.setMessage=function(a,b){this.messages[a]=b;return this};e.prototype.registerCallback=function(a,
|
||||
b){a&&typeof a==="string"&&b&&typeof b==="function"&&(this.handlers[a]=b);return this};e.prototype._validateForm=function(a){this.errors=[];for(var b in this.fields)if(this.fields.hasOwnProperty(b)){var d=this.fields[b]||{},c=this.form[d.name];if(c&&c!==i)d.type=c.type,d.value=c.value,d.checked=c.checked;this._validateField(d)}typeof this.callback==="function"&&this.callback(this.errors,a);if(this.errors.length>0)if(a&&a.preventDefault)a.preventDefault();else return false;return true};e.prototype._validateField=
|
||||
function(a){var b=a.rules.split("|");if(!(a.rules.indexOf("required")===-1&&(!a.value||a.value===""||a.value===i)))for(var d=0,c=b.length;d<c;d++){var f=b[d],e=null,g=false;if(parts=n.exec(f))f=parts[1],e=parts[2];typeof this._hooks[f]==="function"?this._hooks[f].apply(this,[a,e])||(g=true):f.substring(0,9)==="callback_"&&(f=f.substring(9,f.length),typeof this.handlers[f]==="function"&&this.handlers[f].apply(this,[a.value])===false&&(g=true));if(g){(b=this.messages[f]||l[f])?(a=b.replace("%s",a.display),
|
||||
e&&(a=a.replace("%s",this.fields[e]?this.fields[e].display:e)),this.errors.push(a)):this.errors.push("An error has occurred with the "+a.display+" field.");break}}};e.prototype._hooks={required:function(a){var b=a.value;return a.type==="checkbox"?a.checked===true:b!==null&&b!==""},matches:function(a,b){return(el=this.form[b])?a.value===el.value:false},valid_email:function(a){return p.test(a.value)},min_length:function(a,b){return!g.test(b)?false:a.value.length>=b},max_length:function(a,b){return!g.test(b)?
|
||||
false:a.value.length<=b},exact_length:function(a,b){return!g.test(b)?false:a.value.length==b},greater_than:function(a,b){return!h.test(a.value)?false:parseFloat(a.value)>parseFloat(b)},less_than:function(a,b){return!h.test(a.value)?false:parseFloat(a.value)<parseFloat(b)},alpha:function(a){return q.test(a.value)},alpha_numeric:function(a){return r.test(a.value)},alpha_dash:function(a){return s.test(a.value)},numeric:function(a){return h.test(a.value)},integer:function(a){return o.test(a.value)}};
|
||||
j.FormValidator=e})(window,document);
|
0
app/assets/stylesheets/.gitkeep
Normal file
@ -1,7 +1,10 @@
|
||||
/*
|
||||
* This is a manifest file that'll automatically include all the stylesheets available in this directory
|
||||
* and any sub-directories. You're free to add application-wide styles to this file and they'll appear at
|
||||
* the top of the compiled file, but it's generally better to create a new file per style scope.
|
||||
*= require_self
|
||||
*= require_tree .
|
||||
*/
|
||||
*= require ./bootstrap_custom.min.css
|
||||
*= require ./bootstrap_overrides.css
|
||||
*= require ./jquery-ui-1.8.24.custom.css
|
||||
*= require ./custom.css
|
||||
*= require ./status.css
|
||||
*= require ./prettify.css
|
||||
*= require ./sidebar.css
|
||||
*/
|
||||
|
||||
|
9
app/assets/stylesheets/bootstrap_custom.min.css
vendored
Normal file
6
app/assets/stylesheets/bootstrap_overrides.css
vendored
Normal file
@ -1,85 +1,545 @@
|
||||
html, body { height: 100%; margin: 0; padding: 0; font-family: Helvetica, Arial, sans-serif; color: #333333; }
|
||||
img { border: 0; }
|
||||
h2 { margin-top: 5px; }
|
||||
h3 { margin-top: 0; }
|
||||
a { color: #2565a5; text-decoration: none; }
|
||||
a:hover, a:hover div,
|
||||
a:hover .info { color: #0066ff; text-decoration: underline; }
|
||||
ul { margin: 0; padding: 0 0 0 20px; }
|
||||
ul li { margin-left: 15px; }
|
||||
table { border-collapse: collapse; }
|
||||
#logo { margin: 6px 0 0 20px; font-size: 45px; font-weight: bold; font-family: Tahoma, Geneva, Kalimati, sans-serif; }
|
||||
#logo a { color: #d62020; }
|
||||
#logo a span, .paygray { color: #666666; }
|
||||
#logo a:hover { text-decoration: none; }
|
||||
#logo.small { font-size: 30px; color: #666666; }
|
||||
#options { float: right; text-align: right; }
|
||||
#options span { padding-right: 10px; }
|
||||
#login { padding: 6px; border: 1px solid #bbbbbb; border-collapse: separate; border-spacing: 3px; background-color: #eeeeee; background: -webkit-gradient(linear, left top, right top, from(#dddddd), to(#f5f5f5)); background: -moz-linear-gradient(left, #dddddd, #f5f5f5); filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#dddddd', endColorstr='#f5f5f5', GradientType=1)); }
|
||||
#menu { height: 40px; margin: 0 auto; border: 1px solid #dddddd; background-color: #d6d6d6; background: -webkit-gradient(linear, left top, left bottom, from(#eeeeee), to(#bbbbbb)); background: -moz-linear-gradient(top, #eeeeee, #bbbbbb); filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#eeeeee', endColorstr='#bbbbbb')); }
|
||||
#menu div { float: left; }
|
||||
#menu div a { color: #777777; padding: 10px 30px; display: block; font-weight: bold; }
|
||||
#menu div:hover { background-color: #cccccc; }
|
||||
#menu div:hover a { color: #000000; text-decoration: none; }
|
||||
#menu .selected,
|
||||
#menu .selected:hover { background-color: #e5e5e5; }
|
||||
#menu .selected a { color: #000000; }
|
||||
.userlogin { display:none; }
|
||||
.login_info { font-weight: bold; text-align: right; font-size: 12px; }
|
||||
.round { -moz-border-radius: 7px; -webkit-border-radius: 7px; }
|
||||
.text_center { text-align: center; }
|
||||
.big { font-size: 18px; }
|
||||
.large { font-size: 20px; }
|
||||
.xlarge { font-size: 30px; }
|
||||
.small { font-size: 12px; }
|
||||
.action { margin-right: 30px; position: relative; top: 25px; font-weight: bold; }
|
||||
.nicetable { font-size: 14px; border: 1px solid #bbbbbb; }
|
||||
.nicetable .header { font-weight: bold; background-color: #e5e5e5; }
|
||||
.nicetable .header td { padding-top: 3px; }
|
||||
.nicetable td { padding: 2px 10px; border-bottom: 1px solid #bbbbbb; }
|
||||
.nicetable .stripe { background-color: #f9f9f9; }
|
||||
.table_no_header { font-size: 14px; border: 1px solid #bbbbbb; }
|
||||
.table_no_header td { padding: 2px 10px; border-bottom: 1px solid #bbbbbb; }
|
||||
.table_no_header .left { font-weight: bold; }
|
||||
.pagination { font-size: 14px; }
|
||||
.centerme { display: table; margin: 0 auto; }
|
||||
.fixedwidth { width: 990px; display: table; margin: 0 auto; }
|
||||
|
||||
/* error messages */
|
||||
.errorExplanation { background-color: #ffffe0; display: table; margin-bottom: 20px; padding: 10px; border: 1px solid #aaaaaa; }
|
||||
.field_with_errors { display: inline; }
|
||||
|
||||
/* main layout */
|
||||
#wrapper { min-height: 100%; position: relative; }
|
||||
#header { height: 75px; }
|
||||
#menuwrap { padding: 0 20px; }
|
||||
#content { padding: 23px 20px 58px 23px; }
|
||||
|
||||
/* shortcuts */
|
||||
.FL { float: left; }
|
||||
.FR { float: right; }
|
||||
.FN { float: none; }
|
||||
.DT { display: table; }
|
||||
.CL { clear: left; }
|
||||
.UL { text-decoration: underline; }
|
||||
.TAR { text-align: right; }
|
||||
.TAC { text-align: center; }
|
||||
.VAT { vertical-align: top; }
|
||||
.PB10 { padding-bottom: 10px; }
|
||||
.PR20 { padding-right: 20px; }
|
||||
.PL20 { padding-left: 20px; }
|
||||
.PL30 { padding-left: 30px; }
|
||||
.MR20 { margin-right: 20px; }
|
||||
.MR60 { margin-right: 60px; }
|
||||
.ML20 { margin-left: 20px; }
|
||||
.W100 { width: 100%; }
|
||||
.left20 { position: relative; left: -20px; }
|
||||
.up2 { position: relative; top: -2px; }
|
||||
.up20 { position: relative; top: -20px; }
|
||||
|
||||
/* form styling */
|
||||
input[type='text'],
|
||||
input[type='password'] { border: 1px inset #999999; width: 165px; }
|
||||
input[type='text']:focus,
|
||||
input[type='password']:focus { background-color: #ffffdd; }
|
||||
input[type='submit'] { font-size: 14px; padding: 3px 6px; color: #333333; }
|
||||
/* bootstrap overrides */
|
||||
.navbar-collapse { max-height: 350px; }
|
||||
.breadcrumb { margin-top: 10px; margin-bottom: 20px; } /* margins to make sure breadcrumb and h4.breadcrumb line up properly */
|
||||
.table td { word-break: break-word; }
|
||||
|
||||
/* misc */
|
||||
body { padding-top: 70px; }
|
||||
.break-word { word-break: break-word; }
|
||||
.col-pad { padding: 0 15px; }
|
||||
|
||||
/* multiline forms */
|
||||
.form-horizontal .multiline-label { margin-top: -10px; }
|
||||
|
||||
/* nested fields */
|
||||
.nested-fields { border: 1px solid #d6d6d6; padding: 10px 10px 0 10px; max-width: 275px; }
|
||||
|
||||
/* prettify code */
|
||||
.customcode,
|
||||
.customcode .str,
|
||||
.customcode .lit,
|
||||
.customcode .pln { color: #00ab9b; }
|
||||
|
||||
/* response types */
|
||||
.response-div { margin-top: 12px; font-size: 0.8em; }
|
||||
.response { background-color: #ddd; margin: 0 1px; border: 1px solid #555; padding: 2px; font-weight: bold; color: #555; cursor: pointer; }
|
||||
.response:hover { background-color: #eeb; }
|
||||
.response.active { background-color: #beb; cursor: initial; }
|
||||
.format-json,
|
||||
.format-xml { display: none; }
|
||||
.format-block { min-height: 200px; }
|
||||
.format-block-lg { min-height: 350px; }
|
||||
|
||||
|
||||
/* Sticky footer styles
|
||||
-------------------------------------------------- */
|
||||
|
||||
html,
|
||||
body {
|
||||
height: 100%;
|
||||
/* The html and body elements cannot have any padding or margin. */
|
||||
}
|
||||
|
||||
/* Wrapper for page content to push down footer */
|
||||
#wrap {
|
||||
min-height: 100%;
|
||||
height: auto;
|
||||
/* Negative indent footer by its height */
|
||||
margin: 0 auto -30px;
|
||||
padding: 0 0 60px;
|
||||
}
|
||||
|
||||
/* Set the fixed height of the footer here */
|
||||
#footer {
|
||||
padding-top: 5px;
|
||||
border-top: 1px solid #ddd;
|
||||
height: 30px;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
#footer .container { background-color: #f5f5f5; }
|
||||
@media (max-width: 767px) {
|
||||
#footer span { display: block; text-align: center; float: none !important; }
|
||||
}
|
||||
|
||||
/* old styles */
|
||||
.infobox {
|
||||
vertical-align:bottom;
|
||||
}
|
||||
.spanlink { cursor: pointer; }
|
||||
.boldlink { font-weight: bold; }
|
||||
.apps a:hover,
|
||||
.apps:hover div { text-decoration: none; }
|
||||
.commentarea { width: 300px; height: 80px; }
|
||||
#options { float: right; text-align: right; }
|
||||
#login {
|
||||
padding: 6px;
|
||||
border: 1px solid #bbbbbb;
|
||||
border-collapse: separate;
|
||||
border-spacing: 3px;
|
||||
background-color: #eeeeee;
|
||||
background: -webkit-gradient(linear, left top, right top, from(#dddddd), to(#f5f5f5));
|
||||
background: -moz-linear-gradient(left, #dddddd, #f5f5f5);
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#dddddd', endColorstr='#f5f5f5', GradientType=1);
|
||||
}
|
||||
#channel_info { padding-bottom: 20px; }
|
||||
.signed_in_channel_header {display:inline-block; }
|
||||
.public_private_icon {display:inline-block;}
|
||||
.signed_in_channel_header { width:300px;height: 10px;}
|
||||
#list_progress_bar { width:300px;height: 10px;}
|
||||
.public_channel_box {
|
||||
margin:5px;border-radius:7px;height:300px;border:solid 1px red;display:inline-block;width:260px;vertical-align:top
|
||||
}
|
||||
.public_channel_inner {
|
||||
margin-left:10px;
|
||||
margin-right:5px
|
||||
}
|
||||
.public_channel_name {
|
||||
height: 11px;
|
||||
font-size: 1.3em;
|
||||
font-weight:bold;
|
||||
color: #2565A5;
|
||||
}
|
||||
.public_channel_user {
|
||||
height:15px;
|
||||
margin-left:7px;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
.progressbar {
|
||||
height:10px;
|
||||
|
||||
}
|
||||
.public_channel_desc {
|
||||
height:140px;
|
||||
word-break:break-all;
|
||||
}
|
||||
.public_channel_url {
|
||||
height:10px;
|
||||
}
|
||||
.public_channel_tags {
|
||||
height:57px;
|
||||
margin-bottom:10px;
|
||||
overflow: hidden;
|
||||
}
|
||||
.public_channel_thumbnail {
|
||||
position:absolute;border:2px solid red;display:none
|
||||
}
|
||||
h1.channel_info {
|
||||
margin:0;
|
||||
padding-top: 10px
|
||||
}
|
||||
|
||||
div.list_tags {
|
||||
padding-top:20px;
|
||||
}
|
||||
.channelLinks {
|
||||
padding: 0 0 0 0;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
.channelLinks li {
|
||||
padding : 7px;
|
||||
border: 1px solid #CCC ;
|
||||
border-radius: 15px;
|
||||
-webkit-border-radius: 15px;
|
||||
-moz-border-radius: 15px;
|
||||
display:inline;
|
||||
margin:0 0 0 0;
|
||||
list-style: none;
|
||||
}
|
||||
#comments { width: 100%; }
|
||||
#commentsmain { float:left; width: 190px; background-color: #fffff9; border: 1px dashed #d0d0d0; padding: 5px; margin-right: 16px; overflow: hidden; display:none }
|
||||
.commentlink { float:left;width:100%}
|
||||
#public_statuses { width: 455px; height: 380px; overflow: auto; font-size: 1.5em; }
|
||||
#public_statuses div { margin-left: 15px; }
|
||||
.statusIFrame {
|
||||
border : 1px solid #cccccc ;
|
||||
background-color: #ffffff;
|
||||
}
|
||||
|
||||
.userlogin { display:none; }
|
||||
.login_info { font-weight: bold; text-align: right; font-size: 12px; }
|
||||
.round { -moz-border-radius: 7px; -webkit-border-radius: 7px; }
|
||||
.text_center { text-align: center; }
|
||||
.big { font-size: 18px; }
|
||||
.large { font-size: 20px; }
|
||||
.xlarge { font-size: 30px; }
|
||||
.small { font-size: 12px; }
|
||||
.action { margin-right: 20px; position: relative; top: 35px; font-weight: bold; padding:10px;}
|
||||
.action_reverse { margin-right: 20px; position: relative; top: 35px; font-weight: bold; background-color: #d62020; padding:8px; color: #000000;}
|
||||
.action_reverse a { color: #FFFFFF;}
|
||||
.section_header_reverse { margin: 15px 0 5px 0; display: inline-block; font-weight: bold; color:#FFFFFF; background-color: #d62020; padding:8px;}
|
||||
.nicetable { font-size: 14px; border: 1px solid #bbbbbb; }
|
||||
.nicetable-borderless { font-size: 14px; border: none; }
|
||||
.nicetable-borderless p { margin-left:20px; margin-top:5px; margin-bottom:10px; }
|
||||
.nicetable-borderless h3 { margin-bottom:0; }
|
||||
.nicetable-borderless h3.signed_in_channel_header { margin-bottom:10px; }
|
||||
.nicetable .header { font-weight: bold; background-color: #e5e5e5; }
|
||||
.nicetable .header td { padding-top: 3px; }
|
||||
.nicetable td { padding: 2px 10px; border-bottom: 1px solid #bbbbbb; word-wrap:word}
|
||||
.nicetable .stripe { background-color: #f9f9f9; }
|
||||
.nicetable .disabled { background-color: #eee; }
|
||||
.nicetable .disabled a { color: #888; }
|
||||
.fulltable { width: 95%; }
|
||||
|
||||
.fullform { width: 100%; margin-bottom: 1.5em;}
|
||||
.deletecol { width: 1em; }
|
||||
|
||||
.table_no_header { font-size: 14px; }
|
||||
.table_no_header td { padding: 2px 10px; }
|
||||
.table_no_header .left { font-weight: bold; }
|
||||
|
||||
.max_width_400 { max-width:40; word-wrap:break-word; }
|
||||
.helplink { float: right; margin-top: 3px; }
|
||||
.votediv,
|
||||
.votedivphoto,
|
||||
.votedivlink { color: #3478e3; }
|
||||
.votedivlink { padding-right: 20px; }
|
||||
.votediv { float: right; display: none;}
|
||||
.voteicon { padding: 2px; background: #f8f8f8; border: 1px solid #ddd; cursor: pointer; }
|
||||
.voteicon:hover { text-decoration: none; }
|
||||
.voteicon img { position: relative; top: 3px; }
|
||||
.nestedcomment { padding-left: 30px; }
|
||||
.commenttable { padding: 0; margin: 0; width: 100%; }
|
||||
.commenttable td { padding: 4px 10px 4px 5px; }
|
||||
.commentchannel { color: #f08600; font-weight: bold; }
|
||||
.commentbody { color: #333333; }
|
||||
.commentdiv { width:100%; padding: 5px 0 5px 0;
|
||||
font-size: 12px;
|
||||
border-top: 1px solid #aaa; }
|
||||
.gravatar { border: 1px solid #999; }
|
||||
.noavatar { width: 50px; height: 50px; border: 1px solid #ddd; color: #ddd; text-align: center; }
|
||||
.noavatartext { padding-top: 5px; }
|
||||
.prettydate { color: #aaa; }
|
||||
.username a { color: #3478E3; font-weight: bold; }
|
||||
|
||||
.timeago {
|
||||
|
||||
|
||||
font-size: 0.8em;
|
||||
color: #ccc;
|
||||
}
|
||||
|
||||
.pagination { font-size: 14px; }
|
||||
.centerme { display: table; margin: 0 auto; }
|
||||
.fixedwidth { width: 960px; display: table; margin: 0 auto; }
|
||||
.code { margin: 10px 0; background-color: #fafafa; white-space: pre-wrap; font: 12px Monaco, Lucida Console, monospace; color: #000000; border: 1px solid #bbbbbb; padding: 10px; }
|
||||
.apps { padding: 0 30px 40px 0; font-size: 20px; float: left; text-align: center; margin: 0 auto; width: 150px; }
|
||||
input[type="text"].shortfield { width: 30px; }
|
||||
input[type="text"].midfield { width: 120px; }
|
||||
textarea.tweet { margin-top: 0.5em; width: 40em; height: 3em; }
|
||||
|
||||
/* error messages */
|
||||
.errorExplanation { width: 95%; background-color: #ffffe0; display: table; margin-bottom: 20px; padding: 10px; border: 1px solid #aaaaaa; }
|
||||
#error {
|
||||
color: red;
|
||||
}
|
||||
.field_with_errors { display: inline; }
|
||||
/*.error_box { margin-top: 15px; padding: 5px; background-color: #f99; color: #300; border: 1px solid #f66; }*/
|
||||
.warning_box { margin: 15px 0 15px 0; padding: 10px; background-color: #fc3; color: #000; border: 1px solid #f90; }
|
||||
|
||||
/* main layout */
|
||||
#maincontent { float: left; width:980px; padding-bottom: 20px; margin-left:10px; overflow:false}
|
||||
#maincontent.thin { width: 400px; }
|
||||
#maincontent.medium {
|
||||
|
||||
width: 500px;
|
||||
}
|
||||
|
||||
#maincontent .thinghttptable { table-layout:fixed; width: 400px; word-wrap: break-word; }
|
||||
#maincontent .thinghttptable .url { width: 100px; }
|
||||
#maincontent .thinghttptable .apikey { width: 80px; }
|
||||
|
||||
input#upload_csv { padding-top:5px;margin:20px 0;}
|
||||
|
||||
#sidebar { }
|
||||
#sidebar.wide { width: 520px; }
|
||||
#sidebar.medium { width: 470px; }
|
||||
#sidebar.narrow { width: 470px; margin-left: 520px;}
|
||||
#sidebar .helplink { float: right; margin-top: 5px; font-weight: normal; font-size: 12px; }
|
||||
|
||||
/* shortcuts */
|
||||
.FL { float: left; }
|
||||
.FR { float: right; }
|
||||
.FN { float: none; }
|
||||
.DT { display: table; }
|
||||
.CL { clear: left; }
|
||||
.CR { clear: right; }
|
||||
.CB { clear: both; }
|
||||
.UL { text-decoration: underline; }
|
||||
.TAR { text-align: right; }
|
||||
.TAC { text-align: center; }
|
||||
.VAT { vertical-align: top; }
|
||||
.PB10 { padding-bottom: 10px; }
|
||||
.PR20 { padding-right: 20px; }
|
||||
.PL20 { padding-left: 20px; }
|
||||
.PL30 { padding-left: 30px; }
|
||||
.MT10 { margin-top: 5px; }
|
||||
.MR20 { margin-right: 20px; }
|
||||
.MR60 { margin-right: 60px; margin-bottom: 20px; }
|
||||
.ML20 { margin-left: 20px; }
|
||||
.ML60 { margin-left: 60px; }
|
||||
.W50 { width: 50%; }
|
||||
.W100 { width: 100%; }
|
||||
.left20 { position: relative; left: -20px; }
|
||||
.up2 { position: relative; top: -2px; }
|
||||
.up20 { position: relative; top: -20px; }
|
||||
|
||||
/* form styling */
|
||||
input.video_narrow {width:100px; }
|
||||
/* home page */
|
||||
#feature_signup {}
|
||||
#feature_signup a {border-bottom: 3px solid #d62020; text-decoration: none; font-weight: bold;}
|
||||
#feature_signup a:hover {text-decoration: none;}
|
||||
|
||||
|
||||
/* jQuery Nav */
|
||||
#nav { list-style: none; padding:0 10px 0 92px; margin: 0;}
|
||||
#nav li { float:left; margin: 0px 0px 0px 10px;padding:0px 15px 0px 0px;position:relative;font-size:14px;line-height: 1.4;zoom: 1}
|
||||
#nav li:last-child {padding:0;}
|
||||
#nav .current-cat a,
|
||||
#nav .current_page_item a {color: #d62020; border-bottom:5px solid #2565A5;}
|
||||
#nav .current-cat li a,
|
||||
#nav .current_page_item li a {color: #000}
|
||||
#nav .current-cat li a:hover,
|
||||
#nav .current_page_item li a:hover {color: #d62020; border-bottom:5px solid #2565A5;}
|
||||
|
||||
#nav li a { text-transform: uppercase; font-weight: bold; text-decoration: none; color: #000;border:none; text-decoration: none; float: left; border-bottom:5px solid #FFFFFF;}
|
||||
#nav li a:hover {color:#d62020; border-bottom:5px solid #aaa;}
|
||||
#nav li span { width: 12px; height:20px; background: url('arrow-down.gif') no-repeat left 7px;margin:0;padding:0;position: absolute;right:0;top:0}
|
||||
#nav ul {display:none}
|
||||
#nav li span.child { width: 12px; height: 20px; background: url('arrow-right.gif') no-repeat left 10px;margin:0;padding:0;position: absolute;right:0;top:0; }
|
||||
|
||||
#nav li a.rss {background: url(rss_feed.png) right 0px no-repeat; padding:0px 30px 0px 0;}
|
||||
#nav li a.rss:hover {background: url(rss_feed.png) right -24px no-repeat; padding:0px 30px 0px 0; border-bottom:0 solid #aaa;}
|
||||
|
||||
/* jQuery Overrides */
|
||||
.ui-widget { font-size: .8em; }
|
||||
.ui-widget-header {
|
||||
border: 1px solid #0867A3;
|
||||
background: #0867a3 none top right no-repeat;
|
||||
}
|
||||
.ui-progressbar {
|
||||
height: 5px;
|
||||
}
|
||||
.ui-progressbar-value {
|
||||
background-color: green;
|
||||
}
|
||||
.ui-widget-content a{
|
||||
color:blue;
|
||||
text-decoration: underline;
|
||||
margin:0;
|
||||
}
|
||||
.ui-widget-content p {
|
||||
margin:0 0 10px 10px;
|
||||
|
||||
}
|
||||
|
||||
.column { width: 470px; float: left; padding-bottom: 50px; padding-left: 5px; }
|
||||
.portlet { margin: 0 1em 1em 0; }
|
||||
|
||||
.portlet-header { margin: 0.3em; padding: 7px 7px 7px 7px; }
|
||||
.portlet-header .ui-icon { float: right; margin-top: -2px; cursor:pointer; cursor:hand;}
|
||||
.portlet-content { padding: 0.4em; }
|
||||
.ui-sortable-placeholder { border: 1px dotted black; visibility: visible !important; height: 50px !important; }
|
||||
.ui-sortable-placeholder * { visibility: hidden; }
|
||||
|
||||
.buttonlets {
|
||||
float:left;
|
||||
overflow:hidden;
|
||||
max-width:95px;
|
||||
}
|
||||
|
||||
.tweetButton {
|
||||
width:80px;
|
||||
}
|
||||
.facebookButton {
|
||||
width:85px;
|
||||
}
|
||||
.googleplusButton {
|
||||
width:70px;
|
||||
}
|
||||
|
||||
.padded {
|
||||
margin-top:2px;
|
||||
}
|
||||
|
||||
.ui-button {
|
||||
height: 19px;
|
||||
line-height:1em;
|
||||
}
|
||||
.ui-button-text-only .ui-button-text {
|
||||
padding: 0.2em 1em 0.2em 0.4em ;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.ui-state-active {
|
||||
border: 1px solid #2565a5;
|
||||
background: #ddd none 50% 50% repeat-x;
|
||||
color: #2565a5;
|
||||
}
|
||||
|
||||
.ui-state-default {
|
||||
border: 1px solid #ccc;
|
||||
color: #2565a5;
|
||||
}
|
||||
|
||||
.ui-state-hover {
|
||||
background: #ccc none 50% 50% repeat-x;
|
||||
}
|
||||
|
||||
.topLink {
|
||||
font-size: 16px;
|
||||
|
||||
}
|
||||
.developerlink {
|
||||
|
||||
padding:7px;
|
||||
margin-left:10px;
|
||||
border-bottom-left-radius: 15px;
|
||||
border-bottom-right-radius: 15px;
|
||||
-webkit-border-bottom-left-radius: 15px;
|
||||
-webkit-border-bottom-right-radius: 15px;
|
||||
-moz-border-bottom-left-radius: 15px;
|
||||
-moz-border-bottom-right-radius: 15px;
|
||||
background-color:#aaa;
|
||||
|
||||
}
|
||||
|
||||
.addpluginlink {
|
||||
color:white;
|
||||
padding:7px;
|
||||
margin-left:10px;
|
||||
border-radius: 5px;
|
||||
-webkit-border-radius: 5px;
|
||||
-moz-border-radius: 5px;
|
||||
background-color:#aaa;
|
||||
cursor:pointer;
|
||||
}
|
||||
.addpluginlink:hover {
|
||||
background-color: #ccc;
|
||||
}
|
||||
|
||||
.pluginrectangle {
|
||||
display:none;
|
||||
background-color:#aaa;
|
||||
border-radius:5px;
|
||||
position:absolute;
|
||||
height:70%;
|
||||
width:100%;
|
||||
top:30px;
|
||||
right:0;
|
||||
}
|
||||
|
||||
|
||||
.developerlink:hover {
|
||||
background-color: #ccc;
|
||||
}
|
||||
.developerlink a {
|
||||
color : white;
|
||||
text-decoration:none;
|
||||
}
|
||||
|
||||
.channelInfo {
|
||||
max-width: 500px;
|
||||
}
|
||||
.channelDescription {
|
||||
width: 450px;
|
||||
height: 60px;
|
||||
|
||||
}
|
||||
|
||||
.socialButtons {
|
||||
float:right;
|
||||
display:inline;
|
||||
|
||||
}
|
||||
|
||||
.dev-info-dialog {
|
||||
border : 2px solid #bbb;
|
||||
|
||||
}
|
||||
.dev-info-dialog .ui-dialog-titlebar {
|
||||
background-color: #aaa;
|
||||
color : white;
|
||||
}
|
||||
|
||||
|
||||
.dev-info-dialog a {
|
||||
text-decoration : underline;
|
||||
}
|
||||
|
||||
.ui-tabs {
|
||||
height:100%;
|
||||
}
|
||||
.dev-info-dialog ul {
|
||||
/* display: inline; */
|
||||
list-style-type: none;
|
||||
background-color: whitesmoke;
|
||||
}
|
||||
#devInfo {
|
||||
font-size: 1.2em;
|
||||
}
|
||||
.dev-info-dialog ul li {
|
||||
display: inline;
|
||||
list-style-type: none;
|
||||
}
|
||||
|
||||
.dev_info_table1 {
|
||||
margin-bottom:10px;
|
||||
}
|
||||
|
||||
.dev_info_table2 {
|
||||
margin-left: 10px;
|
||||
}
|
||||
.dev_info_table2 td {
|
||||
max-width:300px;
|
||||
}
|
||||
.watchButtonLabel {
|
||||
white-space: nowrap;
|
||||
padding-right:10px;
|
||||
}
|
||||
|
||||
.watchButtonImage {
|
||||
float:left;background-position:left;background-image:url('eye.png');background-repeat:no-repeat;width:19px;height:16px;
|
||||
}
|
||||
|
||||
.watchButtonPadding {
|
||||
padding-right : 5px;
|
||||
}
|
||||
|
||||
.chartOptions {
|
||||
display: none;
|
||||
}
|
||||
.fade {
|
||||
display: none;
|
||||
font-size : 1.2em;
|
||||
}
|
||||
.ui-state-hover, .ui-widget-content .ui-state-hover, .ui-widget-header .ui-state-hover, .ui-state-focus, .ui-widget-content .ui-state-focus, .ui-widget-header .ui-state-focus { border: 1px solid #d82020; background: #f5cece url(images/ui-bg_glass_65_ffffff_1x400.png) 50% 50% repeat-x; font-weight: bold; color: #d82020; }
|
||||
.ui-state-hover a, .ui-state-hover a:hover { color: #d82020; text-decoration: none; }
|
||||
.ui-state-active, .ui-widget-content .ui-state-active, .ui-widget-header .ui-state-active { border: 1px solid #d82020; background: #f5cece url(images/ui-bg_glass_65_ffffff_1x400.png) 50% 50% repeat-x; font-weight: bold; color: #d82020; }
|
||||
.ui-state-active a, .ui-state-active a:link, .ui-state-active a:visited { color: #d82020; text-decoration: none; }
|
||||
.ui-widget :active { outline: none; }
|
||||
|
||||
.ui-tabs .ui-tabs-panel {
|
||||
padding-top:0;
|
||||
}
|
||||
|
||||
.addportlet {
|
||||
padding:5px;
|
||||
width:50px;
|
||||
margin:5px;
|
||||
border-radius:5px;
|
||||
border : 2px solid red;
|
||||
cursor: pointer;
|
||||
display:inline-block;
|
||||
}
|
||||
|
||||
.channel_stats_location {
|
||||
clear:right; float:right;width:475px;padding-top:23px
|
||||
}
|
||||
.channel_stats_text {
|
||||
font-weight:bold;font-size:1.2em
|
||||
}
|
||||
.channel_time_text {
|
||||
color:black;font-size:1em;
|
||||
}
|
||||
|
||||
|
BIN
app/assets/stylesheets/images/ui-bg_diagonals-thick_18_b81900_40x40.png
Executable file
After Width: | Height: | Size: 260 B |
BIN
app/assets/stylesheets/images/ui-bg_diagonals-thick_20_666666_40x40.png
Executable file
After Width: | Height: | Size: 251 B |
BIN
app/assets/stylesheets/images/ui-bg_flat_10_000000_40x100.png
Executable file
After Width: | Height: | Size: 178 B |
BIN
app/assets/stylesheets/images/ui-bg_glass_100_f6f6f6_1x400.png
Executable file
After Width: | Height: | Size: 104 B |
BIN
app/assets/stylesheets/images/ui-bg_glass_100_fdf5ce_1x400.png
Executable file
After Width: | Height: | Size: 125 B |
BIN
app/assets/stylesheets/images/ui-bg_glass_65_ffffff_1x400.png
Executable file
After Width: | Height: | Size: 105 B |
BIN
app/assets/stylesheets/images/ui-bg_gloss-wave_35_f6a828_500x100.png
Executable file
After Width: | Height: | Size: 3.7 KiB |
BIN
app/assets/stylesheets/images/ui-bg_highlight-soft_100_eeeeee_1x100.png
Executable file
After Width: | Height: | Size: 90 B |
BIN
app/assets/stylesheets/images/ui-bg_highlight-soft_75_ffe45c_1x100.png
Executable file
After Width: | Height: | Size: 129 B |
BIN
app/assets/stylesheets/images/ui-icons_222222_256x240.png
Executable file
After Width: | Height: | Size: 4.3 KiB |
BIN
app/assets/stylesheets/images/ui-icons_228ef1_256x240.png
Executable file
After Width: | Height: | Size: 4.3 KiB |
BIN
app/assets/stylesheets/images/ui-icons_ef8c08_256x240.png
Executable file
After Width: | Height: | Size: 4.3 KiB |
BIN
app/assets/stylesheets/images/ui-icons_ffd27a_256x240.png
Executable file
After Width: | Height: | Size: 4.3 KiB |
BIN
app/assets/stylesheets/images/ui-icons_ffffff_256x240.png
Executable file
After Width: | Height: | Size: 4.3 KiB |
563
app/assets/stylesheets/jquery-ui-1.8.24.custom.css
vendored
Executable file
@ -0,0 +1,563 @@
|
||||
/*!
|
||||
* jQuery UI CSS Framework 1.8.24
|
||||
*
|
||||
* Copyright 2012, AUTHORS.txt (http://jqueryui.com/about)
|
||||
* Dual licensed under the MIT or GPL Version 2 licenses.
|
||||
* http://jquery.org/license
|
||||
*
|
||||
* http://docs.jquery.com/UI/Theming/API
|
||||
*/
|
||||
|
||||
/* Layout helpers
|
||||
----------------------------------*/
|
||||
.ui-helper-hidden { display: none; }
|
||||
.ui-helper-hidden-accessible { position: absolute !important; clip: rect(1px 1px 1px 1px); clip: rect(1px,1px,1px,1px); }
|
||||
.ui-helper-reset { margin: 0; padding: 0; border: 0; outline: 0; line-height: 1.3; text-decoration: none; font-size: 100%; list-style: none; }
|
||||
.ui-helper-clearfix:before, .ui-helper-clearfix:after { content: ""; display: table; }
|
||||
.ui-helper-clearfix:after { clear: both; }
|
||||
.ui-helper-clearfix { zoom: 1; }
|
||||
.ui-helper-zfix { width: 100%; height: 100%; top: 0; left: 0; position: absolute; opacity: 0; filter:Alpha(Opacity=0); }
|
||||
|
||||
|
||||
/* Interaction Cues
|
||||
----------------------------------*/
|
||||
.ui-state-disabled { cursor: default !important; }
|
||||
|
||||
|
||||
/* Icons
|
||||
----------------------------------*/
|
||||
|
||||
/* states and images */
|
||||
.ui-icon { display: block; text-indent: -99999px; overflow: hidden; background-repeat: no-repeat; }
|
||||
|
||||
|
||||
/* Misc visuals
|
||||
----------------------------------*/
|
||||
|
||||
/* Overlays */
|
||||
.ui-widget-overlay { position: absolute; top: 0; left: 0; width: 100%; height: 100%; }
|
||||
|
||||
|
||||
/*!
|
||||
* jQuery UI CSS Framework 1.8.24
|
||||
*
|
||||
* Copyright 2012, AUTHORS.txt (http://jqueryui.com/about)
|
||||
* Dual licensed under the MIT or GPL Version 2 licenses.
|
||||
* http://jquery.org/license
|
||||
*
|
||||
* http://docs.jquery.com/UI/Theming/API
|
||||
*
|
||||
* To view and modify this theme, visit http://jqueryui.com/themeroller/?ctl=themeroller&ffDefault=Trebuchet%20MS,%20Tahoma,%20Verdana,%20Arial,%20sans-serif&fwDefault=bold&fsDefault=1.1em&cornerRadius=4px&bgColorHeader=f6a828&bgTextureHeader=12_gloss_wave.png&bgImgOpacityHeader=35&borderColorHeader=e78f08&fcHeader=ffffff&iconColorHeader=ffffff&bgColorContent=eeeeee&bgTextureContent=03_highlight_soft.png&bgImgOpacityContent=100&borderColorContent=dddddd&fcContent=333333&iconColorContent=222222&bgColorDefault=f6f6f6&bgTextureDefault=02_glass.png&bgImgOpacityDefault=100&borderColorDefault=cccccc&fcDefault=1c94c4&iconColorDefault=ef8c08&bgColorHover=fdf5ce&bgTextureHover=02_glass.png&bgImgOpacityHover=100&borderColorHover=fbcb09&fcHover=c77405&iconColorHover=ef8c08&bgColorActive=ffffff&bgTextureActive=02_glass.png&bgImgOpacityActive=65&borderColorActive=fbd850&fcActive=eb8f00&iconColorActive=ef8c08&bgColorHighlight=ffe45c&bgTextureHighlight=03_highlight_soft.png&bgImgOpacityHighlight=75&borderColorHighlight=fed22f&fcHighlight=363636&iconColorHighlight=228ef1&bgColorError=b81900&bgTextureError=08_diagonals_thick.png&bgImgOpacityError=18&borderColorError=cd0a0a&fcError=ffffff&iconColorError=ffd27a&bgColorOverlay=666666&bgTextureOverlay=08_diagonals_thick.png&bgImgOpacityOverlay=20&opacityOverlay=50&bgColorShadow=000000&bgTextureShadow=01_flat.png&bgImgOpacityShadow=10&opacityShadow=20&thicknessShadow=5px&offsetTopShadow=-5px&offsetLeftShadow=-5px&cornerRadiusShadow=5px
|
||||
*/
|
||||
|
||||
|
||||
/* Component containers
|
||||
----------------------------------*/
|
||||
.ui-widget { font-family: Trebuchet MS, Tahoma, Verdana, Arial, sans-serif; font-size: 1.1em; }
|
||||
.ui-widget .ui-widget { font-size: 1em; }
|
||||
.ui-widget input, .ui-widget select, .ui-widget textarea, .ui-widget button { font-family: Trebuchet MS, Tahoma, Verdana, Arial, sans-serif; font-size: 1em; }
|
||||
.ui-widget-content { border: 1px solid #dddddd; background: #eeeeee url(images/ui-bg_highlight-soft_100_eeeeee_1x100.png) 50% top repeat-x; color: #333333; }
|
||||
.ui-widget-content a { color: #333333; }
|
||||
.ui-widget-header { border: 1px solid #e78f08; background: #f6a828 url(images/ui-bg_gloss-wave_35_f6a828_500x100.png) 50% 50% repeat-x; color: #ffffff; font-weight: bold; }
|
||||
.ui-widget-header a { color: #ffffff; }
|
||||
|
||||
/* Interaction states
|
||||
----------------------------------*/
|
||||
.ui-state-default, .ui-widget-content .ui-state-default, .ui-widget-header .ui-state-default { border: 1px solid #cccccc; background: #f6f6f6 url(images/ui-bg_glass_100_f6f6f6_1x400.png) 50% 50% repeat-x; font-weight: bold; color: #1c94c4; }
|
||||
.ui-state-default a, .ui-state-default a:link, .ui-state-default a:visited { color: #1c94c4; text-decoration: none; }
|
||||
.ui-state-hover, .ui-widget-content .ui-state-hover, .ui-widget-header .ui-state-hover, .ui-state-focus, .ui-widget-content .ui-state-focus, .ui-widget-header .ui-state-focus { border: 1px solid #fbcb09; background: #fdf5ce url(images/ui-bg_glass_100_fdf5ce_1x400.png) 50% 50% repeat-x; font-weight: bold; color: #c77405; }
|
||||
.ui-state-hover a, .ui-state-hover a:hover { color: #c77405; text-decoration: none; }
|
||||
.ui-state-active, .ui-widget-content .ui-state-active, .ui-widget-header .ui-state-active { border: 1px solid #fbd850; background: #ffffff url(images/ui-bg_glass_65_ffffff_1x400.png) 50% 50% repeat-x; font-weight: bold; color: #eb8f00; }
|
||||
.ui-state-active a, .ui-state-active a:link, .ui-state-active a:visited { color: #eb8f00; text-decoration: none; }
|
||||
.ui-widget :active { outline: none; }
|
||||
|
||||
/* Interaction Cues
|
||||
----------------------------------*/
|
||||
.ui-state-highlight, .ui-widget-content .ui-state-highlight, .ui-widget-header .ui-state-highlight {border: 1px solid #fed22f; background: #ffe45c url(images/ui-bg_highlight-soft_75_ffe45c_1x100.png) 50% top repeat-x; color: #363636; }
|
||||
.ui-state-highlight a, .ui-widget-content .ui-state-highlight a,.ui-widget-header .ui-state-highlight a { color: #363636; }
|
||||
.ui-state-error, .ui-widget-content .ui-state-error, .ui-widget-header .ui-state-error {border: 1px solid #cd0a0a; background: #b81900 url(images/ui-bg_diagonals-thick_18_b81900_40x40.png) 50% 50% repeat; color: #ffffff; }
|
||||
.ui-state-error a, .ui-widget-content .ui-state-error a, .ui-widget-header .ui-state-error a { color: #ffffff; }
|
||||
.ui-state-error-text, .ui-widget-content .ui-state-error-text, .ui-widget-header .ui-state-error-text { color: #ffffff; }
|
||||
.ui-priority-primary, .ui-widget-content .ui-priority-primary, .ui-widget-header .ui-priority-primary { font-weight: bold; }
|
||||
.ui-priority-secondary, .ui-widget-content .ui-priority-secondary, .ui-widget-header .ui-priority-secondary { opacity: .7; filter:Alpha(Opacity=70); font-weight: normal; }
|
||||
.ui-state-disabled, .ui-widget-content .ui-state-disabled, .ui-widget-header .ui-state-disabled { opacity: .35; filter:Alpha(Opacity=35); background-image: none; }
|
||||
|
||||
/* Icons
|
||||
----------------------------------*/
|
||||
|
||||
/* states and images */
|
||||
.ui-icon { width: 16px; height: 16px; background-image: url(images/ui-icons_222222_256x240.png); }
|
||||
.ui-widget-content .ui-icon {background-image: url(images/ui-icons_222222_256x240.png); }
|
||||
.ui-widget-header .ui-icon {background-image: url(images/ui-icons_ffffff_256x240.png); }
|
||||
.ui-state-default .ui-icon { background-image: url(images/ui-icons_ef8c08_256x240.png); }
|
||||
.ui-state-hover .ui-icon, .ui-state-focus .ui-icon {background-image: url(images/ui-icons_ef8c08_256x240.png); }
|
||||
.ui-state-active .ui-icon {background-image: url(images/ui-icons_ef8c08_256x240.png); }
|
||||
.ui-state-highlight .ui-icon {background-image: url(images/ui-icons_228ef1_256x240.png); }
|
||||
.ui-state-error .ui-icon, .ui-state-error-text .ui-icon {background-image: url(images/ui-icons_ffd27a_256x240.png); }
|
||||
|
||||
/* positioning */
|
||||
.ui-icon-carat-1-n { background-position: 0 0; }
|
||||
.ui-icon-carat-1-ne { background-position: -16px 0; }
|
||||
.ui-icon-carat-1-e { background-position: -32px 0; }
|
||||
.ui-icon-carat-1-se { background-position: -48px 0; }
|
||||
.ui-icon-carat-1-s { background-position: -64px 0; }
|
||||
.ui-icon-carat-1-sw { background-position: -80px 0; }
|
||||
.ui-icon-carat-1-w { background-position: -96px 0; }
|
||||
.ui-icon-carat-1-nw { background-position: -112px 0; }
|
||||
.ui-icon-carat-2-n-s { background-position: -128px 0; }
|
||||
.ui-icon-carat-2-e-w { background-position: -144px 0; }
|
||||
.ui-icon-triangle-1-n { background-position: 0 -16px; }
|
||||
.ui-icon-triangle-1-ne { background-position: -16px -16px; }
|
||||
.ui-icon-triangle-1-e { background-position: -32px -16px; }
|
||||
.ui-icon-triangle-1-se { background-position: -48px -16px; }
|
||||
.ui-icon-triangle-1-s { background-position: -64px -16px; }
|
||||
.ui-icon-triangle-1-sw { background-position: -80px -16px; }
|
||||
.ui-icon-triangle-1-w { background-position: -96px -16px; }
|
||||
.ui-icon-triangle-1-nw { background-position: -112px -16px; }
|
||||
.ui-icon-triangle-2-n-s { background-position: -128px -16px; }
|
||||
.ui-icon-triangle-2-e-w { background-position: -144px -16px; }
|
||||
.ui-icon-arrow-1-n { background-position: 0 -32px; }
|
||||
.ui-icon-arrow-1-ne { background-position: -16px -32px; }
|
||||
.ui-icon-arrow-1-e { background-position: -32px -32px; }
|
||||
.ui-icon-arrow-1-se { background-position: -48px -32px; }
|
||||
.ui-icon-arrow-1-s { background-position: -64px -32px; }
|
||||
.ui-icon-arrow-1-sw { background-position: -80px -32px; }
|
||||
.ui-icon-arrow-1-w { background-position: -96px -32px; }
|
||||
.ui-icon-arrow-1-nw { background-position: -112px -32px; }
|
||||
.ui-icon-arrow-2-n-s { background-position: -128px -32px; }
|
||||
.ui-icon-arrow-2-ne-sw { background-position: -144px -32px; }
|
||||
.ui-icon-arrow-2-e-w { background-position: -160px -32px; }
|
||||
.ui-icon-arrow-2-se-nw { background-position: -176px -32px; }
|
||||
.ui-icon-arrowstop-1-n { background-position: -192px -32px; }
|
||||
.ui-icon-arrowstop-1-e { background-position: -208px -32px; }
|
||||
.ui-icon-arrowstop-1-s { background-position: -224px -32px; }
|
||||
.ui-icon-arrowstop-1-w { background-position: -240px -32px; }
|
||||
.ui-icon-arrowthick-1-n { background-position: 0 -48px; }
|
||||
.ui-icon-arrowthick-1-ne { background-position: -16px -48px; }
|
||||
.ui-icon-arrowthick-1-e { background-position: -32px -48px; }
|
||||
.ui-icon-arrowthick-1-se { background-position: -48px -48px; }
|
||||
.ui-icon-arrowthick-1-s { background-position: -64px -48px; }
|
||||
.ui-icon-arrowthick-1-sw { background-position: -80px -48px; }
|
||||
.ui-icon-arrowthick-1-w { background-position: -96px -48px; }
|
||||
.ui-icon-arrowthick-1-nw { background-position: -112px -48px; }
|
||||
.ui-icon-arrowthick-2-n-s { background-position: -128px -48px; }
|
||||
.ui-icon-arrowthick-2-ne-sw { background-position: -144px -48px; }
|
||||
.ui-icon-arrowthick-2-e-w { background-position: -160px -48px; }
|
||||
.ui-icon-arrowthick-2-se-nw { background-position: -176px -48px; }
|
||||
.ui-icon-arrowthickstop-1-n { background-position: -192px -48px; }
|
||||
.ui-icon-arrowthickstop-1-e { background-position: -208px -48px; }
|
||||
.ui-icon-arrowthickstop-1-s { background-position: -224px -48px; }
|
||||
.ui-icon-arrowthickstop-1-w { background-position: -240px -48px; }
|
||||
.ui-icon-arrowreturnthick-1-w { background-position: 0 -64px; }
|
||||
.ui-icon-arrowreturnthick-1-n { background-position: -16px -64px; }
|
||||
.ui-icon-arrowreturnthick-1-e { background-position: -32px -64px; }
|
||||
.ui-icon-arrowreturnthick-1-s { background-position: -48px -64px; }
|
||||
.ui-icon-arrowreturn-1-w { background-position: -64px -64px; }
|
||||
.ui-icon-arrowreturn-1-n { background-position: -80px -64px; }
|
||||
.ui-icon-arrowreturn-1-e { background-position: -96px -64px; }
|
||||
.ui-icon-arrowreturn-1-s { background-position: -112px -64px; }
|
||||
.ui-icon-arrowrefresh-1-w { background-position: -128px -64px; }
|
||||
.ui-icon-arrowrefresh-1-n { background-position: -144px -64px; }
|
||||
.ui-icon-arrowrefresh-1-e { background-position: -160px -64px; }
|
||||
.ui-icon-arrowrefresh-1-s { background-position: -176px -64px; }
|
||||
.ui-icon-arrow-4 { background-position: 0 -80px; }
|
||||
.ui-icon-arrow-4-diag { background-position: -16px -80px; }
|
||||
.ui-icon-extlink { background-position: -32px -80px; }
|
||||
.ui-icon-newwin { background-position: -48px -80px; }
|
||||
.ui-icon-refresh { background-position: -64px -80px; }
|
||||
.ui-icon-shuffle { background-position: -80px -80px; }
|
||||
.ui-icon-transfer-e-w { background-position: -96px -80px; }
|
||||
.ui-icon-transferthick-e-w { background-position: -112px -80px; }
|
||||
.ui-icon-folder-collapsed { background-position: 0 -96px; }
|
||||
.ui-icon-folder-open { background-position: -16px -96px; }
|
||||
.ui-icon-document { background-position: -32px -96px; }
|
||||
.ui-icon-document-b { background-position: -48px -96px; }
|
||||
.ui-icon-note { background-position: -64px -96px; }
|
||||
.ui-icon-mail-closed { background-position: -80px -96px; }
|
||||
.ui-icon-mail-open { background-position: -96px -96px; }
|
||||
.ui-icon-suitcase { background-position: -112px -96px; }
|
||||
.ui-icon-comment { background-position: -128px -96px; }
|
||||
.ui-icon-person { background-position: -144px -96px; }
|
||||
.ui-icon-print { background-position: -160px -96px; }
|
||||
.ui-icon-trash { background-position: -176px -96px; }
|
||||
.ui-icon-locked { background-position: -192px -96px; }
|
||||
.ui-icon-unlocked { background-position: -208px -96px; }
|
||||
.ui-icon-bookmark { background-position: -224px -96px; }
|
||||
.ui-icon-tag { background-position: -240px -96px; }
|
||||
.ui-icon-home { background-position: 0 -112px; }
|
||||
.ui-icon-flag { background-position: -16px -112px; }
|
||||
.ui-icon-calendar { background-position: -32px -112px; }
|
||||
.ui-icon-cart { background-position: -48px -112px; }
|
||||
.ui-icon-pencil { background-position: -64px -112px; }
|
||||
.ui-icon-clock { background-position: -80px -112px; }
|
||||
.ui-icon-disk { background-position: -96px -112px; }
|
||||
.ui-icon-calculator { background-position: -112px -112px; }
|
||||
.ui-icon-zoomin { background-position: -128px -112px; }
|
||||
.ui-icon-zoomout { background-position: -144px -112px; }
|
||||
.ui-icon-search { background-position: -160px -112px; }
|
||||
.ui-icon-wrench { background-position: -176px -112px; }
|
||||
.ui-icon-gear { background-position: -192px -112px; }
|
||||
.ui-icon-heart { background-position: -208px -112px; }
|
||||
.ui-icon-star { background-position: -224px -112px; }
|
||||
.ui-icon-link { background-position: -240px -112px; }
|
||||
.ui-icon-cancel { background-position: 0 -128px; }
|
||||
.ui-icon-plus { background-position: -16px -128px; }
|
||||
.ui-icon-plusthick { background-position: -32px -128px; }
|
||||
.ui-icon-minus { background-position: -48px -128px; }
|
||||
.ui-icon-minusthick { background-position: -64px -128px; }
|
||||
.ui-icon-close { background-position: -80px -128px; }
|
||||
.ui-icon-closethick { background-position: -96px -128px; }
|
||||
.ui-icon-key { background-position: -112px -128px; }
|
||||
.ui-icon-lightbulb { background-position: -128px -128px; }
|
||||
.ui-icon-scissors { background-position: -144px -128px; }
|
||||
.ui-icon-clipboard { background-position: -160px -128px; }
|
||||
.ui-icon-copy { background-position: -176px -128px; }
|
||||
.ui-icon-contact { background-position: -192px -128px; }
|
||||
.ui-icon-image { background-position: -208px -128px; }
|
||||
.ui-icon-video { background-position: -224px -128px; }
|
||||
.ui-icon-script { background-position: -240px -128px; }
|
||||
.ui-icon-alert { background-position: 0 -144px; }
|
||||
.ui-icon-info { background-position: -16px -144px; }
|
||||
.ui-icon-notice { background-position: -32px -144px; }
|
||||
.ui-icon-help { background-position: -48px -144px; }
|
||||
.ui-icon-check { background-position: -64px -144px; }
|
||||
.ui-icon-bullet { background-position: -80px -144px; }
|
||||
.ui-icon-radio-off { background-position: -96px -144px; }
|
||||
.ui-icon-radio-on { background-position: -112px -144px; }
|
||||
.ui-icon-pin-w { background-position: -128px -144px; }
|
||||
.ui-icon-pin-s { background-position: -144px -144px; }
|
||||
.ui-icon-play { background-position: 0 -160px; }
|
||||
.ui-icon-pause { background-position: -16px -160px; }
|
||||
.ui-icon-seek-next { background-position: -32px -160px; }
|
||||
.ui-icon-seek-prev { background-position: -48px -160px; }
|
||||
.ui-icon-seek-end { background-position: -64px -160px; }
|
||||
.ui-icon-seek-start { background-position: -80px -160px; }
|
||||
/* ui-icon-seek-first is deprecated, use ui-icon-seek-start instead */
|
||||
.ui-icon-seek-first { background-position: -80px -160px; }
|
||||
.ui-icon-stop { background-position: -96px -160px; }
|
||||
.ui-icon-eject { background-position: -112px -160px; }
|
||||
.ui-icon-volume-off { background-position: -128px -160px; }
|
||||
.ui-icon-volume-on { background-position: -144px -160px; }
|
||||
.ui-icon-power { background-position: 0 -176px; }
|
||||
.ui-icon-signal-diag { background-position: -16px -176px; }
|
||||
.ui-icon-signal { background-position: -32px -176px; }
|
||||
.ui-icon-battery-0 { background-position: -48px -176px; }
|
||||
.ui-icon-battery-1 { background-position: -64px -176px; }
|
||||
.ui-icon-battery-2 { background-position: -80px -176px; }
|
||||
.ui-icon-battery-3 { background-position: -96px -176px; }
|
||||
.ui-icon-circle-plus { background-position: 0 -192px; }
|
||||
.ui-icon-circle-minus { background-position: -16px -192px; }
|
||||
.ui-icon-circle-close { background-position: -32px -192px; }
|
||||
.ui-icon-circle-triangle-e { background-position: -48px -192px; }
|
||||
.ui-icon-circle-triangle-s { background-position: -64px -192px; }
|
||||
.ui-icon-circle-triangle-w { background-position: -80px -192px; }
|
||||
.ui-icon-circle-triangle-n { background-position: -96px -192px; }
|
||||
.ui-icon-circle-arrow-e { background-position: -112px -192px; }
|
||||
.ui-icon-circle-arrow-s { background-position: -128px -192px; }
|
||||
.ui-icon-circle-arrow-w { background-position: -144px -192px; }
|
||||
.ui-icon-circle-arrow-n { background-position: -160px -192px; }
|
||||
.ui-icon-circle-zoomin { background-position: -176px -192px; }
|
||||
.ui-icon-circle-zoomout { background-position: -192px -192px; }
|
||||
.ui-icon-circle-check { background-position: -208px -192px; }
|
||||
.ui-icon-circlesmall-plus { background-position: 0 -208px; }
|
||||
.ui-icon-circlesmall-minus { background-position: -16px -208px; }
|
||||
.ui-icon-circlesmall-close { background-position: -32px -208px; }
|
||||
.ui-icon-squaresmall-plus { background-position: -48px -208px; }
|
||||
.ui-icon-squaresmall-minus { background-position: -64px -208px; }
|
||||
.ui-icon-squaresmall-close { background-position: -80px -208px; }
|
||||
.ui-icon-grip-dotted-vertical { background-position: 0 -224px; }
|
||||
.ui-icon-grip-dotted-horizontal { background-position: -16px -224px; }
|
||||
.ui-icon-grip-solid-vertical { background-position: -32px -224px; }
|
||||
.ui-icon-grip-solid-horizontal { background-position: -48px -224px; }
|
||||
.ui-icon-gripsmall-diagonal-se { background-position: -64px -224px; }
|
||||
.ui-icon-grip-diagonal-se { background-position: -80px -224px; }
|
||||
|
||||
|
||||
/* Misc visuals
|
||||
----------------------------------*/
|
||||
|
||||
/* Corner radius */
|
||||
.ui-corner-all, .ui-corner-top, .ui-corner-left, .ui-corner-tl { -moz-border-radius-topleft: 4px; -webkit-border-top-left-radius: 4px; -khtml-border-top-left-radius: 4px; border-top-left-radius: 4px; }
|
||||
.ui-corner-all, .ui-corner-top, .ui-corner-right, .ui-corner-tr { -moz-border-radius-topright: 4px; -webkit-border-top-right-radius: 4px; -khtml-border-top-right-radius: 4px; border-top-right-radius: 4px; }
|
||||
.ui-corner-all, .ui-corner-bottom, .ui-corner-left, .ui-corner-bl { -moz-border-radius-bottomleft: 4px; -webkit-border-bottom-left-radius: 4px; -khtml-border-bottom-left-radius: 4px; border-bottom-left-radius: 4px; }
|
||||
.ui-corner-all, .ui-corner-bottom, .ui-corner-right, .ui-corner-br { -moz-border-radius-bottomright: 4px; -webkit-border-bottom-right-radius: 4px; -khtml-border-bottom-right-radius: 4px; border-bottom-right-radius: 4px; }
|
||||
|
||||
/* Overlays */
|
||||
.ui-widget-overlay { background: #666666 url(images/ui-bg_diagonals-thick_20_666666_40x40.png) 50% 50% repeat; opacity: .50;filter:Alpha(Opacity=50); }
|
||||
.ui-widget-shadow { margin: -5px 0 0 -5px; padding: 5px; background: #000000 url(images/ui-bg_flat_10_000000_40x100.png) 50% 50% repeat-x; opacity: .20;filter:Alpha(Opacity=20); -moz-border-radius: 5px; -khtml-border-radius: 5px; -webkit-border-radius: 5px; border-radius: 5px; }/*!
|
||||
* jQuery UI Resizable 1.8.24
|
||||
*
|
||||
* Copyright 2012, AUTHORS.txt (http://jqueryui.com/about)
|
||||
* Dual licensed under the MIT or GPL Version 2 licenses.
|
||||
* http://jquery.org/license
|
||||
*
|
||||
* http://docs.jquery.com/UI/Resizable#theming
|
||||
*/
|
||||
.ui-resizable { position: relative;}
|
||||
.ui-resizable-handle { position: absolute;font-size: 0.1px; display: block; }
|
||||
.ui-resizable-disabled .ui-resizable-handle, .ui-resizable-autohide .ui-resizable-handle { display: none; }
|
||||
.ui-resizable-n { cursor: n-resize; height: 7px; width: 100%; top: -5px; left: 0; }
|
||||
.ui-resizable-s { cursor: s-resize; height: 7px; width: 100%; bottom: -5px; left: 0; }
|
||||
.ui-resizable-e { cursor: e-resize; width: 7px; right: -5px; top: 0; height: 100%; }
|
||||
.ui-resizable-w { cursor: w-resize; width: 7px; left: -5px; top: 0; height: 100%; }
|
||||
.ui-resizable-se { cursor: se-resize; width: 12px; height: 12px; right: 1px; bottom: 1px; }
|
||||
.ui-resizable-sw { cursor: sw-resize; width: 9px; height: 9px; left: -5px; bottom: -5px; }
|
||||
.ui-resizable-nw { cursor: nw-resize; width: 9px; height: 9px; left: -5px; top: -5px; }
|
||||
.ui-resizable-ne { cursor: ne-resize; width: 9px; height: 9px; right: -5px; top: -5px;}/*!
|
||||
* jQuery UI Selectable 1.8.24
|
||||
*
|
||||
* Copyright 2012, AUTHORS.txt (http://jqueryui.com/about)
|
||||
* Dual licensed under the MIT or GPL Version 2 licenses.
|
||||
* http://jquery.org/license
|
||||
*
|
||||
* http://docs.jquery.com/UI/Selectable#theming
|
||||
*/
|
||||
.ui-selectable-helper { position: absolute; z-index: 100; border:1px dotted black; }
|
||||
/*!
|
||||
* jQuery UI Accordion 1.8.24
|
||||
*
|
||||
* Copyright 2012, AUTHORS.txt (http://jqueryui.com/about)
|
||||
* Dual licensed under the MIT or GPL Version 2 licenses.
|
||||
* http://jquery.org/license
|
||||
*
|
||||
* http://docs.jquery.com/UI/Accordion#theming
|
||||
*/
|
||||
/* IE/Win - Fix animation bug - #4615 */
|
||||
.ui-accordion { width: 100%; }
|
||||
.ui-accordion .ui-accordion-header { cursor: pointer; position: relative; margin-top: 1px; zoom: 1; }
|
||||
.ui-accordion .ui-accordion-li-fix { display: inline; }
|
||||
.ui-accordion .ui-accordion-header-active { border-bottom: 0 !important; }
|
||||
.ui-accordion .ui-accordion-header a { display: block; font-size: 1em; padding: .5em .5em .5em .7em; }
|
||||
.ui-accordion-icons .ui-accordion-header a { padding-left: 2.2em; }
|
||||
.ui-accordion .ui-accordion-header .ui-icon { position: absolute; left: .5em; top: 50%; margin-top: -8px; }
|
||||
.ui-accordion .ui-accordion-content { padding: 1em 2.2em; border-top: 0; margin-top: -2px; position: relative; top: 1px; margin-bottom: 2px; overflow: auto; display: none; zoom: 1; }
|
||||
.ui-accordion .ui-accordion-content-active { display: block; }
|
||||
/*!
|
||||
* jQuery UI Autocomplete 1.8.24
|
||||
*
|
||||
* Copyright 2012, AUTHORS.txt (http://jqueryui.com/about)
|
||||
* Dual licensed under the MIT or GPL Version 2 licenses.
|
||||
* http://jquery.org/license
|
||||
*
|
||||
* http://docs.jquery.com/UI/Autocomplete#theming
|
||||
*/
|
||||
.ui-autocomplete { position: absolute; cursor: default; }
|
||||
|
||||
/* workarounds */
|
||||
* html .ui-autocomplete { width:1px; } /* without this, the menu expands to 100% in IE6 */
|
||||
|
||||
/*
|
||||
* jQuery UI Menu 1.8.24
|
||||
*
|
||||
* Copyright 2010, AUTHORS.txt (http://jqueryui.com/about)
|
||||
* Dual licensed under the MIT or GPL Version 2 licenses.
|
||||
* http://jquery.org/license
|
||||
*
|
||||
* http://docs.jquery.com/UI/Menu#theming
|
||||
*/
|
||||
.ui-menu {
|
||||
list-style:none;
|
||||
padding: 2px;
|
||||
margin: 0;
|
||||
display:block;
|
||||
float: left;
|
||||
}
|
||||
.ui-menu .ui-menu {
|
||||
margin-top: -3px;
|
||||
}
|
||||
.ui-menu .ui-menu-item {
|
||||
margin:0;
|
||||
padding: 0;
|
||||
zoom: 1;
|
||||
float: left;
|
||||
clear: left;
|
||||
width: 100%;
|
||||
}
|
||||
.ui-menu .ui-menu-item a {
|
||||
text-decoration:none;
|
||||
display:block;
|
||||
padding:.2em .4em;
|
||||
line-height:1.5;
|
||||
zoom:1;
|
||||
}
|
||||
.ui-menu .ui-menu-item a.ui-state-hover,
|
||||
.ui-menu .ui-menu-item a.ui-state-active {
|
||||
font-weight: normal;
|
||||
margin: -1px;
|
||||
}
|
||||
/*!
|
||||
* jQuery UI Button 1.8.24
|
||||
*
|
||||
* Copyright 2012, AUTHORS.txt (http://jqueryui.com/about)
|
||||
* Dual licensed under the MIT or GPL Version 2 licenses.
|
||||
* http://jquery.org/license
|
||||
*
|
||||
* http://docs.jquery.com/UI/Button#theming
|
||||
*/
|
||||
.ui-button { display: inline-block; position: relative; padding: 0; margin-right: .1em; text-decoration: none !important; cursor: pointer; text-align: center; zoom: 1; overflow: visible; } /* the overflow property removes extra width in IE */
|
||||
.ui-button-icon-only { width: 2.2em; } /* to make room for the icon, a width needs to be set here */
|
||||
button.ui-button-icon-only { width: 2.4em; } /* button elements seem to need a little more width */
|
||||
.ui-button-icons-only { width: 3.4em; }
|
||||
button.ui-button-icons-only { width: 3.7em; }
|
||||
|
||||
/*button text element */
|
||||
.ui-button .ui-button-text { display: block; line-height: 1.4; }
|
||||
.ui-button-text-only .ui-button-text { padding: .4em 1em; }
|
||||
.ui-button-icon-only .ui-button-text, .ui-button-icons-only .ui-button-text { padding: .4em; text-indent: -9999999px; }
|
||||
.ui-button-text-icon-primary .ui-button-text, .ui-button-text-icons .ui-button-text { padding: .4em 1em .4em 2.1em; }
|
||||
.ui-button-text-icon-secondary .ui-button-text, .ui-button-text-icons .ui-button-text { padding: .4em 2.1em .4em 1em; }
|
||||
.ui-button-text-icons .ui-button-text { padding-left: 2.1em; padding-right: 2.1em; }
|
||||
/* no icon support for input elements, provide padding by default */
|
||||
input.ui-button { padding: .4em 1em; }
|
||||
|
||||
/*button icon element(s) */
|
||||
.ui-button-icon-only .ui-icon, .ui-button-text-icon-primary .ui-icon, .ui-button-text-icon-secondary .ui-icon, .ui-button-text-icons .ui-icon, .ui-button-icons-only .ui-icon { position: absolute; top: 50%; margin-top: -8px; }
|
||||
.ui-button-icon-only .ui-icon { left: 50%; margin-left: -8px; }
|
||||
.ui-button-text-icon-primary .ui-button-icon-primary, .ui-button-text-icons .ui-button-icon-primary, .ui-button-icons-only .ui-button-icon-primary { left: .5em; }
|
||||
.ui-button-text-icon-secondary .ui-button-icon-secondary, .ui-button-text-icons .ui-button-icon-secondary, .ui-button-icons-only .ui-button-icon-secondary { right: .5em; }
|
||||
.ui-button-text-icons .ui-button-icon-secondary, .ui-button-icons-only .ui-button-icon-secondary { right: .5em; }
|
||||
|
||||
/*button sets*/
|
||||
.ui-buttonset { margin-right: 7px; }
|
||||
.ui-buttonset .ui-button { margin-left: 0; margin-right: -.3em; }
|
||||
|
||||
/* workarounds */
|
||||
button.ui-button::-moz-focus-inner { border: 0; padding: 0; } /* reset extra padding in Firefox */
|
||||
/*!
|
||||
* jQuery UI Dialog 1.8.24
|
||||
*
|
||||
* Copyright 2012, AUTHORS.txt (http://jqueryui.com/about)
|
||||
* Dual licensed under the MIT or GPL Version 2 licenses.
|
||||
* http://jquery.org/license
|
||||
*
|
||||
* http://docs.jquery.com/UI/Dialog#theming
|
||||
*/
|
||||
.ui-dialog { position: absolute; padding: .2em; width: 300px; overflow: hidden; }
|
||||
.ui-dialog .ui-dialog-titlebar { padding: .4em 1em; position: relative; }
|
||||
.ui-dialog .ui-dialog-title { float: left; margin: .1em 16px .1em 0; }
|
||||
.ui-dialog .ui-dialog-titlebar-close { position: absolute; right: .3em; top: 50%; width: 19px; margin: -10px 0 0 0; padding: 1px; height: 18px; }
|
||||
.ui-dialog .ui-dialog-titlebar-close span { display: block; margin: 1px; }
|
||||
.ui-dialog .ui-dialog-titlebar-close:hover, .ui-dialog .ui-dialog-titlebar-close:focus { padding: 0; }
|
||||
.ui-dialog .ui-dialog-content { position: relative; border: 0; padding: .5em 1em; background: none; overflow: auto; zoom: 1; }
|
||||
.ui-dialog .ui-dialog-buttonpane { text-align: left; border-width: 1px 0 0 0; background-image: none; margin: .5em 0 0 0; padding: .3em 1em .5em .4em; }
|
||||
.ui-dialog .ui-dialog-buttonpane .ui-dialog-buttonset { float: right; }
|
||||
.ui-dialog .ui-dialog-buttonpane button { margin: .5em .4em .5em 0; cursor: pointer; }
|
||||
.ui-dialog .ui-resizable-se { width: 14px; height: 14px; right: 3px; bottom: 3px; }
|
||||
.ui-draggable .ui-dialog-titlebar { cursor: move; }
|
||||
/*!
|
||||
* jQuery UI Slider 1.8.24
|
||||
*
|
||||
* Copyright 2012, AUTHORS.txt (http://jqueryui.com/about)
|
||||
* Dual licensed under the MIT or GPL Version 2 licenses.
|
||||
* http://jquery.org/license
|
||||
*
|
||||
* http://docs.jquery.com/UI/Slider#theming
|
||||
*/
|
||||
.ui-slider { position: relative; text-align: left; }
|
||||
.ui-slider .ui-slider-handle { position: absolute; z-index: 2; width: 1.2em; height: 1.2em; cursor: default; }
|
||||
.ui-slider .ui-slider-range { position: absolute; z-index: 1; font-size: .7em; display: block; border: 0; background-position: 0 0; }
|
||||
|
||||
.ui-slider-horizontal { height: .8em; }
|
||||
.ui-slider-horizontal .ui-slider-handle { top: -.3em; margin-left: -.6em; }
|
||||
.ui-slider-horizontal .ui-slider-range { top: 0; height: 100%; }
|
||||
.ui-slider-horizontal .ui-slider-range-min { left: 0; }
|
||||
.ui-slider-horizontal .ui-slider-range-max { right: 0; }
|
||||
|
||||
.ui-slider-vertical { width: .8em; height: 100px; }
|
||||
.ui-slider-vertical .ui-slider-handle { left: -.3em; margin-left: 0; margin-bottom: -.6em; }
|
||||
.ui-slider-vertical .ui-slider-range { left: 0; width: 100%; }
|
||||
.ui-slider-vertical .ui-slider-range-min { bottom: 0; }
|
||||
.ui-slider-vertical .ui-slider-range-max { top: 0; }/*!
|
||||
* jQuery UI Tabs 1.8.24
|
||||
*
|
||||
* Copyright 2012, AUTHORS.txt (http://jqueryui.com/about)
|
||||
* Dual licensed under the MIT or GPL Version 2 licenses.
|
||||
* http://jquery.org/license
|
||||
*
|
||||
* http://docs.jquery.com/UI/Tabs#theming
|
||||
*/
|
||||
.ui-tabs { position: relative; padding: .2em; zoom: 1; } /* position: relative prevents IE scroll bug (element with position: relative inside container with overflow: auto appear as "fixed") */
|
||||
.ui-tabs .ui-tabs-nav { margin: 0; padding: .2em .2em 0; }
|
||||
.ui-tabs .ui-tabs-nav li { list-style: none; float: left; position: relative; top: 1px; margin: 0 .2em 1px 0; border-bottom: 0 !important; padding: 0; white-space: nowrap; }
|
||||
.ui-tabs .ui-tabs-nav li a { float: left; padding: .5em 1em; text-decoration: none; }
|
||||
.ui-tabs .ui-tabs-nav li.ui-tabs-selected { margin-bottom: 0; padding-bottom: 1px; }
|
||||
.ui-tabs .ui-tabs-nav li.ui-tabs-selected a, .ui-tabs .ui-tabs-nav li.ui-state-disabled a, .ui-tabs .ui-tabs-nav li.ui-state-processing a { cursor: text; }
|
||||
.ui-tabs .ui-tabs-nav li a, .ui-tabs.ui-tabs-collapsible .ui-tabs-nav li.ui-tabs-selected a { cursor: pointer; } /* first selector in group seems obsolete, but required to overcome bug in Opera applying cursor: text overall if defined elsewhere... */
|
||||
.ui-tabs .ui-tabs-panel { display: block; border-width: 0; padding: 1em 1.4em; background: none; }
|
||||
.ui-tabs .ui-tabs-hide { display: none !important; }
|
||||
/*!
|
||||
* jQuery UI Datepicker 1.8.24
|
||||
*
|
||||
* Copyright 2012, AUTHORS.txt (http://jqueryui.com/about)
|
||||
* Dual licensed under the MIT or GPL Version 2 licenses.
|
||||
* http://jquery.org/license
|
||||
*
|
||||
* http://docs.jquery.com/UI/Datepicker#theming
|
||||
*/
|
||||
.ui-datepicker { width: 17em; padding: .2em .2em 0; display: none; }
|
||||
.ui-datepicker .ui-datepicker-header { position:relative; padding:.2em 0; }
|
||||
.ui-datepicker .ui-datepicker-prev, .ui-datepicker .ui-datepicker-next { position:absolute; top: 2px; width: 1.8em; height: 1.8em; }
|
||||
.ui-datepicker .ui-datepicker-prev-hover, .ui-datepicker .ui-datepicker-next-hover { top: 1px; }
|
||||
.ui-datepicker .ui-datepicker-prev { left:2px; }
|
||||
.ui-datepicker .ui-datepicker-next { right:2px; }
|
||||
.ui-datepicker .ui-datepicker-prev-hover { left:1px; }
|
||||
.ui-datepicker .ui-datepicker-next-hover { right:1px; }
|
||||
.ui-datepicker .ui-datepicker-prev span, .ui-datepicker .ui-datepicker-next span { display: block; position: absolute; left: 50%; margin-left: -8px; top: 50%; margin-top: -8px; }
|
||||
.ui-datepicker .ui-datepicker-title { margin: 0 2.3em; line-height: 1.8em; text-align: center; }
|
||||
.ui-datepicker .ui-datepicker-title select { font-size:1em; margin:1px 0; }
|
||||
.ui-datepicker select.ui-datepicker-month-year {width: 100%;}
|
||||
.ui-datepicker select.ui-datepicker-month,
|
||||
.ui-datepicker select.ui-datepicker-year { width: 49%;}
|
||||
.ui-datepicker table {width: 100%; font-size: .9em; border-collapse: collapse; margin:0 0 .4em; }
|
||||
.ui-datepicker th { padding: .7em .3em; text-align: center; font-weight: bold; border: 0; }
|
||||
.ui-datepicker td { border: 0; padding: 1px; }
|
||||
.ui-datepicker td span, .ui-datepicker td a { display: block; padding: .2em; text-align: right; text-decoration: none; }
|
||||
.ui-datepicker .ui-datepicker-buttonpane { background-image: none; margin: .7em 0 0 0; padding:0 .2em; border-left: 0; border-right: 0; border-bottom: 0; }
|
||||
.ui-datepicker .ui-datepicker-buttonpane button { float: right; margin: .5em .2em .4em; cursor: pointer; padding: .2em .6em .3em .6em; width:auto; overflow:visible; }
|
||||
.ui-datepicker .ui-datepicker-buttonpane button.ui-datepicker-current { float:left; }
|
||||
|
||||
/* with multiple calendars */
|
||||
.ui-datepicker.ui-datepicker-multi { width:auto; }
|
||||
.ui-datepicker-multi .ui-datepicker-group { float:left; }
|
||||
.ui-datepicker-multi .ui-datepicker-group table { width:95%; margin:0 auto .4em; }
|
||||
.ui-datepicker-multi-2 .ui-datepicker-group { width:50%; }
|
||||
.ui-datepicker-multi-3 .ui-datepicker-group { width:33.3%; }
|
||||
.ui-datepicker-multi-4 .ui-datepicker-group { width:25%; }
|
||||
.ui-datepicker-multi .ui-datepicker-group-last .ui-datepicker-header { border-left-width:0; }
|
||||
.ui-datepicker-multi .ui-datepicker-group-middle .ui-datepicker-header { border-left-width:0; }
|
||||
.ui-datepicker-multi .ui-datepicker-buttonpane { clear:left; }
|
||||
.ui-datepicker-row-break { clear:both; width:100%; font-size:0em; }
|
||||
|
||||
/* RTL support */
|
||||
.ui-datepicker-rtl { direction: rtl; }
|
||||
.ui-datepicker-rtl .ui-datepicker-prev { right: 2px; left: auto; }
|
||||
.ui-datepicker-rtl .ui-datepicker-next { left: 2px; right: auto; }
|
||||
.ui-datepicker-rtl .ui-datepicker-prev:hover { right: 1px; left: auto; }
|
||||
.ui-datepicker-rtl .ui-datepicker-next:hover { left: 1px; right: auto; }
|
||||
.ui-datepicker-rtl .ui-datepicker-buttonpane { clear:right; }
|
||||
.ui-datepicker-rtl .ui-datepicker-buttonpane button { float: left; }
|
||||
.ui-datepicker-rtl .ui-datepicker-buttonpane button.ui-datepicker-current { float:right; }
|
||||
.ui-datepicker-rtl .ui-datepicker-group { float:right; }
|
||||
.ui-datepicker-rtl .ui-datepicker-group-last .ui-datepicker-header { border-right-width:0; border-left-width:1px; }
|
||||
.ui-datepicker-rtl .ui-datepicker-group-middle .ui-datepicker-header { border-right-width:0; border-left-width:1px; }
|
||||
|
||||
/* IE6 IFRAME FIX (taken from datepicker 1.5.3 */
|
||||
.ui-datepicker-cover {
|
||||
position: absolute; /*must have*/
|
||||
z-index: -1; /*must have*/
|
||||
filter: mask(); /*must have*/
|
||||
top: -4px; /*must have*/
|
||||
left: -4px; /*must have*/
|
||||
width: 200px; /*must have*/
|
||||
height: 200px; /*must have*/
|
||||
}/*!
|
||||
* jQuery UI Progressbar 1.8.24
|
||||
*
|
||||
* Copyright 2012, AUTHORS.txt (http://jqueryui.com/about)
|
||||
* Dual licensed under the MIT or GPL Version 2 licenses.
|
||||
* http://jquery.org/license
|
||||
*
|
||||
* http://docs.jquery.com/UI/Progressbar#theming
|
||||
*/
|
||||
.ui-progressbar { height:2em; text-align: left; overflow: hidden; }
|
||||
.ui-progressbar .ui-progressbar-value {margin: -1px; height:100%; }
|
31
app/assets/stylesheets/prettify.css
Normal file
@ -0,0 +1,31 @@
|
||||
.com { color: #93a1a1; }
|
||||
.lit { color: #195f91; }
|
||||
.pun, .opn, .clo { color: #93a1a1; }
|
||||
.fun { color: #dc322f; }
|
||||
.str, .atv { color: #D14; }
|
||||
.kwd, .prettyprint .tag { color: #1e347b; }
|
||||
.typ, .atn, .dec, .var { color: teal; }
|
||||
.pln { color: #48484c; }
|
||||
|
||||
.prettyprint {
|
||||
padding: 8px;
|
||||
background-color: #f7f7f9;
|
||||
border: 1px solid #e1e1e8;
|
||||
}
|
||||
.prettyprint.linenums {
|
||||
-webkit-box-shadow: inset 40px 0 0 #fbfbfc, inset 41px 0 0 #ececf0;
|
||||
-moz-box-shadow: inset 40px 0 0 #fbfbfc, inset 41px 0 0 #ececf0;
|
||||
box-shadow: inset 40px 0 0 #fbfbfc, inset 41px 0 0 #ececf0;
|
||||
}
|
||||
|
||||
/* Specify class=linenums on a pre to get line numbering */
|
||||
ol.linenums {
|
||||
margin: 0 0 0 33px; /* IE indents via margin-left */
|
||||
}
|
||||
ol.linenums li {
|
||||
padding-left: 12px;
|
||||
color: #bebec5;
|
||||
line-height: 20px;
|
||||
text-shadow: 0 1px 0 #fff;
|
||||
}
|
||||
|
28
app/assets/stylesheets/sidebar.css
Normal file
@ -0,0 +1,28 @@
|
||||
.affix-top,.affix{
|
||||
position: static;
|
||||
}
|
||||
|
||||
#bootstrap-sidebar {
|
||||
background-color: #eee;
|
||||
border: 1px solid #bbb;
|
||||
border-radius: 5px;
|
||||
padding: 2px 0;
|
||||
}
|
||||
|
||||
#bootstrap-sidebar li a:hover {
|
||||
background-color: #fafafa;
|
||||
}
|
||||
|
||||
#bootstrap-sidebar li.active {
|
||||
border:0 #666 solid;
|
||||
border-right-width:4px;
|
||||
}
|
||||
|
||||
@media (min-width: 979px) {
|
||||
#bootstrap-sidebar.affix-top, #bootstrap-sidebar.affix {
|
||||
position: fixed;
|
||||
top:90px;
|
||||
width:228px;
|
||||
}
|
||||
}
|
||||
|
9
app/assets/stylesheets/status.css
Normal file
@ -0,0 +1,9 @@
|
||||
.recent_status {
|
||||
font-family: sans-serif;
|
||||
margin: 4px;
|
||||
}
|
||||
.timeago {
|
||||
font-size: 0.8em;
|
||||
color: #ccc;
|
||||
margin-left:10px;
|
||||
}
|
@ -1,42 +1,50 @@
|
||||
class ApiKeysController < ApplicationController
|
||||
include KeyUtilities
|
||||
include KeyUtilities, ApiKeys
|
||||
|
||||
before_filter :require_user, :set_channels_menu
|
||||
before_filter :require_user, :set_channels_menu
|
||||
|
||||
def index
|
||||
@channel = current_user.channels.find(params[:channel_id])
|
||||
@write_key = @channel.api_keys.write_keys.first
|
||||
@read_keys = @channel.api_keys.read_keys
|
||||
end
|
||||
def index
|
||||
api_index params[:channel_id]
|
||||
end
|
||||
|
||||
def destroy
|
||||
current_user.api_keys.find_by_api_key(params[:id]).try(:destroy)
|
||||
redirect_to :back
|
||||
end
|
||||
def destroy
|
||||
current_user.api_keys.find_by_api_key(params[:id]).try(:destroy)
|
||||
redirect_to :back
|
||||
end
|
||||
|
||||
def create
|
||||
@channel = current_user.channels.find(params[:channel_id])
|
||||
@api_key = @channel.api_keys.write_keys.first
|
||||
def create
|
||||
@channel = current_user.channels.find(params[:channel_id])
|
||||
@api_key = @channel.api_keys.write_keys.first
|
||||
|
||||
# if no api key found or read api key
|
||||
if (@api_key.nil? or params[:write] == '0')
|
||||
@api_key = ApiKey.new
|
||||
@api_key.channel_id = @channel.id
|
||||
@api_key.user_id = current_user.id
|
||||
@api_key.write_flag = params[:write]
|
||||
end
|
||||
# if no api key found or read api key
|
||||
if (@api_key.nil? || params[:write] == '0')
|
||||
@api_key = ApiKey.new
|
||||
@api_key.channel_id = @channel.id
|
||||
@api_key.user_id = current_user.id
|
||||
@api_key.write_flag = params[:write]
|
||||
end
|
||||
|
||||
# set new api key and save
|
||||
@api_key.api_key = generate_api_key
|
||||
@api_key.save
|
||||
# set new api key and save
|
||||
@api_key.api_key = generate_api_key
|
||||
@api_key.save
|
||||
|
||||
# redirect
|
||||
redirect_to channel_api_keys_path(@channel)
|
||||
end
|
||||
# redirect
|
||||
# redirect_to channel_api_keys_path(@channel.id)
|
||||
redirect_to channel_path(@channel.id, :anchor => "apikeys")
|
||||
end
|
||||
|
||||
def update
|
||||
@api_key = current_user.api_keys.find_by_api_key(params[:id])
|
||||
@api_key.update_attributes(api_key_params)
|
||||
redirect_to :back
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# only allow these params
|
||||
def api_key_params
|
||||
params.require(:api_key).permit(:note)
|
||||
end
|
||||
|
||||
def update
|
||||
@api_key = current_user.api_keys.find_by_api_key(params[:id])
|
||||
@api_key.update_attributes(params[:api_key])
|
||||
redirect_to :back
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -1,51 +1,134 @@
|
||||
class ApplicationController < ActionController::Base
|
||||
# include all helpers for controllers
|
||||
# include all helpers for controllers
|
||||
helper :all
|
||||
# include these helper methods for views
|
||||
helper_method :current_user_session, :current_user, :get_header_value
|
||||
# include these helper methods for views
|
||||
helper_method :current_user_session, :current_user, :logged_in?, :is_admin?, :get_header_value, :to_bytes
|
||||
protect_from_forgery
|
||||
before_filter :set_variables
|
||||
before_filter :allow_cross_domain_access, :set_variables
|
||||
|
||||
# set up some variables across the entire application
|
||||
# responds with blank
|
||||
def respond_with_blank
|
||||
respond_to do |format|
|
||||
format.html { render :text => '' }
|
||||
format.json { render :json => {}.to_json }
|
||||
# fix xml response line breaks
|
||||
format.xml { render :xml => {}.to_xml.gsub("\n", '').gsub("<hash>", "\n<hash>") }
|
||||
end
|
||||
end
|
||||
|
||||
# responds with an error
|
||||
def respond_with_error(error_code)
|
||||
error_response = ErrorResponse.new(error_code)
|
||||
respond_to do |format|
|
||||
format.html { render :text => error_response.error_code, :status => error_response.http_status }
|
||||
format.json { render :json => error_response.to_json, :status => error_response.http_status }
|
||||
format.xml { render :xml => error_response.to_xml, :status => error_response.http_status }
|
||||
end
|
||||
end
|
||||
|
||||
# set up some variables across the entire application
|
||||
def set_variables
|
||||
@api_domain ||= api_domain
|
||||
@ssl_api_domain ||= ssl_api_domain
|
||||
@locale ||= get_locale
|
||||
I18n.locale = ALLOWED_LOCALES.include?(@locale) ? @locale : I18n.default_locale
|
||||
I18n.locale = @locale
|
||||
# sets timezone for current user, all DateTime outputs will be automatically formatted
|
||||
Time.zone = current_user.time_zone if current_user
|
||||
end
|
||||
|
||||
|
||||
# get the locale, but don't fail if header value doesn't exist
|
||||
def get_locale
|
||||
locale = get_header_value('HTTP_ACCEPT_LANGUAGE')
|
||||
# only look for 'pt-br' as first 5 letters, can make more robust in future if other languages are needed
|
||||
locale = locale[0..4].downcase if locale
|
||||
|
||||
if locale and ALLOWED_LOCALES.include?(locale[0..1].downcase)
|
||||
locale = locale[0..1].downcase
|
||||
elsif locale and ALLOWED_LOCALES.include?(locale[0..4].downcase)
|
||||
locale = locale[0..4].downcase
|
||||
else
|
||||
locale = I18n.default_locale
|
||||
end
|
||||
|
||||
return locale
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_channels_menu
|
||||
@menu = 'channels'
|
||||
end
|
||||
# allow javascript requests from any domain
|
||||
def allow_cross_domain_access
|
||||
response.headers['Access-Control-Allow-Origin'] = '*'
|
||||
response.headers['Access-Control-Allow-Methods'] = 'GET, POST, PUT, OPTIONS, DELETE, PATCH'
|
||||
response.headers['Access-Control-Allow-Headers'] = 'origin, content-type, X-Requested-With'
|
||||
response.headers['Access-Control-Max-Age'] = '1800'
|
||||
end
|
||||
|
||||
def logged_in?
|
||||
true if current_user
|
||||
end
|
||||
|
||||
# check that user's email address matches admin
|
||||
def is_admin?
|
||||
current_user && ADMIN_EMAILS.include?(current_user.email)
|
||||
end
|
||||
|
||||
def set_admin_menu
|
||||
@menu = 'admin'
|
||||
end
|
||||
|
||||
# converts a string to a byte string for c output
|
||||
def to_bytes(input, separator='.', prefix='')
|
||||
return '' if input == nil
|
||||
output = []
|
||||
# split the input array using the separator, and add necessary prefixes to each item
|
||||
input.split(separator).each { |i| output.push(prefix + i) }
|
||||
# rejoin the array into a comma separated string
|
||||
return output.join(', ')
|
||||
end
|
||||
|
||||
def set_channels_menu
|
||||
@menu = 'channels'
|
||||
end
|
||||
|
||||
def set_apps_menu
|
||||
@menu = 'apps'
|
||||
end
|
||||
|
||||
def set_plugins_menu
|
||||
@menu = 'plugins'
|
||||
end
|
||||
|
||||
def set_devices_menu
|
||||
@menu = 'devices'
|
||||
end
|
||||
|
||||
def current_user_session
|
||||
return @current_user_session if defined?(@current_user_session)
|
||||
@current_user_session = UserSession.find
|
||||
end
|
||||
|
||||
|
||||
def current_user
|
||||
return @current_user if defined?(@current_user)
|
||||
@current_user = current_user_session && current_user_session.record
|
||||
end
|
||||
|
||||
# check that user is logged in
|
||||
def require_user
|
||||
if current_user.nil?
|
||||
redirect_to login_path
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def require_user
|
||||
logger.info "Require User"
|
||||
if current_user.nil?
|
||||
respond_to do |format|
|
||||
format.html {
|
||||
session[:link_back] = request.url
|
||||
logger.debug "Redirecting to login"
|
||||
redirect_to login_path
|
||||
return true
|
||||
}
|
||||
format.json do
|
||||
render :json => {'error' => 'Could not authenticate you.'}, :status => :unauthorized
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
def require_no_user
|
||||
if current_user
|
||||
store_location
|
||||
@ -54,142 +137,205 @@ class ApplicationController < ActionController::Base
|
||||
end
|
||||
end
|
||||
|
||||
def store_location
|
||||
if params[:controller] != "user_sessions"
|
||||
session[:return_to] = request.fullpath
|
||||
end
|
||||
def require_admin
|
||||
unless current_user && is_admin?
|
||||
render :nothing => true, :status => 403 and return
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def store_location
|
||||
if params[:controller] != "user_sessions"
|
||||
session[:return_to] = request.fullpath
|
||||
end
|
||||
end
|
||||
|
||||
def redirect_back_or_default(default)
|
||||
redirect_to(session[:return_to] || default)
|
||||
session[:return_to] = nil
|
||||
end
|
||||
|
||||
def domain
|
||||
u = request.url
|
||||
begin
|
||||
# the number 12 is the position at which to begin searching for '/', so we don't get the intitial '/' from http://
|
||||
u = u[0..u.index('/', 12)]
|
||||
rescue
|
||||
u += '/'
|
||||
end
|
||||
# uncomment the line below for https support in a production environment
|
||||
#u = u.sub(/http:/, 'https:') if Rails.env == 'production'
|
||||
return u
|
||||
end
|
||||
def domain(ssl=true)
|
||||
u = request.url
|
||||
begin
|
||||
# the number 12 is the position at which to begin searching for '/', so we don't get the intitial '/' from http://
|
||||
u = u[0..u.index('/', 12)]
|
||||
rescue
|
||||
u += '/'
|
||||
end
|
||||
u = u.sub(/http:/, 'https:') if (Rails.env == 'production' and ssl)
|
||||
return u
|
||||
end
|
||||
|
||||
# gets the api key
|
||||
def get_userkey
|
||||
return get_header_value('THINGSPEAKAPIKEY') || params[:key] || params[:api_key] || params[:apikey]
|
||||
end
|
||||
def ssl
|
||||
(Rails.env == 'production') ? 'https' : 'http'
|
||||
end
|
||||
|
||||
# get specified header value
|
||||
def get_header_value(name)
|
||||
value = nil
|
||||
for header in request.env
|
||||
value = header[1] if (header[0].upcase.index(name.upcase))
|
||||
end
|
||||
return value
|
||||
end
|
||||
# domain for the api
|
||||
def api_domain
|
||||
(Rails.env == 'production') ? API_DOMAIN : domain
|
||||
end
|
||||
|
||||
# ssl domain for the api
|
||||
def ssl_api_domain; (Rails.env == 'production') ? api_domain.sub('http', 'https'): api_domain; end
|
||||
|
||||
# gets the api key
|
||||
def get_apikey
|
||||
key = get_header_value(HTTP_HEADER_API_KEY_NAME) || params[:key] || params[:api_key] || params[:apikey]
|
||||
key.strip if key.present?
|
||||
return key
|
||||
end
|
||||
|
||||
# get specified header value
|
||||
def get_header_value(name)
|
||||
name.upcase!
|
||||
request.env.select {|header| header.upcase.index(name) }.values[0]
|
||||
end
|
||||
|
||||
# generates a hash key unique to the user and url
|
||||
def cache_key(type)
|
||||
cache_key = request.host + request.path
|
||||
user_id = current_user ? current_user.id : '0'
|
||||
|
||||
params.each do |key, value|
|
||||
# add the parameter if appropriate
|
||||
cache_key += "&#{key}=#{value}" if key != 'callback' && key != 'controller' && key != 'action' && key != 'format'
|
||||
end
|
||||
|
||||
return "#{user_id}-#{type}-#{cache_key}"
|
||||
end
|
||||
|
||||
# reads a file using the relative path to the file
|
||||
def read_file(file_path)
|
||||
path = file_path[0, file_path.rindex('/')]
|
||||
filename = file_path[file_path.rindex('/') + 1, file_path.length]
|
||||
output = ''
|
||||
|
||||
File.open("#{File.expand_path(path)}/#{filename}", 'r') do |f|
|
||||
while line = f.gets
|
||||
output += line
|
||||
end
|
||||
end
|
||||
|
||||
return output
|
||||
end
|
||||
|
||||
# prepends or appends text
|
||||
def add_prepend_append(input)
|
||||
output = input.to_s
|
||||
output = params[:prepend] + output if params[:prepend]
|
||||
output += params[:append] if params[:append]
|
||||
return output
|
||||
end
|
||||
|
||||
# gets the same data for showing or editing
|
||||
def get_channel_data
|
||||
@channel = current_user.channels.find(params[:channel_id]) if params[:channel_id]
|
||||
@channel = current_user.channels.find(params[:id]) if @channel.nil? and params[:id]
|
||||
|
||||
if @channel.ranking.blank?
|
||||
@channel.ranking = @channel.calc_ranking
|
||||
end
|
||||
|
||||
@key = @channel.api_keys.write_keys.first.try(:api_key) || ""
|
||||
end
|
||||
|
||||
def check_permissions(channel)
|
||||
render :text => t(:channel_permission) and return if (current_user.nil? || (channel.user_id != current_user.id))
|
||||
end
|
||||
def check_permissions(channel)
|
||||
render :text => t(:channel_permission) and return if (current_user.nil? || (channel.user_id != current_user.id))
|
||||
end
|
||||
|
||||
# checks permission for channel using api_key
|
||||
def channel_permission?(channel, api_key)
|
||||
if channel.public_flag or (api_key and api_key.channel_id == channel.id) or (current_user and channel.user_id == current_user.id)
|
||||
return true
|
||||
else
|
||||
return false
|
||||
end
|
||||
end
|
||||
# checks permission for channel using api_key
|
||||
def channel_permission?(channel, api_key)
|
||||
if channel.public_flag or (api_key and api_key.channel_id == channel.id) or (current_user and channel.user_id == current_user.id)
|
||||
return true
|
||||
else
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
# outputs error for bad channel
|
||||
def bad_channel_xml
|
||||
channel_unauthorized = Channel.new
|
||||
channel_unauthorized.id = -1
|
||||
return channel_unauthorized.to_xml(:only => :id)
|
||||
end
|
||||
# outputs error for bad channel
|
||||
def bad_channel_xml
|
||||
channel_unauthorized = Channel.new
|
||||
channel_unauthorized.id = -1
|
||||
return channel_unauthorized.to_xml(:only => :id)
|
||||
end
|
||||
|
||||
# outputs error for bad feed
|
||||
def bad_feed_xml
|
||||
feed_unauthorized = Feed.new
|
||||
feedl_unauthorized.id = -1
|
||||
return feed_unauthorized.to_xml(:only => :entry_id)
|
||||
end
|
||||
# outputs error for bad feed
|
||||
def bad_feed_xml
|
||||
feed_unauthorized = Feed.new
|
||||
feed_unauthorized.id = -1
|
||||
return feed_unauthorized.to_xml(:only => :entry_id)
|
||||
end
|
||||
|
||||
# options: days = how many days ago, start = start date, end = end date, offset = timezone offset
|
||||
def get_date_range(params)
|
||||
# set timezone correctly
|
||||
set_time_zone(params)
|
||||
# options: days = how many days ago, start = start date, end = end date, offset = timezone offset
|
||||
def get_date_range(params)
|
||||
# set timezone correctly
|
||||
set_time_zone(params)
|
||||
|
||||
# if results are specified without start or days parameters, allow start date to be larger
|
||||
get_old_data = (params[:results] && params[:start].blank? and params[:days].blank?) ? true : false
|
||||
# allow more past data if necessary
|
||||
get_old_data = (params[:results].present? || params[:start].present? || params[:days].present?) ? true : false
|
||||
|
||||
start_date = (get_old_data) ? (Time.now - 1.year) : (Time.now - 1.day)
|
||||
end_date = Time.now
|
||||
start_date = (Time.now - params[:days].to_i.days) if params[:days]
|
||||
start_date = DateTime.strptime(params[:start]) if params[:start]
|
||||
end_date = DateTime.strptime(params[:end]) if params[:end]
|
||||
date_range = (start_date..end_date)
|
||||
# only get a maximum of 30 days worth of data
|
||||
date_range = (end_date - 30.days..end_date) if ((end_date - start_date) > 30.days and !get_old_data)
|
||||
|
||||
return date_range
|
||||
end
|
||||
start_date = (get_old_data) ? Time.parse('2010-01-01') : (Time.now - 1.day)
|
||||
end_date = Time.now
|
||||
start_date = (Time.now - params[:days].to_i.days) if params[:days]
|
||||
start_date = DateTime.parse(params[:start]) if params[:start]
|
||||
end_date = DateTime.parse(params[:end]) if params[:end]
|
||||
date_range = (start_date..end_date)
|
||||
# only get a maximum of 30 days worth of data
|
||||
date_range = (end_date - 30.days..end_date) if ((end_date - start_date) > 30.days and !get_old_data)
|
||||
return date_range
|
||||
end
|
||||
|
||||
|
||||
def is_a_number?(s)
|
||||
s.to_s.gsub(/,/, '.').match(/\A[+-]?\d+?(\.\d+)?\Z/) == nil ? false : true
|
||||
end
|
||||
|
||||
def set_time_zone(params)
|
||||
# set timezone correctly
|
||||
if params[:offset]
|
||||
def set_time_zone(params)
|
||||
# set timezone correctly
|
||||
if params[:offset]
|
||||
# check for 0 offset first since it's the most common
|
||||
if params[:offset] == '0'
|
||||
Time.zone = 'UTC'
|
||||
else
|
||||
Time.zone = set_timezone_from_offset(params[:offset])
|
||||
Time.zone = set_timezone_from_offset(params[:offset])
|
||||
end
|
||||
elsif current_user
|
||||
Time.zone = current_user.time_zone
|
||||
else
|
||||
Time.zone = 'UTC'
|
||||
end
|
||||
end
|
||||
elsif current_user
|
||||
Time.zone = current_user.time_zone
|
||||
else
|
||||
Time.zone = 'UTC'
|
||||
end
|
||||
end
|
||||
|
||||
# use the offset to find an appropriate timezone
|
||||
def set_timezone_from_offset(offset)
|
||||
offset = offset.to_i
|
||||
# keep track of whether a match was found
|
||||
found = false
|
||||
|
||||
|
||||
# loop through each timezone
|
||||
ActiveSupport::TimeZone.zones_map.each do |z|
|
||||
# set time zone
|
||||
Time.zone = z[0]
|
||||
timestring = Time.zone.now.to_s
|
||||
|
||||
|
||||
# if time zone matches the offset, leave it as the current timezone
|
||||
if (timestring.slice(-5..-3).to_i == offset and timestring.slice(-2..-1).to_i == 0)
|
||||
found = true
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
# if no time zone found, set to utc
|
||||
Time.zone = 'UTC' if !found
|
||||
|
||||
|
||||
return Time.zone
|
||||
end
|
||||
|
||||
def help
|
||||
Helper.instance
|
||||
end
|
||||
|
||||
class Helper
|
||||
include Singleton
|
||||
include ActionView::Helpers::TextHelper
|
||||
end
|
||||
end
|
||||
|
||||
|
9
app/controllers/apps_controller.rb
Normal file
@ -0,0 +1,9 @@
|
||||
class AppsController < ApplicationController
|
||||
|
||||
def index
|
||||
@menu = 'apps'
|
||||
@title = 'Internet of Things Apps' if current_user.nil?
|
||||
# @twitters = TwitterAccount.find(:all, :conditions => { :user_id => current_user.id }) if current_user
|
||||
end
|
||||
|
||||
end
|
@ -1,258 +1,474 @@
|
||||
class ChannelsController < ApplicationController
|
||||
before_filter :require_user, :except => [ :show, :post_data ]
|
||||
before_filter :set_channels_menu
|
||||
protect_from_forgery :except => :post_data
|
||||
require 'csv'
|
||||
include ChannelsHelper, ApiKeys
|
||||
before_filter :require_user, :except => [ :show, :post_data, :social_show, :social_feed, :public]
|
||||
before_filter :set_channels_menu
|
||||
layout 'application', :except => [:social_show, :social_feed]
|
||||
protect_from_forgery :except => :post_data
|
||||
require 'csv'
|
||||
|
||||
def index
|
||||
@channels = current_user.channels
|
||||
end
|
||||
# view list of watched channels
|
||||
def watched
|
||||
@channels = current_user.watched_channels
|
||||
end
|
||||
|
||||
def show
|
||||
@channel = Channel.find(params[:id]) if params[:id]
|
||||
@domain = domain
|
||||
# user watches a channel
|
||||
def watch
|
||||
@watching = Watching.find_by_user_id_and_channel_id(current_user.id, params[:id])
|
||||
|
||||
# if owner of channel
|
||||
get_channel_data if current_user and @channel.user_id == current_user.id
|
||||
end
|
||||
# add watching
|
||||
if params[:flag] == 'true'
|
||||
@watching = Watching.new(:user_id => current_user.id, :channel_id => params[:id]) if @watching.nil?
|
||||
@watching.save
|
||||
# delete watching
|
||||
else
|
||||
@watching.delete if !@watching.nil?
|
||||
end
|
||||
|
||||
def edit
|
||||
get_channel_data
|
||||
end
|
||||
render :text => '1'
|
||||
end
|
||||
|
||||
def update
|
||||
@channel = current_user.channels.find(params[:id])
|
||||
@channel.update_attributes(params[:channel])
|
||||
# list public channels
|
||||
def public
|
||||
# get channels by ids
|
||||
if params[:channel_ids].present?
|
||||
flash[:notice] = t(:selected_channels)
|
||||
@channels = Channel.public_viewable.by_array(params[:channel_ids]).order('ranking desc, updated_at DESC').paginate :page => params[:page]
|
||||
# get channels that match a tag
|
||||
elsif params[:tag].present?
|
||||
flash[:notice] = "#{t(:tag).capitalize}: #{params[:tag]}"
|
||||
@channels = Channel.public_viewable.active.order('ranking desc, updated_at DESC').with_tag(params[:tag]).paginate :page => params[:page]
|
||||
# normal channel list
|
||||
else
|
||||
flash[:notice] = t(:featured_channels)
|
||||
@channels = Channel.public_viewable.active.order('ranking desc, updated_at DESC').paginate :page => params[:page]
|
||||
end
|
||||
|
||||
redirect_to channel_path(@channel.id)
|
||||
end
|
||||
respond_to do |format|
|
||||
format.html
|
||||
format.json { render :json => Channel.paginated_hash(@channels).to_json }
|
||||
format.xml { render :xml => Channel.paginated_hash(@channels).to_xml(:root => 'response') }
|
||||
end
|
||||
end
|
||||
|
||||
# widget for social feeds
|
||||
def social_feed
|
||||
# get domain based on ssl
|
||||
@domain = domain((get_header_value('x_ssl') == 'true'))
|
||||
end
|
||||
|
||||
# main page for a socialsensornetwork.com project
|
||||
def social_show
|
||||
@channel = Channel.find_by_slug(params[:slug])
|
||||
|
||||
# redirect home if wrong slug
|
||||
redirect_to '/' and return if @channel.nil?
|
||||
|
||||
api_key = ApiKey.find(:first, :conditions => { :channel_id => @channel.id, :write_flag => 1 } )
|
||||
@post_url = "/update?key=#{api_key.api_key}"
|
||||
|
||||
# names of non-blank channel fields
|
||||
@fields = []
|
||||
@channel.attribute_names.each do |attr|
|
||||
@fields.push(attr) if attr.index('field') and !@channel[attr].blank?
|
||||
end
|
||||
end
|
||||
|
||||
def social_new
|
||||
@channel = Channel.new
|
||||
end
|
||||
|
||||
def social_create
|
||||
@channel = Channel.new(channel_params)
|
||||
|
||||
# check for blank name
|
||||
@channel.errors.add(:base, t(:social_channel_error_name_blank)) if @channel.name.blank?
|
||||
|
||||
# check for blank slug
|
||||
@channel.errors.add(:base, t(:social_channel_error_slug_blank)) if @channel.slug.blank?
|
||||
|
||||
# check for at least one field
|
||||
fields = false
|
||||
@channel.attribute_names.each do |attr|
|
||||
if (attr.index('field') or attr.index('status')) and !@channel[attr].blank?
|
||||
fields = true
|
||||
break
|
||||
end
|
||||
end
|
||||
@channel.errors.add(:base, t(:social_channel_error_fields)) if !fields
|
||||
|
||||
# check for existing slug
|
||||
if @channel.errors.count == 0
|
||||
@channel.errors.add(:base, t(:social_channel_error_slug_exists)) if Channel.find_by_slug(@channel.slug)
|
||||
end
|
||||
|
||||
# if there are no errors
|
||||
if @channel.errors.count == 0
|
||||
@channel.user_id = current_user.id
|
||||
@channel.social = true
|
||||
@channel.public_flag = true
|
||||
@channel.save
|
||||
|
||||
# create an api key for this channel
|
||||
channel.add_write_api_key
|
||||
|
||||
redirect_to channels_path
|
||||
else
|
||||
render :action => :social_new
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
def index
|
||||
|
||||
@channels = current_user.channels
|
||||
respond_to do |format|
|
||||
format.html
|
||||
format.json { render :json => @channels }
|
||||
end
|
||||
end
|
||||
|
||||
def show
|
||||
|
||||
@channel = Channel.find(params[:id]) if params[:id]
|
||||
|
||||
@title = @channel.name
|
||||
@domain = domain
|
||||
@mychannel = (current_user && current_user.id == @channel.user_id)
|
||||
@width = Chart.default_width
|
||||
@height = Chart.default_height
|
||||
|
||||
api_index @channel.id
|
||||
# if owner of channel
|
||||
get_channel_data if @mychannel
|
||||
respond_to do |format|
|
||||
format.html do
|
||||
if @mychannel
|
||||
render "private_show"
|
||||
session[:errors] = nil
|
||||
else
|
||||
render "public_show"
|
||||
session[:errors] = nil
|
||||
end
|
||||
end
|
||||
format.json { render :json => @channel }
|
||||
end
|
||||
end
|
||||
|
||||
def edit
|
||||
get_channel_data
|
||||
end
|
||||
|
||||
|
||||
def update
|
||||
|
||||
@channel = current_user.channels.find(params[:id])
|
||||
puts params[:channel].inspect
|
||||
# make sure channel isn't social
|
||||
#render :text => '' and return if @channel.social
|
||||
if params["channel"]["video_type"].blank? && !params["channel"]["video_id"].blank?
|
||||
@channel.errors.add(:base, t(:channel_video_type_blank))
|
||||
end
|
||||
if @channel.errors.count <= 0
|
||||
@channel.save_tags(params[:tags][:name])
|
||||
@channel.assign_attributes(channel_params)
|
||||
@channel.set_windows
|
||||
@channel.save
|
||||
else
|
||||
session[:errors] = @channel.errors
|
||||
redirect_to channel_path(@channel.id, :anchor => "channelsettings") and return
|
||||
end
|
||||
|
||||
flash[:notice] = t(:channel_update_success)
|
||||
redirect_to channel_path(@channel.id)
|
||||
|
||||
end
|
||||
|
||||
def create
|
||||
channel = current_user.channels.create(:field1 => "#{t(:channel_default_field)} 1")
|
||||
channel.set_windows
|
||||
channel.save
|
||||
channel.add_write_api_key
|
||||
|
||||
# redirect to edit the newly created channel
|
||||
redirect_to edit_channel_path(channel)
|
||||
@channel_id = channel.id
|
||||
redirect_to channel_path(@channel_id, :anchor => "channelsettings")
|
||||
end
|
||||
|
||||
# clear all data from a channel
|
||||
def clear
|
||||
channel = current_user.channels.find(params[:id])
|
||||
channel.delete_feeds
|
||||
channel.update_attribute(:last_entry_id, nil)
|
||||
redirect_to channel_path(channel.id)
|
||||
end
|
||||
|
||||
def destroy
|
||||
channel = current_user.channels.find(params[:id])
|
||||
channel.destroy
|
||||
redirect_to channels_path
|
||||
end
|
||||
end
|
||||
|
||||
def destroy
|
||||
channel = current_user.channels.find(params[:id])
|
||||
channel.destroy
|
||||
# response is '0' if failure, 'entry_id' if success
|
||||
def post_data
|
||||
|
||||
redirect_to channels_path
|
||||
end
|
||||
status = '0'
|
||||
feed = Feed.new
|
||||
|
||||
# response is '0' if failure, 'entry_id' if success
|
||||
def post_data
|
||||
status = '0'
|
||||
feed = Feed.new
|
||||
|
||||
api_key = ApiKey.find_by_api_key(get_userkey)
|
||||
api_key = ApiKey.find_by_api_key(get_apikey)
|
||||
|
||||
# if write persmission, allow post
|
||||
if (api_key && api_key.write_flag)
|
||||
channel = Channel.find(api_key.channel_id)
|
||||
# if write permission, allow post
|
||||
if (api_key && api_key.write_flag)
|
||||
channel = api_key.channel
|
||||
|
||||
# update entry_id for channel and feed
|
||||
entry_id = channel.last_entry_id.nil? ? 1 : channel.last_entry_id + 1
|
||||
channel.last_entry_id = entry_id
|
||||
feed.entry_id = entry_id
|
||||
# don't rate limit if tstream parameter is present
|
||||
tstream = params[:tstream] || false;
|
||||
|
||||
# try to get created_at datetime if appropriate
|
||||
if params[:created_at]
|
||||
begin
|
||||
feed.created_at = DateTime.parse(params[:created_at])
|
||||
# if invalid datetime, don't do anything--rails will set created_at
|
||||
rescue
|
||||
end
|
||||
end
|
||||
|
||||
# modify parameters
|
||||
params.each do |key, value|
|
||||
# strip line feeds from end of parameters
|
||||
params[key] = value.sub(/\\n$/, '').sub(/\\r$/, '') if value
|
||||
# use ip address if found
|
||||
params[key] = request.remote_addr if value.upcase == 'IP_ADDRESS'
|
||||
end
|
||||
|
||||
# set feed details
|
||||
feed.channel_id = channel.id
|
||||
feed.raw_data = params
|
||||
feed.field1 = params[:field1] if params[:field1]
|
||||
feed.field2 = params[:field2] if params[:field2]
|
||||
feed.field3 = params[:field3] if params[:field3]
|
||||
feed.field4 = params[:field4] if params[:field4]
|
||||
feed.field5 = params[:field5] if params[:field5]
|
||||
feed.field6 = params[:field6] if params[:field6]
|
||||
feed.field7 = params[:field7] if params[:field7]
|
||||
feed.field8 = params[:field8] if params[:field8]
|
||||
feed.status = params[:status] if params[:status]
|
||||
feed.latitude = params[:lat] if params[:lat]
|
||||
feed.latitude = params[:latitude] if params[:latitude]
|
||||
feed.longitude = params[:long] if params[:long]
|
||||
feed.longitude = params[:longitude] if params[:longitude]
|
||||
feed.elevation = params[:elevation] if params[:elevation]
|
||||
# don't rate limit if talkback_key parameter is present
|
||||
talkback_key = params[:talkback_key] || false;
|
||||
|
||||
if channel.save && feed.save
|
||||
status = entry_id
|
||||
end
|
||||
end
|
||||
|
||||
# output response code
|
||||
render :text => '0', :status => 400 and return if status == '0'
|
||||
render :text => status
|
||||
end
|
||||
# rate limit posts if channel is not social and timespan is smaller than the allowed window
|
||||
render :text => '0' and return if (RATE_LIMIT && !tstream && !talkback_key && !channel.social && Time.now < channel.updated_at + RATE_LIMIT_FREQUENCY.to_i.seconds)
|
||||
|
||||
# if social channel, latitude MUST be present
|
||||
render :text => '0' and return if (channel.social && params[:latitude].blank?)
|
||||
|
||||
# update entry_id for channel and feed
|
||||
entry_id = channel.next_entry_id
|
||||
channel.last_entry_id = entry_id
|
||||
feed.entry_id = entry_id
|
||||
|
||||
# try to get created_at datetime if appropriate
|
||||
if params[:created_at].present?
|
||||
begin
|
||||
feed.created_at = DateTime.parse(params[:created_at])
|
||||
# if invalid datetime, don't do anything--rails will set created_at
|
||||
rescue
|
||||
end
|
||||
end
|
||||
|
||||
# modify parameters
|
||||
params.each do |key, value|
|
||||
# this fails so much due to encoding problems that we need to ignore errors
|
||||
begin
|
||||
# strip line feeds from end of parameters
|
||||
params[key] = value.sub(/\\n$/, '').sub(/\\r$/, '') if value
|
||||
# use ip address if found
|
||||
params[key] = get_header_value('X_REAL_IP') if value.try(:upcase) == 'IP_ADDRESS'
|
||||
rescue
|
||||
end
|
||||
end
|
||||
|
||||
# set feed details
|
||||
feed.channel_id = channel.id
|
||||
feed.field1 = params[:field1] || params['1'] if params[:field1] || params['1']
|
||||
feed.field2 = params[:field2] || params['2'] if params[:field2] || params['2']
|
||||
feed.field3 = params[:field3] || params['3'] if params[:field3] || params['3']
|
||||
feed.field4 = params[:field4] || params['4'] if params[:field4] || params['4']
|
||||
feed.field5 = params[:field5] || params['5'] if params[:field5] || params['5']
|
||||
feed.field6 = params[:field6] || params['6'] if params[:field6] || params['6']
|
||||
feed.field7 = params[:field7] || params['7'] if params[:field7] || params['7']
|
||||
feed.field8 = params[:field8] || params['8'] if params[:field8] || params['8']
|
||||
feed.status = params[:status] if params[:status]
|
||||
feed.latitude = params[:lat] if params[:lat]
|
||||
feed.latitude = params[:latitude] if params[:latitude]
|
||||
feed.longitude = params[:long] if params[:long]
|
||||
feed.longitude = params[:longitude] if params[:longitude]
|
||||
feed.elevation = params[:elevation] if params[:elevation]
|
||||
feed.location = params[:location] if params[:location]
|
||||
|
||||
# if the saves were successful
|
||||
if channel.save && feed.save
|
||||
status = entry_id
|
||||
|
||||
# check for tweet
|
||||
if params[:twitter] && params[:tweet]
|
||||
# check username
|
||||
twitter_account = TwitterAccount.find_by_user_id_and_screen_name(api_key.user_id, params[:twitter])
|
||||
if twitter_account
|
||||
twitter_account.tweet(params[:tweet])
|
||||
end
|
||||
end
|
||||
else
|
||||
raise "Channel or Feed didn't save correctly"
|
||||
end
|
||||
end
|
||||
|
||||
# if there is a talkback to execute
|
||||
if params[:talkback_key].present?
|
||||
talkback = Talkback.find_by_api_key(params[:talkback_key])
|
||||
command = talkback.execute_command! if talkback.present?
|
||||
end
|
||||
|
||||
# output response code
|
||||
render(:text => '0', :status => 400) and return if status == '0'
|
||||
|
||||
# if there is a talkback_key and a command that was executed
|
||||
if params[:talkback_key].present? && command.present?
|
||||
respond_to do |format|
|
||||
format.html { render :text => command.command_string }
|
||||
format.json { render :json => command.to_json }
|
||||
format.xml { render :xml => command.to_xml(Command.public_options) }
|
||||
end and return
|
||||
end
|
||||
|
||||
# if there is a talkback_key but no command
|
||||
respond_with_blank and return if params[:talkback_key].present? && command.blank?
|
||||
|
||||
# normal route, respond with the entry id of the feed
|
||||
render :text => status
|
||||
end
|
||||
|
||||
# import view
|
||||
def import
|
||||
get_channel_data
|
||||
end
|
||||
|
||||
# upload csv file to channel
|
||||
def upload
|
||||
channel = Channel.find(params[:id])
|
||||
check_permissions(channel)
|
||||
|
||||
# if no data
|
||||
if params[:upload].blank? || params[:upload][:csv].blank?
|
||||
flash[:error] = t(:upload_no_file)
|
||||
redirect_to channel_path(channel.id, :anchor => "dataimport") and return
|
||||
end
|
||||
|
||||
# set time zone
|
||||
Time.zone = params[:feed][:time_zone]
|
||||
|
||||
# read data from uploaded file
|
||||
csv_array = CSV.parse(params[:upload][:csv].read)
|
||||
if csv_array.nil? || csv_array.blank?
|
||||
flash[:error] = t(:upload_no_data)
|
||||
redirect_to channel_path(channel.id, :anchor => "dataimport") and return
|
||||
end
|
||||
|
||||
# does the column have headers
|
||||
headers = has_headers?(csv_array)
|
||||
|
||||
# remember the column positions
|
||||
entry_id_column = -1
|
||||
latitude_column = -1
|
||||
longitude_column = -1
|
||||
elevation_column = -1
|
||||
location_column = -1
|
||||
status_column = -1
|
||||
if headers
|
||||
csv_array[0].each_with_index do |column, index|
|
||||
entry_id_column = index if column.downcase == 'entry_id'
|
||||
latitude_column = index if column.downcase == 'latitude'
|
||||
longitude_column = index if column.downcase == 'longitude'
|
||||
elevation_column = index if column.downcase == 'elevation'
|
||||
location_column = index if column.downcase == 'location'
|
||||
status_column = index if column.downcase == 'status'
|
||||
end
|
||||
end
|
||||
|
||||
# delete the first row if it contains headers
|
||||
csv_array.delete_at(0) if headers
|
||||
|
||||
# determine if the date can be parsed
|
||||
parse_date = date_parsable?(csv_array[0][0]) unless csv_array[0].nil? || csv_array[0][0].nil?
|
||||
|
||||
# if 2 or more rows
|
||||
if !csv_array[1].blank?
|
||||
date1 = parse_date ? Time.parse(csv_array[0][0]) : Time.at(csv_array[0][0])
|
||||
date2 = parse_date ? Time.parse(csv_array[1][0]) : Time.at(csv_array[1][0])
|
||||
|
||||
# reverse the array if 1st date is larger than 2nd date
|
||||
csv_array = csv_array.reverse if date1 > date2
|
||||
end
|
||||
|
||||
# loop through each row
|
||||
csv_array.each do |row|
|
||||
# if row isn't blank
|
||||
if !row.blank?
|
||||
feed = Feed.new
|
||||
|
||||
# set location and status then delete the rows
|
||||
# these 5 deletes must be performed in the proper (reverse) order
|
||||
feed.status = row.delete_at(status_column) if status_column > 0
|
||||
feed.location = row.delete_at(location_column) if location_column > 0
|
||||
feed.elevation = row.delete_at(elevation_column) if elevation_column > 0
|
||||
feed.longitude = row.delete_at(longitude_column) if longitude_column > 0
|
||||
feed.latitude = row.delete_at(latitude_column) if latitude_column > 0
|
||||
|
||||
# remove entry_id column if necessary
|
||||
row.delete_at(entry_id_column) if entry_id_column > 0
|
||||
|
||||
# update entry_id for channel and feed
|
||||
entry_id = channel.last_entry_id.nil? ? 1 : channel.last_entry_id + 1
|
||||
channel.last_entry_id = entry_id
|
||||
feed.entry_id = entry_id
|
||||
|
||||
# set feed data
|
||||
feed.channel_id = channel.id
|
||||
feed.created_at = parse_date ? Time.zone.parse(row[0]) : Time.zone.at(row[0].to_f)
|
||||
feed.field1 = row[1]
|
||||
feed.field2 = row[2]
|
||||
feed.field3 = row[3]
|
||||
feed.field4 = row[4]
|
||||
feed.field5 = row[5]
|
||||
feed.field6 = row[6]
|
||||
feed.field7 = row[7]
|
||||
feed.field8 = row[8]
|
||||
|
||||
# save channel and feed
|
||||
feed.save
|
||||
channel.save
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
# set the user's time zone back
|
||||
set_time_zone(params)
|
||||
|
||||
# redirect
|
||||
flash[:notice] = t(:upload_successful)
|
||||
redirect_to channel_path(channel.id, :anchor => "dataimport")
|
||||
end
|
||||
|
||||
|
||||
# import view
|
||||
def import
|
||||
get_channel_data
|
||||
end
|
||||
private
|
||||
|
||||
# upload csv file to channel
|
||||
def upload
|
||||
# if no data
|
||||
render :text => t(:select_file) and return if params[:upload].blank? or params[:upload][:csv].blank?
|
||||
# only allow these params
|
||||
def channel_params
|
||||
params.require(:channel).permit(:name, :url, :description, :latitude, :longitude, :field1, :field2, :field3, :field4, :field5, :field6, :field7, :field8, :elevation, :public_flag, :status, :video_id, :video_type)
|
||||
end
|
||||
|
||||
channel = Channel.find(params[:channel_id])
|
||||
channel_id = channel.id
|
||||
# make sure channel belongs to current user
|
||||
check_permissions(channel)
|
||||
|
||||
# set time zone
|
||||
Time.zone = params[:feed][:time_zone]
|
||||
# determine if the date can be parsed
|
||||
def date_parsable?(date)
|
||||
return !is_a_number?(date)
|
||||
end
|
||||
|
||||
# read data from uploaded file
|
||||
csv_array = CSV.parse(params[:upload][:csv].read)
|
||||
# determine if the csv file has headers
|
||||
def has_headers?(csv_array)
|
||||
headers = false
|
||||
|
||||
# does the column have headers
|
||||
headers = has_headers?(csv_array)
|
||||
# if there are at least 2 rows
|
||||
if (csv_array[0] and csv_array[1])
|
||||
row0_integers = 0
|
||||
row1_integers = 0
|
||||
|
||||
# remember the column positions
|
||||
entry_id_column = -1
|
||||
latitude_column = -1
|
||||
longitude_column = -1
|
||||
elevation_column = -1
|
||||
status_column = -1
|
||||
if headers
|
||||
csv_array[0].each_with_index do |column, index|
|
||||
entry_id_column = index if column.downcase == 'entry_id'
|
||||
latitude_column = index if column.downcase == 'latitude'
|
||||
longitude_column = index if column.downcase == 'longitude'
|
||||
elevation_column = index if column.downcase == 'elevation'
|
||||
status_column = index if column.downcase == 'status'
|
||||
end
|
||||
end
|
||||
# if first row, first value contains 'create' or 'date', assume it has headers
|
||||
if (csv_array[0][0].downcase.include?('create') or csv_array[0][0].downcase.include?('date'))
|
||||
headers = true
|
||||
else
|
||||
# count integers in row0
|
||||
csv_array[0].each_with_index do |value, i|
|
||||
row0_integers += 1 if is_a_number?(value)
|
||||
end
|
||||
|
||||
# delete the first row if it contains headers
|
||||
csv_array.delete_at(0) if headers
|
||||
# count integers in row1
|
||||
csv_array[1].each_with_index do |value, i|
|
||||
row1_integers += 1 if is_a_number?(value)
|
||||
end
|
||||
|
||||
# determine if the date can be parsed
|
||||
parse_date = date_parsable?(csv_array[0][0])
|
||||
# if row1 has more integers, assume row0 is headers
|
||||
headers = true if row1_integers > row0_integers
|
||||
end
|
||||
end
|
||||
|
||||
# if 2 or more rows
|
||||
if !csv_array[1].blank?
|
||||
date1 = parse_date ? Time.parse(csv_array[0][0]) : Time.at(csv_array[0][0])
|
||||
date2 = parse_date ? Time.parse(csv_array[1][0]) : Time.at(csv_array[1][0])
|
||||
return headers
|
||||
end
|
||||
|
||||
# reverse the array if 1st date is larger than 2nd date
|
||||
csv_array = csv_array.reverse if date1 > date2
|
||||
end
|
||||
end
|
||||
|
||||
# loop through each row
|
||||
csv_array.each do |row|
|
||||
# if row isn't blank
|
||||
if !row.blank?
|
||||
feed = Feed.new
|
||||
|
||||
# set location and status then delete the rows
|
||||
# these 4 deletes must be performed in the proper (reverse) order
|
||||
feed.status = row.delete_at(status_column) if status_column > 0
|
||||
feed.elevation = row.delete_at(elevation_column) if elevation_column > 0
|
||||
feed.longitude = row.delete_at(longitude_column) if longitude_column > 0
|
||||
feed.latitude = row.delete_at(latitude_column) if latitude_column > 0
|
||||
|
||||
# remove entry_id column if necessary
|
||||
row.delete_at(entry_id_column) if entry_id_column > 0
|
||||
|
||||
# update entry_id for channel and feed
|
||||
entry_id = channel.last_entry_id.nil? ? 1 : channel.last_entry_id + 1
|
||||
channel.last_entry_id = entry_id
|
||||
feed.entry_id = entry_id
|
||||
|
||||
# set feed data
|
||||
feed.channel_id = channel_id
|
||||
feed.created_at = parse_date ? Time.zone.parse(row[0]) : Time.zone.at(row[0].to_f)
|
||||
feed.raw_data = row.to_s
|
||||
feed.field1 = row[1]
|
||||
feed.field2 = row[2]
|
||||
feed.field3 = row[3]
|
||||
feed.field4 = row[4]
|
||||
feed.field5 = row[5]
|
||||
feed.field6 = row[6]
|
||||
feed.field7 = row[7]
|
||||
feed.field8 = row[8]
|
||||
|
||||
# save channel and feed
|
||||
feed.save
|
||||
channel.save
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
# set the user's time zone back
|
||||
set_time_zone(params)
|
||||
|
||||
# redirect
|
||||
redirect_to channel_path(channel.id)
|
||||
end
|
||||
|
||||
|
||||
private
|
||||
|
||||
# determine if the date can be parsed
|
||||
def date_parsable?(date)
|
||||
return !is_a_number?(date)
|
||||
end
|
||||
|
||||
# determine if the csv file has headers
|
||||
def has_headers?(csv_array)
|
||||
headers = false
|
||||
|
||||
# if there are at least 2 rows
|
||||
if (csv_array[0] and csv_array[1])
|
||||
row0_integers = 0
|
||||
row1_integers = 0
|
||||
|
||||
# if first row, first value contains 'create' or 'date', assume it has headers
|
||||
if (csv_array[0][0].downcase.include?('create') or csv_array[0][0].downcase.include?('date'))
|
||||
headers = true
|
||||
else
|
||||
# count integers in row0
|
||||
csv_array[0].each_with_index do |value, i|
|
||||
row0_integers += 1 if is_a_number?(value)
|
||||
end
|
||||
|
||||
# count integers in row1
|
||||
csv_array[1].each_with_index do |value, i|
|
||||
row1_integers += 1 if is_a_number?(value)
|
||||
end
|
||||
|
||||
# if row1 has more integers, assume row0 is headers
|
||||
headers = true if row1_integers > row0_integers
|
||||
end
|
||||
end
|
||||
|
||||
return headers
|
||||
end
|
||||
|
||||
end
|
@ -1,78 +1,110 @@
|
||||
class ChartsController < ApplicationController
|
||||
before_filter :require_user, :only => [:edit]
|
||||
def edit
|
||||
# params[:id] is the windows ID
|
||||
@channel = current_user.channels.find(params[:channel_id])
|
||||
|
||||
def index
|
||||
set_channels_menu
|
||||
@channel = Channel.find(params[:channel_id])
|
||||
@channel_id = params[:channel_id]
|
||||
@domain = domain
|
||||
window_id = params[:id]
|
||||
logger.debug "Windows ID is #{window_id}"
|
||||
window_detail = @channel.windows.find(window_id).becomes(ChartWindow).window_detail
|
||||
options = window_detail.options unless window_detail.nil?
|
||||
logger.debug "Options for window #{window_id} are " + options.inspect
|
||||
|
||||
# default chart size
|
||||
@width = default_width
|
||||
@height = default_height
|
||||
render :partial => "charts/config", :locals => {
|
||||
:displayconfig => false,
|
||||
:title => @channel.name,
|
||||
:src => "/channels/#{@channel.id}/charts/#{window_id}",
|
||||
:options => options,
|
||||
:index => window_id,
|
||||
:width => Chart.default_width,
|
||||
:height => Chart.default_height
|
||||
}
|
||||
end
|
||||
|
||||
check_permissions(@channel)
|
||||
end
|
||||
def index
|
||||
|
||||
def show
|
||||
# allow these parameters when creating feed querystring
|
||||
feed_params = ['key','days','start','end','round','timescale','average','median','sum']
|
||||
set_channels_menu
|
||||
@channel = Channel.find(params[:channel_id])
|
||||
@channel_id = params[:channel_id]
|
||||
@domain = domain
|
||||
|
||||
# default chart size
|
||||
@width = default_width
|
||||
@height = default_height
|
||||
# default chart size
|
||||
@width = Chart.default_width
|
||||
@height = Chart.default_height
|
||||
|
||||
# add extra parameters to querystring
|
||||
@qs = ''
|
||||
params.each do |p|
|
||||
@qs += "&#{p[0]}=#{p[1]}" if feed_params.include?(p[0])
|
||||
end
|
||||
check_permissions(@channel)
|
||||
end
|
||||
|
||||
# fix chart colors if necessary
|
||||
params[:color] = fix_color(params[:color])
|
||||
params[:bgcolor] = fix_color(params[:bgcolor])
|
||||
def show
|
||||
|
||||
@domain = domain
|
||||
render :layout => false
|
||||
end
|
||||
# allow these parameters when creating feed querystring
|
||||
feed_params = ['key','days','start','end','round','timescale','average','median','sum','results','location','status']
|
||||
|
||||
# save chart options
|
||||
def update
|
||||
@channel = Channel.find(params[:channel_id])
|
||||
@status = 0
|
||||
# default chart size
|
||||
@width = Chart.default_width
|
||||
@height = Chart.default_height
|
||||
|
||||
# check permissions
|
||||
if @channel.user_id == current_user.id
|
||||
# add extra parameters to querystring
|
||||
@qs = ''
|
||||
params.each do |p|
|
||||
@qs += "&#{p[0]}=#{p[1]}" if feed_params.include?(p[0])
|
||||
end
|
||||
|
||||
# save data
|
||||
@channel["options#{params[:id]}"] = params[:options]
|
||||
if @channel.save
|
||||
@status = 1
|
||||
end
|
||||
# fix chart colors if necessary
|
||||
params[:color] = fix_color(params[:color])
|
||||
params[:bgcolor] = fix_color(params[:bgcolor])
|
||||
|
||||
end
|
||||
# set ssl
|
||||
@ssl = (get_header_value('x_ssl') == 'true')
|
||||
@domain = domain(@ssl)
|
||||
|
||||
# return response: 1=success, 0=failure
|
||||
render :json => @status.to_json
|
||||
end
|
||||
# should data be pushed off the end in dynamic chart
|
||||
@push = (params[:push] and params[:push] == 'false') ? false : true
|
||||
@results = params[:results]
|
||||
render :layout => false
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def default_width
|
||||
450
|
||||
end
|
||||
# save chart options
|
||||
def update
|
||||
#Check to see if we're using the new options, or the old
|
||||
|
||||
def default_height
|
||||
250
|
||||
end
|
||||
@channel = Channel.find(params[:channel_id])
|
||||
@status = 0
|
||||
|
||||
# fixes chart color if user forgets the leading '#'
|
||||
def fix_color(color)
|
||||
# check for 3 or 6 character hexadecimal value
|
||||
if (color and color.match(/^([0-9]|[a-f]|[A-F]){3}(([0-9]|[a-f]|[A-F]){3})?$/))
|
||||
color = '#' + color
|
||||
end
|
||||
# check permissions
|
||||
if @channel.user_id == current_user.id
|
||||
logger.debug "Saving Data with new options " + params[:newOptions].to_s
|
||||
# save data
|
||||
if params[:newOptions]
|
||||
logger.debug "Updating new style options on window id #{params[:id]} with #{params[:newOptions][:options]}"
|
||||
chart_window = @channel.windows.find(params[:id]).becomes(ChartWindow)
|
||||
chart_window.window_detail.options = params[:newOptions][:options]
|
||||
if !chart_window.save
|
||||
raise "Couldn't save the Chart Window"
|
||||
end
|
||||
end
|
||||
if @channel.save
|
||||
@status = 1
|
||||
end
|
||||
|
||||
return color
|
||||
end
|
||||
end
|
||||
|
||||
# return response: 1=success, 0=failure
|
||||
render :json => @status.to_json
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# fixes chart color if user forgets the leading '#'
|
||||
def fix_color(color)
|
||||
# check for 3 or 6 character hexadecimal value
|
||||
if (color and color.match(/^([0-9]|[a-f]|[A-F]){3}(([0-9]|[a-f]|[A-F]){3})?$/))
|
||||
color = '#' + color
|
||||
end
|
||||
|
||||
return color
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
47
app/controllers/comments_controller.rb
Normal file
@ -0,0 +1,47 @@
|
||||
class CommentsController < ApplicationController
|
||||
before_filter :require_user
|
||||
|
||||
def index
|
||||
redirect_to channel_path(:id => params[:channel_id], :public => true)
|
||||
end
|
||||
|
||||
def create
|
||||
render :text => '' and return if params[:userlogin].length > 0
|
||||
|
||||
@channel = Channel.find(params[:channel_id])
|
||||
@comment = @channel.comments.new
|
||||
@comment.user = current_user
|
||||
@comment.ip_address = get_header_value('X_REAL_IP')
|
||||
@comment.parent_id = params[:parent_id]
|
||||
@comment.body = params[:comment][:body].gsub(/<\/?[^>]*>/, '').gsub(/\n/, '<br />')
|
||||
# save comment
|
||||
if @comment.save
|
||||
flash[:success] = "Thanks for adding a comment!"
|
||||
else
|
||||
flash[:error] = "Comment can't be blank!"
|
||||
end
|
||||
redirect_to :back
|
||||
end
|
||||
|
||||
def vote
|
||||
# make sure this is a post
|
||||
render :text => '' and return if !request.post?
|
||||
|
||||
@comment = Comment.find(params[:id])
|
||||
@comment.flags += 1
|
||||
# delete if too many flags
|
||||
if (@comment.flags > 3)
|
||||
@comment.destroy
|
||||
render :text => ''
|
||||
# else save
|
||||
else
|
||||
@comment.save
|
||||
render :text => '1'
|
||||
end
|
||||
end
|
||||
|
||||
def destroy
|
||||
comment = current_user.comments.find(params[:id]).destroy
|
||||
redirect_to :back
|
||||
end
|
||||
end
|
8
app/controllers/cors_controller.rb
Normal file
@ -0,0 +1,8 @@
|
||||
class CorsController < ApplicationController
|
||||
skip_before_filter :verify_authenticity_token
|
||||
|
||||
# dummy method that responds with status 200 for CORS preflighting
|
||||
def preflight; render :nothing => true; end
|
||||
|
||||
end
|
||||
|
19
app/controllers/docs_controller.rb
Normal file
@ -0,0 +1,19 @@
|
||||
class DocsController < ApplicationController
|
||||
|
||||
def index; ;end
|
||||
|
||||
def talkback
|
||||
# default values
|
||||
@talkback_id = 3
|
||||
@talkback_api_key = 'XXXXXXXXXXXXXXXX'
|
||||
|
||||
# if user is signed in
|
||||
if current_user && current_user.talkbacks.any?
|
||||
@talkback = current_user.talkbacks.order('updated_at desc').first
|
||||
@talkback_id = @talkback.id
|
||||
@talkback_api_key = @talkback.api_key
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
@ -1,570 +1,254 @@
|
||||
class FeedController < ApplicationController
|
||||
require 'csv'
|
||||
layout 'application', :except => :index
|
||||
include FeedHelper
|
||||
require 'csv'
|
||||
layout 'application', :except => [:index, :debug]
|
||||
|
||||
def index
|
||||
channel = Channel.find(params[:channel_id])
|
||||
api_key = ApiKey.find_by_api_key(get_userkey)
|
||||
@success = channel_permission?(channel, api_key)
|
||||
def index
|
||||
feed_factory = FeedFactory.new(params)
|
||||
channel = Channel.find(params[:channel_id])
|
||||
api_key = ApiKey.find_by_api_key(get_apikey)
|
||||
@success = channel_permission?(channel, api_key)
|
||||
|
||||
# set timezone correctly
|
||||
set_time_zone(params)
|
||||
# set callback for jsonp
|
||||
@callback = params[:callback] if params[:callback]
|
||||
|
||||
# set limits
|
||||
limit = params[:results].to_i if params[:results]
|
||||
# set csv headers if necessary
|
||||
@csv_headers = feed_factory.feed_select_options if params[:format] == 'csv'
|
||||
|
||||
# check for access
|
||||
if @success
|
||||
# set timezone correctly
|
||||
set_time_zone(params)
|
||||
|
||||
# create options hash
|
||||
channel_options = { :only => channel_select_data(channel) }
|
||||
select_options = feed_select_data(channel)
|
||||
# check for access
|
||||
if @success
|
||||
|
||||
# get feed based on conditions
|
||||
feeds = Feed.find(
|
||||
:all,
|
||||
:conditions => { :channel_id => channel.id, :created_at => get_date_range(params) },
|
||||
:select => select_options,
|
||||
:order => 'created_at desc',
|
||||
:limit => limit
|
||||
)
|
||||
|
||||
# keep track of whether data has been rounded already
|
||||
rounded = false
|
||||
|
||||
# if a feed has data
|
||||
if !feeds.empty?
|
||||
# convert to timescales if necessary
|
||||
if timeparam_valid?(params[:timescale])
|
||||
feeds = feeds_into_timescales(feeds)
|
||||
# convert to sums if necessary
|
||||
elsif timeparam_valid?(params[:sum])
|
||||
feeds = feeds_into_sums(feeds)
|
||||
rounded = true
|
||||
# convert to averages if necessary
|
||||
elsif timeparam_valid?(params[:average])
|
||||
feeds = feeds_into_averages(feeds)
|
||||
rounded = true
|
||||
# convert to medians if necessary
|
||||
elsif timeparam_valid?(params[:median])
|
||||
feeds = feeds_into_medians(feeds)
|
||||
rounded = true
|
||||
end
|
||||
end
|
||||
|
||||
# if a feed needs to be rounded
|
||||
if params[:round] and !rounded
|
||||
feeds = object_round(feeds, params[:round].to_i)
|
||||
if feed_factory.cache_feeds
|
||||
# check cache for stored value
|
||||
feed_output_cache_key = cache_key('feed_output')
|
||||
channel_output_cache_key = cache_key('channel_output')
|
||||
@feed_output = Rails.cache.read(feed_output_cache_key)
|
||||
@channel_output = Rails.cache.read(channel_output_cache_key)
|
||||
end
|
||||
|
||||
# set output correctly
|
||||
if params[:format] == 'xml'
|
||||
@channel_output = channel.to_xml(channel_options).sub('</channel>', '').strip
|
||||
@feed_output = feeds.to_xml(:skip_instruct => true).gsub(/\n/, "\n ").chop.chop
|
||||
elsif params[:format] == 'csv'
|
||||
@feed_output = feeds
|
||||
else
|
||||
@channel_output = channel.to_json(channel_options).chop
|
||||
@feed_output = feeds.to_json
|
||||
end
|
||||
# if cache miss, get data
|
||||
if @feed_output.nil? or @channel_output.nil?
|
||||
|
||||
# else no access, set error code
|
||||
else
|
||||
if params[:format] == 'xml'
|
||||
@channel_output = bad_channel_xml
|
||||
else
|
||||
@channel_output = '-1'.to_json
|
||||
end
|
||||
end
|
||||
# get feeds
|
||||
feeds = feed_factory.get_output_feeds
|
||||
|
||||
# set callback for jsonp
|
||||
@callback = params[:callback] if params[:callback]
|
||||
# set output correctly
|
||||
if params[:format] == 'xml'
|
||||
@channel_output = channel.to_xml(channel.select_options).sub('</channel>', '').strip
|
||||
@feed_output = feeds.to_xml(:skip_instruct => true).gsub(/\n/, "\n ").chop.chop
|
||||
elsif params[:format] == 'csv'
|
||||
@feed_output = feeds
|
||||
else
|
||||
@channel_output = channel.to_json(channel.select_options).chop
|
||||
@feed_output = feeds.to_json(:only => feed_factory.feed_select_options)
|
||||
end
|
||||
|
||||
# set csv headers if necessary
|
||||
@csv_headers = select_options if params[:format] == 'csv'
|
||||
if feed_factory.cache_feeds
|
||||
# save to cache
|
||||
Rails.cache.write(feed_output_cache_key, @feed_output, :expires_in => 5.minutes)
|
||||
Rails.cache.write(channel_output_cache_key, @channel_output, :expires_in => 5.minutes)
|
||||
end
|
||||
|
||||
# output proper http response if error
|
||||
render :text => '-1', :status => 400 and return if !@success
|
||||
end # end if feeds not empty
|
||||
|
||||
# output data in proper format
|
||||
respond_to do |format|
|
||||
format.html
|
||||
format.json
|
||||
format.xml
|
||||
format.csv
|
||||
end
|
||||
end
|
||||
# else no access, set error code
|
||||
else
|
||||
if params[:format] == 'xml'
|
||||
@channel_output = bad_channel_xml
|
||||
else
|
||||
@channel_output = '-1'.to_json
|
||||
end
|
||||
end
|
||||
|
||||
def show
|
||||
@channel = Channel.find(params[:channel_id])
|
||||
@api_key = ApiKey.find_by_api_key(get_userkey)
|
||||
output = '-1'
|
||||
# output proper http response if error
|
||||
render :text => '-1', :status => 400 and return if !@success
|
||||
|
||||
# get most recent entry if necessary
|
||||
params[:id] = @channel.last_entry_id if params[:id] == 'last'
|
||||
# output data in proper format
|
||||
respond_to do |format|
|
||||
format.html
|
||||
format.json
|
||||
format.xml
|
||||
format.csv
|
||||
end
|
||||
end
|
||||
|
||||
# set timezone correctly
|
||||
set_time_zone(params)
|
||||
def last_sum
|
||||
last_method = method('last_group_call')
|
||||
last_method.call('sums')
|
||||
end
|
||||
|
||||
@feed = Feed.find(
|
||||
:first,
|
||||
:conditions => { :channel_id => @channel.id, :entry_id => params[:id] },
|
||||
:select => feed_select_data(@channel)
|
||||
)
|
||||
@success = channel_permission?(@channel, @api_key)
|
||||
def last_median
|
||||
last_method = method('last_group_call')
|
||||
last_method.call('medians')
|
||||
end
|
||||
|
||||
def last_average
|
||||
last_method = method('last_group_call')
|
||||
last_method.call('averages')
|
||||
end
|
||||
|
||||
def last_group_call(arg)
|
||||
@channel = Channel.find(params[:channel_id])
|
||||
@api_key = ApiKey.find_by_api_key(get_apikey)
|
||||
set_time_zone(params)
|
||||
|
||||
# limit for the number of results to get
|
||||
limit = 30
|
||||
limit = params[:sum].to_i if params[:sum].present?
|
||||
limit = params[:median].to_i if params[:median].present?
|
||||
limit = params[:average].to_i if params[:average].present?
|
||||
# max limit of 100 past results
|
||||
limit = 100 if limit > 100
|
||||
|
||||
# get the last (limit) feeds
|
||||
last_feeds = Feed.where(:channel_id => @channel.id).limit(limit).order('created_at desc')
|
||||
# put feeds in correct order (oldest to most recent)
|
||||
last_feeds.reverse!
|
||||
|
||||
feeds_into = self.method("feeds_into_#{arg}")
|
||||
feed = feeds_into.call(last_feeds, params).last if last_feeds.length > 0
|
||||
create_group_result(feed)
|
||||
end
|
||||
|
||||
def create_group_result(feed)
|
||||
@success = channel_permission?(@channel, @api_key)
|
||||
|
||||
# if a feed needs to be rounded
|
||||
if params[:round] && feed.present?
|
||||
feed = item_round(feed, params[:round].to_i)
|
||||
end
|
||||
|
||||
# check for access
|
||||
if @success && feed.present?
|
||||
# set output correctly
|
||||
if params[:format] == 'xml'
|
||||
output = feed.to_xml
|
||||
elsif params[:format] == 'csv'
|
||||
@csv_headers = Feed.select_options(@channel, params)
|
||||
elsif (params[:format] == 'txt' or params[:format] == 'text')
|
||||
output = add_prepend_append(feed["field#{params[:field_id]}"])
|
||||
else
|
||||
output = feed.to_json
|
||||
end
|
||||
# else set error code
|
||||
else
|
||||
if params[:format] == 'xml'
|
||||
output = bad_feed_xml
|
||||
else
|
||||
output = '-1'.to_json
|
||||
end
|
||||
end
|
||||
|
||||
# output data in proper format
|
||||
respond_to do |format|
|
||||
format.html { render :json => output }
|
||||
format.json { render :json => output, :callback => params[:callback] }
|
||||
format.xml { render :xml => output }
|
||||
format.csv
|
||||
format.text { render :text => output }
|
||||
end
|
||||
end
|
||||
|
||||
def show
|
||||
|
||||
@channel = Channel.find(params[:channel_id])
|
||||
@api_key = ApiKey.find_by_api_key(get_apikey)
|
||||
output = '-1'
|
||||
|
||||
# set timezone correctly
|
||||
set_time_zone(params)
|
||||
|
||||
# make sure field parameter is set correctly, changes "field1" to "1"
|
||||
params[:field_id] = params[:field_id].sub('field', '') if params[:field_id].present?
|
||||
|
||||
# if last entry
|
||||
if params[:id] == 'last' && params[:field_id].present? && params[:field_id].to_i != 0
|
||||
# look for a feed where the value isn't null
|
||||
@feed = Feed.where(:channel_id => @channel.id)
|
||||
.where("field? is not null", params[:field_id].to_i)
|
||||
.select(Feed.select_options(@channel, params))
|
||||
.order('entry_id desc')
|
||||
.first
|
||||
# else get by entry
|
||||
else
|
||||
# get most recent entry if necessary
|
||||
params[:id] = @channel.last_entry_id if params[:id] == 'last'
|
||||
@feed = Feed.where(:channel_id => @channel.id, :entry_id => params[:id]).select(Feed.select_options(@channel, params)).first
|
||||
end
|
||||
|
||||
@success = channel_permission?(@channel, @api_key)
|
||||
|
||||
# if a feed needs to be rounded
|
||||
if params[:round]
|
||||
@feed = item_round(@feed, params[:round].to_i)
|
||||
end
|
||||
|
||||
# check for access
|
||||
if @success
|
||||
# set output correctly
|
||||
if params[:format] == 'xml'
|
||||
output = @feed.to_xml
|
||||
elsif params[:format] == 'csv'
|
||||
@csv_headers = feed_select_data(@channel)
|
||||
elsif (params[:format] == 'txt' or params[:format] == 'text')
|
||||
output = add_prepend_append(@feed["field#{params[:field_id]}"])
|
||||
else
|
||||
output = @feed.to_json
|
||||
end
|
||||
# else set error code
|
||||
else
|
||||
if params[:format] == 'xml'
|
||||
output = bad_feed_xml
|
||||
else
|
||||
output = '-1'.to_json
|
||||
end
|
||||
end
|
||||
# check for access
|
||||
if @success and @feed
|
||||
|
||||
# output data in proper format
|
||||
respond_to do |format|
|
||||
format.html { render :json => output }
|
||||
format.json { render :json => output, :callback => params[:callback] }
|
||||
format.xml { render :xml => output }
|
||||
format.csv
|
||||
format.text { render :text => output }
|
||||
end
|
||||
end
|
||||
# set output correctly
|
||||
if params[:format] == 'xml'
|
||||
output = @feed.to_xml
|
||||
elsif params[:format] == 'csv'
|
||||
@csv_headers = Feed.select_options(@channel, params)
|
||||
elsif (params[:format] == 'txt' or params[:format] == 'text')
|
||||
output = add_prepend_append(@feed["field#{params[:field_id]}"])
|
||||
else
|
||||
output = @feed.to_json
|
||||
|
||||
private
|
||||
|
||||
# only output these fields for channel
|
||||
def channel_select_data(channel)
|
||||
only = [:name, :created_at, :updated_at, :id, :last_entry_id]
|
||||
only += [:description] unless channel.description.blank?
|
||||
only += [:latitude] unless channel.latitude.blank?
|
||||
only += [:longitude] unless channel.longitude.blank?
|
||||
only += [:elevation] unless channel.elevation.blank?
|
||||
only += [:field1] unless channel.field1.blank?
|
||||
only += [:field2] unless channel.field2.blank?
|
||||
only += [:field3] unless channel.field3.blank?
|
||||
only += [:field4] unless channel.field4.blank?
|
||||
only += [:field5] unless channel.field5.blank?
|
||||
only += [:field6] unless channel.field6.blank?
|
||||
only += [:field7] unless channel.field7.blank?
|
||||
only += [:field8] unless channel.field8.blank?
|
||||
|
||||
return only
|
||||
end
|
||||
|
||||
# only output these fields for feed
|
||||
def feed_select_data(channel)
|
||||
only = [:created_at]
|
||||
only += [:entry_id] unless timeparam_valid?(params[:timescale]) or timeparam_valid?(params[:average]) or timeparam_valid?(params[:median]) or timeparam_valid?(params[:sum])
|
||||
only += [:field1] unless channel.field1.blank? or (params[:field_id] and params[:field_id] != '1')
|
||||
only += [:field2] unless channel.field2.blank? or (params[:field_id] and params[:field_id] != '2')
|
||||
only += [:field3] unless channel.field3.blank? or (params[:field_id] and params[:field_id] != '3')
|
||||
only += [:field4] unless channel.field4.blank? or (params[:field_id] and params[:field_id] != '4')
|
||||
only += [:field5] unless channel.field5.blank? or (params[:field_id] and params[:field_id] != '5')
|
||||
only += [:field6] unless channel.field6.blank? or (params[:field_id] and params[:field_id] != '6')
|
||||
only += [:field7] unless channel.field7.blank? or (params[:field_id] and params[:field_id] != '7')
|
||||
only += [:field8] unless channel.field8.blank? or (params[:field_id] and params[:field_id] != '8')
|
||||
|
||||
# add geolocation data if necessary
|
||||
if params[:location] and params[:location].upcase == 'TRUE'
|
||||
only += [:latitude]
|
||||
only += [:longitude]
|
||||
only += [:elevation]
|
||||
end
|
||||
|
||||
# add status if necessary
|
||||
only += [:status] if params[:status] and params[:status].upcase == 'TRUE'
|
||||
|
||||
return only
|
||||
end
|
||||
|
||||
# checks for valid timescale
|
||||
def timeparam_valid?(timeparam)
|
||||
valid_minutes = [10, 15, 20, 30, 60, 240, 720, 1440]
|
||||
if timeparam and valid_minutes.include?(timeparam.to_i)
|
||||
return true
|
||||
else
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
# applies rounding to an enumerable object
|
||||
def object_round(object, round=nil, match='field')
|
||||
object.each_with_index do |o, index|
|
||||
object[index] = item_round(o, round, match)
|
||||
end
|
||||
|
||||
return object
|
||||
# else set error code
|
||||
else
|
||||
if params[:format] == 'xml'
|
||||
output = bad_feed_xml
|
||||
else
|
||||
output = '-1'.to_json
|
||||
end
|
||||
end
|
||||
|
||||
# applies rounding to a single item's attributes if necessary
|
||||
def item_round(item, round=nil, match='field')
|
||||
# for each attribute
|
||||
item.attribute_names.each do |attr|
|
||||
# only add non-null numeric fields
|
||||
if attr.index(match) and !item[attr].nil? and is_a_number?(item[attr])
|
||||
# keep track of whether the value contains commas
|
||||
comma_flag = (item[attr].to_s.index(',')) ? true : false
|
||||
|
||||
# replace commas with decimals if appropriate
|
||||
item[attr] = item[attr].to_s.gsub(/,/, '.') if comma_flag
|
||||
|
||||
# do the actual rounding
|
||||
item[attr] = sprintf "%.#{round}f", item[attr]
|
||||
|
||||
# replace decimals with commas if appropriate
|
||||
item[attr] = item[attr].to_s.gsub(/\./, ',') if comma_flag
|
||||
end
|
||||
end
|
||||
|
||||
# output new item
|
||||
return item
|
||||
# output data in proper format
|
||||
respond_to do |format|
|
||||
format.html { render :json => output }
|
||||
format.json { render :json => output, :callback => params[:callback] }
|
||||
format.xml { render :xml => output }
|
||||
format.csv
|
||||
format.text { render :text => output }
|
||||
end
|
||||
end
|
||||
|
||||
# slice feed into timescales
|
||||
def feeds_into_timescales(feeds)
|
||||
# convert timescale (minutes) into seconds
|
||||
seconds = params[:timescale].to_i * 60
|
||||
# get floored time ranges
|
||||
start_time = get_floored_time(feeds.first.created_at, seconds)
|
||||
end_time = get_floored_time(feeds.last.created_at, seconds)
|
||||
def debug
|
||||
@time_start = Time.now
|
||||
|
||||
# create empty array with appropriate size
|
||||
timeslices = Array.new((((end_time - start_time) / seconds).abs).floor)
|
||||
channel = Channel.find(params[:channel_id])
|
||||
api_key = ApiKey.find_by_api_key(get_apikey)
|
||||
@success = channel_permission?(channel, api_key)
|
||||
|
||||
# create options hash
|
||||
select_options = Feed.select_options(channel, params)
|
||||
|
||||
# create a blank clone of the first feed so that we only get the necessary attributes
|
||||
empty_feed = create_empty_clone(feeds.first)
|
||||
# get feed based on conditions
|
||||
feeds = Feed.find(
|
||||
:all,
|
||||
:conditions => { :channel_id => channel.id, :created_at => get_date_range(params) },
|
||||
:select => select_options,
|
||||
:order => 'created_at desc'
|
||||
)
|
||||
@count = feeds.count
|
||||
@time_after_db = Time.now
|
||||
|
||||
# add feeds to array
|
||||
feeds.each do |f|
|
||||
i = ((f.created_at - start_time) / seconds).floor
|
||||
f.created_at = start_time + i * seconds
|
||||
timeslices[i] = f if timeslices[i].nil?
|
||||
end
|
||||
# sort properly
|
||||
feeds.reverse!
|
||||
|
||||
# fill in empty array elements
|
||||
timeslices.each_index do |i|
|
||||
if timeslices[i].nil?
|
||||
current_feed = empty_feed.clone
|
||||
current_feed.created_at = (start_time + (i * seconds))
|
||||
timeslices[i] = current_feed
|
||||
end
|
||||
end
|
||||
@time_after_sort = Time.now
|
||||
|
||||
return timeslices
|
||||
end
|
||||
@channel_output = channel.to_json(channel.select_options).chop
|
||||
@feed_output = feeds.to_json
|
||||
|
||||
# slice feed into averages
|
||||
def feeds_into_averages(feeds)
|
||||
# convert timescale (minutes) into seconds
|
||||
seconds = params[:average].to_i * 60
|
||||
# get floored time ranges
|
||||
start_time = get_floored_time(feeds.first.created_at, seconds)
|
||||
end_time = get_floored_time(feeds.last.created_at, seconds)
|
||||
|
||||
# create empty array with appropriate size
|
||||
timeslices = Array.new(((end_time - start_time) / seconds).floor)
|
||||
|
||||
# create a blank clone of the first feed so that we only get the necessary attributes
|
||||
empty_feed = create_empty_clone(feeds.first)
|
||||
|
||||
# add feeds to array
|
||||
feeds.each do |f|
|
||||
i = ((f.created_at - start_time) / seconds).floor
|
||||
f.created_at = start_time + i * seconds
|
||||
# create multidimensional array
|
||||
timeslices[i] = [] if timeslices[i].nil?
|
||||
timeslices[i].push(f)
|
||||
end
|
||||
|
||||
# keep track of whether numbers use commas as decimals
|
||||
comma_flag = false
|
||||
|
||||
# fill in array
|
||||
timeslices.each_index do |i|
|
||||
# insert empty values
|
||||
if timeslices[i].nil?
|
||||
current_feed = empty_feed.clone
|
||||
current_feed.created_at = (start_time + (i * seconds))
|
||||
timeslices[i] = current_feed
|
||||
# else average the inner array
|
||||
else
|
||||
sum_feed = empty_feed.clone
|
||||
sum_feed.created_at = timeslices[i].first.created_at
|
||||
# for each feed
|
||||
timeslices[i].each do |f|
|
||||
# for each attribute, add to sum_feed so that we have the total
|
||||
sum_feed.attribute_names.each do |attr|
|
||||
|
||||
# only add non-null integer fields
|
||||
if attr.index('field') and !f[attr].nil? and is_a_number?(f[attr])
|
||||
# set comma_flag once if we find a number with a comma
|
||||
comma_flag = true if !comma_flag and f[attr].to_s.index(',')
|
||||
|
||||
# set initial data
|
||||
if sum_feed[attr].nil?
|
||||
sum_feed[attr] = parsefloat(f[attr])
|
||||
# add data
|
||||
elsif f[attr]
|
||||
sum_feed[attr] = parsefloat(sum_feed[attr]) + parsefloat(f[attr])
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
# set to the averaged feed
|
||||
timeslices[i] = object_average(sum_feed, timeslices[i].length, comma_flag, params[:round])
|
||||
end
|
||||
end
|
||||
|
||||
return timeslices
|
||||
end
|
||||
|
||||
# slice feed into medians
|
||||
def feeds_into_medians(feeds)
|
||||
# convert timescale (minutes) into seconds
|
||||
seconds = params[:median].to_i * 60
|
||||
# get floored time ranges
|
||||
start_time = get_floored_time(feeds.first.created_at, seconds)
|
||||
end_time = get_floored_time(feeds.last.created_at, seconds)
|
||||
|
||||
# create empty array with appropriate size
|
||||
timeslices = Array.new(((end_time - start_time) / seconds).floor)
|
||||
|
||||
# create a blank clone of the first feed so that we only get the necessary attributes
|
||||
empty_feed = create_empty_clone(feeds.first)
|
||||
|
||||
# add feeds to array
|
||||
feeds.each do |f|
|
||||
i = ((f.created_at - start_time) / seconds).floor
|
||||
f.created_at = start_time + i * seconds
|
||||
# create multidimensional array
|
||||
timeslices[i] = [] if timeslices[i].nil?
|
||||
timeslices[i].push(f)
|
||||
end
|
||||
|
||||
# keep track of whether numbers use commas as decimals
|
||||
comma_flag = false
|
||||
|
||||
# fill in array
|
||||
timeslices.each_index do |i|
|
||||
# insert empty values
|
||||
if timeslices[i].nil?
|
||||
current_feed = empty_feed.clone
|
||||
current_feed.created_at = (start_time + (i * seconds))
|
||||
timeslices[i] = current_feed
|
||||
# else get median values for the inner array
|
||||
else
|
||||
|
||||
# create blank hash called 'fields' to hold data
|
||||
fields = {}
|
||||
|
||||
# for each feed
|
||||
timeslices[i].each do |f|
|
||||
|
||||
# for each attribute
|
||||
f.attribute_names.each do |attr|
|
||||
if attr.index('field')
|
||||
|
||||
# create blank array for each field
|
||||
fields["#{attr}"] = [] if fields["#{attr}"].nil?
|
||||
|
||||
# push numeric field data onto its array
|
||||
if is_a_number?(f[attr])
|
||||
# set comma_flag once if we find a number with a comma
|
||||
comma_flag = true if !comma_flag and f[attr].to_s.index(',')
|
||||
|
||||
fields["#{attr}"].push(parsefloat(f[attr]))
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
# sort fields arrays
|
||||
fields.each_key do |key|
|
||||
fields[key] = fields[key].compact.sort
|
||||
end
|
||||
|
||||
# get the median
|
||||
median_feed = empty_feed.clone
|
||||
median_feed.created_at = timeslices[i].first.created_at
|
||||
median_feed.attribute_names.each do |attr|
|
||||
median_feed[attr] = object_median(fields[attr], comma_flag, params[:round]) if attr.index('field')
|
||||
end
|
||||
|
||||
timeslices[i] = median_feed
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
return timeslices
|
||||
end
|
||||
|
||||
# slice feed into sums
|
||||
def feeds_into_sums(feeds)
|
||||
# convert timescale (minutes) into seconds
|
||||
seconds = params[:sum].to_i * 60
|
||||
# get floored time ranges
|
||||
start_time = get_floored_time(feeds.first.created_at, seconds)
|
||||
end_time = get_floored_time(feeds.last.created_at, seconds)
|
||||
|
||||
# create empty array with appropriate size
|
||||
timeslices = Array.new(((end_time - start_time) / seconds).floor)
|
||||
|
||||
# create a blank clone of the first feed so that we only get the necessary attributes
|
||||
empty_feed = create_empty_clone(feeds.first)
|
||||
|
||||
# add feeds to array
|
||||
feeds.each do |f|
|
||||
i = ((f.created_at - start_time) / seconds).floor
|
||||
f.created_at = start_time + i * seconds
|
||||
# create multidimensional array
|
||||
timeslices[i] = [] if timeslices[i].nil?
|
||||
timeslices[i].push(f)
|
||||
end
|
||||
|
||||
# keep track of whether numbers use commas as decimals
|
||||
comma_flag = false
|
||||
|
||||
# fill in array
|
||||
timeslices.each_index do |i|
|
||||
# insert empty values
|
||||
if timeslices[i].nil?
|
||||
current_feed = empty_feed.clone
|
||||
current_feed.created_at = (start_time + (i * seconds))
|
||||
timeslices[i] = current_feed
|
||||
# else sum the inner array
|
||||
else
|
||||
sum_feed = empty_feed.clone
|
||||
sum_feed.created_at = timeslices[i].first.created_at
|
||||
# for each feed
|
||||
timeslices[i].each do |f|
|
||||
# for each attribute, add to sum_feed so that we have the total
|
||||
sum_feed.attribute_names.each do |attr|
|
||||
# only add non-null integer fields
|
||||
if attr.index('field') and !f[attr].nil? and is_a_number?(f[attr])
|
||||
|
||||
# set comma_flag once if we find a number with a comma
|
||||
comma_flag = true if !comma_flag and f[attr].to_s.index(',')
|
||||
|
||||
# set initial data
|
||||
if sum_feed[attr].nil?
|
||||
sum_feed[attr] = parsefloat(f[attr])
|
||||
# add data
|
||||
elsif f[attr]
|
||||
sum_feed[attr] = parsefloat(sum_feed[attr]) + parsefloat(f[attr])
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# set to the summed feed
|
||||
timeslices[i] = object_sum(sum_feed, comma_flag, params[:round])
|
||||
end
|
||||
end
|
||||
|
||||
return timeslices
|
||||
end
|
||||
|
||||
def is_a_number?(s)
|
||||
s.to_s.gsub(/,/, '.').match(/\A[+-]?\d+?(\.\d+)?\Z/) == nil ? false : true
|
||||
end
|
||||
|
||||
def parsefloat(number)
|
||||
return number.to_s.gsub(/,/, '.').to_f
|
||||
end
|
||||
|
||||
# gets the median for an object
|
||||
def object_median(object, comma_flag=false, round=nil)
|
||||
return nil if object.nil?
|
||||
length = object.length
|
||||
return nil if length == 0
|
||||
output = ''
|
||||
|
||||
# do the calculation
|
||||
if length % 2 == 0
|
||||
output = (object[(length - 1) / 2] + object[length / 2]) / 2
|
||||
else
|
||||
output = object[(length - 1) / 2]
|
||||
end
|
||||
|
||||
output = sprintf "%.#{round}f", output if round and is_a_number?(output)
|
||||
|
||||
# replace decimals with commas if appropriate
|
||||
output = output.to_s.gsub(/\./, ',') if comma_flag
|
||||
|
||||
return output.to_s
|
||||
end
|
||||
|
||||
# averages a summed object over length
|
||||
def object_average(object, length, comma_flag=false, round=nil)
|
||||
object.attribute_names.each do |attr|
|
||||
# only average non-null integer fields
|
||||
if !object[attr].nil? and is_a_number?(object[attr])
|
||||
if round
|
||||
object[attr] = sprintf "%.#{round}f", (parsefloat(object[attr]) / length)
|
||||
else
|
||||
object[attr] = (parsefloat(object[attr]) / length).to_s
|
||||
end
|
||||
# replace decimals with commas if appropriate
|
||||
object[attr] = object[attr].gsub(/\./, ',') if comma_flag
|
||||
end
|
||||
end
|
||||
|
||||
return object
|
||||
end
|
||||
|
||||
# formats a summed object correctly
|
||||
def object_sum(object, comma_flag=false, round=nil)
|
||||
object.attribute_names.each do |attr|
|
||||
# only average non-null integer fields
|
||||
if !object[attr].nil? and is_a_number?(object[attr])
|
||||
if round
|
||||
object[attr] = sprintf "%.#{round}f", parsefloat(object[attr])
|
||||
else
|
||||
object[attr] = parsefloat(object[attr]).to_s
|
||||
end
|
||||
# replace decimals with commas if appropriate
|
||||
object[attr] = object[attr].gsub(/\./, ',') if comma_flag
|
||||
end
|
||||
end
|
||||
|
||||
return object
|
||||
end
|
||||
|
||||
# creates an empty clone of an object
|
||||
def create_empty_clone(object)
|
||||
empty_clone = object.dup
|
||||
empty_clone.attribute_names.each { |attr| empty_clone[attr] = nil }
|
||||
return empty_clone
|
||||
end
|
||||
|
||||
# gets time floored to proper interval
|
||||
def get_floored_time(input_time, seconds)
|
||||
return Time.zone.at((input_time.to_f / seconds).floor * seconds)
|
||||
end
|
||||
@time_after_json = Time.now
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
@ -1,20 +1,23 @@
|
||||
class MailerController < ApplicationController
|
||||
|
||||
def resetpassword
|
||||
@user = User.find_by_login_or_email(params[:user][:login])
|
||||
def resetpassword
|
||||
# protect against bots
|
||||
render :text => '' and return if params[:userlogin].length > 0
|
||||
|
||||
if @user.nil?
|
||||
session[:mail_message] = t(:account_not_found)
|
||||
else
|
||||
begin
|
||||
@user.reset_perishable_token!
|
||||
# Mailer.password_reset(@user, "https://www.thingspeak.com/users/#{@user.id}/reset_password?token=#{@user.perishable_token}").deliver
|
||||
session[:mail_message] = t(:password_reset_mailed)
|
||||
rescue
|
||||
session[:mail_message] = t(:password_reset_error)
|
||||
end
|
||||
end
|
||||
redirect_to login_path
|
||||
end
|
||||
@user = User.find_by_login_or_email(params[:user][:login])
|
||||
if @user.nil?
|
||||
sleep 2
|
||||
session[:mail_message] = t(:account_not_found)
|
||||
else
|
||||
begin
|
||||
@user.reset_perishable_token!
|
||||
Mailer.password_reset(@user, "#{RESET_PASSWORD_URL}#{@user.id}?token=#{@user.perishable_token}").deliver
|
||||
session[:mail_message] = t(:password_reset_mailed)
|
||||
rescue
|
||||
session[:mail_message] = t(:password_reset_error)
|
||||
end
|
||||
end
|
||||
redirect_to login_path
|
||||
end
|
||||
|
||||
end
|
||||
|
44
app/controllers/maps_controller.rb
Normal file
@ -0,0 +1,44 @@
|
||||
class MapsController < ApplicationController
|
||||
|
||||
# show map with channel's location
|
||||
def channel_show
|
||||
set_map_vars
|
||||
render :layout => false
|
||||
end
|
||||
|
||||
# show social map with feed points as markers
|
||||
def show
|
||||
set_map_vars
|
||||
render :layout => false
|
||||
end
|
||||
|
||||
# set map variables
|
||||
def set_map_vars
|
||||
# allow these parameters when creating feed querystring
|
||||
feed_params = ['key','days','start','end','round','timescale','average','median','sum','results','status']
|
||||
|
||||
# default map size
|
||||
@width = default_width
|
||||
@height = default_height
|
||||
|
||||
# add extra parameters to querystring
|
||||
@qs = ''
|
||||
params.each do |p|
|
||||
@qs += "&#{p[0]}=#{p[1]}" if feed_params.include?(p[0])
|
||||
end
|
||||
|
||||
# set ssl
|
||||
@ssl = (get_header_value('x_ssl') == 'true')
|
||||
@map_domain = @ssl ? 'https://maps-api-ssl.google.com' : 'http://maps.google.com'
|
||||
@domain = domain(@ssl)
|
||||
end
|
||||
|
||||
private
|
||||
def default_width
|
||||
450
|
||||
end
|
||||
|
||||
def default_height
|
||||
250
|
||||
end
|
||||
end
|
@ -1,7 +1,23 @@
|
||||
class PagesController < ApplicationController
|
||||
layout 'application', :except => [:social_home]
|
||||
|
||||
def home
|
||||
@menu = 'home'
|
||||
end
|
||||
def home
|
||||
@menu = 'home'
|
||||
@title = 'Internet of Things'
|
||||
end
|
||||
|
||||
def social_home; ; end
|
||||
|
||||
def features
|
||||
@menu = 'features'
|
||||
end
|
||||
|
||||
def about
|
||||
@menu = 'about'
|
||||
end
|
||||
|
||||
def headers
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
16
app/controllers/pipes_controller.rb
Normal file
@ -0,0 +1,16 @@
|
||||
class PipesController < ApplicationController
|
||||
before_filter :require_admin, :set_admin_menu
|
||||
|
||||
def index
|
||||
@pipes = Pipe.paginate :page => params[:page], :order => 'created_at DESC'
|
||||
end
|
||||
|
||||
def new
|
||||
@pipe = Pipe.new
|
||||
end
|
||||
|
||||
def create
|
||||
|
||||
end
|
||||
|
||||
end
|
161
app/controllers/plugins_controller.rb
Normal file
@ -0,0 +1,161 @@
|
||||
class PluginsController < ApplicationController
|
||||
before_filter :require_user, :except => [:show_public, :show]
|
||||
before_filter :set_plugins_menu
|
||||
before_filter :check_permission, :only => ['edit', 'update', 'ajax_update', 'destroy']
|
||||
|
||||
def check_permission
|
||||
@plugin = Plugin.find(params[:id])
|
||||
if @plugin.user_id != current_user.id
|
||||
render :text=> "#{t(:permission)} #{t(:plugin)}", :layout => true
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
def index
|
||||
@plugins = current_user.plugins
|
||||
|
||||
end
|
||||
|
||||
def public_plugins
|
||||
|
||||
channel_id = params[:channel_id].to_i
|
||||
return if channel_id.nil?
|
||||
#private page should display all plugins
|
||||
#plugins = current_user.plugins.where("private_flag = true")
|
||||
@plugin_windows = []
|
||||
plugins = current_user.plugins
|
||||
plugins.each do |plugin|
|
||||
plugin.make_windows channel_id, api_domain #will only make the window the first time
|
||||
@plugin_windows = @plugin_windows + plugin.public_dashboard_windows(channel_id)
|
||||
|
||||
end
|
||||
|
||||
respond_to do |format|
|
||||
format.html { render :partial => 'plugins' }
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
def private_plugins
|
||||
channel_id = params[:channel_id].to_i
|
||||
return if channel_id.nil?
|
||||
#private page should display all plugins
|
||||
@plugin_windows = []
|
||||
|
||||
plugins = current_user.plugins
|
||||
|
||||
plugins.each do |plugin|
|
||||
plugin.make_windows channel_id, api_domain #will only make the window the first time
|
||||
@plugin_windows = @plugin_windows + plugin.private_dashboard_windows(channel_id)
|
||||
end
|
||||
respond_to do |format|
|
||||
format.html { render :partial => 'plugins' }
|
||||
end
|
||||
end
|
||||
|
||||
def create
|
||||
# add plugin with defaults
|
||||
@plugin = Plugin.new
|
||||
@plugin.html = read_file('app/views/plugins/default.html')
|
||||
@plugin.css = read_file('app/views/plugins/default.css')
|
||||
@plugin.js = read_file('app/views/plugins/default.js')
|
||||
@plugin.user_id = current_user.id
|
||||
@plugin.private_flag = true
|
||||
@plugin.save
|
||||
|
||||
# now that the plugin is saved, we can create the default name
|
||||
@plugin.name = "#{t(:plugin_default_name)} #{@plugin.id}"
|
||||
@plugin.save
|
||||
|
||||
# redirect to edit the newly created plugin
|
||||
redirect_to edit_plugin_path(@plugin.id)
|
||||
end
|
||||
|
||||
def show
|
||||
# Have to check permissions in the method so I can use show to display public, or private plugins
|
||||
@plugin = Plugin.find(params[:id])
|
||||
if @plugin.private?
|
||||
return if require_user
|
||||
render :text=> "#{t(:permission)} #{t(:plugin)}", :layout => true and return if check_permission
|
||||
end
|
||||
@output = @plugin.html.sub('%%PLUGIN_CSS%%', @plugin.css).sub('%%PLUGIN_JAVASCRIPT%%', @plugin.js)
|
||||
|
||||
if @plugin.private?
|
||||
render :layout => false and return
|
||||
else
|
||||
if request.url.include? api_domain
|
||||
render :layout => false and return
|
||||
else
|
||||
|
||||
protocol = ssl
|
||||
host = api_domain.split('://')[1]
|
||||
|
||||
redirect_to :host => host,
|
||||
:protocol => protocol,
|
||||
:controller => "plugins",
|
||||
:action => "show",
|
||||
:id => @plugin.id and return
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def show_public
|
||||
|
||||
@plugin = Plugin.find(params[:id])
|
||||
@output = @plugin.html.sub('%%PLUGIN_CSS%%', @plugin.css).sub('%%PLUGIN_JAVASCRIPT%%', @plugin.js)
|
||||
if @plugin.private?
|
||||
render :layout => false
|
||||
else
|
||||
if request.url.include? 'api_domain'
|
||||
render :layout => false
|
||||
else
|
||||
|
||||
redirect_to :host => api_domain,
|
||||
:controller => "plugins",
|
||||
:action => "show",
|
||||
:id => @plugin.id
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def edit
|
||||
end
|
||||
|
||||
def update
|
||||
@plugin.update_attribute(:name, params[:plugin][:name])
|
||||
@plugin.update_attribute(:private_flag, params[:plugin][:private_flag])
|
||||
@plugin.update_attribute(:css, params[:plugin][:css])
|
||||
@plugin.update_attribute(:js, params[:plugin][:js])
|
||||
@plugin.update_attribute(:html,params[:plugin][:html])
|
||||
|
||||
if @plugin.save
|
||||
|
||||
@plugin.update_all_windows
|
||||
redirect_to plugins_path and return
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
def ajax_update
|
||||
status = 0
|
||||
@plugin.update_attribute(:name, params[:plugin][:name])
|
||||
@plugin.update_attribute(:private_flag, params[:plugin][:private_flag])
|
||||
@plugin.update_attribute(:css, params[:plugin][:css])
|
||||
@plugin.update_attribute(:js, params[:plugin][:js])
|
||||
@plugin.update_attribute(:html, params[:plugin][:html])
|
||||
|
||||
if @plugin.save
|
||||
@plugin.update_all_windows
|
||||
status = 1
|
||||
end
|
||||
|
||||
# return response: 1=success, 0=failure
|
||||
render :json => status.to_json
|
||||
end
|
||||
|
||||
def destroy
|
||||
@plugin.destroy
|
||||
redirect_to plugins_path
|
||||
end
|
||||
end
|
@ -1,114 +1,158 @@
|
||||
class StatusController < ApplicationController
|
||||
require 'csv'
|
||||
require 'csv'
|
||||
layout false
|
||||
|
||||
def index
|
||||
@channel = Channel.find(params[:channel_id])
|
||||
@api_key = ApiKey.find_by_api_key(get_userkey)
|
||||
@success = channel_permission?(@channel, @api_key)
|
||||
def recent
|
||||
logger.info "Domain is #{@domain}"
|
||||
channel = Channel.find(params[:channel_id])
|
||||
@channel_id = channel.id
|
||||
if channel.public_flag || (current_user && current_user.id == channel.user_id)
|
||||
@statuses = channel.recent_statuses
|
||||
respond_to do |format|
|
||||
format.html { render :partial => 'status/recent' }
|
||||
format.json { render :json => @statuses}
|
||||
end
|
||||
else
|
||||
respond_to do |format|
|
||||
format.json { render :json => 'Status are not public' }
|
||||
format.html { render :text => 'Sorry the statuses are not public' }
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
# check for access
|
||||
if @success
|
||||
# create options hash
|
||||
channel_options = { :only => channel_select_terse(@channel) }
|
||||
|
||||
# display only 1 day by default
|
||||
params[:days] = 1 if !params[:days]
|
||||
def index
|
||||
@channel = Channel.find(params[:channel_id])
|
||||
@api_key = ApiKey.find_by_api_key(get_apikey)
|
||||
@success = channel_permission?(@channel, @api_key)
|
||||
|
||||
# get feed based on conditions
|
||||
@feeds = Feed.find(
|
||||
:all,
|
||||
:conditions => { :channel_id => @channel.id, :created_at => get_date_range(params) },
|
||||
:select => [:created_at, :status],
|
||||
:order => 'created_at'
|
||||
)
|
||||
# check for access
|
||||
if @success
|
||||
# create options hash
|
||||
channel_options = { :only => channel_select_terse(@channel) }
|
||||
|
||||
# set output correctly
|
||||
if params[:format] == 'xml'
|
||||
@channel_output = @channel.to_xml(channel_options).sub('</channel>', '').strip
|
||||
@feed_output = @feeds.to_xml(:skip_instruct => true).gsub(/\n/, "\n ").chop.chop
|
||||
elsif params[:format] == 'csv'
|
||||
@csv_headers = [:created_at, :status]
|
||||
@feed_output = @feeds
|
||||
else
|
||||
@channel_output = @channel.to_json(channel_options).chop
|
||||
@feed_output = @feeds.to_json
|
||||
end
|
||||
# else set error code
|
||||
else
|
||||
if params[:format] == 'xml'
|
||||
@channel_output = bad_channel_xml
|
||||
else
|
||||
@channel_output = '-1'.to_json
|
||||
end
|
||||
end
|
||||
# display only 1 day by default
|
||||
params[:days] = 1 if !params[:days]
|
||||
|
||||
# set callback for jsonp
|
||||
@callback = params[:callback] if params[:callback]
|
||||
|
||||
# output data in proper format
|
||||
respond_to do |format|
|
||||
format.html { render :text => @feed_output }
|
||||
format.json { render "feed/index" }
|
||||
format.xml { render "feed/index" }
|
||||
format.csv { render "feed/index" }
|
||||
end
|
||||
end
|
||||
# set limits
|
||||
limit = (request.format == 'csv') ? 1000000 : 8000
|
||||
limit = params[:results].to_i if (params[:results] and params[:results].to_i < 8000)
|
||||
|
||||
def show
|
||||
@channel = Channel.find(params[:channel_id])
|
||||
@api_key = ApiKey.find_by_api_key(params[:key])
|
||||
output = '-1'
|
||||
# get feed based on conditions
|
||||
@feeds = @channel.feeds.find(
|
||||
:all,
|
||||
:conditions => { :created_at => get_date_range(params) },
|
||||
:select => [:created_at, :entry_id, :status],
|
||||
:order => 'created_at desc',
|
||||
:limit => limit
|
||||
)
|
||||
|
||||
# get most recent entry if necessary
|
||||
params[:id] = @channel.last_entry_id if params[:id] == 'last'
|
||||
# sort properly
|
||||
@feeds.reverse!
|
||||
|
||||
@feed = Feed.find(
|
||||
:first,
|
||||
:conditions => { :channel_id => @channel.id, :entry_id => params[:id] },
|
||||
:select => [:created_at, :status]
|
||||
)
|
||||
@success = channel_permission?(@channel, @api_key)
|
||||
|
||||
# check for access
|
||||
if @success
|
||||
# set output correctly
|
||||
if params[:format] == 'xml'
|
||||
output = @feed.to_xml
|
||||
elsif params[:format] == 'csv'
|
||||
@csv_headers = [:created_at, :entry_id, :status]
|
||||
elsif (params[:format] == 'txt' or params[:format] == 'text')
|
||||
output = add_prepend_append(@feed.status)
|
||||
else
|
||||
output = @feed.to_json
|
||||
end
|
||||
# else set error code
|
||||
else
|
||||
if params[:format] == 'xml'
|
||||
output = bad_feed_xml
|
||||
else
|
||||
output = '-1'.to_json
|
||||
end
|
||||
end
|
||||
# set output correctly
|
||||
if request.format == 'xml'
|
||||
@channel_output = @channel.to_xml(channel_options).sub('</channel>', '').strip
|
||||
@feed_output = @feeds.to_xml(:skip_instruct => true).gsub(/\n/, "\n ").chop.chop
|
||||
elsif request.format == 'csv'
|
||||
@csv_headers = [:created_at, :entry_id, :status]
|
||||
@feed_output = @feeds
|
||||
else
|
||||
@channel_output = @channel.to_json(channel_options).chop
|
||||
@feed_output = @feeds.to_json
|
||||
end
|
||||
# else set error code
|
||||
else
|
||||
if params[:format] == 'xml'
|
||||
@channel_output = bad_channel_xml
|
||||
else
|
||||
@channel_output = '-1'.to_json
|
||||
|
||||
# output data in proper format
|
||||
respond_to do |format|
|
||||
format.html { render :json => output }
|
||||
format.json { render :json => output, :callback => params[:callback] }
|
||||
format.xml { render :xml => output }
|
||||
format.csv { render :action => 'feed/show' }
|
||||
format.text { render :text => output }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# set callback for jsonp
|
||||
@callback = params[:callback] if params[:callback]
|
||||
|
||||
# output data in proper format
|
||||
respond_to do |format|
|
||||
|
||||
format.html { render :template => 'feed/index' }
|
||||
format.json { render :template => 'feed/index' }
|
||||
format.xml { render :template => 'feed/index' }
|
||||
format.csv { render :template => 'feed/index' }
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
def show
|
||||
@channel = Channel.find(params[:channel_id])
|
||||
@api_key = ApiKey.find_by_api_key(get_apikey)
|
||||
output = '-1'
|
||||
|
||||
# get most recent entry if necessary
|
||||
params[:id] = @channel.last_entry_id if params[:id] == 'last'
|
||||
|
||||
|
||||
@feed = @channel.feeds.find(
|
||||
:first,
|
||||
:conditions => { :entry_id => params[:id] },
|
||||
:select => [:created_at, :entry_id, :status]
|
||||
)
|
||||
|
||||
@success = channel_permission?(@channel, @api_key)
|
||||
|
||||
# check for access
|
||||
if @success
|
||||
# set output correctly
|
||||
|
||||
if request.format == 'xml'
|
||||
output = @feed.to_xml
|
||||
elsif request.format == 'csv'
|
||||
@csv_headers = [:created_at, :entry_id, :status]
|
||||
elsif (request.format == 'txt' or request.format == 'text')
|
||||
|
||||
output = add_prepend_append(@feed.status)
|
||||
else
|
||||
output = @feed.to_json
|
||||
end
|
||||
|
||||
# else set error code
|
||||
else
|
||||
if request.format == 'xml'
|
||||
|
||||
output = bad_feed_xml
|
||||
else
|
||||
output = '-1'.to_json
|
||||
end
|
||||
end
|
||||
|
||||
# output data in proper format
|
||||
respond_to do |format|
|
||||
format.html { render :json => output }
|
||||
format.json { render :json => output, :callback => params[:callback] }
|
||||
|
||||
format.xml { render :xml => output }
|
||||
format.csv { render :action => 'feed/show' }
|
||||
|
||||
format.text { render :text => output }
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# only output these fields for channel
|
||||
def channel_select_terse(channel)
|
||||
only = [:name]
|
||||
only += [:latitude] unless channel.latitude.nil?
|
||||
only += [:longitude] unless channel.longitude.nil?
|
||||
only += [:elevation] unless channel.elevation.nil? or channel.elevation.empty?
|
||||
|
||||
return only
|
||||
end
|
||||
|
||||
private
|
||||
# only output these fields for channel
|
||||
def channel_select_terse(channel)
|
||||
only = [:name]
|
||||
only += [:latitude] unless channel.latitude.nil?
|
||||
only += [:longitude] unless channel.longitude.nil?
|
||||
only += [:elevation] unless channel.elevation.nil? or channel.elevation.empty?
|
||||
|
||||
return only
|
||||
end
|
||||
|
||||
end
|
||||
|
@ -1,15 +1,15 @@
|
||||
class SubdomainsController < ApplicationController
|
||||
|
||||
# show a blank page if subdomain
|
||||
def index
|
||||
render :text => ''
|
||||
end
|
||||
# show a blank page if subdomain
|
||||
def index
|
||||
render :text => ''
|
||||
end
|
||||
|
||||
# output the file crossdomain.xml.erb
|
||||
def crossdomain
|
||||
respond_to do |format|
|
||||
format.xml
|
||||
end
|
||||
end
|
||||
# output the file crossdomain.xml.erb
|
||||
def crossdomain
|
||||
respond_to do |format|
|
||||
format.xml
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
37
app/controllers/tags_controller.rb
Normal file
@ -0,0 +1,37 @@
|
||||
class TagsController < ApplicationController
|
||||
def index
|
||||
|
||||
render 'show' and return if params[:channel_id].nil?
|
||||
|
||||
channel = Channel.find(params[:channel_id])
|
||||
if current_user && channel.nil?
|
||||
tag = Tag.find_by_name(params[:id], :include => :channels, :conditions => ['channels.public_flag = true OR channels.user_id = ?', current_user.id])
|
||||
else
|
||||
channels = []
|
||||
channel.tags.each do |tag|
|
||||
channels << tag.channel_ids
|
||||
end
|
||||
|
||||
channels = channels.flatten.uniq
|
||||
|
||||
end
|
||||
redirect_to public_channels_path(:channel_ids => channels)
|
||||
end
|
||||
|
||||
def create
|
||||
redirect_to tag_path(params[:tag][:name])
|
||||
end
|
||||
|
||||
def show
|
||||
# if user is logged in, search their channels also
|
||||
if current_user
|
||||
tag = Tag.find_by_name(params[:id], :include => :channels, :conditions => ['channels.public_flag = true OR channels.user_id = ?', current_user.id])
|
||||
# else only search public channels
|
||||
else
|
||||
tag = Tag.find_by_name(params[:id], :include => :channels, :conditions => ['channels.public_flag = true'])
|
||||
end
|
||||
|
||||
@results = tag.channels if tag
|
||||
end
|
||||
|
||||
end
|
@ -3,42 +3,53 @@ class UserSessionsController < ApplicationController
|
||||
before_filter :require_user, :only => :destroy
|
||||
|
||||
def new
|
||||
@title = t(:signin)
|
||||
@title = t(:signin)
|
||||
@user_session = UserSession.new
|
||||
@mail_message = session[:mail_message] if !session[:mail_message].nil?
|
||||
@mail_message = session[:mail_message] if !session[:mail_message].nil?
|
||||
end
|
||||
|
||||
def show
|
||||
redirect_to root_path
|
||||
end
|
||||
def show
|
||||
redirect_to root_path
|
||||
end
|
||||
|
||||
def create
|
||||
if params[:userlogin].length > 0
|
||||
render :text => ''
|
||||
else
|
||||
@user_session = UserSession.new(params[:user_session])
|
||||
|
||||
# remember user_id if checkbox is checked
|
||||
if params[:user_session][:remember_id] == '1'
|
||||
cookies['user_id'] = { :value => params[:user_session][:login], :expires => 1.month.from_now }
|
||||
else
|
||||
cookies.delete 'user_id'
|
||||
end
|
||||
if params[:userlogin].length > 0
|
||||
render :text => ''
|
||||
else
|
||||
@user_session = UserSession.new(params[:user_session])
|
||||
|
||||
if @user_session.save
|
||||
redirect_to root_path and return
|
||||
else
|
||||
# prevent timing and brute force password attacks
|
||||
sleep 1
|
||||
@failed = true
|
||||
render :action => :new
|
||||
end
|
||||
end
|
||||
# remember user_id if checkbox is checked
|
||||
if params[:user_session][:remember_id] == '1'
|
||||
cookies['user_id'] = { :value => params[:user_session][:login], :expires => 1.month.from_now }
|
||||
else
|
||||
cookies.delete 'user_id'
|
||||
end
|
||||
|
||||
if @user_session.save
|
||||
# if link_back, redirect back
|
||||
redirect_to session[:link_back] and return if session[:link_back]
|
||||
redirect_to channels_path and return
|
||||
else
|
||||
# log to failedlogins
|
||||
failed = Failedlogin.new
|
||||
failed.login = params[:user_session][:login]
|
||||
failed.password = params[:user_session][:password]
|
||||
failed.ip_address = get_header_value('X_REAL_IP')
|
||||
failed.save
|
||||
|
||||
# prevent timing and brute force password attacks
|
||||
sleep 1
|
||||
@failed = true
|
||||
render :action => :new
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def destroy
|
||||
session[:link_back] = nil
|
||||
current_user_session.destroy
|
||||
reset_session
|
||||
redirect_to root_path
|
||||
reset_session
|
||||
redirect_to root_path
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -1,85 +1,180 @@
|
||||
class UsersController < ApplicationController
|
||||
include KeyUtilities
|
||||
before_filter :require_no_user, :only => [:new, :create, :forgot_password]
|
||||
before_filter :require_user, :only => [:show, :edit, :update, :change_password]
|
||||
before_filter :require_user, :only => [:show, :edit, :update, :change_password, :edit_profile]
|
||||
|
||||
# generates a new api key
|
||||
def new_api_key
|
||||
current_user.set_new_api_key!
|
||||
redirect_to account_path
|
||||
end
|
||||
|
||||
# edit public profile
|
||||
def edit_profile
|
||||
@user = current_user
|
||||
end
|
||||
|
||||
# update public profile
|
||||
def update_profile
|
||||
@user = current_user # makes our views "cleaner" and more consistent
|
||||
# update
|
||||
@user.update_attributes(user_params)
|
||||
redirect_to account_path
|
||||
end
|
||||
|
||||
# public profile for a user
|
||||
def profile
|
||||
# set params and request.format correctly
|
||||
set_request_details!(params)
|
||||
|
||||
@user = User.find_by_login(params[:id])
|
||||
|
||||
# output error if user not found
|
||||
render :text => t(:user_not_found) and return if @user.nil?
|
||||
|
||||
# if a json or xml request
|
||||
if request.format == :json || request.format == :xml
|
||||
# authenticate the user if api key matches the target user
|
||||
authenticated = (User.find_by_api_key(get_apikey) == @user)
|
||||
# set options correctly
|
||||
options = authenticated ? User.private_options : User.public_options(@user)
|
||||
end
|
||||
|
||||
respond_to do |format|
|
||||
format.html
|
||||
format.json { render :json => @user.as_json(options) }
|
||||
format.xml { render :xml => @user.to_xml(options) }
|
||||
end
|
||||
end
|
||||
|
||||
# list all public channels for a user
|
||||
def list_channels
|
||||
@user = User.find_by_login(params[:id])
|
||||
|
||||
# output error if user not found
|
||||
render :text => t(:user_not_found) and return if @user.nil?
|
||||
|
||||
# if html request
|
||||
if request.format == :html
|
||||
@title = "Internet of Things - Public Channels for #{@user.login}"
|
||||
@channels = @user.channels.public_viewable.paginate :page => params[:page], :order => 'last_entry_id DESC'
|
||||
# if a json or xml request
|
||||
elsif request.format == :json || request.format == :xml
|
||||
# authenticate the user if api key matches the target user
|
||||
authenticated = (User.find_by_api_key(get_apikey) == @user)
|
||||
# get all channels if authenticated, otherwise only public ones
|
||||
channels = authenticated ? @user.channels : @user.channels.public_viewable
|
||||
# set channels correctly
|
||||
@channels = { channels: channels.as_json(Channel.public_options) }
|
||||
end
|
||||
|
||||
respond_to do |format|
|
||||
format.html
|
||||
format.json { render :json => @channels }
|
||||
format.xml { render :xml => @channels.to_xml(:root => 'response') }
|
||||
end
|
||||
end
|
||||
|
||||
def new
|
||||
@title = t(:signup)
|
||||
@title = t(:signup)
|
||||
@user = User.new
|
||||
end
|
||||
|
||||
|
||||
def create
|
||||
@user = User.new(params[:user])
|
||||
@user = User.new(user_params)
|
||||
@user.api_key = generate_api_key(16, 'user')
|
||||
|
||||
# save user
|
||||
if @user.valid?
|
||||
if @user.save
|
||||
redirect_back_or_default account_path
|
||||
end
|
||||
else
|
||||
render :action => :new
|
||||
end
|
||||
# save user
|
||||
if @user.valid?
|
||||
|
||||
if @user.save
|
||||
redirect_back_or_default channels_path and return
|
||||
end
|
||||
else
|
||||
render :action => :new
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
def show
|
||||
@menu = 'account'
|
||||
@user = current_user
|
||||
@menu = 'account'
|
||||
@user = @current_user
|
||||
end
|
||||
|
||||
|
||||
def edit
|
||||
@menu = 'account'
|
||||
@user = current_user
|
||||
@menu = 'account'
|
||||
@user = @current_user
|
||||
end
|
||||
|
||||
# displays forgot password page
|
||||
def forgot_password
|
||||
end
|
||||
# displays forgot password page
|
||||
def forgot_password
|
||||
@user = User.new
|
||||
end
|
||||
|
||||
# this action is called from an email link when a password reset is requested
|
||||
def reset_password
|
||||
# if user has been logged in (due to previous form submission)
|
||||
if !current_user.nil?
|
||||
@user = current_user
|
||||
@user.errors.add(t(:password_problem))
|
||||
@valid_link = true
|
||||
else
|
||||
@user = User.find_by_id(params[:id])
|
||||
# make sure tokens match and password reset is within last 10 minutes
|
||||
if @user.perishable_token == params[:token] && @user.updated_at > 600.seconds.ago
|
||||
@valid_link = true
|
||||
# log the user in
|
||||
@user_session = UserSession.new(@user)
|
||||
@user_session.save
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# do the actual password change
|
||||
def change_password
|
||||
# protect against bots
|
||||
render :text => '' and return if params[:userlogin].length > 0
|
||||
# this action is called from an email link when a password reset is requested
|
||||
def reset_password
|
||||
# if user has been logged in (due to previous form submission)
|
||||
if !current_user.nil?
|
||||
@user = current_user
|
||||
@user.errors.add(:base, t(:password_problem))
|
||||
@valid_link = true
|
||||
else
|
||||
@user = User.find_by_id(params[:id])
|
||||
# make sure tokens match and password reset is within last 10 minutes
|
||||
if @user.perishable_token == params[:token] && @user.updated_at > 600.seconds.ago
|
||||
@valid_link = true
|
||||
# log the user in
|
||||
@user_session = UserSession.new(@user)
|
||||
@user_session.save
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# do the actual password change
|
||||
def change_password
|
||||
@user = current_user
|
||||
# if no password entered, redirect
|
||||
redirect_to reset_password_path and return if params[:user][:password].empty?
|
||||
# check current password and update
|
||||
if @user.update_attributes(params[:user])
|
||||
# if no password entered, redirect
|
||||
redirect_to reset_password_path and return if params[:user][:password].empty?
|
||||
# check current password and update
|
||||
if @user.update_attributes(user_params)
|
||||
redirect_to account_path
|
||||
else
|
||||
redirect_to reset_password_path
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def update
|
||||
@menu = 'account'
|
||||
@user = current_user # makes our views "cleaner" and more consistent
|
||||
# check current password and update
|
||||
if @user.valid_password?(params[:password_current]) && @user.update_attributes(params[:user])
|
||||
@menu = 'account'
|
||||
@user = @current_user # makes our views "cleaner" and more consistent
|
||||
# check current password and update
|
||||
if @user.valid_password?(params[:password_current]) && @user.update_attributes(user_params)
|
||||
redirect_to account_path
|
||||
else
|
||||
@user.errors.add :base, t(:password_incorrect)
|
||||
render :edit
|
||||
@user.errors.add(:base, t(:password_incorrect))
|
||||
render :action => :edit
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
private
|
||||
|
||||
# only allow these params
|
||||
def user_params
|
||||
params.require(:user).permit(:email, :login, :time_zone, :public_flag, :bio, :website, :password, :password_confirmation)
|
||||
end
|
||||
|
||||
# set params[:id] and request.format correctly
|
||||
def set_request_details!(params)
|
||||
# set format
|
||||
new_format = 'html' if params[:glob].end_with?('.html')
|
||||
new_format = 'json' if params[:glob].end_with?('.json')
|
||||
new_format = 'xml' if params[:glob].end_with?('.xml')
|
||||
|
||||
# remove the format from the end of the glob
|
||||
params[:id] = params[:glob].chomp(".#{new_format}")
|
||||
|
||||
# set the new format if it exists
|
||||
request.format = new_format.to_sym if new_format.present?
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
219
app/controllers/windows_controller.rb
Normal file
@ -0,0 +1,219 @@
|
||||
class WindowsController < ApplicationController
|
||||
before_filter :require_user, :except => [:index, :html, :iframe]
|
||||
|
||||
def hide
|
||||
window = Window.find(params[:id])
|
||||
window.show_flag = false
|
||||
if window.save
|
||||
render :text => window.id.to_s
|
||||
else
|
||||
render :text => '-1'
|
||||
end
|
||||
end
|
||||
|
||||
# Call WindowsController.display when we want to display a window on the dashboard
|
||||
# params[:visibility_flag] is whether it is the private or public dashboard
|
||||
# params[:plugin] is for displaying a plugin, instead of a window
|
||||
# params[:id] is the window ID for conventional windows, but the plugin_id for plugins
|
||||
# params[:channel_id] is the channel_id
|
||||
def display
|
||||
@visibility = params[:visibility_flag]
|
||||
|
||||
window = Window.find(params[:id])
|
||||
window = Window.new if window.nil?
|
||||
window.show_flag = true
|
||||
#Just save this change, then modify the object before rendering the JSON
|
||||
savedWindow = window.save
|
||||
|
||||
config_window window
|
||||
|
||||
@mychannel = current_user && current_user.id == window.channel.user_id
|
||||
|
||||
if savedWindow
|
||||
render :json => window.to_json
|
||||
else
|
||||
render :json => 'An error occurred'.to_json
|
||||
end
|
||||
end
|
||||
|
||||
def config_window(window)
|
||||
if window.type == "PluginWindow"
|
||||
pluginName = Plugin.find(window.window_detail.plugin_id).name
|
||||
window.title = t(window.title, {:name => pluginName})
|
||||
elsif window.type == "ChartWindow"
|
||||
window.title = t(window.title, {:field_number => window.window_detail.field_number})
|
||||
options = window.becomes(ChartWindow).window_detail.options if !window.becomes(ChartWindow).window_detail.nil?
|
||||
options ||= ""
|
||||
window.html["::OPTIONS::"] = options unless window.html.nil? || window.html.index("::OPTIONS::").nil?
|
||||
else
|
||||
window.title = t(window.title)
|
||||
end
|
||||
end
|
||||
def html
|
||||
window = Window.find(params[:id])
|
||||
options = window.window_detail.options unless window.window_detail.nil? || window.type!="ChartWindow"
|
||||
window.html["::OPTIONS::"] = options unless window.html.nil? || window.html.index("::OPTIONS::").nil?
|
||||
html = window.html
|
||||
|
||||
render :text => html
|
||||
end
|
||||
|
||||
def iframe
|
||||
window = Window.find(params[:id])
|
||||
options = window.window_detail.options unless window.window_detail.nil? || window.type!="ChartWindow"
|
||||
window.html["::OPTIONS::"] = options unless window.html.nil? || window.html.index("::OPTIONS::").nil?
|
||||
iframe_html = window.html
|
||||
|
||||
iframe_html = iframe_html.gsub(/src=\"[\/.]/, 'src="' + api_domain);
|
||||
render :text => iframe_html
|
||||
end
|
||||
|
||||
def index
|
||||
channel = Channel.find(params[:channel_id])
|
||||
|
||||
channel.update_status_portlet false if (channel.windows.select { |w| w.wtype == :status && w.private_flag == false } )
|
||||
channel.update_status_portlet true if (channel.windows.select { |w| w.wtype == :status && w.private_flag == true } )
|
||||
channel.update_video_portlet false if (channel.windows.select { |w| w.wtype == :video && w.private_flag == false } )
|
||||
channel.update_video_portlet true if (channel.windows.select { |w| w.wtype == :video && w.private_flag == true } )
|
||||
channel.update_location_portlet false if (channel.windows.select { |w| w.wtype == :location && w.private_flag == false } )
|
||||
channel.update_location_portlet true if (channel.windows.select { |w| w.wtype == :location && w.private_flag == true } )
|
||||
channel.update_chart_portlets if (channel.windows.select { |w| w.wtype == :chart } )
|
||||
windows = channel.public_windows(true).order(:position) unless params[:channel_id].nil?
|
||||
|
||||
if channel.recent_statuses.nil? || channel.recent_statuses.size <= 0
|
||||
@windows = windows.delete_if { |w| w.wtype == "status" }
|
||||
else
|
||||
@windows = windows
|
||||
end
|
||||
|
||||
@windows.each do |window|
|
||||
|
||||
if window.type == "PluginWindow"
|
||||
pluginName = Plugin.find(window.window_detail.plugin_id).name
|
||||
window.title = t(window.title, {:name => pluginName})
|
||||
elsif window.type == "ChartWindow"
|
||||
window.title = t(window.title, {:field_number => window.window_detail.field_number})
|
||||
options = window.becomes(ChartWindow).window_detail.options if !window.becomes(ChartWindow).window_detail.nil?
|
||||
options ||= ""
|
||||
window.html["::OPTIONS::"] = options unless window.html.nil? || window.html.index("::OPTIONS::").nil?
|
||||
else
|
||||
window.title = t(window.title)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
respond_to do |format|
|
||||
format.html
|
||||
format.json { render :json => @windows.as_json( :include => [:window_detail] ) }
|
||||
end
|
||||
end
|
||||
|
||||
# This is going to display windows that are hidden (show_flag = false)
|
||||
# The "visibility_flag" param indicates whether it's public or private visibility
|
||||
def hidden_windows
|
||||
@visibility = params[:visibility_flag]
|
||||
channel = Channel.find(params[:channel_id])
|
||||
|
||||
if @visibility == "private"
|
||||
@windows = channel.private_windows(false) unless channel.nil?
|
||||
else
|
||||
@windows = channel.public_windows(false) unless channel.nil?
|
||||
end
|
||||
@windows.reject! { |window| window.type == "PluginWindow" }
|
||||
@windows.each do |window|
|
||||
if window.type == "PluginWindow"
|
||||
elsif window.type == "ChartWindow"
|
||||
window.title = t(window.title, {:field_number => window.window_detail.field_number})
|
||||
options = window.becomes(ChartWindow).window_detail.options unless window.becomes(ChartWindow).window_detail.nil?
|
||||
options ||= ""
|
||||
window.html["::OPTIONS::"] = options unless window.html.nil? || window.html.index("::OPTIONS::").nil?
|
||||
else
|
||||
window.title = t(window.title)
|
||||
end
|
||||
end
|
||||
|
||||
respond_to do |format|
|
||||
format.html { render :partial => "hidden_windows" }
|
||||
# format.json { render :json => @windows.as_json( :include => [:window_detail] ) }
|
||||
end
|
||||
end
|
||||
|
||||
def private_windows
|
||||
channel = Channel.find(params[:channel_id])
|
||||
|
||||
channel.update_status_portlet false if (channel.windows.select { |w| w.wtype == :status && w.private_flag == false } )
|
||||
channel.update_status_portlet true if (channel.windows.select { |w| w.wtype == :status && w.private_flag == true } )
|
||||
channel.update_video_portlet false if (channel.windows.select { |w| w.wtype == :video && w.private_flag == false } )
|
||||
channel.update_video_portlet true if (channel.windows.select { |w| w.wtype == :video && w.private_flag == true } )
|
||||
channel.update_location_portlet false if (channel.windows.select { |w| w.wtype == :location && w.private_flag == false } )
|
||||
channel.update_location_portlet true if (channel.windows.select { |w| w.wtype == :location && w.private_flag == true } )
|
||||
channel.update_chart_portlets if (channel.windows.select { |w| w.wtype == :chart } )
|
||||
|
||||
windows = channel.private_windows(true).order(:position) unless params[:channel_id].nil?
|
||||
|
||||
if channel.recent_statuses.nil? || channel.recent_statuses.size <= 0
|
||||
@windows = windows.delete_if { |w| w.wtype == "status" }
|
||||
else
|
||||
@windows = windows
|
||||
end
|
||||
|
||||
@windows.each do |window|
|
||||
if window.type == "PluginWindow"
|
||||
windowDetail = window.window_detail
|
||||
pluginName = Plugin.find(windowDetail.plugin_id).name
|
||||
window.title = t(window.title, {:name => pluginName})
|
||||
elsif window.type == "ChartWindow"
|
||||
window.title = t(window.title, {:field_number => window.window_detail.field_number})
|
||||
options = window.becomes(ChartWindow).window_detail.options unless window.becomes(ChartWindow).window_detail.nil?
|
||||
options ||= ""
|
||||
window.html["::OPTIONS::"] = options unless window.html.nil? || window.html.index("::OPTIONS::").nil?
|
||||
else
|
||||
window.title = t(window.title)
|
||||
end
|
||||
end
|
||||
|
||||
respond_to do |format|
|
||||
format.html
|
||||
format.json { render :json => @windows.as_json( :include => [:window_detail] ) }
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def update
|
||||
logger.info "We're trying to update the windows with " + params.to_s
|
||||
#params for this put are going to look like
|
||||
# page"=>"{\"col\":0,\"positions\":[1,2,3]}"
|
||||
#So.. the position values are Windows.id They should get updated with the ordinal value based
|
||||
# on their array position and the column should get updated according to col value.
|
||||
# Since the windows are order by position, when a window record changes from
|
||||
# col1,position0 -> col0,position0 the entire new column is reordered.
|
||||
# The old column is missing a position, but the remaining are just left to their order
|
||||
# (ie., 0,1,2 become 1,2) Unless they are also changed
|
||||
|
||||
# First parse the JSON in params["page"] ...
|
||||
values = JSON(params[:page])
|
||||
|
||||
# .. then find each window and update with new ordinal position and col.
|
||||
logger.info "Channel id = " + params[:channel_id].to_s
|
||||
@channel = current_user.channels.find(params[:channel_id])
|
||||
col = values["col"]
|
||||
saved = true
|
||||
values["positions"].each_with_index do |p,i|
|
||||
windows = @channel.windows.where({:id => p}) unless p.nil?
|
||||
unless windows.nil? || windows.empty?
|
||||
w = windows[0]
|
||||
w.position = i
|
||||
w.col = col
|
||||
if !w.save
|
||||
saved = false
|
||||
end
|
||||
end
|
||||
end
|
||||
if saved
|
||||
render :text => '0'
|
||||
else
|
||||
render :text => '-1'
|
||||
end
|
||||
|
||||
end
|
||||
end
|
@ -1,2 +1,12 @@
|
||||
module ApplicationHelper
|
||||
|
||||
def is_a_number?(s)
|
||||
s.to_s.gsub(/,/, '.').match(/\A[+-]?\d+?(\.\d+)?\Z/) == nil ? false : true
|
||||
end
|
||||
|
||||
def parsefloat(number)
|
||||
return number.to_s.gsub(/,/, '.').to_f
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
2
app/helpers/apps_helper.rb
Normal file
@ -0,0 +1,2 @@
|
||||
module AppsHelper
|
||||
end
|
11
app/helpers/channels_helper.rb
Normal file
@ -0,0 +1,11 @@
|
||||
module ChannelsHelper
|
||||
include ApplicationHelper
|
||||
def auth_channels_path
|
||||
if current_user
|
||||
'/channels'
|
||||
else
|
||||
'/channels/public'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
2
app/helpers/comments_helper.rb
Normal file
@ -0,0 +1,2 @@
|
||||
module CommentsHelper
|
||||
end
|
@ -1,2 +1,365 @@
|
||||
module FeedHelper
|
||||
include ApplicationHelper
|
||||
|
||||
# applies rounding to an enumerable object
|
||||
def object_round(object, round=nil, match='field')
|
||||
object.each_with_index do |o, index|
|
||||
object[index] = item_round(o, round, match)
|
||||
end
|
||||
|
||||
return object
|
||||
end
|
||||
|
||||
# applies rounding to a single item's attributes if necessary
|
||||
def item_round(item, round=nil, match='field')
|
||||
return nil if item.nil?
|
||||
|
||||
# for each attribute
|
||||
item.attribute_names.each do |attr|
|
||||
# only add non-null numeric fields
|
||||
if attr.index(match) and !item[attr].nil? and is_a_number?(item[attr])
|
||||
# keep track of whether the value contains commas
|
||||
comma_flag = (item[attr].to_s.index(',')) ? true : false
|
||||
|
||||
# replace commas with decimals if appropriate
|
||||
item[attr] = item[attr].to_s.gsub(/,/, '.') if comma_flag
|
||||
|
||||
# do the actual rounding
|
||||
item[attr] = sprintf "%.#{round}f", item[attr]
|
||||
|
||||
# replace decimals with commas if appropriate
|
||||
item[attr] = item[attr].to_s.gsub(/\./, ',') if comma_flag
|
||||
end
|
||||
end
|
||||
|
||||
# output new item
|
||||
return item
|
||||
end
|
||||
# gets the median for an object
|
||||
def object_median(object, comma_flag=false, round=nil)
|
||||
return nil if object.nil?
|
||||
length = object.length
|
||||
return nil if length == 0
|
||||
output = ''
|
||||
|
||||
# do the calculation
|
||||
if length % 2 == 0
|
||||
output = (object[(length - 1) / 2] + object[length / 2]) / 2
|
||||
else
|
||||
output = object[(length - 1) / 2]
|
||||
end
|
||||
|
||||
output = sprintf "%.#{round}f", output if round and is_a_number?(output)
|
||||
|
||||
# replace decimals with commas if appropriate
|
||||
output = output.to_s.gsub(/\./, ',') if comma_flag
|
||||
|
||||
return output.to_s
|
||||
end
|
||||
|
||||
# averages a summed object over length
|
||||
def object_average(object, length, comma_flag=false, round=nil)
|
||||
object.attribute_names.each do |attr|
|
||||
# only average non-null integer fields
|
||||
if !object[attr].nil? and is_a_number?(object[attr])
|
||||
if round
|
||||
object[attr] = sprintf "%.#{round}f", (parsefloat(object[attr]) / length)
|
||||
else
|
||||
object[attr] = (parsefloat(object[attr]) / length).to_s
|
||||
end
|
||||
# replace decimals with commas if appropriate
|
||||
object[attr] = object[attr].gsub(/\./, ',') if comma_flag
|
||||
end
|
||||
end
|
||||
|
||||
return object
|
||||
end
|
||||
|
||||
# formats a summed object correctly
|
||||
def object_sum(object, comma_flag=false, round=nil)
|
||||
object.attribute_names.each do |attr|
|
||||
# only average non-null integer fields
|
||||
if !object[attr].nil? and is_a_number?(object[attr])
|
||||
if round
|
||||
object[attr] = sprintf "%.#{round}f", parsefloat(object[attr])
|
||||
else
|
||||
object[attr] = parsefloat(object[attr]).to_s
|
||||
end
|
||||
# replace decimals with commas if appropriate
|
||||
object[attr] = object[attr].gsub(/\./, ',') if comma_flag
|
||||
end
|
||||
end
|
||||
|
||||
return object
|
||||
end
|
||||
|
||||
|
||||
def create_empty_clone(object)
|
||||
empty_clone = object.dup
|
||||
empty_clone.attribute_names.each { |attr| empty_clone[attr] = nil }
|
||||
return empty_clone
|
||||
end
|
||||
def get_floored_time(input_time, seconds)
|
||||
return Time.zone.at((input_time.to_f / seconds).floor * seconds)
|
||||
end
|
||||
# slice feed into timescales
|
||||
def feeds_into_timescales(feeds, params)
|
||||
|
||||
# convert timescale (minutes) into seconds
|
||||
seconds = params[:timescale].to_i * 60
|
||||
# get floored time ranges
|
||||
start_time = get_floored_time(feeds.first.created_at, seconds)
|
||||
end_time = get_floored_time(feeds.last.created_at, seconds)
|
||||
|
||||
# create empty array with appropriate size
|
||||
timeslices = Array.new((((end_time - start_time) / seconds).abs).floor)
|
||||
|
||||
# create a blank clone of the first feed so that we only get the necessary attributes
|
||||
empty_feed = create_empty_clone(feeds.first)
|
||||
|
||||
# add feeds to array
|
||||
feeds.each do |f|
|
||||
i = ((f.created_at - start_time) / seconds).floor
|
||||
f.created_at = start_time + i * seconds
|
||||
timeslices[i] = f if timeslices[i].nil?
|
||||
end
|
||||
|
||||
# fill in empty array elements
|
||||
timeslices.each_index do |i|
|
||||
if timeslices[i].nil?
|
||||
current_feed = empty_feed.dup
|
||||
current_feed.created_at = (start_time + (i * seconds))
|
||||
timeslices[i] = current_feed
|
||||
end
|
||||
end
|
||||
|
||||
return timeslices
|
||||
end
|
||||
|
||||
|
||||
# slice feed into sums
|
||||
def feeds_into_sums(feeds, params)
|
||||
# convert timescale (minutes) into seconds
|
||||
seconds = params[:sum].to_i * 60
|
||||
# get floored time ranges
|
||||
start_time = get_floored_time(feeds.first.created_at, seconds)
|
||||
end_time = get_floored_time(feeds.last.created_at, seconds)
|
||||
|
||||
# create empty array with appropriate size
|
||||
timeslices = Array.new((((end_time - start_time) / seconds).abs).floor)
|
||||
|
||||
# create a blank clone of the first feed so that we only get the necessary attributes
|
||||
empty_feed = create_empty_clone(feeds.first)
|
||||
|
||||
# add feeds to array
|
||||
feeds.each do |f|
|
||||
i = ((f.created_at - start_time) / seconds).floor
|
||||
f.created_at = start_time + i * seconds
|
||||
# create multidimensional array
|
||||
timeslices[i] = [] if timeslices[i].nil?
|
||||
timeslices[i].push(f)
|
||||
end
|
||||
|
||||
# keep track of whether numbers use commas as decimals
|
||||
comma_flag = false
|
||||
|
||||
# fill in array
|
||||
timeslices.each_index do |i|
|
||||
# insert empty values
|
||||
if timeslices[i].nil?
|
||||
current_feed = empty_feed.dup
|
||||
current_feed.created_at = (start_time + (i * seconds))
|
||||
timeslices[i] = current_feed
|
||||
# else sum the inner array
|
||||
else
|
||||
sum_feed = empty_feed.dup
|
||||
sum_feed.created_at = timeslices[i].first.created_at
|
||||
# for each feed
|
||||
timeslices[i].each do |f|
|
||||
# for each attribute, add to sum_feed so that we have the total
|
||||
sum_feed.attribute_names.each do |attr|
|
||||
# only add non-null integer fields
|
||||
if attr.index('field') and !f[attr].nil? and is_a_number?(f[attr])
|
||||
|
||||
# set comma_flag once if we find a number with a comma
|
||||
comma_flag = true if !comma_flag and f[attr].to_s.index(',')
|
||||
|
||||
# set initial data
|
||||
if sum_feed[attr].nil?
|
||||
sum_feed[attr] = parsefloat(f[attr])
|
||||
# add data
|
||||
elsif f[attr]
|
||||
sum_feed[attr] = parsefloat(sum_feed[attr]) + parsefloat(f[attr])
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# set to the summed feed
|
||||
timeslices[i] = object_sum(sum_feed, comma_flag, params[:round])
|
||||
end
|
||||
end
|
||||
|
||||
return timeslices
|
||||
end
|
||||
|
||||
def feeds_into_averages(feeds, params)
|
||||
|
||||
# convert timescale (minutes) into seconds
|
||||
seconds = params[:average].to_i * 60
|
||||
# get floored time ranges
|
||||
start_time = get_floored_time(feeds.first.created_at, seconds)
|
||||
end_time = get_floored_time(feeds.last.created_at, seconds)
|
||||
|
||||
# create empty array with appropriate size
|
||||
timeslices = Array.new((((end_time - start_time) / seconds).abs).floor)
|
||||
|
||||
# create a blank clone of the first feed so that we only get the necessary attributes
|
||||
empty_feed = create_empty_clone(feeds.first)
|
||||
|
||||
# add feeds to array normalizing created time for timeslices
|
||||
feeds.each do |f|
|
||||
i = ((f.created_at - start_time) / seconds).floor
|
||||
f.created_at = start_time + i * seconds
|
||||
# create multidimensional array that will hold all feeds for each timeslice
|
||||
timeslices[i] = [] if timeslices[i].nil?
|
||||
timeslices[i].push(f)
|
||||
end
|
||||
|
||||
# keep track of whether numbers use commas as decimals
|
||||
comma_flag = false
|
||||
|
||||
# fill in array
|
||||
timeslices.each_index do |i|
|
||||
# insert empty values if there wasn't a feed value for a slice, just enter an empty feed
|
||||
if timeslices[i].nil?
|
||||
current_feed = empty_feed.dup
|
||||
current_feed.created_at = (start_time + (i * seconds))
|
||||
timeslices[i] = current_feed
|
||||
# else average the inner array
|
||||
else
|
||||
sum_feed = empty_feed.dup
|
||||
sum_feed.created_at = timeslices[i].first.created_at
|
||||
# for each feed
|
||||
timeslices[i].each do |f|
|
||||
# for each attribute, add to sum_feed so that we have the total
|
||||
|
||||
sum_feed.attribute_names.each do |attr|
|
||||
|
||||
# only add non-null integer fields
|
||||
if attr.index('field') and !f[attr].nil? and is_a_number?(f[attr])
|
||||
# set comma_flag once if we find a number with a comma
|
||||
comma_flag = true if !comma_flag and f[attr].to_s.index(',')
|
||||
# set initial data
|
||||
if sum_feed[attr].nil?
|
||||
sum_feed[attr] = parsefloat(f[attr])
|
||||
elsif f[attr] # add data
|
||||
sum_feed[attr] = parsefloat(sum_feed[attr]) + parsefloat(f[attr])
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
# set to the averaged feed
|
||||
timeslices[i] = object_average(sum_feed, timeslices[i].length, comma_flag, params[:round])
|
||||
end
|
||||
end
|
||||
|
||||
return timeslices
|
||||
end
|
||||
|
||||
# slice feed into medians
|
||||
def feeds_into_medians(feeds, params)
|
||||
# convert timescale (minutes) into seconds
|
||||
seconds = params[:median].to_i * 60
|
||||
# get floored time ranges
|
||||
start_time = get_floored_time(feeds.first.created_at, seconds)
|
||||
end_time = get_floored_time(feeds.last.created_at, seconds)
|
||||
|
||||
# create empty array with appropriate size
|
||||
timeslices = Array.new((((end_time - start_time) / seconds).abs).floor)
|
||||
|
||||
# create a blank clone of the first feed so that we only get the necessary attributes
|
||||
empty_feed = create_empty_clone(feeds.first)
|
||||
|
||||
# add feeds to array
|
||||
feeds.each do |f|
|
||||
i = ((f.created_at - start_time) / seconds).floor
|
||||
f.created_at = start_time + i * seconds
|
||||
# create multidimensional array
|
||||
timeslices[i] = [] if timeslices[i].nil?
|
||||
timeslices[i].push(f)
|
||||
end
|
||||
|
||||
# keep track of whether numbers use commas as decimals
|
||||
comma_flag = false
|
||||
|
||||
# fill in array
|
||||
timeslices.each_index do |i|
|
||||
# insert empty values
|
||||
if timeslices[i].nil?
|
||||
current_feed = empty_feed.dup
|
||||
current_feed.created_at = (start_time + (i * seconds))
|
||||
timeslices[i] = current_feed
|
||||
# else get median values for the inner array
|
||||
else
|
||||
|
||||
# create blank hash called 'fields' to hold data
|
||||
fields = {}
|
||||
|
||||
# for each feed
|
||||
timeslices[i].each do |f|
|
||||
|
||||
# for each attribute
|
||||
f.attribute_names.each do |attr|
|
||||
if attr.index('field')
|
||||
|
||||
# create blank array for each field
|
||||
fields["#{attr}"] = [] if fields["#{attr}"].nil?
|
||||
|
||||
# push numeric field data onto its array
|
||||
if is_a_number?(f[attr])
|
||||
# set comma_flag once if we find a number with a comma
|
||||
comma_flag = true if !comma_flag and f[attr].to_s.index(',')
|
||||
|
||||
fields["#{attr}"].push(parsefloat(f[attr]))
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
# sort fields arrays
|
||||
fields.each_key do |key|
|
||||
fields[key] = fields[key].compact.sort
|
||||
end
|
||||
|
||||
# get the median
|
||||
median_feed = empty_feed.dup
|
||||
median_feed.created_at = timeslices[i].first.created_at
|
||||
median_feed.attribute_names.each do |attr|
|
||||
median_feed[attr] = object_median(fields[attr], comma_flag, params[:round]) if attr.index('field')
|
||||
end
|
||||
|
||||
timeslices[i] = median_feed
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
return timeslices
|
||||
end
|
||||
|
||||
# checks for valid timescale
|
||||
def timeparam_valid?(timeparam)
|
||||
valid_minutes = [10, 15, 20, 30, 60, 240, 720, 1440]
|
||||
if timeparam and valid_minutes.include?(timeparam.to_i)
|
||||
return true
|
||||
else
|
||||
return false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
2
app/helpers/maps_helper.rb
Normal file
@ -0,0 +1,2 @@
|
||||
module MapsHelper
|
||||
end
|
@ -1,2 +1,28 @@
|
||||
module PagesHelper
|
||||
def blog_entries
|
||||
blog = ''
|
||||
begin
|
||||
Timeout::timeout(5) do
|
||||
# get the blog data
|
||||
blog_url = "http://community.thingspeak.com"
|
||||
doc = Nokogiri::HTML(open(blog_url, "User-Agent" => "Ruby/#{RUBY_VERSION}").read)
|
||||
|
||||
# parse out the html we need
|
||||
doc.css("img").remove
|
||||
doc.css("script").remove
|
||||
doc.css("iframe").remove
|
||||
doc.css("div.post").each_with_index do |d, i|
|
||||
# only show 3 posts
|
||||
if (i < 3)
|
||||
blog += d.css("h2").to_s
|
||||
blog += d.css("div.entry").to_s
|
||||
blog += "<br /><br />"
|
||||
end
|
||||
end
|
||||
end
|
||||
rescue Timeout::Error
|
||||
rescue
|
||||
end
|
||||
blog
|
||||
end
|
||||
end
|
||||
|
2
app/helpers/pipes_helper.rb
Normal file
@ -0,0 +1,2 @@
|
||||
module PipesHelper
|
||||
end
|
2
app/helpers/plugins_helper.rb
Normal file
@ -0,0 +1,2 @@
|
||||
module PluginsHelper
|
||||
end
|
14
app/jobs/clear_channel_job.rb
Normal file
@ -0,0 +1,14 @@
|
||||
class ClearChannelJob
|
||||
@queue = :clear_channel
|
||||
|
||||
def self.perform(channel_id)
|
||||
Feed.delete_all(["channel_id = ?", channel_id])
|
||||
DailyFeed.delete_all(["channel_id = ?", channel_id])
|
||||
if channel = Channel.find(channel_id)
|
||||
channel.last_entry_id = nil
|
||||
channel.clearing = false
|
||||
channel.save
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -1,14 +1,27 @@
|
||||
# == Schema Information
|
||||
#
|
||||
# Table name: api_keys
|
||||
#
|
||||
# id :integer not null, primary key
|
||||
# api_key :string(16)
|
||||
# channel_id :integer
|
||||
# user_id :integer
|
||||
# write_flag :boolean default(FALSE)
|
||||
# created_at :datetime
|
||||
# updated_at :datetime
|
||||
# note :string(255)
|
||||
#
|
||||
|
||||
class ApiKey < ActiveRecord::Base
|
||||
belongs_to :channel
|
||||
belongs_to :user
|
||||
|
||||
validates_uniqueness_of :api_key
|
||||
|
||||
scope :write_keys, :conditions => { :write_flag => true }
|
||||
scope :read_keys, :conditions => { :write_flag => false }
|
||||
scope :write_keys, lambda { where("write_flag = true") }
|
||||
scope :read_keys, lambda { where("write_flag = false") }
|
||||
|
||||
attr_readonly :created_at
|
||||
attr_accessible :note
|
||||
|
||||
def to_s
|
||||
api_key
|
||||
@ -22,18 +35,3 @@ end
|
||||
|
||||
|
||||
|
||||
|
||||
# == Schema Information
|
||||
#
|
||||
# Table name: api_keys
|
||||
#
|
||||
# id :integer(4) not null, primary key
|
||||
# api_key :string(16)
|
||||
# channel_id :integer(4)
|
||||
# user_id :integer(4)
|
||||
# write_flag :boolean(1) default(FALSE)
|
||||
# created_at :datetime
|
||||
# updated_at :datetime
|
||||
# note :string(255)
|
||||
#
|
||||
|
||||
|
@ -1,18 +1,392 @@
|
||||
# == Schema Information
|
||||
#
|
||||
# Table name: channels
|
||||
#
|
||||
# id :integer not null, primary key
|
||||
# user_id :integer
|
||||
# name :string(255)
|
||||
# description :string(255)
|
||||
# latitude :decimal(15, 10)
|
||||
# longitude :decimal(15, 10)
|
||||
# field1 :string(255)
|
||||
# field2 :string(255)
|
||||
# field3 :string(255)
|
||||
# field4 :string(255)
|
||||
# field5 :string(255)
|
||||
# field6 :string(255)
|
||||
# field7 :string(255)
|
||||
# field8 :string(255)
|
||||
# scale1 :integer
|
||||
# scale2 :integer
|
||||
# scale3 :integer
|
||||
# scale4 :integer
|
||||
# scale5 :integer
|
||||
# scale6 :integer
|
||||
# scale7 :integer
|
||||
# scale8 :integer
|
||||
# created_at :datetime
|
||||
# updated_at :datetime
|
||||
# elevation :string(255)
|
||||
# last_entry_id :integer
|
||||
# public_flag :boolean default(FALSE)
|
||||
# options1 :string(255)
|
||||
# options2 :string(255)
|
||||
# options3 :string(255)
|
||||
# options4 :string(255)
|
||||
# options5 :string(255)
|
||||
# options6 :string(255)
|
||||
# options7 :string(255)
|
||||
# options8 :string(255)
|
||||
# social :boolean default(FALSE)
|
||||
# slug :string(255)
|
||||
# status :string(255)
|
||||
# url :string(255)
|
||||
# video_id :string(255)
|
||||
# video_type :string(255)
|
||||
# clearing :boolean default(FALSE), not null
|
||||
# ranking :integer
|
||||
#
|
||||
|
||||
class Channel < ActiveRecord::Base
|
||||
include KeyUtilities
|
||||
|
||||
|
||||
belongs_to :user
|
||||
has_many :feeds
|
||||
has_many :api_keys
|
||||
has_many :feeds
|
||||
has_many :daily_feeds
|
||||
has_many :api_keys, :dependent => :destroy
|
||||
has_many :taggings
|
||||
has_many :tags, :through => :taggings
|
||||
has_many :comments, :dependent => :destroy
|
||||
has_many :windows, :dependent => :destroy, :autosave => true
|
||||
|
||||
self.include_root_in_json = true
|
||||
|
||||
attr_readonly :created_at
|
||||
attr_protected :user_id, :last_entry_id
|
||||
|
||||
after_create :set_initial_default_name
|
||||
before_validation :set_default_name
|
||||
after_destroy :delete_feeds
|
||||
|
||||
validates :name, :presence => true, :on => :update
|
||||
after_commit :set_default_name
|
||||
after_commit :set_ranking, :unless => "ranking == calc_ranking"
|
||||
|
||||
before_destroy :delete_feeds
|
||||
|
||||
validates :video_type, :presence => true, :if => lambda{ |channel| !channel.video_id.nil? && !channel.video_id.empty?}
|
||||
|
||||
scope :public_viewable, lambda { where("public_flag = true AND social != true") }
|
||||
scope :is_public, lambda { where("public_flag = true") }
|
||||
scope :active, lambda { where("channels.last_entry_id > 1 and channels.updated_at > ?", DateTime.now.utc - 7.day) }
|
||||
scope :being_cleared, lambda { where("clearing = true") }
|
||||
scope :by_array, lambda {|ids| { :conditions => ["id in (?)", ids.uniq] } }
|
||||
scope :with_tag, lambda {|name| joins(:tags).where("tags.name = ?", name) }
|
||||
|
||||
# pagination variables
|
||||
cattr_reader :per_page
|
||||
@@per_page = 15
|
||||
|
||||
# select options
|
||||
def select_options
|
||||
only = [:name, :created_at, :updated_at, :id, :last_entry_id]
|
||||
only += [:description] unless self.description.blank?
|
||||
only += [:latitude] unless self.latitude.blank?
|
||||
only += [:longitude] unless self.longitude.blank?
|
||||
only += [:elevation] unless self.elevation.blank?
|
||||
only += [:field1] unless self.field1.blank?
|
||||
only += [:field2] unless self.field2.blank?
|
||||
only += [:field3] unless self.field3.blank?
|
||||
only += [:field4] unless self.field4.blank?
|
||||
only += [:field5] unless self.field5.blank?
|
||||
only += [:field6] unless self.field6.blank?
|
||||
only += [:field7] unless self.field7.blank?
|
||||
only += [:field8] unless self.field8.blank?
|
||||
|
||||
# return a hash
|
||||
return { :only => only }
|
||||
end
|
||||
|
||||
# adds a feed to the channel
|
||||
def add_status_feed(status)
|
||||
# update the entry_id for the channel
|
||||
entry_id = self.next_entry_id
|
||||
self.last_entry_id = entry_id
|
||||
self.save
|
||||
# create the new feed with the correct status and entry_id
|
||||
self.feeds.create(:status => status, :entry_id => entry_id)
|
||||
end
|
||||
|
||||
# get next last_entry_id for a channel
|
||||
def next_entry_id
|
||||
self.last_entry_id.nil? ? 1 : self.last_entry_id + 1
|
||||
end
|
||||
|
||||
# for internal admin use, shows the ids of a channel per month (useful as a proxy for growth)
|
||||
def show_growth
|
||||
output = []
|
||||
date = self.feeds.order("entry_id asc").first.created_at
|
||||
|
||||
# while the date is in the past
|
||||
while (date < Time.now)
|
||||
# get a feed on that day
|
||||
feed = self.feeds.where("created_at > ?", date).where("created_at < ?", date + 1.day).first
|
||||
# output the date and feed id
|
||||
output << "#{date.strftime('%Y-%m-%d')},#{feed.id}" if feed.present?
|
||||
# set the date 1 month further
|
||||
date = date + 1.month
|
||||
end
|
||||
|
||||
# show the output
|
||||
puts output.join("\n")
|
||||
end
|
||||
|
||||
# paginated hash for json and xml output
|
||||
# channels input must be paginated
|
||||
def self.paginated_hash(channels)
|
||||
{
|
||||
pagination:
|
||||
{
|
||||
current_page: channels.current_page,
|
||||
per_page: channels.per_page,
|
||||
total_entries: channels.total_entries,
|
||||
},
|
||||
channels: channels.as_json(Channel.public_options)
|
||||
}
|
||||
end
|
||||
|
||||
# for to_json or to_xml, return only the public attributes
|
||||
def self.public_options
|
||||
{
|
||||
:root => false,
|
||||
:only => [:id, :name, :description, :latitude, :longitude, :last_entry_id, :elevation, :created_at, :ranking],
|
||||
:methods => :username,
|
||||
:include => { :tags => {:only => [:id, :name]}}
|
||||
}
|
||||
end
|
||||
|
||||
# login name of the user who created the channel
|
||||
def username; self.user.try(:login); end
|
||||
|
||||
# custom as_json method to allow: root => false
|
||||
def as_json(options = nil)
|
||||
root = include_root_in_json
|
||||
root = options[:root] if options.try(:key?, :root)
|
||||
if root
|
||||
root = self.class.model_name.element if root == true
|
||||
{ root => serializable_hash(options) }
|
||||
else
|
||||
serializable_hash(options)
|
||||
end
|
||||
end
|
||||
|
||||
def private_windows *hidden
|
||||
if hidden.size >= 1
|
||||
return windows.where("private_flag = true and show_flag = #{hidden[0].to_s}")
|
||||
else
|
||||
return windows.where("private_flag = true" )
|
||||
end
|
||||
end
|
||||
|
||||
# overloaded version witthout private/public flag for the has_many dependent destroy action
|
||||
def public_windows hidden
|
||||
return windows.where("private_flag = false and show_flag = #{hidden}")
|
||||
end
|
||||
# measure of activity in terms of feeds per time period
|
||||
|
||||
def public?
|
||||
return public_flag
|
||||
end
|
||||
|
||||
def video_changed?
|
||||
video_id_changed? || video_type_changed?
|
||||
end
|
||||
|
||||
def location_changed?
|
||||
latitude_changed? || longitude_changed?
|
||||
end
|
||||
|
||||
def feeds_changed?
|
||||
field1_changed? ||
|
||||
field2_changed? ||
|
||||
field3_changed? ||
|
||||
field4_changed? ||
|
||||
field5_changed? ||
|
||||
field6_changed? ||
|
||||
field7_changed? ||
|
||||
field8_changed?
|
||||
end
|
||||
|
||||
def update_chart_portlets
|
||||
self.fields.each do |field|
|
||||
update_chart_portlet field, true
|
||||
update_chart_portlet field, false
|
||||
end
|
||||
#remove portlets for fields that don't exist
|
||||
#iterate all chart windows... and look for a matching field
|
||||
chartWindows = windows.where(:wtype => :chart )
|
||||
chartWindows.each do |window|
|
||||
if self.send(window.name).blank?
|
||||
window.destroy
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
def update_status_portlet isPrivate
|
||||
|
||||
window = windows.where(:wtype => :status, :private_flag => isPrivate )
|
||||
|
||||
status_html = "<iframe class=\"statusIFrame\" width=\"450\" height=\"260\" frameborder=\"0\" src=\"/channels/#{id}/status/recent\"></iframe>"
|
||||
|
||||
if window.nil? || window[0].nil?
|
||||
|
||||
window = PortletWindow.new
|
||||
window.wtype = :status
|
||||
window.position = 1
|
||||
window.col = 1
|
||||
window.title = "window_status"
|
||||
else
|
||||
|
||||
window = window[0]
|
||||
end
|
||||
|
||||
window.private_flag = isPrivate
|
||||
window.html = status_html
|
||||
window.window_detail = PortletWindowDetail.new if window.window_detail.nil?
|
||||
self.windows.push window
|
||||
|
||||
end
|
||||
|
||||
def video_fields_valid?
|
||||
!video_id.nil? && !video_id.empty? && !video_type.nil? && !video_type.empty?
|
||||
end
|
||||
|
||||
def update_video_portlet isPrivate
|
||||
window = windows.where(:wtype => :video, :private_flag => isPrivate )
|
||||
if video_fields_valid?
|
||||
youtube_html = "<iframe class=\"youtube-player\" type=\"text/html\" width=\"452\" height=\"260\" src=\"https://www.youtube.com/embed/#{video_id}?wmode=transparent\" frameborder=\"0\" wmode=\"Opaque\" ></iframe>"
|
||||
vimeo_html = "<iframe class=\"vimeo-player\" type=\"text/html\" width=\"452\" height=\"260\" src=\"http://player.vimeo.com/video/#{video_id}\" frameborder=\"0\"></iframe>"
|
||||
if window.nil? || window[0].nil?
|
||||
window = PortletWindow.new
|
||||
window.wtype = :video
|
||||
window.position = 1
|
||||
window.col = 1
|
||||
window.title = "window_channel_video"
|
||||
else
|
||||
window = window[0]
|
||||
end
|
||||
window.private_flag = isPrivate
|
||||
window.html = youtube_html if video_type == 'youtube'
|
||||
window.html = vimeo_html if video_type == 'vimeo'
|
||||
window.window_detail = PortletWindowDetail.new if window.window_detail.nil?
|
||||
self.windows.push window
|
||||
else
|
||||
unless window[0].nil?
|
||||
window[0].delete
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def update_location_portlet isPrivate
|
||||
window = windows.where(:wtype => :location, :private_flag => isPrivate )
|
||||
if !latitude.nil? && !longitude.nil?
|
||||
maps_html = "<iframe width=\"450\" height=\"260\" frameborder=\"0\" scrolling=\"no\" " +
|
||||
"src=\"/channels/#{id}/maps/channel_show?width=450&height=260\"></iframe>"
|
||||
if window.nil? || window[0].nil?
|
||||
window = PortletWindow.new
|
||||
window.wtype = :location
|
||||
window.position = 0
|
||||
window.col = 1
|
||||
window.title = "window_map"
|
||||
else
|
||||
window = window[0]
|
||||
end
|
||||
window.private_flag = isPrivate
|
||||
window.html = maps_html
|
||||
window.window_detail = PortletWindowDetail.new if window.window_detail.nil?
|
||||
|
||||
self.windows.push window
|
||||
|
||||
else
|
||||
unless window[0].nil?
|
||||
|
||||
window[0].delete
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# get recent status messages from channel
|
||||
def recent_statuses
|
||||
self.feeds.select('status, created_at, entry_id').order('created_at DESC').limit(30).collect {|f| f unless f.status.blank? }.compact
|
||||
end
|
||||
|
||||
def latest_feed
|
||||
self.feeds.where(:entry_id => self.last_entry_id).first
|
||||
end
|
||||
|
||||
def delete_feeds
|
||||
if self.feeds.count < 1000
|
||||
Feed.delete_all(["channel_id = ?", self.id])
|
||||
DailyFeed.delete_all(["channel_id = ?", self.id])
|
||||
begin
|
||||
self.update_attribute(:last_entry_id, nil)
|
||||
rescue Exception => e
|
||||
end
|
||||
|
||||
else
|
||||
self.update_attribute(:clearing, true)
|
||||
Resque.enqueue(ClearChannelJob, self.id)
|
||||
end
|
||||
end
|
||||
|
||||
# true if channel is active
|
||||
def active?
|
||||
return (last_entry_id and updated_at and last_entry_id > 1 and updated_at > DateTime.now.utc - 1.days)
|
||||
end
|
||||
|
||||
def list_tags
|
||||
(self.tags.collect { |t| t.name }).join(', ')
|
||||
end
|
||||
|
||||
def save_tags(tags)
|
||||
# for each tag
|
||||
tags.split(',').each do |name|
|
||||
tag = Tag.find_by_name(name.strip)
|
||||
# save if new tag
|
||||
if tag.nil?
|
||||
tag = Tag.new
|
||||
tag.name = name.strip
|
||||
tag.save
|
||||
end
|
||||
|
||||
tagging = Tagging.find(:first, :conditions => { :tag_id => tag.id, :channel_id => self.id})
|
||||
# save if new tagging
|
||||
if tagging.nil?
|
||||
tagging = Tagging.new
|
||||
tagging.channel_id = self.id
|
||||
tagging.tag_id = tag.id
|
||||
tagging.save
|
||||
end
|
||||
end
|
||||
|
||||
# delete any tags that were removed
|
||||
self.remove_tags(tags)
|
||||
end
|
||||
|
||||
# if tags don't exist anymore, remove them
|
||||
def remove_tags(tags)
|
||||
tag_array = tags.split(',')
|
||||
# remove white space
|
||||
tag_array = tag_array.collect {|t| t.strip }
|
||||
|
||||
# get all taggings for this channel
|
||||
taggings = Tagging.find(:all, :conditions => { :channel_id => self.id }, :include => :tag)
|
||||
|
||||
# check for existence
|
||||
taggings.each do |tagging|
|
||||
# if tagging is not in list
|
||||
if !tag_array.include?(tagging.tag.name)
|
||||
# delete tagging
|
||||
tagging.delete
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def add_write_api_key
|
||||
write_key = self.api_keys.new
|
||||
@ -22,61 +396,102 @@ class Channel < ActiveRecord::Base
|
||||
write_key.save
|
||||
end
|
||||
|
||||
def queue_react
|
||||
self.reacts.on_insertion.each do |react|
|
||||
begin
|
||||
Resque.enqueue(ReactJob, react.id)
|
||||
rescue Exception => e
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def field_label(field_number)
|
||||
self.attributes["field#{field_number}"]
|
||||
end
|
||||
|
||||
def delete_feeds
|
||||
Feed.delete_all(["channel_id = ?", self.id])
|
||||
|
||||
def fields
|
||||
fields = attribute_names.reject { |x|
|
||||
!(x.index('field') && self[x] && !self[x].empty?)
|
||||
}
|
||||
end
|
||||
|
||||
private
|
||||
def calc_ranking
|
||||
result = 0
|
||||
result = result + 15 unless name.blank?
|
||||
result = result + 20 unless description.blank?
|
||||
result = result + 15 unless latitude.blank? || longitude.blank?
|
||||
result = result + 15 unless url.blank?
|
||||
result = result + 15 unless video_id.blank? || video_type.blank?
|
||||
|
||||
def set_default_name
|
||||
self.name = "#{I18n.t(:channel_default_name)} #{self.id}" if self.name.blank?
|
||||
result = result + 20 unless tags.empty?
|
||||
result
|
||||
end
|
||||
|
||||
def set_initial_default_name
|
||||
update_attribute(:name, "#{I18n.t(:channel_default_name)} #{self.id}")
|
||||
def set_windows
|
||||
#check for video window
|
||||
if video_changed?
|
||||
update_video_portlet true
|
||||
update_video_portlet false
|
||||
end
|
||||
|
||||
#does channel have a location and corresponding google map
|
||||
if location_changed?
|
||||
update_location_portlet true
|
||||
update_location_portlet false
|
||||
end
|
||||
|
||||
#does channel have status and corresponding status window. Add the status window no matter what. Only display if it has values
|
||||
update_status_portlet true
|
||||
update_status_portlet false
|
||||
|
||||
#does channel have a window for every chart element
|
||||
if feeds_changed?
|
||||
update_chart_portlets
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_ranking
|
||||
update_attribute(:ranking, calc_ranking) unless ranking == calc_ranking
|
||||
|
||||
end
|
||||
def update_chart_portlet (field, isPrivate)
|
||||
|
||||
chartWindows = windows.where(:type => "ChartWindow", :name => "field#{field.last.to_s}", :private_flag => isPrivate )
|
||||
if chartWindows.nil? || chartWindows[0].nil?
|
||||
window = ChartWindow.new
|
||||
window.wtype = :chart
|
||||
window.position = 0
|
||||
window.col = 0
|
||||
window.title = "window_field_chart"
|
||||
window.name = field.to_s
|
||||
window.window_detail = ChartWindowDetail.new
|
||||
window.window_detail.options = "&results=60&dynamic=true"
|
||||
else
|
||||
window = chartWindows[0]
|
||||
# If there are options, use them.. if options are not available, then assign defaults
|
||||
window.window_detail.options ||= "&results=60&dynamic=true"
|
||||
end
|
||||
|
||||
window.window_detail.field_number = field.last
|
||||
window.private_flag = isPrivate
|
||||
windows.push window
|
||||
window.html ="<iframe id=\"iframe#{window.id}\" width=\"450\" height=\"260\" style=\"border: 1px solid #cccccc;\" src=\"/channels/#{id}/charts/#{field.last.to_s}?width=450&height=260::OPTIONS::\" ></iframe>"
|
||||
|
||||
if !window.save
|
||||
raise "The Window could not be saved"
|
||||
end
|
||||
end
|
||||
|
||||
def set_default_name
|
||||
update_attribute(:name, "#{I18n.t(:channel_default_name)} #{self.id}") if self.name.blank?
|
||||
end
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
end
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# == Schema Information
|
||||
#
|
||||
# Table name: channels
|
||||
#
|
||||
# id :integer(4) not null, primary key
|
||||
# user_id :integer(4)
|
||||
# name :string(255)
|
||||
# description :string(255)
|
||||
# latitude :decimal(15, 10)
|
||||
# longitude :decimal(15, 10)
|
||||
# field1 :text
|
||||
# field2 :text
|
||||
# field3 :text
|
||||
# field4 :text
|
||||
# field5 :text
|
||||
# field6 :text
|
||||
# field7 :text
|
||||
# field8 :text
|
||||
# scale1 :integer(4)
|
||||
# scale2 :integer(4)
|
||||
# scale3 :integer(4)
|
||||
# scale4 :integer(4)
|
||||
# scale5 :integer(4)
|
||||
# scale6 :integer(4)
|
||||
# scale7 :integer(4)
|
||||
# scale8 :integer(4)
|
||||
# created_at :datetime
|
||||
# updated_at :datetime
|
||||
# elevation :string(255)
|
||||
# last_entry_id :integer(4)
|
||||
# public_flag :boolean(1) default(FALSE)
|
||||
#
|
||||
|
||||
|
8
app/models/chart.rb
Normal file
@ -0,0 +1,8 @@
|
||||
class Chart
|
||||
def self.default_width
|
||||
450
|
||||
end
|
||||
def self.default_height
|
||||
250
|
||||
end
|
||||
end
|
22
app/models/chart_window.rb
Normal file
@ -0,0 +1,22 @@
|
||||
# == Schema Information
|
||||
#
|
||||
# Table name: windows
|
||||
#
|
||||
# id :integer not null, primary key
|
||||
# channel_id :integer
|
||||
# position :integer
|
||||
# created_at :datetime
|
||||
# updated_at :datetime
|
||||
# html :text
|
||||
# col :integer
|
||||
# title :string(255)
|
||||
# wtype :string(255)
|
||||
# name :string(255)
|
||||
# type :string(255)
|
||||
# private_flag :boolean default(FALSE)
|
||||
# show_flag :boolean default(TRUE)
|
||||
#
|
||||
|
||||
class ChartWindow < Window
|
||||
relate_to_details
|
||||
end
|
14
app/models/chart_window_detail.rb
Normal file
@ -0,0 +1,14 @@
|
||||
# == Schema Information
|
||||
#
|
||||
# Table name: chart_window_details
|
||||
#
|
||||
# id :integer not null, primary key
|
||||
# chart_window_id :integer
|
||||
# field_number :integer
|
||||
# created_at :datetime
|
||||
# updated_at :datetime
|
||||
# options :string(255)
|
||||
#
|
||||
|
||||
class ChartWindowDetail < ActiveRecord::Base
|
||||
end
|
32
app/models/comment.rb
Normal file
@ -0,0 +1,32 @@
|
||||
# == Schema Information
|
||||
#
|
||||
# Table name: comments
|
||||
#
|
||||
# id :integer not null, primary key
|
||||
# parent_id :integer
|
||||
# body :text
|
||||
# flags :integer
|
||||
# user_id :integer
|
||||
# ip_address :string(255)
|
||||
# created_at :datetime
|
||||
# updated_at :datetime
|
||||
# channel_id :integer
|
||||
#
|
||||
|
||||
class Comment < ActiveRecord::Base
|
||||
belongs_to :channel
|
||||
belongs_to :user
|
||||
acts_as_tree :order => 'created_at'
|
||||
|
||||
validates :body, :presence => true
|
||||
validates_associated :user
|
||||
|
||||
before_create :set_defaults
|
||||
|
||||
private
|
||||
|
||||
def set_defaults
|
||||
self.flags = 0
|
||||
end
|
||||
end
|
||||
|
47
app/models/daily_feed.rb
Normal file
@ -0,0 +1,47 @@
|
||||
# == Schema Information
|
||||
#
|
||||
# Table name: daily_feeds
|
||||
#
|
||||
# id :integer not null, primary key
|
||||
# channel_id :integer
|
||||
# date :date
|
||||
# calculation :string(20)
|
||||
# result :string(255)
|
||||
# field :integer
|
||||
#
|
||||
|
||||
class DailyFeed < ActiveRecord::Base
|
||||
belongs_to :channel
|
||||
|
||||
self.include_root_in_json = false
|
||||
|
||||
# update a feed if it exists, or else create it
|
||||
def self.my_create_or_update(attributes)
|
||||
# try to get daily feed
|
||||
daily_feed = DailyFeed.where(attributes).first
|
||||
# if there is an existing daily feed
|
||||
if daily_feed.present?
|
||||
# update it
|
||||
daily_feed.update_attributes(attributes)
|
||||
# else create it
|
||||
else
|
||||
daily_feed = DailyFeed.create(attributes)
|
||||
end
|
||||
end
|
||||
|
||||
# gets the calculation type
|
||||
def self.calculation_type(params)
|
||||
output = nil
|
||||
output = 'timescale' if params[:timescale].present?
|
||||
output = 'sum' if params[:sum].present?
|
||||
output = 'average' if params[:average].present?
|
||||
output = 'median' if params[:median].present?
|
||||
return output
|
||||
end
|
||||
|
||||
# checks to see if this is a daily feed
|
||||
def self.valid_params(params)
|
||||
(params[:timescale] == '1440' || params[:sum] == '1440' || params[:average] == '1440' || params[:median] == '1440') ? true : false
|
||||
end
|
||||
|
||||
end
|