diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 00000000..1a2f2e63 Binary files /dev/null and b/.DS_Store differ diff --git a/.gitignore b/.gitignore index 85882f10..b54da91f 100644 --- a/.gitignore +++ b/.gitignore @@ -27,4 +27,4 @@ /config/master.key # solargraph config file -.solargraph.yml \ No newline at end of file +.solargraph.yml diff --git a/.rake_tasks~ b/.rake_tasks~ new file mode 100644 index 00000000..c00fc44b --- /dev/null +++ b/.rake_tasks~ @@ -0,0 +1,55 @@ +about +active_storage:install +app:template +app:update +assets:clean[keep] +assets:clobber +assets:environment +assets:precompile +cache_digests:dependencies +cache_digests:nested_dependencies +cucumber +cucumber:all +cucumber:ok +cucumber:rerun +cucumber:wip +db:create +db:drop +db:environment:set +db:fixtures:load +db:migrate +db:migrate:status +db:rollback +db:schema:cache:clear +db:schema:cache:dump +db:schema:dump +db:schema:load +db:seed +db:setup +db:structure:dump +db:structure:load +db:version +dev:cache +initializers +log:clear +middleware +notes +notes:custom +restart +routes +secret +spec +spec:controllers +spec:helpers +spec:models +spec:requests +spec:routing +spec:views +stats +test +test:db +test:system +time:zones[country_or_offset] +tmp:clear +tmp:create +yarn:install diff --git a/.tool-versions b/.tool-versions new file mode 100644 index 00000000..7f44804c --- /dev/null +++ b/.tool-versions @@ -0,0 +1 @@ +ruby 2.6.3 diff --git a/ABC_Score/.DS_Store b/ABC_Score/.DS_Store new file mode 100644 index 00000000..6406abca Binary files /dev/null and b/ABC_Score/.DS_Store differ diff --git a/ABC_Score/app/channels/application_cable/channel.html b/ABC_Score/app/channels/application_cable/channel.html new file mode 100644 index 00000000..9550da07 --- /dev/null +++ b/ABC_Score/app/channels/application_cable/channel.html @@ -0,0 +1,123 @@ + + + + + + Ruby Critic - Home + + + + + + + + + + + + +
+ + + +
+
+
+ +
+
+ + + Updated + + +
+
+

app/channels/application_cable / channel.rb

+
+
+ +
+ +
+
+
+
+
+ A +
+
+
+
+
4 lines of codes
+
0 methods
+
+
+
N/A complexity/method
+
2 churn
+
+
+
0.0 complexity
+
0 duplications
+
+
+
+
+
+
+ +
+
+
+ + module ApplicationCable + class Channel < ActionCable::Channel::Base
  1. ApplicationCable::Channel has no descriptive comment
+ end +end +
+
+
+ +
+
+
+ + + + + + + + + + + + + diff --git a/ABC_Score/app/channels/application_cable/connection.html b/ABC_Score/app/channels/application_cable/connection.html new file mode 100644 index 00000000..e392760d --- /dev/null +++ b/ABC_Score/app/channels/application_cable/connection.html @@ -0,0 +1,123 @@ + + + + + + Ruby Critic - Home + + + + + + + + + + + + +
+ + + +
+
+
+ +
+
+ + + Updated + + +
+
+

app/channels/application_cable / connection.rb

+
+
+ +
+ +
+
+
+
+
+ A +
+
+
+
+
4 lines of codes
+
0 methods
+
+
+
N/A complexity/method
+
2 churn
+
+
+
0.0 complexity
+
0 duplications
+
+
+
+
+
+
+ +
+
+
+ + module ApplicationCable + class Connection < ActionCable::Connection::Base
  1. ApplicationCable::Connection has no descriptive comment
+ end +end +
+
+
+ +
+
+
+ + + + + + + + + + + + + diff --git a/ABC_Score/app/controllers/accreditations_controller.html b/ABC_Score/app/controllers/accreditations_controller.html new file mode 100644 index 00000000..19248e3f --- /dev/null +++ b/ABC_Score/app/controllers/accreditations_controller.html @@ -0,0 +1,184 @@ + + + + + + Ruby Critic - Home + + + + + + + + + + + + +
+ + + +
+
+
+ +
+
+ + + Updated + + +
+
+

app/controllers / accreditations_controller.rb

+
+
+ +
+ +
+
+
+
+
+ B +
+
+
+
+
65 lines of codes
+
7 methods
+
+
+
5.5 complexity/method
+
15 churn
+
+
+
38.2 complexity
+
24 duplications
+
+
+
+
+
+
+ +
+
+
+ + class AccreditationsController < ApplicationController
  1. AccreditationsController assumes too much for instance variable '@accreditation'
  2. AccreditationsController has no descriptive comment
+ before_action :set_accreditation, only: [:show, :edit, :update, :destroy] + + # GET /accreditations + # Lista os credenciamentos criados + def index + # Lista todas os credenciamentos para um administrador + if current_user.role == "administrator" + @accreditations = Accreditation.all + # Lista os credenciamentos próprios para um professor + else + @accreditations = Accreditation.where(user_id: current_user.id) + end + end + + # GET /accreditations/1 + # Mostra detalhes de um registro criado + def show + end + + # GET /accreditations/1/edit + # Renderiza página para atualizar um registro + def edit + end + + # PATCH/PUT /accreditations/1 + # Faz o tratamento dos dados modificados pelo usuário para decidir se a modificação é válida ou não + def update + respond_to do |format| + # Quando condições da model forem cumpridas, atualiza o registro no banco, redireciona para pagina index da tabela atual e mostra uma mensagem de sucesso + if @accreditation.update(accreditation_params) + format.html { redirect_to accreditations_url, notice: 'Credenciamento atualizado com sucesso!' }
  1. AccreditationsController#update calls 'format.html' 2 times Locations: 0 1
+ # Mostra uma mensagem de erro se condições da model não forem cumpridas + else + format.html { render :edit }
  1. AccreditationsController#update calls 'format.html' 2 times Locations: 0 1
+ end + end + end + + # DELETE /accreditations/1 + # Decide se a exclusão do registro é válida ou não + def destroy
  1. Similar code found in 3 nodes Locations: 0 1 2
+ respond_to do |format| + # Mensagem de sucesso ao excluir o credenciamento quando condições da model forem cumpridas + if @accreditation.destroy + format.html { redirect_to accreditations_url, notice: 'Credenciamento excluído com sucesso!' }
  1. AccreditationsController#destroy calls 'format.html' 2 times Locations: 0 1
+ # Mensagem de erro se condições da model não forem cumpridas + else + format.html { redirect_to accreditations_url, notice: 'Erro: não foi possível excluir o credenciamento!' }
  1. AccreditationsController#destroy calls 'format.html' 2 times Locations: 0 1
+ end + end + end + + private + # Define parametros de Credenciamento + def set_accreditation + @accreditation = Accreditation.find(params[:id]) + end + + # Never trust parameters from the scary internet, only allow the white list through. + def accreditation_params + params.require(:accreditation).permit(:user_id, :start_date, :end_date, :sei_proccess_id) + end +end + +
+
+
+ +
+
+
+ + + + + + + + + + + + + diff --git a/ABC_Score/app/controllers/application_controller.html b/ABC_Score/app/controllers/application_controller.html new file mode 100644 index 00000000..296eaa6f --- /dev/null +++ b/ABC_Score/app/controllers/application_controller.html @@ -0,0 +1,143 @@ + + + + + + Ruby Critic - Home + + + + + + + + + + + + +
+ + + +
+
+
+ +
+
+ + + Updated + + +
+
+

app/controllers / application_controller.rb

+
+
+ +
+ +
+
+
+
+
+ A +
+
+
+
+
24 lines of codes
+
2 methods
+
+
+
2.9 complexity/method
+
13 churn
+
+
+
5.72 complexity
+
0 duplications
+
+
+
+
+
+
+ +
+
+
+ + # frozen_string_literal: true + +module Current
  1. Current has no descriptive comment
+ thread_mattr_accessor :user +end + +class ApplicationController < ActionController::Base
  1. ApplicationController has no descriptive comment
+ before_action :configure_permitted_parameters, if: :devise_controller? + + around_action :set_current_user + def set_current_user + Current.user = current_user + yield + ensure + # to address the thread variable leak issues in Puma/Thin webserver + Current.user = nil + end + + protected + + def configure_permitted_parameters + devise_parameter_sanitizer.permit(:sign_up, keys: %i[full_name role]) + end +end +
+
+
+ +
+
+
+ + + + + + + + + + + + + diff --git a/ABC_Score/app/controllers/home_controller.html b/ABC_Score/app/controllers/home_controller.html new file mode 100644 index 00000000..9f7cffe2 --- /dev/null +++ b/ABC_Score/app/controllers/home_controller.html @@ -0,0 +1,123 @@ + + + + + + Ruby Critic - Home + + + + + + + + + + + + +
+ + + +
+
+
+ +
+
+ + + Updated + + +
+
+

app/controllers / home_controller.rb

+
+
+ +
+ +
+
+
+
+
+ A +
+
+
+
+
4 lines of codes
+
1 methods
+
+
+
0.0 complexity/method
+
8 churn
+
+
+
0.0 complexity
+
0 duplications
+
+
+
+
+
+
+ +
+
+
+ + class HomeController < ApplicationController
  1. HomeController has no descriptive comment
+ def index + end +end +
+
+
+ +
+
+
+ + + + + + + + + + + + + diff --git a/ABC_Score/app/controllers/requirements_controller.html b/ABC_Score/app/controllers/requirements_controller.html new file mode 100644 index 00000000..478af501 --- /dev/null +++ b/ABC_Score/app/controllers/requirements_controller.html @@ -0,0 +1,208 @@ + + + + + + Ruby Critic - Home + + + + + + + + + + + + +
+ + + +
+
+
+ +
+
+ + + Updated + + +
+
+

app/controllers / requirements_controller.rb

+
+
+ +
+ +
+
+
+
+
+ C +
+
+
+
+
89 lines of codes
+
10 methods
+
+
+
5.0 complexity/method
+
17 churn
+
+
+
50.35 complexity
+
24 duplications
+
+
+
+
+
+
+ +
+
+
+ + class RequirementsController < ApplicationController
  1. RequirementsController assumes too much for instance variable '@document'
  2. RequirementsController assumes too much for instance variable '@requirement'
  3. RequirementsController assumes too much for instance variable '@requirement_id'
  4. RequirementsController has no descriptive comment
+ before_action :set_requirement, only: [:show, :edit, :update, :destroy] + + # GET /requirements + # Lista os requisitos criados + def index + # Lista todos os tipos de requisitos criados + @requirements = Requirement.all + end + + # GET /requirements/1 + # Mostra detalhes de um registro criado + def show + end + + # GET /requirements/new + # Renderiza página para criação de umm registro + def new + @requirement = Requirement.new + end + + # GET /requirements/1/edit + # Renderiza página para atualizar um registro + def edit + end + + # POST /requirements + # Faz o tratamento dos dados enviados pelo usuário para decidir se o registro é válido ou não + def create
  1. RequirementsController#create has approx 6 statements
+ @requirement = Requirement.new(requirement_params) + + respond_to do |format| + # Quando condições da model forem cumpridas, cria um novo registro no banco, redireciona para pagina index da entidade atual e mostra uma mensagem de sucesso + if @requirement.save + format.html { redirect_to @requirement, notice: 'Requisitos criados com sucesso!' }
  1. RequirementsController#create calls 'format.html' 2 times Locations: 0 1
+ # Mostra uma mensagem de erro se condições da model não forem cumpridas + else + format.html { render :new }
  1. RequirementsController#create calls 'format.html' 2 times Locations: 0 1
+ end + end + end + + # Exclui documento anexado, caso exista + def delete_document_attachment + @document = ActiveStorage::Attachment.find_by(id: params[:id]) + @requirement_id = params[:requirement_id] + @document&.purge + redirect_to edit_requirement_path(@requirement_id) + end + + # PATCH/PUT /requirements/1 + # Faz o tratamento dos dados modificados pelo usuário para decidir se a modificação é válida ou não + def update + respond_to do |format| + # Quando condições da model forem cumpridas, atualiza o registro no banco, redireciona para pagina de detalhes do registro recém modificado e mostra uma mensagem de sucesso + if @requirement.update(requirement_params) + format.html { redirect_to @requirement, notice: 'Requisitos atualizados com sucesso!' }
  1. RequirementsController#update calls 'format.html' 2 times Locations: 0 1
+ # Mostra uma mensagem de erro se condições da model não forem cumpridas + else + format.html { render :edit }
  1. RequirementsController#update calls 'format.html' 2 times Locations: 0 1
+ end + end + end + + # DELETE /requirements/1 + # Decide se a exclusão do registro é válida ou não + def destroy
  1. Similar code found in 3 nodes Locations: 0 1 2
+ respond_to do |format| + # Mensagem de sucesso ao excluir requisitos quando condições da model forem cumpridas + if @requirement.destroy + format.html { redirect_to requirements_url, notice: 'Requisitos excluídos com sucesso!' }
  1. RequirementsController#destroy calls 'format.html' 2 times Locations: 0 1
+ # Mensagem de erro se condições da model não forem cumpridas + else + format.html { redirect_to requirements_url, notice: 'Erro: não foi possível excluir os requisitos!' }
  1. RequirementsController#destroy calls 'format.html' 2 times Locations: 0 1
+ end + end + end + + private + # Define parametros de Requisito + def set_requirement + @requirement = Requirement.find(params[:id]) + end + + # Never trust parameters from the scary internet, only allow the white list through. + def requirement_params + params.require(:requirement).permit(:title, :content, documents: []) + end +end +
+
+
+ +
+
+
+ + + + + + + + + + + + + diff --git a/ABC_Score/app/controllers/sei_processes_controller.html b/ABC_Score/app/controllers/sei_processes_controller.html new file mode 100644 index 00000000..6641afcc --- /dev/null +++ b/ABC_Score/app/controllers/sei_processes_controller.html @@ -0,0 +1,220 @@ + + + + + + Ruby Critic - Home + + + + + + + + + + + + +
+ + + +
+
+
+ +
+
+ + + Updated + + +
+
+

app/controllers / sei_processes_controller.rb

+
+
+ +
+ +
+
+
+
+
+ C +
+
+
+
+
101 lines of codes
+
9 methods
+
+
+
9.2 complexity/method
+
18 churn
+
+
+
82.78 complexity
+
24 duplications
+
+
+
+
+
+
+ +
+
+
+ + class SeiProcessesController < ApplicationController
  1. SeiProcessesController assumes too much for instance variable '@all_statuses'
  2. SeiProcessesController assumes too much for instance variable '@sei_process'
  3. SeiProcessesController assumes too much for instance variable '@status_filter'
  4. SeiProcessesController has no descriptive comment
  5. SeiProcessesController has at least 6 instance variables
+ before_action :set_sei_process, only: [:show, :edit, :update, :destroy] + + # GET /sei_processes + # Lista os processos e solicitações criados + def index + # Filtra solicitações baseadas nos estados marcados como visíveis + @all_statuses = SeiProcess.all_statuses + session[:statuses] = params[:statuses] || session[:statuses] || @all_statuses.zip([]).to_h
  1. SeiProcessesController#index calls 'session[:statuses]' 2 times Locations: 0 1
+ @status_filter = session[:statuses].keys
  1. SeiProcessesController#index calls 'session[:statuses]' 2 times Locations: 0 1
+ + # Lista todas as solicitações de credenciamento para um administrador + if current_user.role == "administrator" + @sei_processes = SeiProcess.where(status: @status_filter) + # Lista as solicitações próprias para um professor + else + @my_processes = SeiProcess.where(user_id: current_user.id, status: @status_filter) + end + end + + # GET /sei_processes/1 + # Mostra detalhes de um registro criado + def show + end + + # GET /sei_processes/new + # Renderiza página para criação de umm registro + def new + # Renderiza Requisitos de Credenciamento, caso existam, na página de criação de processo ou de abrir solicitação de credenciamento + @requirements = Requirement.find_by(title: 'Requisitos de Credenciamento') + @sei_process = SeiProcess.new + end + + # GET /sei_processes/1/edit + # Renderiza página para atualizar um registro + def edit + end + + # POST /sei_processes + # Faz o tratamento dos dados enviados pelo usuário para decidir se o registro é válido ou não + def create
  1. SeiProcessesController#create has approx 7 statements
+ # Faz correções de entradas inválidas baseando-se nos dados do usuário logado + mandatory_params = {'user_id' => current_user.id, 'status' => 'Espera', 'code' => '0'} + @sei_process = SeiProcess.new(sei_process_params.merge(mandatory_params)) + + respond_to do |format| + # Quando condições da model forem cumpridas, cria um novo registro no banco, redireciona para pagina index da tabela atual e mostra uma mensagem de sucesso + if @sei_process.save + format.html { redirect_to sei_processes_url, notice: 'Processo aberto com sucesso!' }
  1. SeiProcessesController#create calls 'format.html' 2 times Locations: 0 1
+ # Mostra uma mensagem de erro se condições da model não forem cumpridas + else + format.html { render :new }
  1. SeiProcessesController#create calls 'format.html' 2 times Locations: 0 1
+ end + end + end + + # PATCH/PUT /sei_processes/1 + # Faz o tratamento dos dados modificados pelo usuário para decidir se a modificação é válida ou não + def update
  1. SeiProcessesController#update has approx 6 statements
+ respond_to do |format| + # Quando condições da model forem cumpridas, atualiza o registro no banco, redireciona para pagina index da tabela atual e mostra uma mensagem de sucesso + if @sei_process.update(sei_process_params) + format.html { redirect_to sei_processes_url, notice: 'Processo atualizado com sucesso!' }
  1. SeiProcessesController#update calls 'format.html' 2 times Locations: 0 1
+ + # Cria o credenciamento correspondente aa solicitação aprovada + if @sei_process.status == 'Aprovado' && (Accreditation.find_by(sei_process: @sei_process.id) == nil)
  1. SeiProcessesController#update calls '@sei_process.id' 2 times Locations: 0 1
  2. SeiProcessesController#update performs a nil-check
+ Accreditation.create!(user_id: @sei_process.user_id, sei_process_id: @sei_process.id)
  1. SeiProcessesController#update calls '@sei_process.id' 2 times Locations: 0 1
+ end + + # Mostra uma mensagem de erro se condições da model não forem cumpridas + else + format.html { render :edit }
  1. SeiProcessesController#update calls 'format.html' 2 times Locations: 0 1
+ end + end + end + + # DELETE /sei_processes/1 + # Decide se a exclusão do registro é válida ou não + def destroy
  1. Similar code found in 3 nodes Locations: 0 1 2
+ respond_to do |format| + # Mensagem de sucesso ao excluir processo ou solicitação quando condições da model forem cumpridas + if @sei_process.destroy + format.html { redirect_to sei_processes_url, notice: 'Processo excluído com sucesso!' }
  1. SeiProcessesController#destroy calls 'format.html' 2 times Locations: 0 1
+ # Mensagem de erro se condições da model não forem cumpridas + else + format.html { redirect_to sei_processes_url, notice: 'Erro: não foi possível excluir o processo!' }
  1. SeiProcessesController#destroy calls 'format.html' 2 times Locations: 0 1
+ end + end + end + + private + # Define parametros de Processo + def set_sei_process + @sei_process = SeiProcess.find(params[:id]) + end + + # Never trust parameters from the scary internet, only allow the white list through. + def sei_process_params + params.require(:sei_process).permit(:user_id, :status, :code, documents: []) + end +end +
+
+
+ +
+
+
+ + + + + + + + + + + + + diff --git a/ABC_Score/app/helpers/accreditations_helper.html b/ABC_Score/app/helpers/accreditations_helper.html new file mode 100644 index 00000000..7e9be2fd --- /dev/null +++ b/ABC_Score/app/helpers/accreditations_helper.html @@ -0,0 +1,121 @@ + + + + + + Ruby Critic - Home + + + + + + + + + + + + +
+ + + +
+
+
+ +
+
+ + + Updated + + +
+
+

app/helpers / accreditations_helper.rb

+
+
+ +
+ +
+
+
+
+
+ A +
+
+
+
+
2 lines of codes
+
0 methods
+
+
+
N/A complexity/method
+
1 churn
+
+
+
0.0 complexity
+
0 duplications
+
+
+
+
+
+
+ +
+
+
+ + module AccreditationsHelper
  1. AccreditationsHelper has no descriptive comment
+end +
+
+
+ +
+
+
+ + + + + + + + + + + + + diff --git a/ABC_Score/app/helpers/application_helper.html b/ABC_Score/app/helpers/application_helper.html new file mode 100644 index 00000000..fbd45941 --- /dev/null +++ b/ABC_Score/app/helpers/application_helper.html @@ -0,0 +1,121 @@ + + + + + + Ruby Critic - Home + + + + + + + + + + + + +
+ + + +
+
+
+ +
+
+ + + Updated + + +
+
+

app/helpers / application_helper.rb

+
+
+ +
+ +
+
+
+
+
+ A +
+
+
+
+
2 lines of codes
+
0 methods
+
+
+
N/A complexity/method
+
4 churn
+
+
+
0.0 complexity
+
0 duplications
+
+
+
+
+
+
+ +
+
+
+ + module ApplicationHelper
  1. ApplicationHelper has no descriptive comment
+end +
+
+
+ +
+
+
+ + + + + + + + + + + + + diff --git a/ABC_Score/app/helpers/home_helper.html b/ABC_Score/app/helpers/home_helper.html new file mode 100644 index 00000000..d6aa50f4 --- /dev/null +++ b/ABC_Score/app/helpers/home_helper.html @@ -0,0 +1,121 @@ + + + + + + Ruby Critic - Home + + + + + + + + + + + + +
+ + + +
+
+
+ +
+
+ + + Updated + + +
+
+

app/helpers / home_helper.rb

+
+
+ +
+ +
+
+
+
+
+ A +
+
+
+
+
2 lines of codes
+
0 methods
+
+
+
N/A complexity/method
+
2 churn
+
+
+
0.0 complexity
+
0 duplications
+
+
+
+
+
+
+ +
+
+
+ + module HomeHelper
  1. HomeHelper has no descriptive comment
+end +
+
+
+ +
+
+
+ + + + + + + + + + + + + diff --git a/ABC_Score/app/helpers/requirements_helper.html b/ABC_Score/app/helpers/requirements_helper.html new file mode 100644 index 00000000..035b9f1f --- /dev/null +++ b/ABC_Score/app/helpers/requirements_helper.html @@ -0,0 +1,121 @@ + + + + + + Ruby Critic - Home + + + + + + + + + + + + +
+ + + +
+
+
+ +
+
+ + + Updated + + +
+
+

app/helpers / requirements_helper.rb

+
+
+ +
+ +
+
+
+
+
+ A +
+
+
+
+
2 lines of codes
+
0 methods
+
+
+
N/A complexity/method
+
2 churn
+
+
+
0.0 complexity
+
0 duplications
+
+
+
+
+
+
+ +
+
+
+ + module RequirementsHelper
  1. RequirementsHelper has no descriptive comment
+end +
+
+
+ +
+
+
+ + + + + + + + + + + + + diff --git a/ABC_Score/app/helpers/sei_processes_helper.html b/ABC_Score/app/helpers/sei_processes_helper.html new file mode 100644 index 00000000..43f990c4 --- /dev/null +++ b/ABC_Score/app/helpers/sei_processes_helper.html @@ -0,0 +1,121 @@ + + + + + + Ruby Critic - Home + + + + + + + + + + + + +
+ + + +
+
+
+ +
+
+ + + Updated + + +
+
+

app/helpers / sei_processes_helper.rb

+
+
+ +
+ +
+
+
+
+
+ A +
+
+
+
+
2 lines of codes
+
0 methods
+
+
+
N/A complexity/method
+
1 churn
+
+
+
0.0 complexity
+
0 duplications
+
+
+
+
+
+
+ +
+
+
+ + module SeiProcessesHelper
  1. SeiProcessesHelper has no descriptive comment
+end +
+
+
+ +
+
+
+ + + + + + + + + + + + + diff --git a/ABC_Score/app/jobs/application_job.html b/ABC_Score/app/jobs/application_job.html new file mode 100644 index 00000000..42d7398c --- /dev/null +++ b/ABC_Score/app/jobs/application_job.html @@ -0,0 +1,121 @@ + + + + + + Ruby Critic - Home + + + + + + + + + + + + +
+ + + +
+
+
+ +
+
+ + + Updated + + +
+
+

app/jobs / application_job.rb

+
+
+ +
+ +
+
+
+
+
+ A +
+
+
+
+
2 lines of codes
+
0 methods
+
+
+
N/A complexity/method
+
2 churn
+
+
+
0.0 complexity
+
0 duplications
+
+
+
+
+
+
+ +
+
+
+ + class ApplicationJob < ActiveJob::Base
  1. ApplicationJob has no descriptive comment
+end +
+
+
+ +
+
+
+ + + + + + + + + + + + + diff --git a/ABC_Score/app/mailers/application_mailer.html b/ABC_Score/app/mailers/application_mailer.html new file mode 100644 index 00000000..8f3bd09a --- /dev/null +++ b/ABC_Score/app/mailers/application_mailer.html @@ -0,0 +1,123 @@ + + + + + + Ruby Critic - Home + + + + + + + + + + + + +
+ + + +
+
+
+ +
+
+ + + Updated + + +
+
+

app/mailers / application_mailer.rb

+
+
+ +
+ +
+
+
+
+
+ A +
+
+
+
+
4 lines of codes
+
0 methods
+
+
+
N/A complexity/method
+
2 churn
+
+
+
0.0 complexity
+
0 duplications
+
+
+
+
+
+
+ +
+
+
+ + class ApplicationMailer < ActionMailer::Base
  1. ApplicationMailer has no descriptive comment
+ default from: 'from@example.com' + layout 'mailer' +end +
+
+
+ +
+
+
+ + + + + + + + + + + + + diff --git a/ABC_Score/app/models/accreditation.html b/ABC_Score/app/models/accreditation.html new file mode 100644 index 00000000..f0873416 --- /dev/null +++ b/ABC_Score/app/models/accreditation.html @@ -0,0 +1,158 @@ + + + + + + Ruby Critic - Home + + + + + + + + + + + + +
+ + + +
+
+
+ +
+
+ + + Updated + + +
+
+

app/models / accreditation.rb

+
+
+ +
+ +
+
+
+
+
+ A +
+
+
+
+
39 lines of codes
+
5 methods
+
+
+
5.2 complexity/method
+
8 churn
+
+
+
26.1 complexity
+
0 duplications
+
+
+
+
+
+
+ +
+
+
+ + class Accreditation < ApplicationRecord
  1. Accreditation assumes too much for instance variable '@allow_deletion'
  2. Accreditation has no descriptive comment
+ belongs_to :user + belongs_to :sei_process + validates :sei_process, uniqueness: true + + validate :check_role, on: [:create, :update] + # Define se usuário atual está logado e se é administrador do sistema + def current_user_is_admin
  1. Accreditation#current_user_is_admin doesn't depend on instance state (maybe move it to another class?)
+ Current.user != nil && Current.user.role == 'administrator'
  1. Accreditation#current_user_is_admin calls 'Current.user' 2 times
+ end + # Permite criação ou atualização do credenciamento por um administrador + def check_role + unless current_user_is_admin + self.errors.add(:base, 'Usuário sem permissão') + return false + end + true + end + + validate :check_date, on: :update + # Permite atualização com base em decorrência real de um período (data final superior a data inicial) + def check_date + if (start_date == nil) || (end_date == nil) || (end_date < start_date)
  1. Accreditation#check_date performs a nil-check
+ self.errors.add(:end_date, 'inválida') + return false + end + true + end + + before_destroy :check_permission + # Habilita deleção (usado por 'db/seeds.rb') + def allow_deletion!
  1. Accreditation has missing safe method 'allow_deletion!'
+ @allow_deletion = true + end + # Permite deleção de credenciamento por um administrador ou caso metodo 'allow_deletion!' tenha sido chamado pela instancia + def check_permission + throw(:abort) unless @allow_deletion || current_user_is_admin + end +end +
+
+
+ +
+
+
+ + + + + + + + + + + + + diff --git a/ABC_Score/app/models/application_record.html b/ABC_Score/app/models/application_record.html new file mode 100644 index 00000000..f884e370 --- /dev/null +++ b/ABC_Score/app/models/application_record.html @@ -0,0 +1,122 @@ + + + + + + Ruby Critic - Home + + + + + + + + + + + + +
+ + + +
+
+
+ +
+
+ + + Updated + + +
+
+

app/models / application_record.rb

+
+
+ +
+ +
+
+
+
+
+ A +
+
+
+
+
3 lines of codes
+
0 methods
+
+
+
N/A complexity/method
+
4 churn
+
+
+
0.0 complexity
+
0 duplications
+
+
+
+
+
+
+ +
+
+
+ + class ApplicationRecord < ActiveRecord::Base
  1. ApplicationRecord has no descriptive comment
+ self.abstract_class = true +end +
+
+
+ +
+
+
+ + + + + + + + + + + + + diff --git a/ABC_Score/app/models/requirement.html b/ABC_Score/app/models/requirement.html new file mode 100644 index 00000000..927c2725 --- /dev/null +++ b/ABC_Score/app/models/requirement.html @@ -0,0 +1,149 @@ + + + + + + Ruby Critic - Home + + + + + + + + + + + + +
+ + + +
+
+
+ +
+
+ + + Updated + + +
+
+

app/models / requirement.rb

+
+
+ +
+ +
+
+
+
+
+ A +
+
+
+
+
30 lines of codes
+
4 methods
+
+
+
3.5 complexity/method
+
9 churn
+
+
+
14.09 complexity
+
0 duplications
+
+
+
+
+
+
+ +
+
+
+ + class Requirement < ApplicationRecord
  1. Requirement assumes too much for instance variable '@allow_deletion'
  2. Requirement has no descriptive comment
+ validates :title, presence: true, uniqueness: true + has_many_attached :documents + + validate :check_role, on: [:create, :update] + # Define se usuário atual está logado e se é administrador do sistema + def current_user_is_admin
  1. Requirement#current_user_is_admin doesn't depend on instance state (maybe move it to another class?)
+ Current.user != nil && Current.user.role == 'administrator'
  1. Requirement#current_user_is_admin calls 'Current.user' 2 times
+ end + # Permite criação ou atualização de requisitos por um administrador + def check_role + unless current_user_is_admin + self.errors.add(:base, 'Usuário sem permissão') + return false + end + true + end + + before_destroy :check_permission + # Habilita deleção (usado por 'db/seeds.rb') + def allow_deletion!
  1. Requirement has missing safe method 'allow_deletion!'
+ @allow_deletion = true + end + # Permite deleção de requisitos por um administrador ou caso metodo 'allow_deletion!' tenha sido chamado pela instancia + def check_permission + unless @allow_deletion || current_user_is_admin + throw(:abort) + end + end +end +
+
+
+ +
+
+
+ + + + + + + + + + + + + diff --git a/ABC_Score/app/models/sei_process.html b/ABC_Score/app/models/sei_process.html new file mode 100644 index 00000000..7e1544da --- /dev/null +++ b/ABC_Score/app/models/sei_process.html @@ -0,0 +1,169 @@ + + + + + + Ruby Critic - Home + + + + + + + + + + + + +
+ + + +
+
+
+ +
+
+ + + Updated + + +
+
+

app/models / sei_process.rb

+
+
+ +
+ +
+
+
+
+
+ A +
+
+
+
+
50 lines of codes
+
6 methods
+
+
+
3.2 complexity/method
+
10 churn
+
+
+
18.99 complexity
+
0 duplications
+
+
+
+
+
+
+ +
+
+
+ + class SeiProcess < ApplicationRecord
  1. SeiProcess assumes too much for instance variable '@allow_deletion'
  2. SeiProcess has no descriptive comment
+ belongs_to :user + has_many_attached :documents + validates :documents, attached: true + + enum status: { + Espera: 0, + Aprovado: 1, + Rejeitado: 2 + } + # Lista os status possíveis para os registros + def self.all_statuses + %w[Espera Aprovado Rejeitado] + end + + # Define se usuário atual está logado e se é administrador do sistema + def current_user_is_admin
  1. SeiProcess#current_user_is_admin doesn't depend on instance state (maybe move it to another class?)
+ Current.user != nil && Current.user.role == 'administrator'
  1. SeiProcess#current_user_is_admin calls 'Current.user' 2 times
+ end + + validate :check_signed_in, on: :create + # Permite criação de processo ou solicitação caso usuário esteja logado + def check_signed_in + if Current.user == nil
  1. SeiProcess#check_signed_in performs a nil-check
+ self.errors.add(:base, 'Usuário sem permissão') + return false + end + true + end + + validate :check_role, on: :update + # Permite atualização de processo ou solicitação por um administrador + def check_role + unless current_user_is_admin + self.errors.add(:base, 'Usuário sem permissão') + return false + end + true + end + + before_destroy :check_permission + # Habilita deleção (usado por 'db/seeds.rb') + def allow_deletion!
  1. SeiProcess has missing safe method 'allow_deletion!'
+ @allow_deletion = true + end + # Permite deleção de processo ou solicitação por um administrador ou caso metodo 'allow_deletion!' tenha sido chamado pela instancia + def check_permission + throw(:abort) unless @allow_deletion || current_user_is_admin + end +end +
+
+
+ +
+
+
+ + + + + + + + + + + + + diff --git a/ABC_Score/app/models/user.html b/ABC_Score/app/models/user.html new file mode 100644 index 00000000..b1e92988 --- /dev/null +++ b/ABC_Score/app/models/user.html @@ -0,0 +1,132 @@ + + + + + + Ruby Critic - Home + + + + + + + + + + + + +
+ + + +
+
+
+ +
+
+ + + Updated + + +
+
+

app/models / user.rb

+
+
+ +
+ +
+
+
+
+
+ A +
+
+
+
+
13 lines of codes
+
0 methods
+
+
+
N/A complexity/method
+
7 churn
+
+
+
0.0 complexity
+
0 duplications
+
+
+
+
+
+
+ +
+
+
+ + # frozen_string_literal: true + +class User < ApplicationRecord
  1. User has no descriptive comment
+ validates :full_name, presence: true + validates :role, presence: true + + # Include default devise modules. Others available are: + # :confirmable, :lockable, :timeoutable, :trackable and :omniauthable + devise :database_authenticatable, :registerable, + :recoverable, :rememberable, :validatable + + enum role: %i[administrator secretary professor student] +end +
+
+
+ +
+
+
+ + + + + + + + + + + + + diff --git a/ABC_Score/assets/fonts/FontAwesome.otf b/ABC_Score/assets/fonts/FontAwesome.otf new file mode 100755 index 00000000..401ec0f3 Binary files /dev/null and b/ABC_Score/assets/fonts/FontAwesome.otf differ diff --git a/ABC_Score/assets/fonts/Roboto-Medium.ttf b/ABC_Score/assets/fonts/Roboto-Medium.ttf new file mode 100755 index 00000000..39c63d74 Binary files /dev/null and b/ABC_Score/assets/fonts/Roboto-Medium.ttf differ diff --git a/ABC_Score/assets/fonts/Roboto-Regular.ttf b/ABC_Score/assets/fonts/Roboto-Regular.ttf new file mode 100755 index 00000000..8c082c8d Binary files /dev/null and b/ABC_Score/assets/fonts/Roboto-Regular.ttf differ diff --git a/ABC_Score/assets/fonts/fontawesome-webfont.eot b/ABC_Score/assets/fonts/fontawesome-webfont.eot new file mode 100755 index 00000000..e9f60ca9 Binary files /dev/null and b/ABC_Score/assets/fonts/fontawesome-webfont.eot differ diff --git a/ABC_Score/assets/fonts/fontawesome-webfont.svg b/ABC_Score/assets/fonts/fontawesome-webfont.svg new file mode 100755 index 00000000..855c845e --- /dev/null +++ b/ABC_Score/assets/fonts/fontawesome-webfont.svg @@ -0,0 +1,2671 @@ + + + + +Created by FontForge 20120731 at Mon Oct 24 17:37:40 2016 + By ,,, +Copyright Dave Gandy 2016. All rights reserved. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ABC_Score/assets/fonts/fontawesome-webfont.ttf b/ABC_Score/assets/fonts/fontawesome-webfont.ttf new file mode 100755 index 00000000..35acda2f Binary files /dev/null and b/ABC_Score/assets/fonts/fontawesome-webfont.ttf differ diff --git a/ABC_Score/assets/fonts/fontawesome-webfont.woff b/ABC_Score/assets/fonts/fontawesome-webfont.woff new file mode 100755 index 00000000..400014a4 Binary files /dev/null and b/ABC_Score/assets/fonts/fontawesome-webfont.woff differ diff --git a/ABC_Score/assets/fonts/fontawesome-webfont.woff2 b/ABC_Score/assets/fonts/fontawesome-webfont.woff2 new file mode 100755 index 00000000..4d13fc60 Binary files /dev/null and b/ABC_Score/assets/fonts/fontawesome-webfont.woff2 differ diff --git a/ABC_Score/assets/fonts/glyphicons-halflings-regular.eot b/ABC_Score/assets/fonts/glyphicons-halflings-regular.eot new file mode 100755 index 00000000..b93a4953 Binary files /dev/null and b/ABC_Score/assets/fonts/glyphicons-halflings-regular.eot differ diff --git a/ABC_Score/assets/fonts/glyphicons-halflings-regular.svg b/ABC_Score/assets/fonts/glyphicons-halflings-regular.svg new file mode 100755 index 00000000..94fb5490 --- /dev/null +++ b/ABC_Score/assets/fonts/glyphicons-halflings-regular.svg @@ -0,0 +1,288 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/ABC_Score/assets/fonts/glyphicons-halflings-regular.ttf b/ABC_Score/assets/fonts/glyphicons-halflings-regular.ttf new file mode 100755 index 00000000..1413fc60 Binary files /dev/null and b/ABC_Score/assets/fonts/glyphicons-halflings-regular.ttf differ diff --git a/ABC_Score/assets/fonts/glyphicons-halflings-regular.woff b/ABC_Score/assets/fonts/glyphicons-halflings-regular.woff new file mode 100755 index 00000000..9e612858 Binary files /dev/null and b/ABC_Score/assets/fonts/glyphicons-halflings-regular.woff differ diff --git a/ABC_Score/assets/fonts/glyphicons-halflings-regular.woff2 b/ABC_Score/assets/fonts/glyphicons-halflings-regular.woff2 new file mode 100755 index 00000000..64539b54 Binary files /dev/null and b/ABC_Score/assets/fonts/glyphicons-halflings-regular.woff2 differ diff --git a/ABC_Score/assets/images/logo.png b/ABC_Score/assets/images/logo.png new file mode 100755 index 00000000..ef2bb78d Binary files /dev/null and b/ABC_Score/assets/images/logo.png differ diff --git a/ABC_Score/assets/javascripts/application.js b/ABC_Score/assets/javascripts/application.js new file mode 100644 index 00000000..95a9522b --- /dev/null +++ b/ABC_Score/assets/javascripts/application.js @@ -0,0 +1,281 @@ +prettyPrint(); + +// sidebar navigation +$(function() { + var loc = window.location.href; // returns the full URL + if(/overview/.test(loc)) { + $('.overview-nav').addClass('active'); + } + else if(/code_index/.test(loc)) { + $('.code-index-nav').addClass('active'); + } + else if(/smells_index/.test(loc)) { + $('.smells-index-nav').addClass('active'); + } + else if(/simple_cov_index/.test(loc)) { + $('.coverage-index-nav').addClass('active'); + } +}); + +var turbulenceData = turbulenceData || []; +var COLOR = { + 'A' : '#00B50E', + 'B': '#53D700', + 'C': '#FDF400', + 'D': '#FF6C00', + 'F': '#C40009' +}; +$("#churn-vs-complexity-graph-container").highcharts({ + chart: { + type: "scatter", + zoomType: "xy" + }, + title: { + text: "Churn vs Complexity" + }, + xAxis: { + title: { + enabled: true, + text: "Churn" + }, + floor: 0, + startOnTick: true, + endOnTick: true + }, + yAxis: { + title: { + text: "Complexity" + }, + floor: 0, + startOnTick: true, + endOnTick: true + }, + plotOptions: { + series: { + turboThreshold: 0 + }, + scatter: { + marker: { + radius: 5, + states: { + hover: { + enabled: true, + lineColor: "rgb(100,100,100)" + } + } + }, + tooltip: { + headerFormat: "{point.key}
", + pointFormat: "Committed {point.x} times, with Flog score of {point.y}" + } + } + }, + series: [{ + showInLegend: false, + color: "steelblue", + data: getMappedTurbulenceData() + }] +}); + +function getMappedTurbulenceData(){ + var dataWithColorInformation = turbulenceData.map(function(data){ + data.color = COLOR[data.rating]; + return data; + }); + return dataWithColorInformation; +}; + +$(function() { + $("#gpa-chart").highcharts( + { + chart: { + type: 'pie', + events: { + load: addTitle, + redraw: addTitle, + }, + }, + title: { + text: "", + useHTML: true + }, + plotOptions: { + pie: { + shadow: false + } + }, + tooltip: { + formatter: function() { + return ''+ this.point.name +': '+ this.y +' %'; + } + }, + series: [{ + name: 'Browsers', + data: getGpaData(), + size: '90%', + innerSize: '65%', + showInLegend:true, + dataLabels: { + enabled: false + } + }] + }); +}); + +function addTitle() { + if (this.title) { + this.title.destroy(); + } + var r = this.renderer, + x = this.series[0].center[0] + this.plotLeft, + y = this.series[0].center[1] + this.plotTop; + this.title = r.label(''+score.toFixed(2)+'/100', 0, 0, "", 0, 0, true) + .css({ + color: 'black' + }).hide().add(); + var bbox = this.title.getBBox(); + this.title.attr({ + x: x - (bbox.width / 2), + y: y + }).show(); + this.title.useHTML = true; +}; + +function getGpaData(){ + var ratingACount = getRatingWiseCount("A"); + var ratingBCount = getRatingWiseCount("B"); + var ratingCCount = getRatingWiseCount("C"); + var ratingDCount = getRatingWiseCount("D"); + var ratingFCount = getRatingWiseCount("F"); + var total = ratingACount + ratingBCount + ratingCCount + ratingDCount + ratingFCount; + return [ + {name: 'A', y: parseFloat(calculatePercentage(ratingACount, total).toFixed(2)), color: COLOR['A']}, + {name: 'B', y: parseFloat(calculatePercentage(ratingBCount, total).toFixed(2)), color: COLOR['B']}, + {name: 'C', y: parseFloat(calculatePercentage(ratingCCount, total).toFixed(2)), color: COLOR['C']}, + {name: 'D', y: parseFloat(calculatePercentage(ratingDCount, total).toFixed(2)), color: COLOR['D']}, + {name: 'F', y: parseFloat(calculatePercentage(ratingFCount, total).toFixed(2)), color: COLOR['F']}, + ]; +}; + +function calculatePercentage(gradeCount, total){ + return (gradeCount/total)*100; +}; + +function getRatingWiseCount(rating){ + var count = 0; + turbulenceData.forEach(function(data, index){ + if(data.rating === rating){ + count++; + } + }); + return count; +}; + +function emphasizeLineFromFragment() { + emphasizeLine(window.location.hash) +} + +$(".js-file-code").on("click", ".js-smell-location", emphasizeLineFromHref); + +function emphasizeLineFromHref(event) { + if (hrefTargetIsOnCurrentPage(this) && !event.ctrlKey) { + $(".js-file-code li").removeClass("highlight"); + var lineId = "#" + this.href.split("#")[1]; + emphasizeLine(lineId); + return false; + } +} + +function hrefTargetIsOnCurrentPage(aTag) { + return (window.location.pathname === aTag.pathname); +} + +function emphasizeLine(lineReference) { + scrollToTarget(lineReference); + highlightLine(lineReference); +} + +function scrollToTarget(lineReference) { + window.location.hash = lineReference; + $.scrollTo(lineReference, { + duration: 300, + easing: "linear", + offset: {top: -87}, + axis: 'y' + }); +} + +function highlightLine(lineReference) { + $(lineReference).addClass("highlight"); +} + +$("#toggle-code").on("click", showCode); +$("#toggle-smells").on("click", showSmells); +$("#toggle-coverage").on("click", showCoverage); + +function showCode() { + $('#toggle-code').parent('li').addClass('active'); + $('#toggle-smells').parent('li').removeClass('active'); + $('#toggle-coverage').parent('li').removeClass('active'); + $(".smells").hide(); +} + +function showSmells() { + $('#toggle-code').parent('li').removeClass('active'); + $('#toggle-coverage').parent('li').removeClass('active'); + $('#toggle-smells').parent('li').addClass('active'); + $(".smells").show(); +} + +function showCoverage() { + $('#toggle-code').parent('li').removeClass('active'); + $('#toggle-smells').parent('li').removeClass('active'); + $('#toggle-coverage').parent('li').addClass('active'); + $(".coverage").show(); +} + +$("#codeTable") + .tablesorter({ // Sort the table + sortList: [[0,1]] + }); + +$("#js-index-table") + .tablesorter({ // Sort the table + sortList: [[0,0]] + }); + +$(".js-timeago").timeago(); + +$(function(){ + $('.table-header').click(function(){ + $('.table-header').not(this).each(function(){ + $(this).removeClass('active'); + $(this).find('.sort-type').removeClass('table-header-asc'); + $(this).find('.sort-type').removeClass('table-header-desc'); + }); + if($(this).hasClass('active')){ + $(this).find('.sort-type').toggleClass('table-header-asc table-header-desc'); + } + else{ + $(this).addClass('active'); + $(this).find('.sort-type').addClass('table-header-asc'); + } + }); +}); + +function assignIdsToCodeLines(){ + $($('.linenums')[1]).children().each(function(index){ + $(this).attr('id', "L" + index) + }); +}; + +$(document).ready(function(){ + assignIdsToCodeLines(); + emphasizeLineFromFragment(); + initTableFilters(); +}); + +var initTableFilters = function() { + $("#codeTable").filterTable({ignoreColumns: [2], placeholder: 'Filter by Name'}); + $("#js-index-table").filterTable({ignoreColumns: [2, 3, 4, 5], placeholder: 'Filter by Smell or Location', inputSelector: 'form-control'}); +} diff --git a/ABC_Score/assets/javascripts/bootstrap.min.js b/ABC_Score/assets/javascripts/bootstrap.min.js new file mode 100755 index 00000000..9bcd2fcc --- /dev/null +++ b/ABC_Score/assets/javascripts/bootstrap.min.js @@ -0,0 +1,7 @@ +/*! + * Bootstrap v3.3.7 (http://getbootstrap.com) + * Copyright 2011-2016 Twitter, Inc. + * Licensed under the MIT license + */ +if("undefined"==typeof jQuery)throw new Error("Bootstrap's JavaScript requires jQuery");+function(a){"use strict";var b=a.fn.jquery.split(" ")[0].split(".");if(b[0]<2&&b[1]<9||1==b[0]&&9==b[1]&&b[2]<1||b[0]>3)throw new Error("Bootstrap's JavaScript requires jQuery version 1.9.1 or higher, but lower than version 4")}(jQuery),+function(a){"use strict";function b(){var a=document.createElement("bootstrap"),b={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"};for(var c in b)if(void 0!==a.style[c])return{end:b[c]};return!1}a.fn.emulateTransitionEnd=function(b){var c=!1,d=this;a(this).one("bsTransitionEnd",function(){c=!0});var e=function(){c||a(d).trigger(a.support.transition.end)};return setTimeout(e,b),this},a(function(){a.support.transition=b(),a.support.transition&&(a.event.special.bsTransitionEnd={bindType:a.support.transition.end,delegateType:a.support.transition.end,handle:function(b){if(a(b.target).is(this))return b.handleObj.handler.apply(this,arguments)}})})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var c=a(this),e=c.data("bs.alert");e||c.data("bs.alert",e=new d(this)),"string"==typeof b&&e[b].call(c)})}var c='[data-dismiss="alert"]',d=function(b){a(b).on("click",c,this.close)};d.VERSION="3.3.7",d.TRANSITION_DURATION=150,d.prototype.close=function(b){function c(){g.detach().trigger("closed.bs.alert").remove()}var e=a(this),f=e.attr("data-target");f||(f=e.attr("href"),f=f&&f.replace(/.*(?=#[^\s]*$)/,""));var g=a("#"===f?[]:f);b&&b.preventDefault(),g.length||(g=e.closest(".alert")),g.trigger(b=a.Event("close.bs.alert")),b.isDefaultPrevented()||(g.removeClass("in"),a.support.transition&&g.hasClass("fade")?g.one("bsTransitionEnd",c).emulateTransitionEnd(d.TRANSITION_DURATION):c())};var e=a.fn.alert;a.fn.alert=b,a.fn.alert.Constructor=d,a.fn.alert.noConflict=function(){return a.fn.alert=e,this},a(document).on("click.bs.alert.data-api",c,d.prototype.close)}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.button"),f="object"==typeof b&&b;e||d.data("bs.button",e=new c(this,f)),"toggle"==b?e.toggle():b&&e.setState(b)})}var c=function(b,d){this.$element=a(b),this.options=a.extend({},c.DEFAULTS,d),this.isLoading=!1};c.VERSION="3.3.7",c.DEFAULTS={loadingText:"loading..."},c.prototype.setState=function(b){var c="disabled",d=this.$element,e=d.is("input")?"val":"html",f=d.data();b+="Text",null==f.resetText&&d.data("resetText",d[e]()),setTimeout(a.proxy(function(){d[e](null==f[b]?this.options[b]:f[b]),"loadingText"==b?(this.isLoading=!0,d.addClass(c).attr(c,c).prop(c,!0)):this.isLoading&&(this.isLoading=!1,d.removeClass(c).removeAttr(c).prop(c,!1))},this),0)},c.prototype.toggle=function(){var a=!0,b=this.$element.closest('[data-toggle="buttons"]');if(b.length){var c=this.$element.find("input");"radio"==c.prop("type")?(c.prop("checked")&&(a=!1),b.find(".active").removeClass("active"),this.$element.addClass("active")):"checkbox"==c.prop("type")&&(c.prop("checked")!==this.$element.hasClass("active")&&(a=!1),this.$element.toggleClass("active")),c.prop("checked",this.$element.hasClass("active")),a&&c.trigger("change")}else this.$element.attr("aria-pressed",!this.$element.hasClass("active")),this.$element.toggleClass("active")};var d=a.fn.button;a.fn.button=b,a.fn.button.Constructor=c,a.fn.button.noConflict=function(){return a.fn.button=d,this},a(document).on("click.bs.button.data-api",'[data-toggle^="button"]',function(c){var d=a(c.target).closest(".btn");b.call(d,"toggle"),a(c.target).is('input[type="radio"], input[type="checkbox"]')||(c.preventDefault(),d.is("input,button")?d.trigger("focus"):d.find("input:visible,button:visible").first().trigger("focus"))}).on("focus.bs.button.data-api blur.bs.button.data-api",'[data-toggle^="button"]',function(b){a(b.target).closest(".btn").toggleClass("focus",/^focus(in)?$/.test(b.type))})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.carousel"),f=a.extend({},c.DEFAULTS,d.data(),"object"==typeof b&&b),g="string"==typeof b?b:f.slide;e||d.data("bs.carousel",e=new c(this,f)),"number"==typeof b?e.to(b):g?e[g]():f.interval&&e.pause().cycle()})}var c=function(b,c){this.$element=a(b),this.$indicators=this.$element.find(".carousel-indicators"),this.options=c,this.paused=null,this.sliding=null,this.interval=null,this.$active=null,this.$items=null,this.options.keyboard&&this.$element.on("keydown.bs.carousel",a.proxy(this.keydown,this)),"hover"==this.options.pause&&!("ontouchstart"in document.documentElement)&&this.$element.on("mouseenter.bs.carousel",a.proxy(this.pause,this)).on("mouseleave.bs.carousel",a.proxy(this.cycle,this))};c.VERSION="3.3.7",c.TRANSITION_DURATION=600,c.DEFAULTS={interval:5e3,pause:"hover",wrap:!0,keyboard:!0},c.prototype.keydown=function(a){if(!/input|textarea/i.test(a.target.tagName)){switch(a.which){case 37:this.prev();break;case 39:this.next();break;default:return}a.preventDefault()}},c.prototype.cycle=function(b){return b||(this.paused=!1),this.interval&&clearInterval(this.interval),this.options.interval&&!this.paused&&(this.interval=setInterval(a.proxy(this.next,this),this.options.interval)),this},c.prototype.getItemIndex=function(a){return this.$items=a.parent().children(".item"),this.$items.index(a||this.$active)},c.prototype.getItemForDirection=function(a,b){var c=this.getItemIndex(b),d="prev"==a&&0===c||"next"==a&&c==this.$items.length-1;if(d&&!this.options.wrap)return b;var e="prev"==a?-1:1,f=(c+e)%this.$items.length;return this.$items.eq(f)},c.prototype.to=function(a){var b=this,c=this.getItemIndex(this.$active=this.$element.find(".item.active"));if(!(a>this.$items.length-1||a<0))return this.sliding?this.$element.one("slid.bs.carousel",function(){b.to(a)}):c==a?this.pause().cycle():this.slide(a>c?"next":"prev",this.$items.eq(a))},c.prototype.pause=function(b){return b||(this.paused=!0),this.$element.find(".next, .prev").length&&a.support.transition&&(this.$element.trigger(a.support.transition.end),this.cycle(!0)),this.interval=clearInterval(this.interval),this},c.prototype.next=function(){if(!this.sliding)return this.slide("next")},c.prototype.prev=function(){if(!this.sliding)return this.slide("prev")},c.prototype.slide=function(b,d){var e=this.$element.find(".item.active"),f=d||this.getItemForDirection(b,e),g=this.interval,h="next"==b?"left":"right",i=this;if(f.hasClass("active"))return this.sliding=!1;var j=f[0],k=a.Event("slide.bs.carousel",{relatedTarget:j,direction:h});if(this.$element.trigger(k),!k.isDefaultPrevented()){if(this.sliding=!0,g&&this.pause(),this.$indicators.length){this.$indicators.find(".active").removeClass("active");var l=a(this.$indicators.children()[this.getItemIndex(f)]);l&&l.addClass("active")}var m=a.Event("slid.bs.carousel",{relatedTarget:j,direction:h});return a.support.transition&&this.$element.hasClass("slide")?(f.addClass(b),f[0].offsetWidth,e.addClass(h),f.addClass(h),e.one("bsTransitionEnd",function(){f.removeClass([b,h].join(" ")).addClass("active"),e.removeClass(["active",h].join(" ")),i.sliding=!1,setTimeout(function(){i.$element.trigger(m)},0)}).emulateTransitionEnd(c.TRANSITION_DURATION)):(e.removeClass("active"),f.addClass("active"),this.sliding=!1,this.$element.trigger(m)),g&&this.cycle(),this}};var d=a.fn.carousel;a.fn.carousel=b,a.fn.carousel.Constructor=c,a.fn.carousel.noConflict=function(){return a.fn.carousel=d,this};var e=function(c){var d,e=a(this),f=a(e.attr("data-target")||(d=e.attr("href"))&&d.replace(/.*(?=#[^\s]+$)/,""));if(f.hasClass("carousel")){var g=a.extend({},f.data(),e.data()),h=e.attr("data-slide-to");h&&(g.interval=!1),b.call(f,g),h&&f.data("bs.carousel").to(h),c.preventDefault()}};a(document).on("click.bs.carousel.data-api","[data-slide]",e).on("click.bs.carousel.data-api","[data-slide-to]",e),a(window).on("load",function(){a('[data-ride="carousel"]').each(function(){var c=a(this);b.call(c,c.data())})})}(jQuery),+function(a){"use strict";function b(b){var c,d=b.attr("data-target")||(c=b.attr("href"))&&c.replace(/.*(?=#[^\s]+$)/,"");return a(d)}function c(b){return this.each(function(){var c=a(this),e=c.data("bs.collapse"),f=a.extend({},d.DEFAULTS,c.data(),"object"==typeof b&&b);!e&&f.toggle&&/show|hide/.test(b)&&(f.toggle=!1),e||c.data("bs.collapse",e=new d(this,f)),"string"==typeof b&&e[b]()})}var d=function(b,c){this.$element=a(b),this.options=a.extend({},d.DEFAULTS,c),this.$trigger=a('[data-toggle="collapse"][href="#'+b.id+'"],[data-toggle="collapse"][data-target="#'+b.id+'"]'),this.transitioning=null,this.options.parent?this.$parent=this.getParent():this.addAriaAndCollapsedClass(this.$element,this.$trigger),this.options.toggle&&this.toggle()};d.VERSION="3.3.7",d.TRANSITION_DURATION=350,d.DEFAULTS={toggle:!0},d.prototype.dimension=function(){var a=this.$element.hasClass("width");return a?"width":"height"},d.prototype.show=function(){if(!this.transitioning&&!this.$element.hasClass("in")){var b,e=this.$parent&&this.$parent.children(".panel").children(".in, .collapsing");if(!(e&&e.length&&(b=e.data("bs.collapse"),b&&b.transitioning))){var f=a.Event("show.bs.collapse");if(this.$element.trigger(f),!f.isDefaultPrevented()){e&&e.length&&(c.call(e,"hide"),b||e.data("bs.collapse",null));var g=this.dimension();this.$element.removeClass("collapse").addClass("collapsing")[g](0).attr("aria-expanded",!0),this.$trigger.removeClass("collapsed").attr("aria-expanded",!0),this.transitioning=1;var h=function(){this.$element.removeClass("collapsing").addClass("collapse in")[g](""),this.transitioning=0,this.$element.trigger("shown.bs.collapse")};if(!a.support.transition)return h.call(this);var i=a.camelCase(["scroll",g].join("-"));this.$element.one("bsTransitionEnd",a.proxy(h,this)).emulateTransitionEnd(d.TRANSITION_DURATION)[g](this.$element[0][i])}}}},d.prototype.hide=function(){if(!this.transitioning&&this.$element.hasClass("in")){var b=a.Event("hide.bs.collapse");if(this.$element.trigger(b),!b.isDefaultPrevented()){var c=this.dimension();this.$element[c](this.$element[c]())[0].offsetHeight,this.$element.addClass("collapsing").removeClass("collapse in").attr("aria-expanded",!1),this.$trigger.addClass("collapsed").attr("aria-expanded",!1),this.transitioning=1;var e=function(){this.transitioning=0,this.$element.removeClass("collapsing").addClass("collapse").trigger("hidden.bs.collapse")};return a.support.transition?void this.$element[c](0).one("bsTransitionEnd",a.proxy(e,this)).emulateTransitionEnd(d.TRANSITION_DURATION):e.call(this)}}},d.prototype.toggle=function(){this[this.$element.hasClass("in")?"hide":"show"]()},d.prototype.getParent=function(){return a(this.options.parent).find('[data-toggle="collapse"][data-parent="'+this.options.parent+'"]').each(a.proxy(function(c,d){var e=a(d);this.addAriaAndCollapsedClass(b(e),e)},this)).end()},d.prototype.addAriaAndCollapsedClass=function(a,b){var c=a.hasClass("in");a.attr("aria-expanded",c),b.toggleClass("collapsed",!c).attr("aria-expanded",c)};var e=a.fn.collapse;a.fn.collapse=c,a.fn.collapse.Constructor=d,a.fn.collapse.noConflict=function(){return a.fn.collapse=e,this},a(document).on("click.bs.collapse.data-api",'[data-toggle="collapse"]',function(d){var e=a(this);e.attr("data-target")||d.preventDefault();var f=b(e),g=f.data("bs.collapse"),h=g?"toggle":e.data();c.call(f,h)})}(jQuery),+function(a){"use strict";function b(b){var c=b.attr("data-target");c||(c=b.attr("href"),c=c&&/#[A-Za-z]/.test(c)&&c.replace(/.*(?=#[^\s]*$)/,""));var d=c&&a(c);return d&&d.length?d:b.parent()}function c(c){c&&3===c.which||(a(e).remove(),a(f).each(function(){var d=a(this),e=b(d),f={relatedTarget:this};e.hasClass("open")&&(c&&"click"==c.type&&/input|textarea/i.test(c.target.tagName)&&a.contains(e[0],c.target)||(e.trigger(c=a.Event("hide.bs.dropdown",f)),c.isDefaultPrevented()||(d.attr("aria-expanded","false"),e.removeClass("open").trigger(a.Event("hidden.bs.dropdown",f)))))}))}function d(b){return this.each(function(){var c=a(this),d=c.data("bs.dropdown");d||c.data("bs.dropdown",d=new g(this)),"string"==typeof b&&d[b].call(c)})}var e=".dropdown-backdrop",f='[data-toggle="dropdown"]',g=function(b){a(b).on("click.bs.dropdown",this.toggle)};g.VERSION="3.3.7",g.prototype.toggle=function(d){var e=a(this);if(!e.is(".disabled, :disabled")){var f=b(e),g=f.hasClass("open");if(c(),!g){"ontouchstart"in document.documentElement&&!f.closest(".navbar-nav").length&&a(document.createElement("div")).addClass("dropdown-backdrop").insertAfter(a(this)).on("click",c);var h={relatedTarget:this};if(f.trigger(d=a.Event("show.bs.dropdown",h)),d.isDefaultPrevented())return;e.trigger("focus").attr("aria-expanded","true"),f.toggleClass("open").trigger(a.Event("shown.bs.dropdown",h))}return!1}},g.prototype.keydown=function(c){if(/(38|40|27|32)/.test(c.which)&&!/input|textarea/i.test(c.target.tagName)){var d=a(this);if(c.preventDefault(),c.stopPropagation(),!d.is(".disabled, :disabled")){var e=b(d),g=e.hasClass("open");if(!g&&27!=c.which||g&&27==c.which)return 27==c.which&&e.find(f).trigger("focus"),d.trigger("click");var h=" li:not(.disabled):visible a",i=e.find(".dropdown-menu"+h);if(i.length){var j=i.index(c.target);38==c.which&&j>0&&j--,40==c.which&&jdocument.documentElement.clientHeight;this.$element.css({paddingLeft:!this.bodyIsOverflowing&&a?this.scrollbarWidth:"",paddingRight:this.bodyIsOverflowing&&!a?this.scrollbarWidth:""})},c.prototype.resetAdjustments=function(){this.$element.css({paddingLeft:"",paddingRight:""})},c.prototype.checkScrollbar=function(){var a=window.innerWidth;if(!a){var b=document.documentElement.getBoundingClientRect();a=b.right-Math.abs(b.left)}this.bodyIsOverflowing=document.body.clientWidth
',trigger:"hover focus",title:"",delay:0,html:!1,container:!1,viewport:{selector:"body",padding:0}},c.prototype.init=function(b,c,d){if(this.enabled=!0,this.type=b,this.$element=a(c),this.options=this.getOptions(d),this.$viewport=this.options.viewport&&a(a.isFunction(this.options.viewport)?this.options.viewport.call(this,this.$element):this.options.viewport.selector||this.options.viewport),this.inState={click:!1,hover:!1,focus:!1},this.$element[0]instanceof document.constructor&&!this.options.selector)throw new Error("`selector` option must be specified when initializing "+this.type+" on the window.document object!");for(var e=this.options.trigger.split(" "),f=e.length;f--;){var g=e[f];if("click"==g)this.$element.on("click."+this.type,this.options.selector,a.proxy(this.toggle,this));else if("manual"!=g){var h="hover"==g?"mouseenter":"focusin",i="hover"==g?"mouseleave":"focusout";this.$element.on(h+"."+this.type,this.options.selector,a.proxy(this.enter,this)),this.$element.on(i+"."+this.type,this.options.selector,a.proxy(this.leave,this))}}this.options.selector?this._options=a.extend({},this.options,{trigger:"manual",selector:""}):this.fixTitle()},c.prototype.getDefaults=function(){return c.DEFAULTS},c.prototype.getOptions=function(b){return b=a.extend({},this.getDefaults(),this.$element.data(),b),b.delay&&"number"==typeof b.delay&&(b.delay={show:b.delay,hide:b.delay}),b},c.prototype.getDelegateOptions=function(){var b={},c=this.getDefaults();return this._options&&a.each(this._options,function(a,d){c[a]!=d&&(b[a]=d)}),b},c.prototype.enter=function(b){var c=b instanceof this.constructor?b:a(b.currentTarget).data("bs."+this.type);return c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c)),b instanceof a.Event&&(c.inState["focusin"==b.type?"focus":"hover"]=!0),c.tip().hasClass("in")||"in"==c.hoverState?void(c.hoverState="in"):(clearTimeout(c.timeout),c.hoverState="in",c.options.delay&&c.options.delay.show?void(c.timeout=setTimeout(function(){"in"==c.hoverState&&c.show()},c.options.delay.show)):c.show())},c.prototype.isInStateTrue=function(){for(var a in this.inState)if(this.inState[a])return!0;return!1},c.prototype.leave=function(b){var c=b instanceof this.constructor?b:a(b.currentTarget).data("bs."+this.type);if(c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c)),b instanceof a.Event&&(c.inState["focusout"==b.type?"focus":"hover"]=!1),!c.isInStateTrue())return clearTimeout(c.timeout),c.hoverState="out",c.options.delay&&c.options.delay.hide?void(c.timeout=setTimeout(function(){"out"==c.hoverState&&c.hide()},c.options.delay.hide)):c.hide()},c.prototype.show=function(){var b=a.Event("show.bs."+this.type);if(this.hasContent()&&this.enabled){this.$element.trigger(b);var d=a.contains(this.$element[0].ownerDocument.documentElement,this.$element[0]);if(b.isDefaultPrevented()||!d)return;var e=this,f=this.tip(),g=this.getUID(this.type);this.setContent(),f.attr("id",g),this.$element.attr("aria-describedby",g),this.options.animation&&f.addClass("fade");var h="function"==typeof this.options.placement?this.options.placement.call(this,f[0],this.$element[0]):this.options.placement,i=/\s?auto?\s?/i,j=i.test(h);j&&(h=h.replace(i,"")||"top"),f.detach().css({top:0,left:0,display:"block"}).addClass(h).data("bs."+this.type,this),this.options.container?f.appendTo(this.options.container):f.insertAfter(this.$element),this.$element.trigger("inserted.bs."+this.type);var k=this.getPosition(),l=f[0].offsetWidth,m=f[0].offsetHeight;if(j){var n=h,o=this.getPosition(this.$viewport);h="bottom"==h&&k.bottom+m>o.bottom?"top":"top"==h&&k.top-mo.width?"left":"left"==h&&k.left-lg.top+g.height&&(e.top=g.top+g.height-i)}else{var j=b.left-f,k=b.left+f+c;jg.right&&(e.left=g.left+g.width-k)}return e},c.prototype.getTitle=function(){var a,b=this.$element,c=this.options;return a=b.attr("data-original-title")||("function"==typeof c.title?c.title.call(b[0]):c.title)},c.prototype.getUID=function(a){do a+=~~(1e6*Math.random());while(document.getElementById(a));return a},c.prototype.tip=function(){if(!this.$tip&&(this.$tip=a(this.options.template),1!=this.$tip.length))throw new Error(this.type+" `template` option must consist of exactly 1 top-level element!");return this.$tip},c.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".tooltip-arrow")},c.prototype.enable=function(){this.enabled=!0},c.prototype.disable=function(){this.enabled=!1},c.prototype.toggleEnabled=function(){this.enabled=!this.enabled},c.prototype.toggle=function(b){var c=this;b&&(c=a(b.currentTarget).data("bs."+this.type),c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c))),b?(c.inState.click=!c.inState.click,c.isInStateTrue()?c.enter(c):c.leave(c)):c.tip().hasClass("in")?c.leave(c):c.enter(c)},c.prototype.destroy=function(){var a=this;clearTimeout(this.timeout),this.hide(function(){a.$element.off("."+a.type).removeData("bs."+a.type),a.$tip&&a.$tip.detach(),a.$tip=null,a.$arrow=null,a.$viewport=null,a.$element=null})};var d=a.fn.tooltip;a.fn.tooltip=b,a.fn.tooltip.Constructor=c,a.fn.tooltip.noConflict=function(){return a.fn.tooltip=d,this}}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.popover"),f="object"==typeof b&&b;!e&&/destroy|hide/.test(b)||(e||d.data("bs.popover",e=new c(this,f)),"string"==typeof b&&e[b]())})}var c=function(a,b){this.init("popover",a,b)};if(!a.fn.tooltip)throw new Error("Popover requires tooltip.js");c.VERSION="3.3.7",c.DEFAULTS=a.extend({},a.fn.tooltip.Constructor.DEFAULTS,{placement:"right",trigger:"click",content:"",template:''}),c.prototype=a.extend({},a.fn.tooltip.Constructor.prototype),c.prototype.constructor=c,c.prototype.getDefaults=function(){return c.DEFAULTS},c.prototype.setContent=function(){var a=this.tip(),b=this.getTitle(),c=this.getContent();a.find(".popover-title")[this.options.html?"html":"text"](b),a.find(".popover-content").children().detach().end()[this.options.html?"string"==typeof c?"html":"append":"text"](c),a.removeClass("fade top bottom left right in"),a.find(".popover-title").html()||a.find(".popover-title").hide()},c.prototype.hasContent=function(){return this.getTitle()||this.getContent()},c.prototype.getContent=function(){var a=this.$element,b=this.options;return a.attr("data-content")||("function"==typeof b.content?b.content.call(a[0]):b.content)},c.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".arrow")};var d=a.fn.popover;a.fn.popover=b,a.fn.popover.Constructor=c,a.fn.popover.noConflict=function(){return a.fn.popover=d,this}}(jQuery),+function(a){"use strict";function b(c,d){this.$body=a(document.body),this.$scrollElement=a(a(c).is(document.body)?window:c),this.options=a.extend({},b.DEFAULTS,d),this.selector=(this.options.target||"")+" .nav li > a",this.offsets=[],this.targets=[],this.activeTarget=null,this.scrollHeight=0,this.$scrollElement.on("scroll.bs.scrollspy",a.proxy(this.process,this)),this.refresh(),this.process()}function c(c){return this.each(function(){var d=a(this),e=d.data("bs.scrollspy"),f="object"==typeof c&&c;e||d.data("bs.scrollspy",e=new b(this,f)),"string"==typeof c&&e[c]()})}b.VERSION="3.3.7",b.DEFAULTS={offset:10},b.prototype.getScrollHeight=function(){return this.$scrollElement[0].scrollHeight||Math.max(this.$body[0].scrollHeight,document.documentElement.scrollHeight)},b.prototype.refresh=function(){var b=this,c="offset",d=0;this.offsets=[],this.targets=[],this.scrollHeight=this.getScrollHeight(),a.isWindow(this.$scrollElement[0])||(c="position",d=this.$scrollElement.scrollTop()),this.$body.find(this.selector).map(function(){var b=a(this),e=b.data("target")||b.attr("href"),f=/^#./.test(e)&&a(e);return f&&f.length&&f.is(":visible")&&[[f[c]().top+d,e]]||null}).sort(function(a,b){return a[0]-b[0]}).each(function(){b.offsets.push(this[0]),b.targets.push(this[1])})},b.prototype.process=function(){var a,b=this.$scrollElement.scrollTop()+this.options.offset,c=this.getScrollHeight(),d=this.options.offset+c-this.$scrollElement.height(),e=this.offsets,f=this.targets,g=this.activeTarget;if(this.scrollHeight!=c&&this.refresh(),b>=d)return g!=(a=f[f.length-1])&&this.activate(a);if(g&&b=e[a]&&(void 0===e[a+1]||b .dropdown-menu > .active").removeClass("active").end().find('[data-toggle="tab"]').attr("aria-expanded",!1),b.addClass("active").find('[data-toggle="tab"]').attr("aria-expanded",!0),h?(b[0].offsetWidth,b.addClass("in")):b.removeClass("fade"),b.parent(".dropdown-menu").length&&b.closest("li.dropdown").addClass("active").end().find('[data-toggle="tab"]').attr("aria-expanded",!0),e&&e()}var g=d.find("> .active"),h=e&&a.support.transition&&(g.length&&g.hasClass("fade")||!!d.find("> .fade").length);g.length&&h?g.one("bsTransitionEnd",f).emulateTransitionEnd(c.TRANSITION_DURATION):f(),g.removeClass("in")};var d=a.fn.tab;a.fn.tab=b,a.fn.tab.Constructor=c,a.fn.tab.noConflict=function(){return a.fn.tab=d,this};var e=function(c){c.preventDefault(),b.call(a(this),"show")};a(document).on("click.bs.tab.data-api",'[data-toggle="tab"]',e).on("click.bs.tab.data-api",'[data-toggle="pill"]',e)}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.affix"),f="object"==typeof b&&b;e||d.data("bs.affix",e=new c(this,f)),"string"==typeof b&&e[b]()})}var c=function(b,d){this.options=a.extend({},c.DEFAULTS,d),this.$target=a(this.options.target).on("scroll.bs.affix.data-api",a.proxy(this.checkPosition,this)).on("click.bs.affix.data-api",a.proxy(this.checkPositionWithEventLoop,this)),this.$element=a(b),this.affixed=null,this.unpin=null,this.pinnedOffset=null,this.checkPosition()};c.VERSION="3.3.7",c.RESET="affix affix-top affix-bottom",c.DEFAULTS={offset:0,target:window},c.prototype.getState=function(a,b,c,d){var e=this.$target.scrollTop(),f=this.$element.offset(),g=this.$target.height();if(null!=c&&"top"==this.affixed)return e=a-d&&"bottom"},c.prototype.getPinnedOffset=function(){if(this.pinnedOffset)return this.pinnedOffset;this.$element.removeClass(c.RESET).addClass("affix");var a=this.$target.scrollTop(),b=this.$element.offset();return this.pinnedOffset=b.top-a},c.prototype.checkPositionWithEventLoop=function(){setTimeout(a.proxy(this.checkPosition,this),1)},c.prototype.checkPosition=function(){if(this.$element.is(":visible")){var b=this.$element.height(),d=this.options.offset,e=d.top,f=d.bottom,g=Math.max(a(document).height(),a(document.body).height());"object"!=typeof d&&(f=e=d),"function"==typeof e&&(e=d.top(this.$element)),"function"==typeof f&&(f=d.bottom(this.$element));var h=this.getState(g,b,e,f);if(this.affixed!=h){null!=this.unpin&&this.$element.css("top","");var i="affix"+(h?"-"+h:""),j=a.Event(i+".bs.affix");if(this.$element.trigger(j),j.isDefaultPrevented())return;this.affixed=h,this.unpin="bottom"==h?this.getPinnedOffset():null,this.$element.removeClass(c.RESET).addClass(i).trigger(i.replace("affix","affixed")+".bs.affix")}"bottom"==h&&this.$element.offset({top:g-b-f})}};var d=a.fn.affix;a.fn.affix=b,a.fn.affix.Constructor=c,a.fn.affix.noConflict=function(){return a.fn.affix=d,this},a(window).on("load",function(){a('[data-spy="affix"]').each(function(){var c=a(this),d=c.data();d.offset=d.offset||{},null!=d.offsetBottom&&(d.offset.bottom=d.offsetBottom),null!=d.offsetTop&&(d.offset.top=d.offsetTop),b.call(c,d)})})}(jQuery); \ No newline at end of file diff --git a/ABC_Score/assets/javascripts/highcharts.src-4.0.1.js b/ABC_Score/assets/javascripts/highcharts.src-4.0.1.js new file mode 100644 index 00000000..9e063bd7 --- /dev/null +++ b/ABC_Score/assets/javascripts/highcharts.src-4.0.1.js @@ -0,0 +1,17672 @@ +// ==ClosureCompiler== +// @compilation_level SIMPLE_OPTIMIZATIONS + +/** + * @license Highcharts JS v4.0.1 (2014-04-24) + * + * (c) 2009-2014 Torstein Honsi + * + * License: www.highcharts.com/license + */ + +// JSLint options: +/*global Highcharts, document, window, navigator, setInterval, clearInterval, clearTimeout, setTimeout, location, jQuery, $, console, each, grep */ + +(function () { +// encapsulated variables +var UNDEFINED, + doc = document, + win = window, + math = Math, + mathRound = math.round, + mathFloor = math.floor, + mathCeil = math.ceil, + mathMax = math.max, + mathMin = math.min, + mathAbs = math.abs, + mathCos = math.cos, + mathSin = math.sin, + mathPI = math.PI, + deg2rad = mathPI * 2 / 360, + + + // some variables + userAgent = navigator.userAgent, + isOpera = win.opera, + isIE = /msie/i.test(userAgent) && !isOpera, + docMode8 = doc.documentMode === 8, + isWebKit = /AppleWebKit/.test(userAgent), + isFirefox = /Firefox/.test(userAgent), + isTouchDevice = /(Mobile|Android|Windows Phone)/.test(userAgent), + SVG_NS = 'http://www.w3.org/2000/svg', + hasSVG = !!doc.createElementNS && !!doc.createElementNS(SVG_NS, 'svg').createSVGRect, + hasBidiBug = isFirefox && parseInt(userAgent.split('Firefox/')[1], 10) < 4, // issue #38 + useCanVG = !hasSVG && !isIE && !!doc.createElement('canvas').getContext, + Renderer, + hasTouch, + symbolSizes = {}, + idCounter = 0, + garbageBin, + defaultOptions, + dateFormat, // function + globalAnimation, + pathAnim, + timeUnits, + noop = function () {}, + charts = [], + chartCount = 0, + PRODUCT = 'Highcharts', + VERSION = '4.0.1', + + // some constants for frequently used strings + DIV = 'div', + ABSOLUTE = 'absolute', + RELATIVE = 'relative', + HIDDEN = 'hidden', + PREFIX = 'highcharts-', + VISIBLE = 'visible', + PX = 'px', + NONE = 'none', + M = 'M', + L = 'L', + numRegex = /^[0-9]+$/, + NORMAL_STATE = '', + HOVER_STATE = 'hover', + SELECT_STATE = 'select', + MILLISECOND = 'millisecond', + SECOND = 'second', + MINUTE = 'minute', + HOUR = 'hour', + DAY = 'day', + WEEK = 'week', + MONTH = 'month', + YEAR = 'year', + + // Object for extending Axis + AxisPlotLineOrBandExtension, + + // constants for attributes + STROKE_WIDTH = 'stroke-width', + + // time methods, changed based on whether or not UTC is used + makeTime, + timezoneOffset, + getMinutes, + getHours, + getDay, + getDate, + getMonth, + getFullYear, + setMinutes, + setHours, + setDate, + setMonth, + setFullYear, + + + // lookup over the types and the associated classes + seriesTypes = {}; + +// The Highcharts namespace +var Highcharts = win.Highcharts = win.Highcharts ? error(16, true) : {}; + +/** + * Extend an object with the members of another + * @param {Object} a The object to be extended + * @param {Object} b The object to add to the first one + */ +function extend(a, b) { + var n; + if (!a) { + a = {}; + } + for (n in b) { + a[n] = b[n]; + } + return a; +} + +/** + * Deep merge two or more objects and return a third object. If the first argument is + * true, the contents of the second object is copied into the first object. + * Previously this function redirected to jQuery.extend(true), but this had two limitations. + * First, it deep merged arrays, which lead to workarounds in Highcharts. Second, + * it copied properties from extended prototypes. + */ +function merge() { + var i, + args = arguments, + len, + ret = {}, + doCopy = function (copy, original) { + var value, key; + + // An object is replacing a primitive + if (typeof copy !== 'object') { + copy = {}; + } + + for (key in original) { + if (original.hasOwnProperty(key)) { + value = original[key]; + + // Copy the contents of objects, but not arrays or DOM nodes + if (value && typeof value === 'object' && Object.prototype.toString.call(value) !== '[object Array]' + && key !== 'renderTo' && typeof value.nodeType !== 'number') { + copy[key] = doCopy(copy[key] || {}, value); + + // Primitives and arrays are copied over directly + } else { + copy[key] = original[key]; + } + } + } + return copy; + }; + + // If first argument is true, copy into the existing object. Used in setOptions. + if (args[0] === true) { + ret = args[1]; + args = Array.prototype.slice.call(args, 2); + } + + // For each argument, extend the return + len = args.length; + for (i = 0; i < len; i++) { + ret = doCopy(ret, args[i]); + } + + return ret; +} + +/** + * Take an array and turn into a hash with even number arguments as keys and odd numbers as + * values. Allows creating constants for commonly used style properties, attributes etc. + * Avoid it in performance critical situations like looping + */ +function hash() { + var i = 0, + args = arguments, + length = args.length, + obj = {}; + for (; i < length; i++) { + obj[args[i++]] = args[i]; + } + return obj; +} + +/** + * Shortcut for parseInt + * @param {Object} s + * @param {Number} mag Magnitude + */ +function pInt(s, mag) { + return parseInt(s, mag || 10); +} + +/** + * Check for string + * @param {Object} s + */ +function isString(s) { + return typeof s === 'string'; +} + +/** + * Check for object + * @param {Object} obj + */ +function isObject(obj) { + return typeof obj === 'object'; +} + +/** + * Check for array + * @param {Object} obj + */ +function isArray(obj) { + return Object.prototype.toString.call(obj) === '[object Array]'; +} + +/** + * Check for number + * @param {Object} n + */ +function isNumber(n) { + return typeof n === 'number'; +} + +function log2lin(num) { + return math.log(num) / math.LN10; +} +function lin2log(num) { + return math.pow(10, num); +} + +/** + * Remove last occurence of an item from an array + * @param {Array} arr + * @param {Mixed} item + */ +function erase(arr, item) { + var i = arr.length; + while (i--) { + if (arr[i] === item) { + arr.splice(i, 1); + break; + } + } + //return arr; +} + +/** + * Returns true if the object is not null or undefined. Like MooTools' $.defined. + * @param {Object} obj + */ +function defined(obj) { + return obj !== UNDEFINED && obj !== null; +} + +/** + * Set or get an attribute or an object of attributes. Can't use jQuery attr because + * it attempts to set expando properties on the SVG element, which is not allowed. + * + * @param {Object} elem The DOM element to receive the attribute(s) + * @param {String|Object} prop The property or an abject of key-value pairs + * @param {String} value The value if a single property is set + */ +function attr(elem, prop, value) { + var key, + ret; + + // if the prop is a string + if (isString(prop)) { + // set the value + if (defined(value)) { + elem.setAttribute(prop, value); + + // get the value + } else if (elem && elem.getAttribute) { // elem not defined when printing pie demo... + ret = elem.getAttribute(prop); + } + + // else if prop is defined, it is a hash of key/value pairs + } else if (defined(prop) && isObject(prop)) { + for (key in prop) { + elem.setAttribute(key, prop[key]); + } + } + return ret; +} +/** + * Check if an element is an array, and if not, make it into an array. Like + * MooTools' $.splat. + */ +function splat(obj) { + return isArray(obj) ? obj : [obj]; +} + + +/** + * Return the first value that is defined. Like MooTools' $.pick. + */ +function pick() { + var args = arguments, + i, + arg, + length = args.length; + for (i = 0; i < length; i++) { + arg = args[i]; + if (typeof arg !== 'undefined' && arg !== null) { + return arg; + } + } +} + +/** + * Set CSS on a given element + * @param {Object} el + * @param {Object} styles Style object with camel case property names + */ +function css(el, styles) { + if (isIE && !hasSVG) { // #2686 + if (styles && styles.opacity !== UNDEFINED) { + styles.filter = 'alpha(opacity=' + (styles.opacity * 100) + ')'; + } + } + extend(el.style, styles); +} + +/** + * Utility function to create element with attributes and styles + * @param {Object} tag + * @param {Object} attribs + * @param {Object} styles + * @param {Object} parent + * @param {Object} nopad + */ +function createElement(tag, attribs, styles, parent, nopad) { + var el = doc.createElement(tag); + if (attribs) { + extend(el, attribs); + } + if (nopad) { + css(el, {padding: 0, border: NONE, margin: 0}); + } + if (styles) { + css(el, styles); + } + if (parent) { + parent.appendChild(el); + } + return el; +} + +/** + * Extend a prototyped class by new members + * @param {Object} parent + * @param {Object} members + */ +function extendClass(parent, members) { + var object = function () {}; + object.prototype = new parent(); + extend(object.prototype, members); + return object; +} + +/** + * Format a number and return a string based on input settings + * @param {Number} number The input number to format + * @param {Number} decimals The amount of decimals + * @param {String} decPoint The decimal point, defaults to the one given in the lang options + * @param {String} thousandsSep The thousands separator, defaults to the one given in the lang options + */ +function numberFormat(number, decimals, decPoint, thousandsSep) { + var lang = defaultOptions.lang, + // http://kevin.vanzonneveld.net/techblog/article/javascript_equivalent_for_phps_number_format/ + n = +number || 0, + c = decimals === -1 ? + (n.toString().split('.')[1] || '').length : // preserve decimals + (isNaN(decimals = mathAbs(decimals)) ? 2 : decimals), + d = decPoint === undefined ? lang.decimalPoint : decPoint, + t = thousandsSep === undefined ? lang.thousandsSep : thousandsSep, + s = n < 0 ? "-" : "", + i = String(pInt(n = mathAbs(n).toFixed(c))), + j = i.length > 3 ? i.length % 3 : 0; + + return s + (j ? i.substr(0, j) + t : "") + i.substr(j).replace(/(\d{3})(?=\d)/g, "$1" + t) + + (c ? d + mathAbs(n - i).toFixed(c).slice(2) : ""); +} + +/** + * Pad a string to a given length by adding 0 to the beginning + * @param {Number} number + * @param {Number} length + */ +function pad(number, length) { + // Create an array of the remaining length +1 and join it with 0's + return new Array((length || 2) + 1 - String(number).length).join(0) + number; +} + +/** + * Wrap a method with extended functionality, preserving the original function + * @param {Object} obj The context object that the method belongs to + * @param {String} method The name of the method to extend + * @param {Function} func A wrapper function callback. This function is called with the same arguments + * as the original function, except that the original function is unshifted and passed as the first + * argument. + */ +function wrap(obj, method, func) { + var proceed = obj[method]; + obj[method] = function () { + var args = Array.prototype.slice.call(arguments); + args.unshift(proceed); + return func.apply(this, args); + }; +} + +/** + * Based on http://www.php.net/manual/en/function.strftime.php + * @param {String} format + * @param {Number} timestamp + * @param {Boolean} capitalize + */ +dateFormat = function (format, timestamp, capitalize) { + if (!defined(timestamp) || isNaN(timestamp)) { + return 'Invalid date'; + } + format = pick(format, '%Y-%m-%d %H:%M:%S'); + + var date = new Date(timestamp - timezoneOffset), + key, // used in for constuct below + // get the basic time values + hours = date[getHours](), + day = date[getDay](), + dayOfMonth = date[getDate](), + month = date[getMonth](), + fullYear = date[getFullYear](), + lang = defaultOptions.lang, + langWeekdays = lang.weekdays, + + // List all format keys. Custom formats can be added from the outside. + replacements = extend({ + + // Day + 'a': langWeekdays[day].substr(0, 3), // Short weekday, like 'Mon' + 'A': langWeekdays[day], // Long weekday, like 'Monday' + 'd': pad(dayOfMonth), // Two digit day of the month, 01 to 31 + 'e': dayOfMonth, // Day of the month, 1 through 31 + + // Week (none implemented) + //'W': weekNumber(), + + // Month + 'b': lang.shortMonths[month], // Short month, like 'Jan' + 'B': lang.months[month], // Long month, like 'January' + 'm': pad(month + 1), // Two digit month number, 01 through 12 + + // Year + 'y': fullYear.toString().substr(2, 2), // Two digits year, like 09 for 2009 + 'Y': fullYear, // Four digits year, like 2009 + + // Time + 'H': pad(hours), // Two digits hours in 24h format, 00 through 23 + 'I': pad((hours % 12) || 12), // Two digits hours in 12h format, 00 through 11 + 'l': (hours % 12) || 12, // Hours in 12h format, 1 through 12 + 'M': pad(date[getMinutes]()), // Two digits minutes, 00 through 59 + 'p': hours < 12 ? 'AM' : 'PM', // Upper case AM or PM + 'P': hours < 12 ? 'am' : 'pm', // Lower case AM or PM + 'S': pad(date.getSeconds()), // Two digits seconds, 00 through 59 + 'L': pad(mathRound(timestamp % 1000), 3) // Milliseconds (naming from Ruby) + }, Highcharts.dateFormats); + + + // do the replaces + for (key in replacements) { + while (format.indexOf('%' + key) !== -1) { // regex would do it in one line, but this is faster + format = format.replace('%' + key, typeof replacements[key] === 'function' ? replacements[key](timestamp) : replacements[key]); + } + } + + // Optionally capitalize the string and return + return capitalize ? format.substr(0, 1).toUpperCase() + format.substr(1) : format; +}; + +/** + * Format a single variable. Similar to sprintf, without the % prefix. + */ +function formatSingle(format, val) { + var floatRegex = /f$/, + decRegex = /\.([0-9])/, + lang = defaultOptions.lang, + decimals; + + if (floatRegex.test(format)) { // float + decimals = format.match(decRegex); + decimals = decimals ? decimals[1] : -1; + if (val !== null) { + val = numberFormat( + val, + decimals, + lang.decimalPoint, + format.indexOf(',') > -1 ? lang.thousandsSep : '' + ); + } + } else { + val = dateFormat(format, val); + } + return val; +} + +/** + * Format a string according to a subset of the rules of Python's String.format method. + */ +function format(str, ctx) { + var splitter = '{', + isInside = false, + segment, + valueAndFormat, + path, + i, + len, + ret = [], + val, + index; + + while ((index = str.indexOf(splitter)) !== -1) { + + segment = str.slice(0, index); + if (isInside) { // we're on the closing bracket looking back + + valueAndFormat = segment.split(':'); + path = valueAndFormat.shift().split('.'); // get first and leave format + len = path.length; + val = ctx; + + // Assign deeper paths + for (i = 0; i < len; i++) { + val = val[path[i]]; + } + + // Format the replacement + if (valueAndFormat.length) { + val = formatSingle(valueAndFormat.join(':'), val); + } + + // Push the result and advance the cursor + ret.push(val); + + } else { + ret.push(segment); + + } + str = str.slice(index + 1); // the rest + isInside = !isInside; // toggle + splitter = isInside ? '}' : '{'; // now look for next matching bracket + } + ret.push(str); + return ret.join(''); +} + +/** + * Get the magnitude of a number + */ +function getMagnitude(num) { + return math.pow(10, mathFloor(math.log(num) / math.LN10)); +} + +/** + * Take an interval and normalize it to multiples of 1, 2, 2.5 and 5 + * @param {Number} interval + * @param {Array} multiples + * @param {Number} magnitude + * @param {Object} options + */ +function normalizeTickInterval(interval, multiples, magnitude, options) { + var normalized, i; + + // round to a tenfold of 1, 2, 2.5 or 5 + magnitude = pick(magnitude, 1); + normalized = interval / magnitude; + + // multiples for a linear scale + if (!multiples) { + multiples = [1, 2, 2.5, 5, 10]; + + // the allowDecimals option + if (options && options.allowDecimals === false) { + if (magnitude === 1) { + multiples = [1, 2, 5, 10]; + } else if (magnitude <= 0.1) { + multiples = [1 / magnitude]; + } + } + } + + // normalize the interval to the nearest multiple + for (i = 0; i < multiples.length; i++) { + interval = multiples[i]; + if (normalized <= (multiples[i] + (multiples[i + 1] || multiples[i])) / 2) { + break; + } + } + + // multiply back to the correct magnitude + interval *= magnitude; + + return interval; +} + + +/** + * Helper class that contains variuos counters that are local to the chart. + */ +function ChartCounters() { + this.color = 0; + this.symbol = 0; +} + +ChartCounters.prototype = { + /** + * Wraps the color counter if it reaches the specified length. + */ + wrapColor: function (length) { + if (this.color >= length) { + this.color = 0; + } + }, + + /** + * Wraps the symbol counter if it reaches the specified length. + */ + wrapSymbol: function (length) { + if (this.symbol >= length) { + this.symbol = 0; + } + } +}; + + +/** + * Utility method that sorts an object array and keeping the order of equal items. + * ECMA script standard does not specify the behaviour when items are equal. + */ +function stableSort(arr, sortFunction) { + var length = arr.length, + sortValue, + i; + + // Add index to each item + for (i = 0; i < length; i++) { + arr[i].ss_i = i; // stable sort index + } + + arr.sort(function (a, b) { + sortValue = sortFunction(a, b); + return sortValue === 0 ? a.ss_i - b.ss_i : sortValue; + }); + + // Remove index from items + for (i = 0; i < length; i++) { + delete arr[i].ss_i; // stable sort index + } +} + +/** + * Non-recursive method to find the lowest member of an array. Math.min raises a maximum + * call stack size exceeded error in Chrome when trying to apply more than 150.000 points. This + * method is slightly slower, but safe. + */ +function arrayMin(data) { + var i = data.length, + min = data[0]; + + while (i--) { + if (data[i] < min) { + min = data[i]; + } + } + return min; +} + +/** + * Non-recursive method to find the lowest member of an array. Math.min raises a maximum + * call stack size exceeded error in Chrome when trying to apply more than 150.000 points. This + * method is slightly slower, but safe. + */ +function arrayMax(data) { + var i = data.length, + max = data[0]; + + while (i--) { + if (data[i] > max) { + max = data[i]; + } + } + return max; +} + +/** + * Utility method that destroys any SVGElement or VMLElement that are properties on the given object. + * It loops all properties and invokes destroy if there is a destroy method. The property is + * then delete'ed. + * @param {Object} The object to destroy properties on + * @param {Object} Exception, do not destroy this property, only delete it. + */ +function destroyObjectProperties(obj, except) { + var n; + for (n in obj) { + // If the object is non-null and destroy is defined + if (obj[n] && obj[n] !== except && obj[n].destroy) { + // Invoke the destroy + obj[n].destroy(); + } + + // Delete the property from the object. + delete obj[n]; + } +} + + +/** + * Discard an element by moving it to the bin and delete + * @param {Object} The HTML node to discard + */ +function discardElement(element) { + // create a garbage bin element, not part of the DOM + if (!garbageBin) { + garbageBin = createElement(DIV); + } + + // move the node and empty bin + if (element) { + garbageBin.appendChild(element); + } + garbageBin.innerHTML = ''; +} + +/** + * Provide error messages for debugging, with links to online explanation + */ +function error(code, stop) { + var msg = 'Highcharts error #' + code + ': www.highcharts.com/errors/' + code; + if (stop) { + throw msg; + } else if (win.console) { + console.log(msg); + } +} + +/** + * Fix JS round off float errors + * @param {Number} num + */ +function correctFloat(num) { + return parseFloat( + num.toPrecision(14) + ); +} + +/** + * Set the global animation to either a given value, or fall back to the + * given chart's animation option + * @param {Object} animation + * @param {Object} chart + */ +function setAnimation(animation, chart) { + globalAnimation = pick(animation, chart.animation); +} + +/** + * The time unit lookup + */ +/*jslint white: true*/ +timeUnits = hash( + MILLISECOND, 1, + SECOND, 1000, + MINUTE, 60000, + HOUR, 3600000, + DAY, 24 * 3600000, + WEEK, 7 * 24 * 3600000, + MONTH, 31 * 24 * 3600000, + YEAR, 31556952000 +); +/*jslint white: false*/ +/** + * Path interpolation algorithm used across adapters + */ +pathAnim = { + /** + * Prepare start and end values so that the path can be animated one to one + */ + init: function (elem, fromD, toD) { + fromD = fromD || ''; + var shift = elem.shift, + bezier = fromD.indexOf('C') > -1, + numParams = bezier ? 7 : 3, + endLength, + slice, + i, + start = fromD.split(' '), + end = [].concat(toD), // copy + startBaseLine, + endBaseLine, + sixify = function (arr) { // in splines make move points have six parameters like bezier curves + i = arr.length; + while (i--) { + if (arr[i] === M) { + arr.splice(i + 1, 0, arr[i + 1], arr[i + 2], arr[i + 1], arr[i + 2]); + } + } + }; + + if (bezier) { + sixify(start); + sixify(end); + } + + // pull out the base lines before padding + if (elem.isArea) { + startBaseLine = start.splice(start.length - 6, 6); + endBaseLine = end.splice(end.length - 6, 6); + } + + // if shifting points, prepend a dummy point to the end path + if (shift <= end.length / numParams && start.length === end.length) { + while (shift--) { + end = [].concat(end).splice(0, numParams).concat(end); + } + } + elem.shift = 0; // reset for following animations + + // copy and append last point until the length matches the end length + if (start.length) { + endLength = end.length; + while (start.length < endLength) { + + //bezier && sixify(start); + slice = [].concat(start).splice(start.length - numParams, numParams); + if (bezier) { // disable first control point + slice[numParams - 6] = slice[numParams - 2]; + slice[numParams - 5] = slice[numParams - 1]; + } + start = start.concat(slice); + } + } + + if (startBaseLine) { // append the base lines for areas + start = start.concat(startBaseLine); + end = end.concat(endBaseLine); + } + return [start, end]; + }, + + /** + * Interpolate each value of the path and return the array + */ + step: function (start, end, pos, complete) { + var ret = [], + i = start.length, + startVal; + + if (pos === 1) { // land on the final path without adjustment points appended in the ends + ret = complete; + + } else if (i === end.length && pos < 1) { + while (i--) { + startVal = parseFloat(start[i]); + ret[i] = + isNaN(startVal) ? // a letter instruction like M or L + start[i] : + pos * (parseFloat(end[i] - startVal)) + startVal; + + } + } else { // if animation is finished or length not matching, land on right value + ret = end; + } + return ret; + } +}; + +(function ($) { + /** + * The default HighchartsAdapter for jQuery + */ + win.HighchartsAdapter = win.HighchartsAdapter || ($ && { + + /** + * Initialize the adapter by applying some extensions to jQuery + */ + init: function (pathAnim) { + + // extend the animate function to allow SVG animations + var Fx = $.fx, + Step = Fx.step, + dSetter, + Tween = $.Tween, + propHooks = Tween && Tween.propHooks, + opacityHook = $.cssHooks.opacity; + + /*jslint unparam: true*//* allow unused param x in this function */ + $.extend($.easing, { + easeOutQuad: function (x, t, b, c, d) { + return -c * (t /= d) * (t - 2) + b; + } + }); + /*jslint unparam: false*/ + + // extend some methods to check for elem.attr, which means it is a Highcharts SVG object + $.each(['cur', '_default', 'width', 'height', 'opacity'], function (i, fn) { + var obj = Step, + base; + + // Handle different parent objects + if (fn === 'cur') { + obj = Fx.prototype; // 'cur', the getter, relates to Fx.prototype + + } else if (fn === '_default' && Tween) { // jQuery 1.8 model + obj = propHooks[fn]; + fn = 'set'; + } + + // Overwrite the method + base = obj[fn]; + if (base) { // step.width and step.height don't exist in jQuery < 1.7 + + // create the extended function replacement + obj[fn] = function (fx) { + + var elem; + + // Fx.prototype.cur does not use fx argument + fx = i ? fx : this; + + // Don't run animations on textual properties like align (#1821) + if (fx.prop === 'align') { + return; + } + + // shortcut + elem = fx.elem; + + // Fx.prototype.cur returns the current value. The other ones are setters + // and returning a value has no effect. + return elem.attr ? // is SVG element wrapper + elem.attr(fx.prop, fn === 'cur' ? UNDEFINED : fx.now) : // apply the SVG wrapper's method + base.apply(this, arguments); // use jQuery's built-in method + }; + } + }); + + // Extend the opacity getter, needed for fading opacity with IE9 and jQuery 1.10+ + wrap(opacityHook, 'get', function (proceed, elem, computed) { + return elem.attr ? (elem.opacity || 0) : proceed.call(this, elem, computed); + }); + + + // Define the setter function for d (path definitions) + dSetter = function (fx) { + var elem = fx.elem, + ends; + + // Normally start and end should be set in state == 0, but sometimes, + // for reasons unknown, this doesn't happen. Perhaps state == 0 is skipped + // in these cases + if (!fx.started) { + ends = pathAnim.init(elem, elem.d, elem.toD); + fx.start = ends[0]; + fx.end = ends[1]; + fx.started = true; + } + + + // interpolate each value of the path + elem.attr('d', pathAnim.step(fx.start, fx.end, fx.pos, elem.toD)); + }; + + // jQuery 1.8 style + if (Tween) { + propHooks.d = { + set: dSetter + }; + // pre 1.8 + } else { + // animate paths + Step.d = dSetter; + } + + /** + * Utility for iterating over an array. Parameters are reversed compared to jQuery. + * @param {Array} arr + * @param {Function} fn + */ + this.each = Array.prototype.forEach ? + function (arr, fn) { // modern browsers + return Array.prototype.forEach.call(arr, fn); + + } : + function (arr, fn) { // legacy + var i = 0, + len = arr.length; + for (; i < len; i++) { + if (fn.call(arr[i], arr[i], i, arr) === false) { + return i; + } + } + }; + + /** + * Register Highcharts as a plugin in the respective framework + */ + $.fn.highcharts = function () { + var constr = 'Chart', // default constructor + args = arguments, + options, + ret, + chart; + + if (this[0]) { + + if (isString(args[0])) { + constr = args[0]; + args = Array.prototype.slice.call(args, 1); + } + options = args[0]; + + // Create the chart + if (options !== UNDEFINED) { + /*jslint unused:false*/ + options.chart = options.chart || {}; + options.chart.renderTo = this[0]; + chart = new Highcharts[constr](options, args[1]); + ret = this; + /*jslint unused:true*/ + } + + // When called without parameters or with the return argument, get a predefined chart + if (options === UNDEFINED) { + ret = charts[attr(this[0], 'data-highcharts-chart')]; + } + } + + return ret; + }; + + }, + + + /** + * Downloads a script and executes a callback when done. + * @param {String} scriptLocation + * @param {Function} callback + */ + getScript: $.getScript, + + /** + * Return the index of an item in an array, or -1 if not found + */ + inArray: $.inArray, + + /** + * A direct link to jQuery methods. MooTools and Prototype adapters must be implemented for each case of method. + * @param {Object} elem The HTML element + * @param {String} method Which method to run on the wrapped element + */ + adapterRun: function (elem, method) { + return $(elem)[method](); + }, + + /** + * Filter an array + */ + grep: $.grep, + + /** + * Map an array + * @param {Array} arr + * @param {Function} fn + */ + map: function (arr, fn) { + //return jQuery.map(arr, fn); + var results = [], + i = 0, + len = arr.length; + for (; i < len; i++) { + results[i] = fn.call(arr[i], arr[i], i, arr); + } + return results; + + }, + + /** + * Get the position of an element relative to the top left of the page + */ + offset: function (el) { + return $(el).offset(); + }, + + /** + * Add an event listener + * @param {Object} el A HTML element or custom object + * @param {String} event The event type + * @param {Function} fn The event handler + */ + addEvent: function (el, event, fn) { + $(el).bind(event, fn); + }, + + /** + * Remove event added with addEvent + * @param {Object} el The object + * @param {String} eventType The event type. Leave blank to remove all events. + * @param {Function} handler The function to remove + */ + removeEvent: function (el, eventType, handler) { + // workaround for jQuery issue with unbinding custom events: + // http://forum.jQuery.com/topic/javascript-error-when-unbinding-a-custom-event-using-jQuery-1-4-2 + var func = doc.removeEventListener ? 'removeEventListener' : 'detachEvent'; + if (doc[func] && el && !el[func]) { + el[func] = function () {}; + } + + $(el).unbind(eventType, handler); + }, + + /** + * Fire an event on a custom object + * @param {Object} el + * @param {String} type + * @param {Object} eventArguments + * @param {Function} defaultFunction + */ + fireEvent: function (el, type, eventArguments, defaultFunction) { + var event = $.Event(type), + detachedType = 'detached' + type, + defaultPrevented; + + // Remove warnings in Chrome when accessing returnValue (#2790), layerX and layerY. Although Highcharts + // never uses these properties, Chrome includes them in the default click event and + // raises the warning when they are copied over in the extend statement below. + // + // To avoid problems in IE (see #1010) where we cannot delete the properties and avoid + // testing if they are there (warning in chrome) the only option is to test if running IE. + if (!isIE && eventArguments) { + delete eventArguments.layerX; + delete eventArguments.layerY; + delete eventArguments.returnValue; + } + + extend(event, eventArguments); + + // Prevent jQuery from triggering the object method that is named the + // same as the event. For example, if the event is 'select', jQuery + // attempts calling el.select and it goes into a loop. + if (el[type]) { + el[detachedType] = el[type]; + el[type] = null; + } + + // Wrap preventDefault and stopPropagation in try/catch blocks in + // order to prevent JS errors when cancelling events on non-DOM + // objects. #615. + /*jslint unparam: true*/ + $.each(['preventDefault', 'stopPropagation'], function (i, fn) { + var base = event[fn]; + event[fn] = function () { + try { + base.call(event); + } catch (e) { + if (fn === 'preventDefault') { + defaultPrevented = true; + } + } + }; + }); + /*jslint unparam: false*/ + + // trigger it + $(el).trigger(event); + + // attach the method + if (el[detachedType]) { + el[type] = el[detachedType]; + el[detachedType] = null; + } + + if (defaultFunction && !event.isDefaultPrevented() && !defaultPrevented) { + defaultFunction(event); + } + }, + + /** + * Extension method needed for MooTools + */ + washMouseEvent: function (e) { + var ret = e.originalEvent || e; + + // computed by jQuery, needed by IE8 + if (ret.pageX === UNDEFINED) { // #1236 + ret.pageX = e.pageX; + ret.pageY = e.pageY; + } + + return ret; + }, + + /** + * Animate a HTML element or SVG element wrapper + * @param {Object} el + * @param {Object} params + * @param {Object} options jQuery-like animation options: duration, easing, callback + */ + animate: function (el, params, options) { + var $el = $(el); + if (!el.style) { + el.style = {}; // #1881 + } + if (params.d) { + el.toD = params.d; // keep the array form for paths, used in $.fx.step.d + params.d = 1; // because in jQuery, animating to an array has a different meaning + } + + $el.stop(); + if (params.opacity !== UNDEFINED && el.attr) { + params.opacity += 'px'; // force jQuery to use same logic as width and height (#2161) + } + $el.animate(params, options); + + }, + /** + * Stop running animation + */ + stop: function (el) { + $(el).stop(); + } + }); +}(win.jQuery)); + + +// check for a custom HighchartsAdapter defined prior to this file +var globalAdapter = win.HighchartsAdapter, + adapter = globalAdapter || {}; + +// Initialize the adapter +if (globalAdapter) { + globalAdapter.init.call(globalAdapter, pathAnim); +} + + +// Utility functions. If the HighchartsAdapter is not defined, adapter is an empty object +// and all the utility functions will be null. In that case they are populated by the +// default adapters below. +var adapterRun = adapter.adapterRun, + getScript = adapter.getScript, + inArray = adapter.inArray, + each = adapter.each, + grep = adapter.grep, + offset = adapter.offset, + map = adapter.map, + addEvent = adapter.addEvent, + removeEvent = adapter.removeEvent, + fireEvent = adapter.fireEvent, + washMouseEvent = adapter.washMouseEvent, + animate = adapter.animate, + stop = adapter.stop; + + + +/* **************************************************************************** + * Handle the options * + *****************************************************************************/ +var + +defaultLabelOptions = { + enabled: true, + // rotation: 0, + // align: 'center', + x: 0, + y: 15, + /*formatter: function () { + return this.value; + },*/ + style: { + color: '#606060', + cursor: 'default', + fontSize: '11px' + } +}; + +defaultOptions = { + colors: ['#7cb5ec', '#434348', '#90ed7d', '#f7a35c', + '#8085e9', '#f15c80', '#e4d354', '#8085e8', '#8d4653', '#91e8e1'], // docs + symbols: ['circle', 'diamond', 'square', 'triangle', 'triangle-down'], + lang: { + loading: 'Loading...', + months: ['January', 'February', 'March', 'April', 'May', 'June', 'July', + 'August', 'September', 'October', 'November', 'December'], + shortMonths: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'], + weekdays: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'], + decimalPoint: '.', + numericSymbols: ['k', 'M', 'G', 'T', 'P', 'E'], // SI prefixes used in axis labels + resetZoom: 'Reset zoom', + resetZoomTitle: 'Reset zoom level 1:1', + thousandsSep: ',' + }, + global: { + useUTC: true, + //timezoneOffset: 0, + canvasToolsURL: 'http://code.highcharts.com/4.0.1/modules/canvas-tools.js', + VMLRadialGradientURL: 'http://code.highcharts.com/4.0.1/gfx/vml-radial-gradient.png' + }, + chart: { + //animation: true, + //alignTicks: false, + //reflow: true, + //className: null, + //events: { load, selection }, + //margin: [null], + //marginTop: null, + //marginRight: null, + //marginBottom: null, + //marginLeft: null, + borderColor: '#4572A7', + //borderWidth: 0, + borderRadius: 0, + defaultSeriesType: 'line', + ignoreHiddenSeries: true, + //inverted: false, + //shadow: false, + spacing: [10, 10, 15, 10], + //spacingTop: 10, + //spacingRight: 10, + //spacingBottom: 15, + //spacingLeft: 10, + //style: { + // fontFamily: '"Lucida Grande", "Lucida Sans Unicode", Verdana, Arial, Helvetica, sans-serif', // default font + // fontSize: '12px' + //}, + backgroundColor: '#FFFFFF', + //plotBackgroundColor: null, + plotBorderColor: '#C0C0C0', + //plotBorderWidth: 0, + //plotShadow: false, + //zoomType: '' + resetZoomButton: { + theme: { + zIndex: 20 + }, + position: { + align: 'right', + x: -10, + //verticalAlign: 'top', + y: 10 + } + // relativeTo: 'plot' + } + }, + title: { + text: 'Chart title', + align: 'center', + // floating: false, + margin: 15, + // x: 0, + // verticalAlign: 'top', + // y: null, + style: { + color: '#333333', // docs + fontSize: '18px' + } + + }, + subtitle: { + text: '', + align: 'center', + // floating: false + // x: 0, + // verticalAlign: 'top', + // y: null, + style: { + color: '#555555' // docs + } + }, + + plotOptions: { + line: { // base series options + allowPointSelect: false, + showCheckbox: false, + animation: { + duration: 1000 + }, + //connectNulls: false, + //cursor: 'default', + //clip: true, + //dashStyle: null, + //enableMouseTracking: true, + events: {}, + //legendIndex: 0, + //linecap: 'round', + lineWidth: 2, + //shadow: false, + // stacking: null, + marker: { + //enabled: true, + //symbol: null, + lineWidth: 0, + radius: 4, + lineColor: '#FFFFFF', + //fillColor: null, + states: { // states for a single point + hover: { + enabled: true + //radius: base + 2 + }, + select: { + fillColor: '#FFFFFF', + lineColor: '#000000', + lineWidth: 2 + } + } + }, + point: { + events: {} + }, + dataLabels: merge(defaultLabelOptions, { + align: 'center', + //defer: true, + enabled: false, + formatter: function () { + return this.y === null ? '' : numberFormat(this.y, -1); + }, + verticalAlign: 'bottom', // above singular point + y: 0 + // backgroundColor: undefined, + // borderColor: undefined, + // borderRadius: undefined, + // borderWidth: undefined, + // padding: 3, + // shadow: false + }), + cropThreshold: 300, // draw points outside the plot area when the number of points is less than this + pointRange: 0, + //pointStart: 0, + //pointInterval: 1, + //showInLegend: null, // auto: true for standalone series, false for linked series + states: { // states for the entire series + hover: { + //enabled: false, + //lineWidth: base + 1, + marker: { + // lineWidth: base + 1, + // radius: base + 1 + }, + halo: { + size: 10, + opacity: 0.25 + } + }, + select: { + marker: {} + } + }, + stickyTracking: true, + //tooltip: { + //pointFormat: '\u25CF {series.name}: {point.y}' + //valueDecimals: null, + //xDateFormat: '%A, %b %e, %Y', + //valuePrefix: '', + //ySuffix: '' + //} + turboThreshold: 1000 + // zIndex: null + } + }, + labels: { + //items: [], + style: { + //font: defaultFont, + position: ABSOLUTE, + color: '#3E576F' + } + }, + legend: { + enabled: true, + align: 'center', + //floating: false, + layout: 'horizontal', + labelFormatter: function () { + return this.name; + }, + //borderWidth: 0, + borderColor: '#909090', + borderRadius: 0, // docs + navigation: { + // animation: true, + activeColor: '#274b6d', + // arrowSize: 12 + inactiveColor: '#CCC' + // style: {} // text styles + }, + // margin: 20, + // reversed: false, + shadow: false, + // backgroundColor: null, + /*style: { + padding: '5px' + },*/ + itemStyle: { + color: '#333333', // docs + fontSize: '12px', + fontWeight: 'bold' // docs + }, + itemHoverStyle: { + //cursor: 'pointer', removed as of #601 + color: '#000' + }, + itemHiddenStyle: { + color: '#CCC' + }, + itemCheckboxStyle: { + position: ABSOLUTE, + width: '13px', // for IE precision + height: '13px' + }, + // itemWidth: undefined, + // symbolRadius: 0, + // symbolWidth: 16, + symbolPadding: 5, + verticalAlign: 'bottom', + // width: undefined, + x: 0, + y: 0, + title: { + //text: null, + style: { + fontWeight: 'bold' + } + } + }, + + loading: { + // hideDuration: 100, + labelStyle: { + fontWeight: 'bold', + position: RELATIVE, + top: '1em' + }, + // showDuration: 0, + style: { + position: ABSOLUTE, + backgroundColor: 'white', + opacity: 0.5, + textAlign: 'center' + } + }, + + tooltip: { + enabled: true, + animation: hasSVG, + //crosshairs: null, + backgroundColor: 'rgba(249, 249, 249, .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' + }, + //formatter: defaultFormatter, + headerFormat: '{point.key}
', + pointFormat: '\u25CF {series.name}: {point.y}
', // docs + shadow: true, + //shape: 'calout', + //shared: false, + snap: isTouchDevice ? 25 : 10, + style: { + color: '#333333', + cursor: 'default', + fontSize: '12px', + padding: '8px', + whiteSpace: 'nowrap' + } + //xDateFormat: '%A, %b %e, %Y', + //valueDecimals: null, + //valuePrefix: '', + //valueSuffix: '' + }, + + credits: { + enabled: true, + text: 'Highcharts.com', + href: 'http://www.highcharts.com', + position: { + align: 'right', + x: -10, + verticalAlign: 'bottom', + y: -5 + }, + style: { + cursor: 'pointer', + color: '#909090', + fontSize: '9px' + } + } +}; + + + + +// Series defaults +var defaultPlotOptions = defaultOptions.plotOptions, + defaultSeriesOptions = defaultPlotOptions.line; + +// set the default time methods +setTimeMethods(); + + + +/** + * Set the time methods globally based on the useUTC option. Time method can be either + * local time or UTC (default). + */ +function setTimeMethods() { + var useUTC = defaultOptions.global.useUTC, + GET = useUTC ? 'getUTC' : 'get', + SET = useUTC ? 'setUTC' : 'set'; + + + timezoneOffset = ((useUTC && defaultOptions.global.timezoneOffset) || 0) * 60000; + makeTime = useUTC ? Date.UTC : function (year, month, date, hours, minutes, seconds) { + return new Date( + year, + month, + pick(date, 1), + pick(hours, 0), + pick(minutes, 0), + pick(seconds, 0) + ).getTime(); + }; + getMinutes = GET + 'Minutes'; + getHours = GET + 'Hours'; + getDay = GET + 'Day'; + getDate = GET + 'Date'; + getMonth = GET + 'Month'; + getFullYear = GET + 'FullYear'; + setMinutes = SET + 'Minutes'; + setHours = SET + 'Hours'; + setDate = SET + 'Date'; + setMonth = SET + 'Month'; + setFullYear = SET + 'FullYear'; + +} + +/** + * Merge the default options with custom options and return the new options structure + * @param {Object} options The new custom options + */ +function setOptions(options) { + + // Copy in the default options + defaultOptions = merge(true, defaultOptions, options); + + // Apply UTC + setTimeMethods(); + + return defaultOptions; +} + +/** + * Get the updated default options. Until 3.0.7, merely exposing defaultOptions for outside modules + * wasn't enough because the setOptions method created a new object. + */ +function getOptions() { + return defaultOptions; +} + + +/** + * Handle color operations. The object methods are chainable. + * @param {String} input The input color in either rbga or hex format + */ +var rgbaRegEx = /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*\)/, + hexRegEx = /#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/, + rgbRegEx = /rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/; + +var Color = function (input) { + // declare variables + var rgba = [], result, stops; + + /** + * Parse the input color to rgba array + * @param {String} input + */ + function init(input) { + + // Gradients + if (input && input.stops) { + stops = map(input.stops, function (stop) { + return Color(stop[1]); + }); + + // Solid colors + } else { + // rgba + result = rgbaRegEx.exec(input); + if (result) { + rgba = [pInt(result[1]), pInt(result[2]), pInt(result[3]), parseFloat(result[4], 10)]; + } else { + // hex + result = hexRegEx.exec(input); + if (result) { + rgba = [pInt(result[1], 16), pInt(result[2], 16), pInt(result[3], 16), 1]; + } else { + // rgb + result = rgbRegEx.exec(input); + if (result) { + rgba = [pInt(result[1]), pInt(result[2]), pInt(result[3]), 1]; + } + } + } + } + + } + /** + * Return the color a specified format + * @param {String} format + */ + function get(format) { + var ret; + + if (stops) { + ret = merge(input); + ret.stops = [].concat(ret.stops); + each(stops, function (stop, i) { + ret.stops[i] = [ret.stops[i][0], stop.get(format)]; + }); + + // it's NaN if gradient colors on a column chart + } else if (rgba && !isNaN(rgba[0])) { + if (format === 'rgb') { + ret = 'rgb(' + rgba[0] + ',' + rgba[1] + ',' + rgba[2] + ')'; + } else if (format === 'a') { + ret = rgba[3]; + } else { + ret = 'rgba(' + rgba.join(',') + ')'; + } + } else { + ret = input; + } + return ret; + } + + /** + * Brighten the color + * @param {Number} alpha + */ + function brighten(alpha) { + if (stops) { + each(stops, function (stop) { + stop.brighten(alpha); + }); + + } else if (isNumber(alpha) && alpha !== 0) { + var i; + for (i = 0; i < 3; i++) { + rgba[i] += pInt(alpha * 255); + + if (rgba[i] < 0) { + rgba[i] = 0; + } + if (rgba[i] > 255) { + rgba[i] = 255; + } + } + } + return this; + } + /** + * Set the color's opacity to a given alpha value + * @param {Number} alpha + */ + function setOpacity(alpha) { + rgba[3] = alpha; + return this; + } + + // initialize: parse the input + init(input); + + // public methods + return { + get: get, + brighten: brighten, + rgba: rgba, + setOpacity: setOpacity + }; +}; + + +/** + * A wrapper object for SVG elements + */ +function SVGElement() {} + +SVGElement.prototype = { + /** + * Initialize the SVG renderer + * @param {Object} renderer + * @param {String} nodeName + */ + init: function (renderer, nodeName) { + var wrapper = this; + wrapper.element = nodeName === 'span' ? + createElement(nodeName) : + doc.createElementNS(SVG_NS, nodeName); + wrapper.renderer = renderer; + }, + /** + * Default base for animation + */ + opacity: 1, + /** + * Animate a given attribute + * @param {Object} params + * @param {Number} options The same options as in jQuery animation + * @param {Function} complete Function to perform at the end of animation + */ + animate: function (params, options, complete) { + var animOptions = pick(options, globalAnimation, true); + stop(this); // stop regardless of animation actually running, or reverting to .attr (#607) + if (animOptions) { + animOptions = merge(animOptions, {}); //#2625 + if (complete) { // allows using a callback with the global animation without overwriting it + animOptions.complete = complete; + } + animate(this, params, animOptions); + } else { + this.attr(params); + if (complete) { + complete(); + } + } + }, + + /** + * Build an SVG gradient out of a common JavaScript configuration object + */ + colorGradient: function (color, prop, elem) { + var renderer = this.renderer, + colorObject, + gradName, + gradAttr, + gradients, + gradientObject, + stops, + stopColor, + stopOpacity, + radialReference, + n, + id, + key = []; + + // Apply linear or radial gradients + if (color.linearGradient) { + gradName = 'linearGradient'; + } else if (color.radialGradient) { + gradName = 'radialGradient'; + } + + if (gradName) { + gradAttr = color[gradName]; + gradients = renderer.gradients; + stops = color.stops; + radialReference = elem.radialReference; + + // Keep < 2.2 kompatibility + if (isArray(gradAttr)) { + color[gradName] = gradAttr = { + x1: gradAttr[0], + y1: gradAttr[1], + x2: gradAttr[2], + y2: gradAttr[3], + gradientUnits: 'userSpaceOnUse' + }; + } + + // Correct the radial gradient for the radial reference system + if (gradName === 'radialGradient' && radialReference && !defined(gradAttr.gradientUnits)) { + gradAttr = merge(gradAttr, { + cx: (radialReference[0] - radialReference[2] / 2) + gradAttr.cx * radialReference[2], + cy: (radialReference[1] - radialReference[2] / 2) + gradAttr.cy * radialReference[2], + r: gradAttr.r * radialReference[2], + gradientUnits: 'userSpaceOnUse' + }); + } + + // Build the unique key to detect whether we need to create a new element (#1282) + for (n in gradAttr) { + if (n !== 'id') { + key.push(n, gradAttr[n]); + } + } + for (n in stops) { + key.push(stops[n]); + } + key = key.join(','); + + // Check if a gradient object with the same config object is created within this renderer + if (gradients[key]) { + id = gradients[key].attr('id'); + + } else { + + // Set the id and create the element + gradAttr.id = id = PREFIX + idCounter++; + gradients[key] = gradientObject = renderer.createElement(gradName) + .attr(gradAttr) + .add(renderer.defs); + + + // The gradient needs to keep a list of stops to be able to destroy them + gradientObject.stops = []; + each(stops, function (stop) { + var stopObject; + if (stop[1].indexOf('rgba') === 0) { + colorObject = Color(stop[1]); + stopColor = colorObject.get('rgb'); + stopOpacity = colorObject.get('a'); + } else { + stopColor = stop[1]; + stopOpacity = 1; + } + stopObject = renderer.createElement('stop').attr({ + offset: stop[0], + 'stop-color': stopColor, + 'stop-opacity': stopOpacity + }).add(gradientObject); + + // Add the stop element to the gradient + gradientObject.stops.push(stopObject); + }); + } + + // Set the reference to the gradient object + elem.setAttribute(prop, 'url(' + renderer.url + '#' + id + ')'); + } + }, + + /** + * Set or get a given attribute + * @param {Object|String} hash + * @param {Mixed|Undefined} val + */ + attr: function (hash, val) { + var key, + value, + element = this.element, + hasSetSymbolSize, + ret = this, + skipAttr; + + // single key-value pair + if (typeof hash === 'string' && val !== UNDEFINED) { + key = hash; + hash = {}; + hash[key] = val; + } + + // used as a getter: first argument is a string, second is undefined + if (typeof hash === 'string') { + ret = (this[hash + 'Getter'] || this._defaultGetter).call(this, hash, element); + + // setter + } else { + + for (key in hash) { + value = hash[key]; + skipAttr = false; + + + + if (this.symbolName && /^(x|y|width|height|r|start|end|innerR|anchorX|anchorY)/.test(key)) { + if (!hasSetSymbolSize) { + this.symbolAttr(hash); + hasSetSymbolSize = true; + } + skipAttr = true; + } + + if (this.rotation && (key === 'x' || key === 'y')) { + this.doTransform = true; + } + + if (!skipAttr) { + (this[key + 'Setter'] || this._defaultSetter).call(this, value, key, element); + } + + // Let the shadow follow the main element + if (this.shadows && /^(width|height|visibility|x|y|d|transform|cx|cy|r)$/.test(key)) { + this.updateShadows(key, value); + } + } + + // Update transform. Do this outside the loop to prevent redundant updating for batch setting + // of attributes. + if (this.doTransform) { + this.updateTransform(); + this.doTransform = false; + } + + } + + return ret; + }, + + updateShadows: function (key, value) { + var shadows = this.shadows, + i = shadows.length; + while (i--) { + shadows[i].setAttribute( + key, + key === 'height' ? + mathMax(value - (shadows[i].cutHeight || 0), 0) : + key === 'd' ? this.d : value + ); + } + }, + + /** + * Add a class name to an element + */ + addClass: function (className) { + var element = this.element, + currentClassName = attr(element, 'class') || ''; + + if (currentClassName.indexOf(className) === -1) { + attr(element, 'class', currentClassName + ' ' + className); + } + return this; + }, + /* hasClass and removeClass are not (yet) needed + hasClass: function (className) { + return attr(this.element, 'class').indexOf(className) !== -1; + }, + removeClass: function (className) { + attr(this.element, 'class', attr(this.element, 'class').replace(className, '')); + return this; + }, + */ + + /** + * If one of the symbol size affecting parameters are changed, + * check all the others only once for each call to an element's + * .attr() method + * @param {Object} hash + */ + symbolAttr: function (hash) { + var wrapper = this; + + each(['x', 'y', 'r', 'start', 'end', 'width', 'height', 'innerR', 'anchorX', 'anchorY'], function (key) { + wrapper[key] = pick(hash[key], wrapper[key]); + }); + + wrapper.attr({ + d: wrapper.renderer.symbols[wrapper.symbolName]( + wrapper.x, + wrapper.y, + wrapper.width, + wrapper.height, + wrapper + ) + }); + }, + + /** + * Apply a clipping path to this object + * @param {String} id + */ + clip: function (clipRect) { + return this.attr('clip-path', clipRect ? 'url(' + this.renderer.url + '#' + clipRect.id + ')' : NONE); + }, + + /** + * Calculate the coordinates needed for drawing a rectangle crisply and return the + * calculated attributes + * @param {Number} strokeWidth + * @param {Number} x + * @param {Number} y + * @param {Number} width + * @param {Number} height + */ + crisp: function (rect) { + + var wrapper = this, + key, + attribs = {}, + normalizer, + strokeWidth = rect.strokeWidth || wrapper.strokeWidth || (wrapper.attr && wrapper.attr('stroke-width')) || 0; + + normalizer = mathRound(strokeWidth) % 2 / 2; // mathRound because strokeWidth can sometimes have roundoff errors + + // normalize for crisp edges + rect.x = mathFloor(rect.x || wrapper.x || 0) + normalizer; + rect.y = mathFloor(rect.y || wrapper.y || 0) + normalizer; + rect.width = mathFloor((rect.width || wrapper.width || 0) - 2 * normalizer); + rect.height = mathFloor((rect.height || wrapper.height || 0) - 2 * normalizer); + rect.strokeWidth = strokeWidth; + + for (key in rect) { + if (wrapper[key] !== rect[key]) { // only set attribute if changed + wrapper[key] = attribs[key] = rect[key]; + } + } + + return attribs; + }, + + /** + * Set styles for the element + * @param {Object} styles + */ + css: function (styles) { + var elemWrapper = this, + oldStyles = elemWrapper.styles, + newStyles = {}, + elem = elemWrapper.element, + textWidth, + n, + serializedCss = '', + hyphenate, + hasNew = !oldStyles; + + // convert legacy + if (styles && styles.color) { + styles.fill = styles.color; + } + + // Filter out existing styles to increase performance (#2640) + if (oldStyles) { + for (n in styles) { + if (styles[n] !== oldStyles[n]) { + newStyles[n] = styles[n]; + hasNew = true; + } + } + } + if (hasNew) { + textWidth = elemWrapper.textWidth = styles && styles.width && elem.nodeName.toLowerCase() === 'text' && pInt(styles.width); + + // Merge the new styles with the old ones + if (oldStyles) { + styles = extend( + oldStyles, + newStyles + ); + } + + // store object + elemWrapper.styles = styles; + + if (textWidth && (useCanVG || (!hasSVG && elemWrapper.renderer.forExport))) { + delete styles.width; + } + + // serialize and set style attribute + if (isIE && !hasSVG) { + css(elemWrapper.element, styles); + } else { + /*jslint unparam: true*/ + hyphenate = function (a, b) { return '-' + b.toLowerCase(); }; + /*jslint unparam: false*/ + for (n in styles) { + serializedCss += n.replace(/([A-Z])/g, hyphenate) + ':' + styles[n] + ';'; + } + attr(elem, 'style', serializedCss); // #1881 + } + + + // re-build text + if (textWidth && elemWrapper.added) { + elemWrapper.renderer.buildText(elemWrapper); + } + } + + return elemWrapper; + }, + + /** + * Add an event listener + * @param {String} eventType + * @param {Function} handler + */ + on: function (eventType, handler) { + var svgElement = this, + element = svgElement.element; + + // touch + if (hasTouch && eventType === 'click') { + element.ontouchstart = function (e) { + svgElement.touchEventFired = Date.now(); + e.preventDefault(); + handler.call(element, e); + }; + element.onclick = function (e) { + if (userAgent.indexOf('Android') === -1 || Date.now() - (svgElement.touchEventFired || 0) > 1100) { // #2269 + handler.call(element, e); + } + }; + } else { + // simplest possible event model for internal use + element['on' + eventType] = handler; + } + return this; + }, + + /** + * Set the coordinates needed to draw a consistent radial gradient across + * pie slices regardless of positioning inside the chart. The format is + * [centerX, centerY, diameter] in pixels. + */ + setRadialReference: function (coordinates) { + this.element.radialReference = coordinates; + return this; + }, + + /** + * Move an object and its children by x and y values + * @param {Number} x + * @param {Number} y + */ + translate: function (x, y) { + return this.attr({ + translateX: x, + translateY: y + }); + }, + + /** + * Invert a group, rotate and flip + */ + invert: function () { + var wrapper = this; + wrapper.inverted = true; + wrapper.updateTransform(); + return wrapper; + }, + + /** + * Private method to update the transform attribute based on internal + * properties + */ + updateTransform: function () { + var wrapper = this, + translateX = wrapper.translateX || 0, + translateY = wrapper.translateY || 0, + scaleX = wrapper.scaleX, + scaleY = wrapper.scaleY, + inverted = wrapper.inverted, + rotation = wrapper.rotation, + element = wrapper.element, + transform; + + // flipping affects translate as adjustment for flipping around the group's axis + if (inverted) { + translateX += wrapper.attr('width'); + translateY += wrapper.attr('height'); + } + + // Apply translate. Nearly all transformed elements have translation, so instead + // of checking for translate = 0, do it always (#1767, #1846). + transform = ['translate(' + translateX + ',' + translateY + ')']; + + // apply rotation + if (inverted) { + transform.push('rotate(90) scale(-1,1)'); + } else if (rotation) { // text rotation + transform.push('rotate(' + rotation + ' ' + (element.getAttribute('x') || 0) + ' ' + (element.getAttribute('y') || 0) + ')'); + } + + // apply scale + if (defined(scaleX) || defined(scaleY)) { + transform.push('scale(' + pick(scaleX, 1) + ' ' + pick(scaleY, 1) + ')'); + } + + if (transform.length) { + element.setAttribute('transform', transform.join(' ')); + } + }, + /** + * Bring the element to the front + */ + toFront: function () { + var element = this.element; + element.parentNode.appendChild(element); + return this; + }, + + + /** + * Break down alignment options like align, verticalAlign, x and y + * to x and y relative to the chart. + * + * @param {Object} alignOptions + * @param {Boolean} alignByTranslate + * @param {String[Object} box The box to align to, needs a width and height. When the + * box is a string, it refers to an object in the Renderer. For example, when + * box is 'spacingBox', it refers to Renderer.spacingBox which holds width, height + * x and y properties. + * + */ + align: function (alignOptions, alignByTranslate, box) { + var align, + vAlign, + x, + y, + attribs = {}, + alignTo, + renderer = this.renderer, + alignedObjects = renderer.alignedObjects; + + // First call on instanciate + if (alignOptions) { + this.alignOptions = alignOptions; + this.alignByTranslate = alignByTranslate; + if (!box || isString(box)) { // boxes other than renderer handle this internally + this.alignTo = alignTo = box || 'renderer'; + erase(alignedObjects, this); // prevent duplicates, like legendGroup after resize + alignedObjects.push(this); + box = null; // reassign it below + } + + // When called on resize, no arguments are supplied + } else { + alignOptions = this.alignOptions; + alignByTranslate = this.alignByTranslate; + alignTo = this.alignTo; + } + + box = pick(box, renderer[alignTo], renderer); + + // Assign variables + align = alignOptions.align; + vAlign = alignOptions.verticalAlign; + x = (box.x || 0) + (alignOptions.x || 0); // default: left align + y = (box.y || 0) + (alignOptions.y || 0); // default: top align + + // Align + if (align === 'right' || align === 'center') { + x += (box.width - (alignOptions.width || 0)) / + { right: 1, center: 2 }[align]; + } + attribs[alignByTranslate ? 'translateX' : 'x'] = mathRound(x); + + + // Vertical align + if (vAlign === 'bottom' || vAlign === 'middle') { + y += (box.height - (alignOptions.height || 0)) / + ({ bottom: 1, middle: 2 }[vAlign] || 1); + + } + attribs[alignByTranslate ? 'translateY' : 'y'] = mathRound(y); + + // Animate only if already placed + this[this.placed ? 'animate' : 'attr'](attribs); + this.placed = true; + this.alignAttr = attribs; + + return this; + }, + + /** + * Get the bounding box (width, height, x and y) for the element + */ + getBBox: function () { + var wrapper = this, + bBox = wrapper.bBox, + renderer = wrapper.renderer, + width, + height, + rotation = wrapper.rotation, + element = wrapper.element, + styles = wrapper.styles, + rad = rotation * deg2rad, + textStr = wrapper.textStr, + cacheKey; + + // Since numbers are monospaced, and numerical labels appear a lot in a chart, + // we assume that a label of n characters has the same bounding box as others + // of the same length. + if (textStr === '' || numRegex.test(textStr)) { + cacheKey = 'num.' + textStr.toString().length + (styles ? ('|' + styles.fontSize + '|' + styles.fontFamily) : ''); + + } //else { // This code block made demo/waterfall fail, related to buildText + // Caching all strings reduces rendering time by 4-5%. + // TODO: Check how this affects places where bBox is found on the element + //cacheKey = textStr + (styles ? ('|' + styles.fontSize + '|' + styles.fontFamily) : ''); + //} + if (cacheKey) { + bBox = renderer.cache[cacheKey]; + } + + // No cache found + if (!bBox) { + + // SVG elements + if (element.namespaceURI === SVG_NS || renderer.forExport) { + try { // Fails in Firefox if the container has display: none. + + bBox = element.getBBox ? + // SVG: use extend because IE9 is not allowed to change width and height in case + // of rotation (below) + extend({}, element.getBBox()) : + // Canvas renderer and legacy IE in export mode + { + width: element.offsetWidth, + height: element.offsetHeight + }; + } catch (e) {} + + // If the bBox is not set, the try-catch block above failed. The other condition + // is for Opera that returns a width of -Infinity on hidden elements. + if (!bBox || bBox.width < 0) { + bBox = { width: 0, height: 0 }; + } + + + // VML Renderer or useHTML within SVG + } else { + + bBox = wrapper.htmlGetBBox(); + + } + + // True SVG elements as well as HTML elements in modern browsers using the .useHTML option + // need to compensated for rotation + if (renderer.isSVG) { + width = bBox.width; + height = bBox.height; + + // Workaround for wrong bounding box in IE9 and IE10 (#1101, #1505, #1669, #2568) + if (isIE && styles && styles.fontSize === '11px' && height.toPrecision(3) === '16.9') { + bBox.height = height = 14; + } + + // Adjust for rotated text + if (rotation) { + bBox.width = mathAbs(height * mathSin(rad)) + mathAbs(width * mathCos(rad)); + bBox.height = mathAbs(height * mathCos(rad)) + mathAbs(width * mathSin(rad)); + } + } + + // Cache it + wrapper.bBox = bBox; + if (cacheKey) { + renderer.cache[cacheKey] = bBox; + } + } + return bBox; + }, + + /** + * Show the element + */ + show: function (inherit) { + // IE9-11 doesn't handle visibilty:inherit well, so we remove the attribute instead (#2881) + if (inherit && this.element.namespaceURI === SVG_NS) { + this.element.removeAttribute('visibility'); + return this; + } else { + return this.attr({ visibility: inherit ? 'inherit' : VISIBLE }); + } + }, + + /** + * Hide the element + */ + hide: function () { + return this.attr({ visibility: HIDDEN }); + }, + + fadeOut: function (duration) { + var elemWrapper = this; + elemWrapper.animate({ + opacity: 0 + }, { + duration: duration || 150, + complete: function () { + elemWrapper.hide(); + } + }); + }, + + /** + * Add the element + * @param {Object|Undefined} parent Can be an element, an element wrapper or undefined + * to append the element to the renderer.box. + */ + add: function (parent) { + + var renderer = this.renderer, + parentWrapper = parent || renderer, + parentNode = parentWrapper.element || renderer.box, + childNodes, + element = this.element, + zIndex = this.zIndex, + otherElement, + otherZIndex, + i, + inserted; + + if (parent) { + this.parentGroup = parent; + } + + // mark as inverted + this.parentInverted = parent && parent.inverted; + + // build formatted text + if (this.textStr !== undefined) { + renderer.buildText(this); + } + + // mark the container as having z indexed children + if (zIndex) { + parentWrapper.handleZ = true; + zIndex = pInt(zIndex); + } + + // insert according to this and other elements' zIndex + if (parentWrapper.handleZ) { // this element or any of its siblings has a z index + childNodes = parentNode.childNodes; + for (i = 0; i < childNodes.length; i++) { + otherElement = childNodes[i]; + otherZIndex = attr(otherElement, 'zIndex'); + if (otherElement !== element && ( + // insert before the first element with a higher zIndex + pInt(otherZIndex) > zIndex || + // if no zIndex given, insert before the first element with a zIndex + (!defined(zIndex) && defined(otherZIndex)) + + )) { + parentNode.insertBefore(element, otherElement); + inserted = true; + break; + } + } + } + + // default: append at the end + if (!inserted) { + parentNode.appendChild(element); + } + + // mark as added + this.added = true; + + // fire an event for internal hooks + if (this.onAdd) { + this.onAdd(); + } + + return this; + }, + + /** + * Removes a child either by removeChild or move to garbageBin. + * Issue 490; in VML removeChild results in Orphaned nodes according to sIEve, discardElement does not. + */ + safeRemoveChild: function (element) { + var parentNode = element.parentNode; + if (parentNode) { + parentNode.removeChild(element); + } + }, + + /** + * Destroy the element and element wrapper + */ + destroy: function () { + var wrapper = this, + element = wrapper.element || {}, + shadows = wrapper.shadows, + parentToClean = wrapper.renderer.isSVG && element.nodeName === 'SPAN' && wrapper.parentGroup, + grandParent, + key, + i; + + // remove events + element.onclick = element.onmouseout = element.onmouseover = element.onmousemove = element.point = null; + stop(wrapper); // stop running animations + + if (wrapper.clipPath) { + wrapper.clipPath = wrapper.clipPath.destroy(); + } + + // Destroy stops in case this is a gradient object + if (wrapper.stops) { + for (i = 0; i < wrapper.stops.length; i++) { + wrapper.stops[i] = wrapper.stops[i].destroy(); + } + wrapper.stops = null; + } + + // remove element + wrapper.safeRemoveChild(element); + + // destroy shadows + if (shadows) { + each(shadows, function (shadow) { + wrapper.safeRemoveChild(shadow); + }); + } + + // In case of useHTML, clean up empty containers emulating SVG groups (#1960, #2393). + while (parentToClean && parentToClean.div.childNodes.length === 0) { + grandParent = parentToClean.parentGroup; + wrapper.safeRemoveChild(parentToClean.div); + delete parentToClean.div; + parentToClean = grandParent; + } + + // remove from alignObjects + if (wrapper.alignTo) { + erase(wrapper.renderer.alignedObjects, wrapper); + } + + for (key in wrapper) { + delete wrapper[key]; + } + + return null; + }, + + /** + * Add a shadow to the element. Must be done after the element is added to the DOM + * @param {Boolean|Object} shadowOptions + */ + shadow: function (shadowOptions, group, cutOff) { + var shadows = [], + i, + shadow, + element = this.element, + strokeWidth, + shadowWidth, + shadowElementOpacity, + + // compensate for inverted plot area + transform; + + + if (shadowOptions) { + shadowWidth = pick(shadowOptions.width, 3); + shadowElementOpacity = (shadowOptions.opacity || 0.15) / shadowWidth; + transform = this.parentInverted ? + '(-1,-1)' : + '(' + pick(shadowOptions.offsetX, 1) + ', ' + pick(shadowOptions.offsetY, 1) + ')'; + for (i = 1; i <= shadowWidth; i++) { + shadow = element.cloneNode(0); + strokeWidth = (shadowWidth * 2) + 1 - (2 * i); + attr(shadow, { + 'isShadow': 'true', + 'stroke': shadowOptions.color || 'black', + 'stroke-opacity': shadowElementOpacity * i, + 'stroke-width': strokeWidth, + 'transform': 'translate' + transform, + 'fill': NONE + }); + if (cutOff) { + attr(shadow, 'height', mathMax(attr(shadow, 'height') - strokeWidth, 0)); + shadow.cutHeight = strokeWidth; + } + + if (group) { + group.element.appendChild(shadow); + } else { + element.parentNode.insertBefore(shadow, element); + } + + shadows.push(shadow); + } + + this.shadows = shadows; + } + return this; + + }, + + xGetter: function (key) { + if (this.element.nodeName === 'circle') { + key = { x: 'cx', y: 'cy' }[key] || key; + } + return this._defaultGetter(key); + }, + + /** + * Get the current value of an attribute or pseudo attribute, used mainly + * for animation. + */ + _defaultGetter: function (key) { + var ret = pick(this[key], this.element ? this.element.getAttribute(key) : null, 0); + + if (/^[0-9\.]+$/.test(ret)) { // is numerical + ret = parseFloat(ret); + } + return ret; + }, + + + dSetter: function (value, key, element) { + if (value && value.join) { // join path + value = value.join(' '); + } + if (/(NaN| {2}|^$)/.test(value)) { + value = 'M 0 0'; + } + element.setAttribute(key, value); + + this[key] = value; + }, + dashstyleSetter: function (value) { + var i; + value = value && value.toLowerCase(); + if (value) { + value = value + .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(','); // ending comma + + i = value.length; + while (i--) { + value[i] = pInt(value[i]) * this.element.getAttribute('stroke-width'); + } + value = value.join(','); + this.element.setAttribute('stroke-dasharray', value); + } + }, + alignSetter: function (value) { + this.element.setAttribute('text-anchor', { left: 'start', center: 'middle', right: 'end' }[value]); + }, + opacitySetter: function (value, key, element) { + this[key] = value; + element.setAttribute(key, value); + }, + // In Chrome/Win < 6 as well as Batik and PhantomJS as of 1.9.7, the stroke attribute can't be set when the stroke- + // width is 0. #1369 + 'stroke-widthSetter': function (value, key, element) { + if (value === 0) { + value = 0.00001; + } + this.strokeWidth = value; // read in symbol paths like 'callout' + element.setAttribute(key, value); + }, + titleSetter: function (value) { + var titleNode = this.element.getElementsByTagName('title')[0]; + if (!titleNode) { + titleNode = doc.createElementNS(SVG_NS, 'title'); + this.element.appendChild(titleNode); + } + titleNode.textContent = value; + }, + textSetter: function (value) { + if (value !== this.textStr) { + // Delete bBox memo when the text changes + delete this.bBox; + + this.textStr = value; + if (this.added) { + this.renderer.buildText(this); + } + } + }, + fillSetter: function (value, key, element) { + + if (typeof value === 'string') { + element.setAttribute(key, value); + } else if (value) { + this.colorGradient(value, key, element); + } + }, + zIndexSetter: function (value, key, element) { + element.setAttribute(key, value); + this[key] = value; + }, + _defaultSetter: function (value, key, element) { + element.setAttribute(key, value); + } +}; + +// Some shared setters and getters +SVGElement.prototype.yGetter = SVGElement.prototype.xGetter; +SVGElement.prototype.translateXSetter = SVGElement.prototype.translateYSetter = + SVGElement.prototype.rotationSetter = SVGElement.prototype.verticalAlignSetter = + SVGElement.prototype.scaleXSetter = SVGElement.prototype.scaleYSetter = function (value, key) { + this[key] = value; + this.doTransform = true; +}; +SVGElement.prototype.strokeSetter = SVGElement.prototype.fillSetter; + + + +// In Chrome/Win < 6 as well as Batik, the stroke attribute can't be set when the stroke- +// width is 0. #1369 +/*SVGElement.prototype['stroke-widthSetter'] = SVGElement.prototype.strokeSetter = function (value, key) { + this[key] = value; + // Only apply the stroke attribute if the stroke width is defined and larger than 0 + if (this.stroke && this['stroke-width']) { + this.element.setAttribute('stroke', this.stroke); + this.element.setAttribute('stroke-width', this['stroke-width']); + this.hasStroke = true; + } else if (key === 'stroke-width' && value === 0 && this.hasStroke) { + this.element.removeAttribute('stroke'); + this.hasStroke = false; + } +};*/ + + +/** + * The default SVG renderer + */ +var SVGRenderer = function () { + this.init.apply(this, arguments); +}; +SVGRenderer.prototype = { + Element: SVGElement, + + /** + * Initialize the SVGRenderer + * @param {Object} container + * @param {Number} width + * @param {Number} height + * @param {Boolean} forExport + */ + init: function (container, width, height, style, forExport) { + var renderer = this, + loc = location, + boxWrapper, + element, + desc; + + boxWrapper = renderer.createElement('svg') + .attr({ + version: '1.1' + }) + .css(this.getStyle(style)); + element = boxWrapper.element; + container.appendChild(element); + + // For browsers other than IE, add the namespace attribute (#1978) + if (container.innerHTML.indexOf('xmlns') === -1) { + attr(element, 'xmlns', SVG_NS); + } + + // object properties + renderer.isSVG = true; + renderer.box = element; + renderer.boxWrapper = boxWrapper; + renderer.alignedObjects = []; + + // Page url used for internal references. #24, #672, #1070 + renderer.url = (isFirefox || isWebKit) && doc.getElementsByTagName('base').length ? + loc.href + .replace(/#.*?$/, '') // remove the hash + .replace(/([\('\)])/g, '\\$1') // escape parantheses and quotes + .replace(/ /g, '%20') : // replace spaces (needed for Safari only) + ''; + + // Add description + desc = this.createElement('desc').add(); + desc.element.appendChild(doc.createTextNode('Created with ' + PRODUCT + ' ' + VERSION)); + + + renderer.defs = this.createElement('defs').add(); + renderer.forExport = forExport; + renderer.gradients = {}; // Object where gradient SvgElements are stored + renderer.cache = {}; // Cache for numerical bounding boxes + + renderer.setSize(width, height, false); + + + + // Issue 110 workaround: + // In Firefox, if a div is positioned by percentage, its pixel position may land + // between pixels. The container itself doesn't display this, but an SVG element + // inside this container will be drawn at subpixel precision. In order to draw + // sharp lines, this must be compensated for. This doesn't seem to work inside + // iframes though (like in jsFiddle). + var subPixelFix, rect; + if (isFirefox && container.getBoundingClientRect) { + renderer.subPixelFix = subPixelFix = function () { + css(container, { left: 0, top: 0 }); + rect = container.getBoundingClientRect(); + css(container, { + left: (mathCeil(rect.left) - rect.left) + PX, + top: (mathCeil(rect.top) - rect.top) + PX + }); + }; + + // run the fix now + subPixelFix(); + + // run it on resize + addEvent(win, 'resize', subPixelFix); + } + }, + + getStyle: function (style) { + return (this.style = extend({ + fontFamily: '"Lucida Grande", "Lucida Sans Unicode", Arial, Helvetica, sans-serif', // default font + fontSize: '12px' + }, style)); + }, + + /** + * Detect whether the renderer is hidden. This happens when one of the parent elements + * has display: none. #608. + */ + isHidden: function () { + return !this.boxWrapper.getBBox().width; + }, + + /** + * Destroys the renderer and its allocated members. + */ + destroy: function () { + var renderer = this, + rendererDefs = renderer.defs; + renderer.box = null; + renderer.boxWrapper = renderer.boxWrapper.destroy(); + + // Call destroy on all gradient elements + destroyObjectProperties(renderer.gradients || {}); + renderer.gradients = null; + + // Defs are null in VMLRenderer + // Otherwise, destroy them here. + if (rendererDefs) { + renderer.defs = rendererDefs.destroy(); + } + + // Remove sub pixel fix handler + // We need to check that there is a handler, otherwise all functions that are registered for event 'resize' are removed + // See issue #982 + if (renderer.subPixelFix) { + removeEvent(win, 'resize', renderer.subPixelFix); + } + + renderer.alignedObjects = null; + + return null; + }, + + /** + * Create a wrapper for an SVG element + * @param {Object} nodeName + */ + createElement: function (nodeName) { + var wrapper = new this.Element(); + wrapper.init(this, nodeName); + return wrapper; + }, + + /** + * Dummy function for use in canvas renderer + */ + draw: function () {}, + + /** + * Parse a simple HTML string into SVG tspans + * + * @param {Object} textNode The parent text SVG node + */ + buildText: function (wrapper) { + var textNode = wrapper.element, + renderer = this, + forExport = renderer.forExport, + textStr = pick(wrapper.textStr, '').toString(), + hasMarkup = textStr.indexOf('<') !== -1, + lines, + childNodes = textNode.childNodes, + styleRegex, + hrefRegex, + parentX = attr(textNode, 'x'), + textStyles = wrapper.styles, + width = wrapper.textWidth, + textLineHeight = textStyles && textStyles.lineHeight, + i = childNodes.length, + getLineHeight = function (tspan) { + return textLineHeight ? + pInt(textLineHeight) : + renderer.fontMetrics( + /(px|em)$/.test(tspan && tspan.style.fontSize) ? + tspan.style.fontSize : + ((textStyles && textStyles.fontSize) || renderer.style.fontSize || 12) + ).h; + }; + + /// remove old text + while (i--) { + textNode.removeChild(childNodes[i]); + } + + // Skip tspans, add text directly to text node + if (!hasMarkup && textStr.indexOf(' ') === -1) { + textNode.appendChild(doc.createTextNode(textStr)); + return; + + // Complex strings, add more logic + } else { + + styleRegex = /<.*style="([^"]+)".*>/; + hrefRegex = /<.*href="(http[^"]+)".*>/; + + if (width && !wrapper.added) { + this.box.appendChild(textNode); // attach it to the DOM to read offset width + } + + if (hasMarkup) { + lines = textStr + .replace(/<(b|strong)>/g, '') + .replace(/<(i|em)>/g, '') + .replace(//g, '') + .split(//g); + + } else { + lines = [textStr]; + } + + + // remove empty line at end + if (lines[lines.length - 1] === '') { + lines.pop(); + } + + + // build the lines + each(lines, function (line, lineNo) { + var spans, spanNo = 0; + + line = line.replace(//g, '|||'); + spans = line.split('|||'); + + each(spans, function (span) { + if (span !== '' || spans.length === 1) { + var attributes = {}, + tspan = doc.createElementNS(SVG_NS, 'tspan'), + spanStyle; // #390 + if (styleRegex.test(span)) { + spanStyle = span.match(styleRegex)[1].replace(/(;| |^)color([ :])/, '$1fill$2'); + attr(tspan, 'style', spanStyle); + } + if (hrefRegex.test(span) && !forExport) { // Not for export - #1529 + attr(tspan, 'onclick', 'location.href=\"' + span.match(hrefRegex)[1] + '\"'); + css(tspan, { cursor: 'pointer' }); + } + + span = (span.replace(/<(.|\n)*?>/g, '') || ' ') + .replace(/</g, '<') + .replace(/>/g, '>'); + + // Nested tags aren't supported, and cause crash in Safari (#1596) + if (span !== ' ') { + + // add the text node + tspan.appendChild(doc.createTextNode(span)); + + if (!spanNo) { // first span in a line, align it to the left + if (lineNo && parentX !== null) { + attributes.x = parentX; + } + } else { + attributes.dx = 0; // #16 + } + + // add attributes + attr(tspan, attributes); + + // first span on subsequent line, add the line height + if (!spanNo && lineNo) { + + // allow getting the right offset height in exporting in IE + if (!hasSVG && forExport) { + css(tspan, { display: 'block' }); + } + + // Set the line height based on the font size of either + // the text element or the tspan element + attr( + tspan, + 'dy', + getLineHeight(tspan), + // Safari 6.0.2 - too optimized for its own good (#1539) + // TODO: revisit this with future versions of Safari + isWebKit && tspan.offsetHeight + ); + } + + // Append it + textNode.appendChild(tspan); + + spanNo++; + + // check width and apply soft breaks + if (width) { + var words = span.replace(/([^\^])-/g, '$1- ').split(' '), // #1273 + hasWhiteSpace = words.length > 1 && textStyles.whiteSpace !== 'nowrap', + tooLong, + actualWidth, + clipHeight = wrapper._clipHeight, + rest = [], + dy = getLineHeight(), + softLineNo = 1, + bBox; + + while (hasWhiteSpace && (words.length || rest.length)) { + delete wrapper.bBox; // delete cache + bBox = wrapper.getBBox(); + actualWidth = bBox.width; + + // Old IE cannot measure the actualWidth for SVG elements (#2314) + if (!hasSVG && renderer.forExport) { + actualWidth = renderer.measureSpanWidth(tspan.firstChild.data, wrapper.styles); + } + + tooLong = actualWidth > width; + if (!tooLong || words.length === 1) { // new line needed + words = rest; + rest = []; + if (words.length) { + softLineNo++; + + if (clipHeight && softLineNo * dy > clipHeight) { + words = ['...']; + wrapper.attr('title', wrapper.textStr); + } else { + + tspan = doc.createElementNS(SVG_NS, 'tspan'); + attr(tspan, { + dy: dy, + x: parentX + }); + if (spanStyle) { // #390 + attr(tspan, 'style', spanStyle); + } + textNode.appendChild(tspan); + + if (actualWidth > width) { // a single word is pressing it out + width = actualWidth; + } + } + } + } else { // append to existing line tspan + tspan.removeChild(tspan.firstChild); + rest.unshift(words.pop()); + } + if (words.length) { + tspan.appendChild(doc.createTextNode(words.join(' ').replace(/- /g, '-'))); + } + } + } + } + } + }); + }); + } + }, + + /** + * Create a button with preset states + * @param {String} text + * @param {Number} x + * @param {Number} y + * @param {Function} callback + * @param {Object} normalState + * @param {Object} hoverState + * @param {Object} pressedState + */ + button: function (text, x, y, callback, normalState, hoverState, pressedState, disabledState, shape) { + var label = this.label(text, x, y, shape, null, null, null, null, 'button'), + curState = 0, + stateOptions, + stateStyle, + normalStyle, + hoverStyle, + pressedStyle, + disabledStyle, + verticalGradient = { x1: 0, y1: 0, x2: 0, y2: 1 }; + + // Normal state - prepare the attributes + normalState = merge({ + 'stroke-width': 1, + stroke: '#CCCCCC', + fill: { + linearGradient: verticalGradient, + stops: [ + [0, '#FEFEFE'], + [1, '#F6F6F6'] + ] + }, + r: 2, + padding: 5, + style: { + color: 'black' + } + }, normalState); + normalStyle = normalState.style; + delete normalState.style; + + // Hover state + hoverState = merge(normalState, { + stroke: '#68A', + fill: { + linearGradient: verticalGradient, + stops: [ + [0, '#FFF'], + [1, '#ACF'] + ] + } + }, hoverState); + hoverStyle = hoverState.style; + delete hoverState.style; + + // Pressed state + pressedState = merge(normalState, { + stroke: '#68A', + fill: { + linearGradient: verticalGradient, + stops: [ + [0, '#9BD'], + [1, '#CDF'] + ] + } + }, pressedState); + pressedStyle = pressedState.style; + delete pressedState.style; + + // Disabled state + disabledState = merge(normalState, { + style: { + color: '#CCC' + } + }, disabledState); + disabledStyle = disabledState.style; + delete disabledState.style; + + // Add the events. IE9 and IE10 need mouseover and mouseout to funciton (#667). + addEvent(label.element, isIE ? 'mouseover' : 'mouseenter', function () { + if (curState !== 3) { + label.attr(hoverState) + .css(hoverStyle); + } + }); + addEvent(label.element, isIE ? 'mouseout' : 'mouseleave', function () { + if (curState !== 3) { + stateOptions = [normalState, hoverState, pressedState][curState]; + stateStyle = [normalStyle, hoverStyle, pressedStyle][curState]; + label.attr(stateOptions) + .css(stateStyle); + } + }); + + label.setState = function (state) { + label.state = curState = state; + if (!state) { + label.attr(normalState) + .css(normalStyle); + } else if (state === 2) { + label.attr(pressedState) + .css(pressedStyle); + } else if (state === 3) { + label.attr(disabledState) + .css(disabledStyle); + } + }; + + return label + .on('click', function () { + if (curState !== 3) { + callback.call(label); + } + }) + .attr(normalState) + .css(extend({ cursor: 'default' }, normalStyle)); + }, + + /** + * Make a straight line crisper by not spilling out to neighbour pixels + * @param {Array} points + * @param {Number} width + */ + crispLine: function (points, width) { + // points format: [M, 0, 0, L, 100, 0] + // normalize to a crisp line + if (points[1] === points[4]) { + // Substract due to #1129. Now bottom and left axis gridlines behave the same. + points[1] = points[4] = mathRound(points[1]) - (width % 2 / 2); + } + if (points[2] === points[5]) { + points[2] = points[5] = mathRound(points[2]) + (width % 2 / 2); + } + return points; + }, + + + /** + * Draw a path + * @param {Array} path An SVG path in array form + */ + path: function (path) { + var attr = { + fill: NONE + }; + if (isArray(path)) { + attr.d = path; + } else if (isObject(path)) { // attributes + extend(attr, path); + } + return this.createElement('path').attr(attr); + }, + + /** + * Draw and return an SVG circle + * @param {Number} x The x position + * @param {Number} y The y position + * @param {Number} r The radius + */ + circle: function (x, y, r) { + var attr = isObject(x) ? + x : + { + x: x, + y: y, + r: r + }, + wrapper = this.createElement('circle'); + + wrapper.xSetter = function (value) { + this.element.setAttribute('cx', value); + }; + wrapper.ySetter = function (value) { + this.element.setAttribute('cy', value); + }; + return wrapper.attr(attr); + }, + + /** + * Draw and return an arc + * @param {Number} x X position + * @param {Number} y Y position + * @param {Number} r Radius + * @param {Number} innerR Inner radius like used in donut charts + * @param {Number} start Starting angle + * @param {Number} end Ending angle + */ + arc: function (x, y, r, innerR, start, end) { + var arc; + + if (isObject(x)) { + y = x.y; + r = x.r; + innerR = x.innerR; + start = x.start; + end = x.end; + x = x.x; + } + + // Arcs are defined as symbols for the ability to set + // attributes in attr and animate + arc = this.symbol('arc', x || 0, y || 0, r || 0, r || 0, { + innerR: innerR || 0, + start: start || 0, + end: end || 0 + }); + arc.r = r; // #959 + return arc; + }, + + /** + * Draw and return a rectangle + * @param {Number} x Left position + * @param {Number} y Top position + * @param {Number} width + * @param {Number} height + * @param {Number} r Border corner radius + * @param {Number} strokeWidth A stroke width can be supplied to allow crisp drawing + */ + rect: function (x, y, width, height, r, strokeWidth) { + + r = isObject(x) ? x.r : r; + + var wrapper = this.createElement('rect'), + attribs = isObject(x) ? x : x === UNDEFINED ? {} : { + x: x, + y: y, + width: mathMax(width, 0), + height: mathMax(height, 0) + }; + + if (strokeWidth !== UNDEFINED) { + attribs.strokeWidth = strokeWidth; + attribs = wrapper.crisp(attribs); + } + + if (r) { + attribs.r = r; + } + + wrapper.rSetter = function (value) { + attr(this.element, { + rx: value, + ry: value + }); + }; + + return wrapper.attr(attribs); + }, + + /** + * Resize the box and re-align all aligned elements + * @param {Object} width + * @param {Object} height + * @param {Boolean} animate + * + */ + setSize: function (width, height, animate) { + var renderer = this, + alignedObjects = renderer.alignedObjects, + i = alignedObjects.length; + + renderer.width = width; + renderer.height = height; + + renderer.boxWrapper[pick(animate, true) ? 'animate' : 'attr']({ + width: width, + height: height + }); + + while (i--) { + alignedObjects[i].align(); + } + }, + + /** + * Create a group + * @param {String} name The group will be given a class name of 'highcharts-{name}'. + * This can be used for styling and scripting. + */ + g: function (name) { + var elem = this.createElement('g'); + return defined(name) ? elem.attr({ 'class': PREFIX + name }) : elem; + }, + + /** + * Display an image + * @param {String} src + * @param {Number} x + * @param {Number} y + * @param {Number} width + * @param {Number} height + */ + image: function (src, x, y, width, height) { + var attribs = { + preserveAspectRatio: NONE + }, + elemWrapper; + + // optional properties + if (arguments.length > 1) { + extend(attribs, { + x: x, + y: y, + width: width, + height: height + }); + } + + elemWrapper = this.createElement('image').attr(attribs); + + // set the href in the xlink namespace + if (elemWrapper.element.setAttributeNS) { + elemWrapper.element.setAttributeNS('http://www.w3.org/1999/xlink', + 'href', src); + } else { + // could be exporting in IE + // using href throws "not supported" in ie7 and under, requries regex shim to fix later + elemWrapper.element.setAttribute('hc-svg-href', src); + } + + return elemWrapper; + }, + + /** + * Draw a symbol out of pre-defined shape paths from the namespace 'symbol' object. + * + * @param {Object} symbol + * @param {Object} x + * @param {Object} y + * @param {Object} radius + * @param {Object} options + */ + symbol: function (symbol, x, y, width, height, options) { + + var obj, + + // get the symbol definition function + symbolFn = this.symbols[symbol], + + // check if there's a path defined for this symbol + path = symbolFn && symbolFn( + mathRound(x), + mathRound(y), + width, + height, + options + ), + + imageElement, + imageRegex = /^url\((.*?)\)$/, + imageSrc, + imageSize, + centerImage; + + if (path) { + + obj = this.path(path); + // expando properties for use in animate and attr + extend(obj, { + symbolName: symbol, + x: x, + y: y, + width: width, + height: height + }); + if (options) { + extend(obj, options); + } + + + // image symbols + } else if (imageRegex.test(symbol)) { + + // On image load, set the size and position + centerImage = function (img, size) { + if (img.element) { // it may be destroyed in the meantime (#1390) + img.attr({ + width: size[0], + height: size[1] + }); + + if (!img.alignByTranslate) { // #185 + img.translate( + mathRound((width - size[0]) / 2), // #1378 + mathRound((height - size[1]) / 2) + ); + } + } + }; + + imageSrc = symbol.match(imageRegex)[1]; + imageSize = symbolSizes[imageSrc]; + + // Ireate the image synchronously, add attribs async + obj = this.image(imageSrc) + .attr({ + x: x, + y: y + }); + obj.isImg = true; + + if (imageSize) { + centerImage(obj, imageSize); + } else { + // Initialize image to be 0 size so export will still function if there's no cached sizes. + // + obj.attr({ width: 0, height: 0 }); + + // Create a dummy JavaScript image to get the width and height. Due to a bug in IE < 8, + // the created element must be assigned to a variable in order to load (#292). + imageElement = createElement('img', { + onload: function () { + centerImage(obj, symbolSizes[imageSrc] = [this.width, this.height]); + }, + src: imageSrc + }); + } + } + + return obj; + }, + + /** + * An extendable collection of functions for defining symbol paths. + */ + symbols: { + 'circle': function (x, y, w, h) { + var cpw = 0.166 * w; + return [ + M, x + w / 2, y, + 'C', x + w + cpw, y, x + w + cpw, y + h, x + w / 2, y + h, + 'C', x - cpw, y + h, x - cpw, y, x + w / 2, y, + 'Z' + ]; + }, + + 'square': function (x, y, w, h) { + return [ + M, x, y, + L, x + w, y, + x + w, y + h, + x, y + h, + 'Z' + ]; + }, + + 'triangle': function (x, y, w, h) { + return [ + M, x + w / 2, y, + L, x + w, y + h, + x, y + h, + 'Z' + ]; + }, + + 'triangle-down': function (x, y, w, h) { + return [ + M, x, y, + L, x + w, y, + x + w / 2, y + h, + 'Z' + ]; + }, + 'diamond': function (x, y, w, h) { + return [ + M, x + w / 2, y, + L, x + w, y + h / 2, + x + w / 2, y + h, + x, y + h / 2, + 'Z' + ]; + }, + 'arc': function (x, y, w, h, options) { + var start = options.start, + radius = options.r || w || h, + end = options.end - 0.001, // to prevent cos and sin of start and end from becoming equal on 360 arcs (related: #1561) + innerRadius = options.innerR, + open = options.open, + cosStart = mathCos(start), + sinStart = mathSin(start), + cosEnd = mathCos(end), + sinEnd = mathSin(end), + longArc = options.end - start < mathPI ? 0 : 1; + + return [ + M, + x + radius * cosStart, + y + radius * sinStart, + 'A', // arcTo + radius, // x radius + radius, // y radius + 0, // slanting + longArc, // long or short arc + 1, // clockwise + x + radius * cosEnd, + y + radius * sinEnd, + open ? M : L, + x + innerRadius * cosEnd, + y + innerRadius * sinEnd, + 'A', // arcTo + innerRadius, // x radius + innerRadius, // y radius + 0, // slanting + longArc, // long or short arc + 0, // clockwise + x + innerRadius * cosStart, + y + innerRadius * sinStart, + + open ? '' : 'Z' // close + ]; + }, + + /** + * Callout shape used for default tooltips, also used for rounded rectangles in VML + */ + callout: function (x, y, w, h, options) { + var arrowLength = 6, + halfDistance = 6, + r = mathMin((options && options.r) || 0, w, h), + safeDistance = r + halfDistance, + anchorX = options && options.anchorX, + anchorY = options && options.anchorY, + path, + normalizer = mathRound(options.strokeWidth || 0) % 2 / 2; // mathRound because strokeWidth can sometimes have roundoff errors; + + x += normalizer; + y += normalizer; + path = [ + 'M', x + r, y, + 'L', x + w - r, y, // top side + 'C', x + w, y, x + w, y, x + w, y + r, // top-right corner + 'L', x + w, y + h - r, // right side + 'C', x + w, y + h, x + w, y + h, x + w - r, y + h, // bottom-right corner + 'L', x + r, y + h, // bottom side + 'C', x, y + h, x, y + h, x, y + h - r, // bottom-left corner + 'L', x, y + r, // left side + 'C', x, y, x, y, x + r, y // top-right corner + ]; + + if (anchorX && anchorX > w && anchorY > y + safeDistance && anchorY < y + h - safeDistance) { // replace right side + path.splice(13, 3, + 'L', x + w, anchorY - halfDistance, + x + w + arrowLength, anchorY, + x + w, anchorY + halfDistance, + x + w, y + h - r + ); + } else if (anchorX && anchorX < 0 && anchorY > y + safeDistance && anchorY < y + h - safeDistance) { // replace left side + path.splice(33, 3, + 'L', x, anchorY + halfDistance, + x - arrowLength, anchorY, + x, anchorY - halfDistance, + x, y + r + ); + } else if (anchorY && anchorY > h && anchorX > x + safeDistance && anchorX < x + w - safeDistance) { // replace bottom + path.splice(23, 3, + 'L', anchorX + halfDistance, y + h, + anchorX, y + h + arrowLength, + anchorX - halfDistance, y + h, + x + r, y + h + ); + } else if (anchorY && anchorY < 0 && anchorX > x + safeDistance && anchorX < x + w - safeDistance) { // replace top + path.splice(3, 3, + 'L', anchorX - halfDistance, y, + anchorX, y - arrowLength, + anchorX + halfDistance, y, + w - r, y + ); + } + return path; + } + }, + + /** + * Define a clipping rectangle + * @param {String} id + * @param {Number} x + * @param {Number} y + * @param {Number} width + * @param {Number} height + */ + clipRect: function (x, y, width, height) { + var wrapper, + id = PREFIX + idCounter++, + + clipPath = this.createElement('clipPath').attr({ + id: id + }).add(this.defs); + + wrapper = this.rect(x, y, width, height, 0).add(clipPath); + wrapper.id = id; + wrapper.clipPath = clipPath; + + return wrapper; + }, + + + + + + /** + * Add text to the SVG object + * @param {String} str + * @param {Number} x Left position + * @param {Number} y Top position + * @param {Boolean} useHTML Use HTML to render the text + */ + text: function (str, x, y, useHTML) { + + // declare variables + var renderer = this, + fakeSVG = useCanVG || (!hasSVG && renderer.forExport), + wrapper, + attr = {}; + + if (useHTML && !renderer.forExport) { + return renderer.html(str, x, y); + } + + attr.x = Math.round(x || 0); // X is always needed for line-wrap logic + if (y) { + attr.y = Math.round(y); + } + if (str || str === 0) { + attr.text = str; + } + + wrapper = renderer.createElement('text') + .attr(attr); + + // Prevent wrapping from creating false offsetWidths in export in legacy IE (#1079, #1063) + if (fakeSVG) { + wrapper.css({ + position: ABSOLUTE + }); + } + + if (!useHTML) { + wrapper.xSetter = function (value, key, element) { + var childNodes = element.childNodes, + child, + i; + for (i = 1; i < childNodes.length; i++) { + child = childNodes[i]; + // if the x values are equal, the tspan represents a linebreak + if (child.getAttribute('x') === element.getAttribute('x')) { + child.setAttribute('x', value); + } + } + element.setAttribute(key, value); + }; + } + + return wrapper; + }, + + /** + * Utility to return the baseline offset and total line height from the font size + */ + fontMetrics: function (fontSize) { + fontSize = fontSize || this.style.fontSize; + fontSize = /px/.test(fontSize) ? pInt(fontSize) : /em/.test(fontSize) ? parseFloat(fontSize) * 12 : 12; + + // Empirical values found by comparing font size and bounding box height. + // Applies to the default font family. http://jsfiddle.net/highcharts/7xvn7/ + var lineHeight = fontSize < 24 ? fontSize + 4 : mathRound(fontSize * 1.2), + baseline = mathRound(lineHeight * 0.8); + + return { + h: lineHeight, + b: baseline + }; + }, + + /** + * Add a label, a text item that can hold a colored or gradient background + * as well as a border and shadow. + * @param {string} str + * @param {Number} x + * @param {Number} y + * @param {String} shape + * @param {Number} anchorX In case the shape has a pointer, like a flag, this is the + * coordinates it should be pinned to + * @param {Number} anchorY + * @param {Boolean} baseline Whether to position the label relative to the text baseline, + * like renderer.text, or to the upper border of the rectangle. + * @param {String} className Class name for the group + */ + label: function (str, x, y, shape, anchorX, anchorY, useHTML, baseline, className) { + + var renderer = this, + wrapper = renderer.g(className), + text = renderer.text('', 0, 0, useHTML) + .attr({ + zIndex: 1 + }), + //.add(wrapper), + box, + bBox, + alignFactor = 0, + padding = 3, + paddingLeft = 0, + width, + height, + wrapperX, + wrapperY, + crispAdjust = 0, + deferredAttr = {}, + baselineOffset, + needsBox; + + /** + * This function runs after the label is added to the DOM (when the bounding box is + * available), and after the text of the label is updated to detect the new bounding + * box and reflect it in the border box. + */ + function updateBoxSize() { + var boxX, + boxY, + style = text.element.style; + + bBox = (width === undefined || height === undefined || wrapper.styles.textAlign) && text.textStr && + text.getBBox(); + wrapper.width = (width || bBox.width || 0) + 2 * padding + paddingLeft; + wrapper.height = (height || bBox.height || 0) + 2 * padding; + + // update the label-scoped y offset + baselineOffset = padding + renderer.fontMetrics(style && style.fontSize).b; + + + if (needsBox) { + + // create the border box if it is not already present + if (!box) { + boxX = mathRound(-alignFactor * padding); + boxY = baseline ? -baselineOffset : 0; + + wrapper.box = box = shape ? + renderer.symbol(shape, boxX, boxY, wrapper.width, wrapper.height, deferredAttr) : + renderer.rect(boxX, boxY, wrapper.width, wrapper.height, 0, deferredAttr[STROKE_WIDTH]); + box.attr('fill', NONE).add(wrapper); + } + + // apply the box attributes + if (!box.isImg) { // #1630 + box.attr(extend({ + width: mathRound(wrapper.width), + height: mathRound(wrapper.height) + }, deferredAttr)); + } + deferredAttr = null; + } + } + + /** + * This function runs after setting text or padding, but only if padding is changed + */ + function updateTextPadding() { + var styles = wrapper.styles, + textAlign = styles && styles.textAlign, + x = paddingLeft + padding * (1 - alignFactor), + y; + + // determin y based on the baseline + y = baseline ? 0 : baselineOffset; + + // compensate for alignment + if (defined(width) && bBox && (textAlign === 'center' || textAlign === 'right')) { + x += { center: 0.5, right: 1 }[textAlign] * (width - bBox.width); + } + + // update if anything changed + if (x !== text.x || y !== text.y) { + text.attr('x', x); + if (y !== UNDEFINED) { + text.attr('y', y); + } + } + + // record current values + text.x = x; + text.y = y; + } + + /** + * Set a box attribute, or defer it if the box is not yet created + * @param {Object} key + * @param {Object} value + */ + function boxAttr(key, value) { + if (box) { + box.attr(key, value); + } else { + deferredAttr[key] = value; + } + } + + /** + * After the text element is added, get the desired size of the border box + * and add it before the text in the DOM. + */ + wrapper.onAdd = function () { + text.add(wrapper); + wrapper.attr({ + text: str || '', // alignment is available now + x: x, + y: y + }); + + if (box && defined(anchorX)) { + wrapper.attr({ + anchorX: anchorX, + anchorY: anchorY + }); + } + }; + + /* + * Add specific attribute setters. + */ + + // only change local variables + wrapper.widthSetter = function (value) { + width = value; + }; + wrapper.heightSetter = function (value) { + height = value; + }; + wrapper.paddingSetter = function (value) { + if (defined(value) && value !== padding) { + padding = value; + updateTextPadding(); + } + }; + wrapper.paddingLeftSetter = function (value) { + if (defined(value) && value !== paddingLeft) { + paddingLeft = value; + updateTextPadding(); + } + }; + + + // change local variable and prevent setting attribute on the group + wrapper.alignSetter = function (value) { + alignFactor = { left: 0, center: 0.5, right: 1 }[value]; + }; + + // apply these to the box and the text alike + wrapper.textSetter = function (value) { + if (value !== UNDEFINED) { + text.textSetter(value); + } + updateBoxSize(); + updateTextPadding(); + }; + + // apply these to the box but not to the text + wrapper['stroke-widthSetter'] = function (value, key) { + if (value) { + needsBox = true; + } + crispAdjust = value % 2 / 2; + boxAttr(key, value); + }; + wrapper.strokeSetter = wrapper.fillSetter = wrapper.rSetter = function (value, key) { + if (key === 'fill' && value) { + needsBox = true; + } + boxAttr(key, value); + }; + wrapper.anchorXSetter = function (value, key) { + anchorX = value; + boxAttr(key, value + crispAdjust - wrapperX); + }; + wrapper.anchorYSetter = function (value, key) { + anchorY = value; + boxAttr(key, value - wrapperY); + }; + + // rename attributes + wrapper.xSetter = function (value) { + wrapper.x = value; // for animation getter + if (alignFactor) { + value -= alignFactor * ((width || bBox.width) + padding); + } + wrapperX = mathRound(value); + wrapper.attr('translateX', wrapperX); + }; + wrapper.ySetter = function (value) { + wrapperY = wrapper.y = mathRound(value); + wrapper.attr('translateY', wrapperY); + }; + + // Redirect certain methods to either the box or the text + var baseCss = wrapper.css; + return extend(wrapper, { + /** + * Pick up some properties and apply them to the text instead of the wrapper + */ + css: function (styles) { + if (styles) { + var textStyles = {}; + styles = merge(styles); // create a copy to avoid altering the original object (#537) + each(['fontSize', 'fontWeight', 'fontFamily', 'color', 'lineHeight', 'width', 'textDecoration', 'textShadow'], function (prop) { + if (styles[prop] !== UNDEFINED) { + textStyles[prop] = styles[prop]; + delete styles[prop]; + } + }); + text.css(textStyles); + } + return baseCss.call(wrapper, styles); + }, + /** + * Return the bounding box of the box, not the group + */ + getBBox: function () { + return { + width: bBox.width + 2 * padding, + height: bBox.height + 2 * padding, + x: bBox.x - padding, + y: bBox.y - padding + }; + }, + /** + * Apply the shadow to the box + */ + shadow: function (b) { + if (box) { + box.shadow(b); + } + return wrapper; + }, + /** + * Destroy and release memory. + */ + destroy: function () { + + // Added by button implementation + removeEvent(wrapper.element, 'mouseenter'); + removeEvent(wrapper.element, 'mouseleave'); + + if (text) { + text = text.destroy(); + } + if (box) { + box = box.destroy(); + } + // Call base implementation to destroy the rest + SVGElement.prototype.destroy.call(wrapper); + + // Release local pointers (#1298) + wrapper = renderer = updateBoxSize = updateTextPadding = boxAttr = null; + } + }); + } +}; // end SVGRenderer + + +// general renderer +Renderer = SVGRenderer; +// extend SvgElement for useHTML option +extend(SVGElement.prototype, { + /** + * Apply CSS to HTML elements. This is used in text within SVG rendering and + * by the VML renderer + */ + htmlCss: function (styles) { + var wrapper = this, + element = wrapper.element, + textWidth = styles && element.tagName === 'SPAN' && styles.width; + + if (textWidth) { + delete styles.width; + wrapper.textWidth = textWidth; + wrapper.updateTransform(); + } + + wrapper.styles = extend(wrapper.styles, styles); + css(wrapper.element, styles); + + return wrapper; + }, + + /** + * VML and useHTML method for calculating the bounding box based on offsets + * @param {Boolean} refresh Whether to force a fresh value from the DOM or to + * use the cached value + * + * @return {Object} A hash containing values for x, y, width and height + */ + + htmlGetBBox: function () { + var wrapper = this, + element = wrapper.element, + bBox = wrapper.bBox; + + // faking getBBox in exported SVG in legacy IE + if (!bBox) { + // faking getBBox in exported SVG in legacy IE (is this a duplicate of the fix for #1079?) + if (element.nodeName === 'text') { + element.style.position = ABSOLUTE; + } + + bBox = wrapper.bBox = { + x: element.offsetLeft, + y: element.offsetTop, + width: element.offsetWidth, + height: element.offsetHeight + }; + } + + return bBox; + }, + + /** + * VML override private method to update elements based on internal + * properties based on SVG transform + */ + htmlUpdateTransform: function () { + // aligning non added elements is expensive + if (!this.added) { + this.alignOnAdd = true; + return; + } + + var wrapper = this, + renderer = wrapper.renderer, + elem = wrapper.element, + translateX = wrapper.translateX || 0, + translateY = wrapper.translateY || 0, + x = wrapper.x || 0, + y = wrapper.y || 0, + align = wrapper.textAlign || 'left', + alignCorrection = { left: 0, center: 0.5, right: 1 }[align], + shadows = wrapper.shadows; + + // apply translate + css(elem, { + marginLeft: translateX, + marginTop: translateY + }); + if (shadows) { // used in labels/tooltip + each(shadows, function (shadow) { + css(shadow, { + marginLeft: translateX + 1, + marginTop: translateY + 1 + }); + }); + } + + // apply inversion + if (wrapper.inverted) { // wrapper is a group + each(elem.childNodes, function (child) { + renderer.invertChild(child, elem); + }); + } + + if (elem.tagName === 'SPAN') { + + var width, + rotation = wrapper.rotation, + baseline, + textWidth = pInt(wrapper.textWidth), + currentTextTransform = [rotation, align, elem.innerHTML, wrapper.textWidth].join(','); + + if (currentTextTransform !== wrapper.cTT) { // do the calculations and DOM access only if properties changed + + + baseline = renderer.fontMetrics(elem.style.fontSize).b; + + // Renderer specific handling of span rotation + if (defined(rotation)) { + wrapper.setSpanRotation(rotation, alignCorrection, baseline); + } + + width = pick(wrapper.elemWidth, elem.offsetWidth); + + // Update textWidth + if (width > textWidth && /[ \-]/.test(elem.textContent || elem.innerText)) { // #983, #1254 + css(elem, { + width: textWidth + PX, + display: 'block', + whiteSpace: 'normal' + }); + width = textWidth; + } + + wrapper.getSpanCorrection(width, baseline, alignCorrection, rotation, align); + } + + // apply position with correction + css(elem, { + left: (x + (wrapper.xCorr || 0)) + PX, + top: (y + (wrapper.yCorr || 0)) + PX + }); + + // force reflow in webkit to apply the left and top on useHTML element (#1249) + if (isWebKit) { + baseline = elem.offsetHeight; // assigned to baseline for JSLint purpose + } + + // record current text transform + wrapper.cTT = currentTextTransform; + } + }, + + /** + * Set the rotation of an individual HTML span + */ + setSpanRotation: function (rotation, alignCorrection, baseline) { + var rotationStyle = {}, + cssTransformKey = isIE ? '-ms-transform' : isWebKit ? '-webkit-transform' : isFirefox ? 'MozTransform' : isOpera ? '-o-transform' : ''; + + rotationStyle[cssTransformKey] = rotationStyle.transform = 'rotate(' + rotation + 'deg)'; + rotationStyle[cssTransformKey + (isFirefox ? 'Origin' : '-origin')] = rotationStyle.transformOrigin = (alignCorrection * 100) + '% ' + baseline + 'px'; + css(this.element, rotationStyle); + }, + + /** + * Get the correction in X and Y positioning as the element is rotated. + */ + getSpanCorrection: function (width, baseline, alignCorrection) { + this.xCorr = -width * alignCorrection; + this.yCorr = -baseline; + } +}); + +// Extend SvgRenderer for useHTML option. +extend(SVGRenderer.prototype, { + /** + * Create HTML text node. This is used by the VML renderer as well as the SVG + * renderer through the useHTML option. + * + * @param {String} str + * @param {Number} x + * @param {Number} y + */ + html: function (str, x, y) { + var wrapper = this.createElement('span'), + element = wrapper.element, + renderer = wrapper.renderer; + + // Text setter + wrapper.textSetter = function (value) { + if (value !== element.innerHTML) { + delete this.bBox; + } + element.innerHTML = this.textStr = value; + }; + + // Various setters which rely on update transform + wrapper.xSetter = wrapper.ySetter = wrapper.alignSetter = wrapper.rotationSetter = function (value, key) { + if (key === 'align') { + key = 'textAlign'; // Do not overwrite the SVGElement.align method. Same as VML. + } + wrapper[key] = value; + wrapper.htmlUpdateTransform(); + }; + + // Set the default attributes + wrapper.attr({ + text: str, + x: mathRound(x), + y: mathRound(y) + }) + .css({ + position: ABSOLUTE, + whiteSpace: 'nowrap', + fontFamily: this.style.fontFamily, + fontSize: this.style.fontSize + }); + + // Use the HTML specific .css method + wrapper.css = wrapper.htmlCss; + + // This is specific for HTML within SVG + if (renderer.isSVG) { + wrapper.add = function (svgGroupWrapper) { + + var htmlGroup, + container = renderer.box.parentNode, + parentGroup, + parents = []; + + this.parentGroup = svgGroupWrapper; + + // Create a mock group to hold the HTML elements + if (svgGroupWrapper) { + htmlGroup = svgGroupWrapper.div; + if (!htmlGroup) { + + // Read the parent chain into an array and read from top down + parentGroup = svgGroupWrapper; + while (parentGroup) { + + parents.push(parentGroup); + + // Move up to the next parent group + parentGroup = parentGroup.parentGroup; + } + + // Ensure dynamically updating position when any parent is translated + each(parents.reverse(), function (parentGroup) { + var htmlGroupStyle; + + // Create a HTML div and append it to the parent div to emulate + // the SVG group structure + htmlGroup = parentGroup.div = parentGroup.div || createElement(DIV, { + className: attr(parentGroup.element, 'class') + }, { + position: ABSOLUTE, + left: (parentGroup.translateX || 0) + PX, + top: (parentGroup.translateY || 0) + PX + }, htmlGroup || container); // the top group is appended to container + + // Shortcut + htmlGroupStyle = htmlGroup.style; + + // Set listeners to update the HTML div's position whenever the SVG group + // position is changed + extend(parentGroup, { + translateXSetter: function (value, key) { + htmlGroupStyle.left = value + PX; + parentGroup[key] = value; + parentGroup.doTransform = true; + }, + translateYSetter: function (value, key) { + htmlGroupStyle.top = value + PX; + parentGroup[key] = value; + parentGroup.doTransform = true; + }, + visibilitySetter: function (value, key) { + htmlGroupStyle[key] = value; + } + }); + }); + + } + } else { + htmlGroup = container; + } + + htmlGroup.appendChild(element); + + // Shared with VML: + wrapper.added = true; + if (wrapper.alignOnAdd) { + wrapper.htmlUpdateTransform(); + } + + return wrapper; + }; + } + return wrapper; + } +}); + +/* **************************************************************************** + * * + * START OF INTERNET EXPLORER <= 8 SPECIFIC CODE * + * * + * For applications and websites that don't need IE support, like platform * + * targeted mobile apps and web apps, this code can be removed. * + * * + *****************************************************************************/ + +/** + * @constructor + */ +var VMLRenderer, VMLElement; +if (!hasSVG && !useCanVG) { + +/** + * The VML element wrapper. + */ +Highcharts.VMLElement = VMLElement = { + + /** + * Initialize a new VML element wrapper. It builds the markup as a string + * to minimize DOM traffic. + * @param {Object} renderer + * @param {Object} nodeName + */ + init: function (renderer, nodeName) { + var wrapper = this, + markup = ['<', nodeName, ' filled="f" stroked="f"'], + style = ['position: ', ABSOLUTE, ';'], + isDiv = nodeName === DIV; + + // divs and shapes need size + if (nodeName === 'shape' || isDiv) { + style.push('left:0;top:0;width:1px;height:1px;'); + } + style.push('visibility: ', isDiv ? HIDDEN : VISIBLE); + + markup.push(' style="', style.join(''), '"/>'); + + // create element with default attributes and style + if (nodeName) { + markup = isDiv || nodeName === 'span' || nodeName === 'img' ? + markup.join('') + : renderer.prepVML(markup); + wrapper.element = createElement(markup); + } + + wrapper.renderer = renderer; + }, + + /** + * Add the node to the given parent + * @param {Object} parent + */ + add: function (parent) { + var wrapper = this, + renderer = wrapper.renderer, + element = wrapper.element, + box = renderer.box, + inverted = parent && parent.inverted, + + // get the parent node + parentNode = parent ? + parent.element || parent : + box; + + + // if the parent group is inverted, apply inversion on all children + if (inverted) { // only on groups + renderer.invertChild(element, parentNode); + } + + // append it + parentNode.appendChild(element); + + // align text after adding to be able to read offset + wrapper.added = true; + if (wrapper.alignOnAdd && !wrapper.deferUpdateTransform) { + wrapper.updateTransform(); + } + + // fire an event for internal hooks + if (wrapper.onAdd) { + wrapper.onAdd(); + } + + return wrapper; + }, + + /** + * VML always uses htmlUpdateTransform + */ + updateTransform: SVGElement.prototype.htmlUpdateTransform, + + /** + * Set the rotation of a span with oldIE's filter + */ + setSpanRotation: function () { + // Adjust for alignment and rotation. Rotation of useHTML content is not yet implemented + // but it can probably be implemented for Firefox 3.5+ on user request. FF3.5+ + // has support for CSS3 transform. The getBBox method also needs to be updated + // to compensate for the rotation, like it currently does for SVG. + // Test case: http://jsfiddle.net/highcharts/Ybt44/ + + var rotation = this.rotation, + costheta = mathCos(rotation * deg2rad), + sintheta = mathSin(rotation * deg2rad); + + css(this.element, { + filter: rotation ? ['progid:DXImageTransform.Microsoft.Matrix(M11=', costheta, + ', M12=', -sintheta, ', M21=', sintheta, ', M22=', costheta, + ', sizingMethod=\'auto expand\')'].join('') : NONE + }); + }, + + /** + * Get the positioning correction for the span after rotating. + */ + getSpanCorrection: function (width, baseline, alignCorrection, rotation, align) { + + var costheta = rotation ? mathCos(rotation * deg2rad) : 1, + sintheta = rotation ? mathSin(rotation * deg2rad) : 0, + height = pick(this.elemHeight, this.element.offsetHeight), + quad, + nonLeft = align && align !== 'left'; + + // correct x and y + this.xCorr = costheta < 0 && -width; + this.yCorr = sintheta < 0 && -height; + + // correct for baseline and corners spilling out after rotation + quad = costheta * sintheta < 0; + this.xCorr += sintheta * baseline * (quad ? 1 - alignCorrection : alignCorrection); + this.yCorr -= costheta * baseline * (rotation ? (quad ? alignCorrection : 1 - alignCorrection) : 1); + // correct for the length/height of the text + if (nonLeft) { + this.xCorr -= width * alignCorrection * (costheta < 0 ? -1 : 1); + if (rotation) { + this.yCorr -= height * alignCorrection * (sintheta < 0 ? -1 : 1); + } + css(this.element, { + textAlign: align + }); + } + }, + + /** + * Converts a subset of an SVG path definition to its VML counterpart. Takes an array + * as the parameter and returns a string. + */ + pathToVML: function (value) { + // convert paths + var i = value.length, + path = []; + + while (i--) { + + // Multiply by 10 to allow subpixel precision. + // Substracting half a pixel seems to make the coordinates + // align with SVG, but this hasn't been tested thoroughly + if (isNumber(value[i])) { + path[i] = mathRound(value[i] * 10) - 5; + } else if (value[i] === 'Z') { // close the path + path[i] = 'x'; + } else { + path[i] = value[i]; + + // When the start X and end X coordinates of an arc are too close, + // they are rounded to the same value above. In this case, substract or + // add 1 from the end X and Y positions. #186, #760, #1371, #1410. + if (value.isArc && (value[i] === 'wa' || value[i] === 'at')) { + // Start and end X + if (path[i + 5] === path[i + 7]) { + path[i + 7] += value[i + 7] > value[i + 5] ? 1 : -1; + } + // Start and end Y + if (path[i + 6] === path[i + 8]) { + path[i + 8] += value[i + 8] > value[i + 6] ? 1 : -1; + } + } + } + } + + + // Loop up again to handle path shortcuts (#2132) + /*while (i++ < path.length) { + if (path[i] === 'H') { // horizontal line to + path[i] = 'L'; + path.splice(i + 2, 0, path[i - 1]); + } else if (path[i] === 'V') { // vertical line to + path[i] = 'L'; + path.splice(i + 1, 0, path[i - 2]); + } + }*/ + return path.join(' ') || 'x'; + }, + + /** + * Set the element's clipping to a predefined rectangle + * + * @param {String} id The id of the clip rectangle + */ + clip: function (clipRect) { + var wrapper = this, + clipMembers, + cssRet; + + if (clipRect) { + clipMembers = clipRect.members; + erase(clipMembers, wrapper); // Ensure unique list of elements (#1258) + clipMembers.push(wrapper); + wrapper.destroyClip = function () { + erase(clipMembers, wrapper); + }; + cssRet = clipRect.getCSS(wrapper); + + } else { + if (wrapper.destroyClip) { + wrapper.destroyClip(); + } + cssRet = { clip: docMode8 ? 'inherit' : 'rect(auto)' }; // #1214 + } + + return wrapper.css(cssRet); + + }, + + /** + * Set styles for the element + * @param {Object} styles + */ + css: SVGElement.prototype.htmlCss, + + /** + * Removes a child either by removeChild or move to garbageBin. + * Issue 490; in VML removeChild results in Orphaned nodes according to sIEve, discardElement does not. + */ + safeRemoveChild: function (element) { + // discardElement will detach the node from its parent before attaching it + // to the garbage bin. Therefore it is important that the node is attached and have parent. + if (element.parentNode) { + discardElement(element); + } + }, + + /** + * Extend element.destroy by removing it from the clip members array + */ + destroy: function () { + if (this.destroyClip) { + this.destroyClip(); + } + + return SVGElement.prototype.destroy.apply(this); + }, + + /** + * Add an event listener. VML override for normalizing event parameters. + * @param {String} eventType + * @param {Function} handler + */ + on: function (eventType, handler) { + // simplest possible event model for internal use + this.element['on' + eventType] = function () { + var evt = win.event; + evt.target = evt.srcElement; + handler(evt); + }; + return this; + }, + + /** + * In stacked columns, cut off the shadows so that they don't overlap + */ + cutOffPath: function (path, length) { + + var len; + + path = path.split(/[ ,]/); + len = path.length; + + if (len === 9 || len === 11) { + path[len - 4] = path[len - 2] = pInt(path[len - 2]) - 10 * length; + } + return path.join(' '); + }, + + /** + * Apply a drop shadow by copying elements and giving them different strokes + * @param {Boolean|Object} shadowOptions + */ + shadow: function (shadowOptions, group, cutOff) { + var shadows = [], + i, + element = this.element, + renderer = this.renderer, + shadow, + elemStyle = element.style, + markup, + path = element.path, + strokeWidth, + modifiedPath, + shadowWidth, + shadowElementOpacity; + + // some times empty paths are not strings + if (path && typeof path.value !== 'string') { + path = 'x'; + } + modifiedPath = path; + + if (shadowOptions) { + shadowWidth = pick(shadowOptions.width, 3); + shadowElementOpacity = (shadowOptions.opacity || 0.15) / shadowWidth; + for (i = 1; i <= 3; i++) { + + strokeWidth = (shadowWidth * 2) + 1 - (2 * i); + + // Cut off shadows for stacked column items + if (cutOff) { + modifiedPath = this.cutOffPath(path.value, strokeWidth + 0.5); + } + + markup = ['']; + + shadow = createElement(renderer.prepVML(markup), + null, { + left: pInt(elemStyle.left) + pick(shadowOptions.offsetX, 1), + top: pInt(elemStyle.top) + pick(shadowOptions.offsetY, 1) + } + ); + if (cutOff) { + shadow.cutOff = strokeWidth + 1; + } + + // apply the opacity + markup = ['']; + createElement(renderer.prepVML(markup), null, null, shadow); + + + // insert it + if (group) { + group.element.appendChild(shadow); + } else { + element.parentNode.insertBefore(shadow, element); + } + + // record it + shadows.push(shadow); + + } + + this.shadows = shadows; + } + return this; + }, + updateShadows: noop, // Used in SVG only + + setAttr: function (key, value) { + if (docMode8) { // IE8 setAttribute bug + this.element[key] = value; + } else { + this.element.setAttribute(key, value); + } + }, + classSetter: function (value) { + // IE8 Standards mode has problems retrieving the className unless set like this + this.element.className = value; + }, + dashstyleSetter: function (value, key, element) { + var strokeElem = element.getElementsByTagName('stroke')[0] || + createElement(this.renderer.prepVML(['']), null, null, element); + strokeElem[key] = value || 'solid'; + this[key] = value; /* because changing stroke-width will change the dash length + and cause an epileptic effect */ + }, + dSetter: function (value, key, element) { + var i, + shadows = this.shadows; + value = value || []; + this.d = value.join(' '); // used in getter for animation + + element.path = value = this.pathToVML(value); + + // update shadows + if (shadows) { + i = shadows.length; + while (i--) { + shadows[i].path = shadows[i].cutOff ? this.cutOffPath(value, shadows[i].cutOff) : value; + } + } + this.setAttr(key, value); + }, + fillSetter: function (value, key, element) { + var nodeName = element.nodeName; + if (nodeName === 'SPAN') { // text color + element.style.color = value; + } else if (nodeName !== 'IMG') { // #1336 + element.filled = value !== NONE; + this.setAttr('fillcolor', this.renderer.color(value, element, key, this)); + } + }, + opacitySetter: noop, // Don't bother - animation is too slow and filters introduce artifacts + rotationSetter: function (value, key, element) { + var style = element.style; + this[key] = style[key] = value; // style is for #1873 + + // Correction for the 1x1 size of the shape container. Used in gauge needles. + style.left = -mathRound(mathSin(value * deg2rad) + 1) + PX; + style.top = mathRound(mathCos(value * deg2rad)) + PX; + }, + strokeSetter: function (value, key, element) { + this.setAttr('strokecolor', this.renderer.color(value, element, key)); + }, + 'stroke-widthSetter': function (value, key, element) { + element.stroked = !!value; // VML "stroked" attribute + this[key] = value; // used in getter, issue #113 + if (isNumber(value)) { + value += PX; + } + this.setAttr('strokeweight', value); + }, + titleSetter: function (value, key) { + this.setAttr(key, value); + }, + visibilitySetter: function (value, key, element) { + + // Handle inherited visibility + if (value === 'inherit') { + value = VISIBLE; + } + + // Let the shadow follow the main element + if (this.shadows) { + each(this.shadows, function (shadow) { + shadow.style[key] = value; + }); + } + + // Instead of toggling the visibility CSS property, move the div out of the viewport. + // This works around #61 and #586 + if (element.nodeName === 'DIV') { + value = value === HIDDEN ? '-999em' : 0; + + // In order to redraw, IE7 needs the div to be visible when tucked away + // outside the viewport. So the visibility is actually opposite of + // the expected value. This applies to the tooltip only. + if (!docMode8) { + element.style[key] = value ? VISIBLE : HIDDEN; + } + key = 'top'; + } + element.style[key] = value; + }, + xSetter: function (value, key, element) { + this[key] = value; // used in getter + + if (key === 'x') { + key = 'left'; + } else if (key === 'y') { + key = 'top'; + }/* else { + value = mathMax(0, value); // don't set width or height below zero (#311) + }*/ + + // clipping rectangle special + if (this.updateClipping) { + this[key] = value; // the key is now 'left' or 'top' for 'x' and 'y' + this.updateClipping(); + } else { + // normal + element.style[key] = value; + } + }, + zIndexSetter: function (value, key, element) { + element.style[key] = value; + } +}; +VMLElement = extendClass(SVGElement, VMLElement); + +// Some shared setters +VMLElement.prototype.ySetter = + VMLElement.prototype.widthSetter = + VMLElement.prototype.heightSetter = + VMLElement.prototype.xSetter; + + +/** + * The VML renderer + */ +var VMLRendererExtension = { // inherit SVGRenderer + + Element: VMLElement, + isIE8: userAgent.indexOf('MSIE 8.0') > -1, + + + /** + * Initialize the VMLRenderer + * @param {Object} container + * @param {Number} width + * @param {Number} height + */ + init: function (container, width, height, style) { + var renderer = this, + boxWrapper, + box, + css; + + renderer.alignedObjects = []; + + boxWrapper = renderer.createElement(DIV) + .css(extend(this.getStyle(style), { position: RELATIVE})); + box = boxWrapper.element; + container.appendChild(boxWrapper.element); + + + // generate the containing box + renderer.isVML = true; + renderer.box = box; + renderer.boxWrapper = boxWrapper; + renderer.cache = {}; + + + renderer.setSize(width, height, false); + + // The only way to make IE6 and IE7 print is to use a global namespace. However, + // with IE8 the only way to make the dynamic shapes visible in screen and print mode + // seems to be to add the xmlns attribute and the behaviour style inline. + if (!doc.namespaces.hcv) { + + doc.namespaces.add('hcv', 'urn:schemas-microsoft-com:vml'); + + // Setup default CSS (#2153, #2368, #2384) + css = 'hcv\\:fill, hcv\\:path, hcv\\:shape, hcv\\:stroke' + + '{ behavior:url(#default#VML); display: inline-block; } '; + try { + doc.createStyleSheet().cssText = css; + } catch (e) { + doc.styleSheets[0].cssText += css; + } + + } + }, + + + /** + * Detect whether the renderer is hidden. This happens when one of the parent elements + * has display: none + */ + isHidden: function () { + return !this.box.offsetWidth; + }, + + /** + * Define a clipping rectangle. In VML it is accomplished by storing the values + * for setting the CSS style to all associated members. + * + * @param {Number} x + * @param {Number} y + * @param {Number} width + * @param {Number} height + */ + clipRect: function (x, y, width, height) { + + // create a dummy element + var clipRect = this.createElement(), + isObj = isObject(x); + + // mimic a rectangle with its style object for automatic updating in attr + return extend(clipRect, { + members: [], + left: (isObj ? x.x : x) + 1, + top: (isObj ? x.y : y) + 1, + width: (isObj ? x.width : width) - 1, + height: (isObj ? x.height : height) - 1, + getCSS: function (wrapper) { + var element = wrapper.element, + nodeName = element.nodeName, + isShape = nodeName === 'shape', + inverted = wrapper.inverted, + rect = this, + top = rect.top - (isShape ? element.offsetTop : 0), + left = rect.left, + right = left + rect.width, + bottom = top + rect.height, + ret = { + clip: 'rect(' + + mathRound(inverted ? left : top) + 'px,' + + mathRound(inverted ? bottom : right) + 'px,' + + mathRound(inverted ? right : bottom) + 'px,' + + mathRound(inverted ? top : left) + 'px)' + }; + + // issue 74 workaround + if (!inverted && docMode8 && nodeName === 'DIV') { + extend(ret, { + width: right + PX, + height: bottom + PX + }); + } + return ret; + }, + + // used in attr and animation to update the clipping of all members + updateClipping: function () { + each(clipRect.members, function (member) { + if (member.element) { // Deleted series, like in stock/members/series-remove demo. Should be removed from members, but this will do. + member.css(clipRect.getCSS(member)); + } + }); + } + }); + + }, + + + /** + * Take a color and return it if it's a string, make it a gradient if it's a + * gradient configuration object, and apply opacity. + * + * @param {Object} color The color or config object + */ + color: function (color, elem, prop, wrapper) { + var renderer = this, + colorObject, + regexRgba = /^rgba/, + markup, + fillType, + ret = NONE; + + // Check for linear or radial gradient + if (color && color.linearGradient) { + fillType = 'gradient'; + } else if (color && color.radialGradient) { + fillType = 'pattern'; + } + + + if (fillType) { + + var stopColor, + stopOpacity, + gradient = color.linearGradient || color.radialGradient, + x1, + y1, + x2, + y2, + opacity1, + opacity2, + color1, + color2, + fillAttr = '', + stops = color.stops, + firstStop, + lastStop, + colors = [], + addFillNode = function () { + // Add the fill subnode. When colors attribute is used, the meanings of opacity and o:opacity2 + // are reversed. + markup = ['']; + createElement(renderer.prepVML(markup), null, null, elem); + }; + + // Extend from 0 to 1 + firstStop = stops[0]; + lastStop = stops[stops.length - 1]; + if (firstStop[0] > 0) { + stops.unshift([ + 0, + firstStop[1] + ]); + } + if (lastStop[0] < 1) { + stops.push([ + 1, + lastStop[1] + ]); + } + + // Compute the stops + each(stops, function (stop, i) { + if (regexRgba.test(stop[1])) { + colorObject = Color(stop[1]); + stopColor = colorObject.get('rgb'); + stopOpacity = colorObject.get('a'); + } else { + stopColor = stop[1]; + stopOpacity = 1; + } + + // Build the color attribute + colors.push((stop[0] * 100) + '% ' + stopColor); + + // Only start and end opacities are allowed, so we use the first and the last + if (!i) { + opacity1 = stopOpacity; + color2 = stopColor; + } else { + opacity2 = stopOpacity; + color1 = stopColor; + } + }); + + // Apply the gradient to fills only. + if (prop === 'fill') { + + // Handle linear gradient angle + if (fillType === 'gradient') { + x1 = gradient.x1 || gradient[0] || 0; + y1 = gradient.y1 || gradient[1] || 0; + x2 = gradient.x2 || gradient[2] || 0; + y2 = gradient.y2 || gradient[3] || 0; + fillAttr = 'angle="' + (90 - math.atan( + (y2 - y1) / // y vector + (x2 - x1) // x vector + ) * 180 / mathPI) + '"'; + + addFillNode(); + + // Radial (circular) gradient + } else { + + var r = gradient.r, + sizex = r * 2, + sizey = r * 2, + cx = gradient.cx, + cy = gradient.cy, + radialReference = elem.radialReference, + bBox, + applyRadialGradient = function () { + if (radialReference) { + bBox = wrapper.getBBox(); + cx += (radialReference[0] - bBox.x) / bBox.width - 0.5; + cy += (radialReference[1] - bBox.y) / bBox.height - 0.5; + sizex *= radialReference[2] / bBox.width; + sizey *= radialReference[2] / bBox.height; + } + fillAttr = 'src="' + defaultOptions.global.VMLRadialGradientURL + '" ' + + 'size="' + sizex + ',' + sizey + '" ' + + 'origin="0.5,0.5" ' + + 'position="' + cx + ',' + cy + '" ' + + 'color2="' + color2 + '" '; + + addFillNode(); + }; + + // Apply radial gradient + if (wrapper.added) { + applyRadialGradient(); + } else { + // We need to know the bounding box to get the size and position right + wrapper.onAdd = applyRadialGradient; + } + + // The fill element's color attribute is broken in IE8 standards mode, so we + // need to set the parent shape's fillcolor attribute instead. + ret = color1; + } + + // Gradients are not supported for VML stroke, return the first color. #722. + } else { + ret = stopColor; + } + + // if the color is an rgba color, split it and add a fill node + // to hold the opacity component + } else if (regexRgba.test(color) && elem.tagName !== 'IMG') { + + colorObject = Color(color); + + markup = ['<', prop, ' opacity="', colorObject.get('a'), '"/>']; + createElement(this.prepVML(markup), null, null, elem); + + ret = colorObject.get('rgb'); + + + } else { + var propNodes = elem.getElementsByTagName(prop); // 'stroke' or 'fill' node + if (propNodes.length) { + propNodes[0].opacity = 1; + propNodes[0].type = 'solid'; + } + ret = color; + } + + return ret; + }, + + /** + * Take a VML string and prepare it for either IE8 or IE6/IE7. + * @param {Array} markup A string array of the VML markup to prepare + */ + prepVML: function (markup) { + var vmlStyle = 'display:inline-block;behavior:url(#default#VML);', + isIE8 = this.isIE8; + + markup = markup.join(''); + + if (isIE8) { // add xmlns and style inline + markup = markup.replace('/>', ' xmlns="urn:schemas-microsoft-com:vml" />'); + if (markup.indexOf('style="') === -1) { + markup = markup.replace('/>', ' style="' + vmlStyle + '" />'); + } else { + markup = markup.replace('style="', 'style="' + vmlStyle); + } + + } else { // add namespace + markup = markup.replace('<', ' 1) { + obj.attr({ + x: x, + y: y, + width: width, + height: height + }); + } + return obj; + }, + + /** + * For rectangles, VML uses a shape for rect to overcome bugs and rotation problems + */ + createElement: function (nodeName) { + return nodeName === 'rect' ? this.symbol(nodeName) : SVGRenderer.prototype.createElement.call(this, nodeName); + }, + + /** + * In the VML renderer, each child of an inverted div (group) is inverted + * @param {Object} element + * @param {Object} parentNode + */ + invertChild: function (element, parentNode) { + var ren = this, + parentStyle = parentNode.style, + imgStyle = element.tagName === 'IMG' && element.style; // #1111 + + css(element, { + flip: 'x', + left: pInt(parentStyle.width) - (imgStyle ? pInt(imgStyle.top) : 1), + top: pInt(parentStyle.height) - (imgStyle ? pInt(imgStyle.left) : 1), + rotation: -90 + }); + + // Recursively invert child elements, needed for nested composite shapes like box plots and error bars. #1680, #1806. + each(element.childNodes, function (child) { + ren.invertChild(child, element); + }); + }, + + /** + * Symbol definitions that override the parent SVG renderer's symbols + * + */ + symbols: { + // VML specific arc function + arc: function (x, y, w, h, options) { + var start = options.start, + end = options.end, + radius = options.r || w || h, + innerRadius = options.innerR, + cosStart = mathCos(start), + sinStart = mathSin(start), + cosEnd = mathCos(end), + sinEnd = mathSin(end), + ret; + + if (end - start === 0) { // no angle, don't show it. + return ['x']; + } + + ret = [ + 'wa', // clockwise arc to + x - radius, // left + y - radius, // top + x + radius, // right + y + radius, // bottom + x + radius * cosStart, // start x + y + radius * sinStart, // start y + x + radius * cosEnd, // end x + y + radius * sinEnd // end y + ]; + + if (options.open && !innerRadius) { + ret.push( + 'e', + M, + x,// - innerRadius, + y// - innerRadius + ); + } + + ret.push( + 'at', // anti clockwise arc to + x - innerRadius, // left + y - innerRadius, // top + x + innerRadius, // right + y + innerRadius, // bottom + x + innerRadius * cosEnd, // start x + y + innerRadius * sinEnd, // start y + x + innerRadius * cosStart, // end x + y + innerRadius * sinStart, // end y + 'x', // finish path + 'e' // close + ); + + ret.isArc = true; + return ret; + + }, + // Add circle symbol path. This performs significantly faster than v:oval. + circle: function (x, y, w, h, wrapper) { + + if (wrapper) { + w = h = 2 * wrapper.r; + } + + // Center correction, #1682 + if (wrapper && wrapper.isCircle) { + x -= w / 2; + y -= h / 2; + } + + // Return the path + return [ + 'wa', // clockwisearcto + x, // left + y, // top + x + w, // right + y + h, // bottom + x + w, // start x + y + h / 2, // start y + x + w, // end x + y + h / 2, // end y + //'x', // finish path + 'e' // close + ]; + }, + /** + * Add rectangle symbol path which eases rotation and omits arcsize problems + * compared to the built-in VML roundrect shape. When borders are not rounded, + * use the simpler square path, else use the callout path without the arrow. + */ + rect: function (x, y, w, h, options) { + return SVGRenderer.prototype.symbols[ + !defined(options) || !options.r ? 'square' : 'callout' + ].call(0, x, y, w, h, options); + } + } +}; +Highcharts.VMLRenderer = VMLRenderer = function () { + this.init.apply(this, arguments); +}; +VMLRenderer.prototype = merge(SVGRenderer.prototype, VMLRendererExtension); + + // general renderer + Renderer = VMLRenderer; +} + +// This method is used with exporting in old IE, when emulating SVG (see #2314) +SVGRenderer.prototype.measureSpanWidth = function (text, styles) { + var measuringSpan = doc.createElement('span'), + offsetWidth, + textNode = doc.createTextNode(text); + + measuringSpan.appendChild(textNode); + css(measuringSpan, styles); + this.box.appendChild(measuringSpan); + offsetWidth = measuringSpan.offsetWidth; + discardElement(measuringSpan); // #2463 + return offsetWidth; +}; + + +/* **************************************************************************** + * * + * END OF INTERNET EXPLORER <= 8 SPECIFIC CODE * + * * + *****************************************************************************/ +/* **************************************************************************** + * * + * START OF ANDROID < 3 SPECIFIC CODE. THIS CAN BE REMOVED IF YOU'RE NOT * + * TARGETING THAT SYSTEM. * + * * + *****************************************************************************/ +var CanVGRenderer, + CanVGController; + +if (useCanVG) { + /** + * The CanVGRenderer is empty from start to keep the source footprint small. + * When requested, the CanVGController downloads the rest of the source packaged + * together with the canvg library. + */ + Highcharts.CanVGRenderer = CanVGRenderer = function () { + // Override the global SVG namespace to fake SVG/HTML that accepts CSS + SVG_NS = 'http://www.w3.org/1999/xhtml'; + }; + + /** + * Start with an empty symbols object. This is needed when exporting is used (exporting.src.js will add a few symbols), but + * the implementation from SvgRenderer will not be merged in until first render. + */ + CanVGRenderer.prototype.symbols = {}; + + /** + * Handles on demand download of canvg rendering support. + */ + CanVGController = (function () { + // List of renderering calls + var deferredRenderCalls = []; + + /** + * When downloaded, we are ready to draw deferred charts. + */ + function drawDeferred() { + var callLength = deferredRenderCalls.length, + callIndex; + + // Draw all pending render calls + for (callIndex = 0; callIndex < callLength; callIndex++) { + deferredRenderCalls[callIndex](); + } + // Clear the list + deferredRenderCalls = []; + } + + return { + push: function (func, scriptLocation) { + // Only get the script once + if (deferredRenderCalls.length === 0) { + getScript(scriptLocation, drawDeferred); + } + // Register render call + deferredRenderCalls.push(func); + } + }; + }()); + + Renderer = CanVGRenderer; +} // end CanVGRenderer + +/* **************************************************************************** + * * + * END OF ANDROID < 3 SPECIFIC CODE * + * * + *****************************************************************************/ + +/** + * The Tick class + */ +function Tick(axis, pos, type, noLabel) { + this.axis = axis; + this.pos = pos; + this.type = type || ''; + this.isNew = true; + + if (!type && !noLabel) { + this.addLabel(); + } +} + +Tick.prototype = { + /** + * Write the tick label + */ + addLabel: function () { + var tick = this, + axis = tick.axis, + options = axis.options, + chart = axis.chart, + horiz = axis.horiz, + categories = axis.categories, + names = axis.names, + pos = tick.pos, + labelOptions = options.labels, + str, + tickPositions = axis.tickPositions, + width = (horiz && categories && + !labelOptions.step && !labelOptions.staggerLines && + !labelOptions.rotation && + chart.plotWidth / tickPositions.length) || + (!horiz && (chart.margin[3] || chart.chartWidth * 0.33)), // #1580, #1931 + isFirst = pos === tickPositions[0], + isLast = pos === tickPositions[tickPositions.length - 1], + css, + attr, + value = categories ? + pick(categories[pos], names[pos], pos) : + pos, + label = tick.label, + tickPositionInfo = tickPositions.info, + dateTimeLabelFormat; + + // Set the datetime label format. If a higher rank is set for this position, use that. If not, + // use the general format. + if (axis.isDatetimeAxis && tickPositionInfo) { + dateTimeLabelFormat = options.dateTimeLabelFormats[tickPositionInfo.higherRanks[pos] || tickPositionInfo.unitName]; + } + // set properties for access in render method + tick.isFirst = isFirst; + tick.isLast = isLast; + + // get the string + str = axis.labelFormatter.call({ + axis: axis, + chart: chart, + isFirst: isFirst, + isLast: isLast, + dateTimeLabelFormat: dateTimeLabelFormat, + value: axis.isLog ? correctFloat(lin2log(value)) : value + }); + + // prepare CSS + css = width && { width: mathMax(1, mathRound(width - 2 * (labelOptions.padding || 10))) + PX }; + css = extend(css, labelOptions.style); + + // first call + if (!defined(label)) { + attr = { + align: axis.labelAlign + }; + if (isNumber(labelOptions.rotation)) { + attr.rotation = labelOptions.rotation; + } + if (width && labelOptions.ellipsis) { + attr._clipHeight = axis.len / tickPositions.length; + } + + tick.label = + defined(str) && labelOptions.enabled ? + chart.renderer.text( + str, + 0, + 0, + labelOptions.useHTML + ) + .attr(attr) + // without position absolute, IE export sometimes is wrong + .css(css) + .add(axis.labelGroup) : + null; + + // update + } else if (label) { + label.attr({ + text: str + }) + .css(css); + } + }, + + /** + * Get the offset height or width of the label + */ + getLabelSize: function () { + var label = this.label, + axis = this.axis; + return label ? + label.getBBox()[axis.horiz ? 'height' : 'width'] : + 0; + }, + + /** + * Find how far the labels extend to the right and left of the tick's x position. Used for anti-collision + * detection with overflow logic. + */ + getLabelSides: function () { + var bBox = this.label.getBBox(), + axis = this.axis, + horiz = axis.horiz, + options = axis.options, + labelOptions = options.labels, + size = horiz ? bBox.width : bBox.height, + leftSide = horiz ? + labelOptions.x - size * { left: 0, center: 0.5, right: 1 }[axis.labelAlign] : + 0, + rightSide = horiz ? + size + leftSide : + size; + + return [leftSide, rightSide]; + }, + + /** + * Handle the label overflow by adjusting the labels to the left and right edge, or + * hide them if they collide into the neighbour label. + */ + handleOverflow: function (index, xy) { + var show = true, + axis = this.axis, + isFirst = this.isFirst, + isLast = this.isLast, + horiz = axis.horiz, + pxPos = horiz ? xy.x : xy.y, + reversed = axis.reversed, + tickPositions = axis.tickPositions, + sides = this.getLabelSides(), + leftSide = sides[0], + rightSide = sides[1], + axisLeft, + axisRight, + neighbour, + neighbourEdge, + line = this.label.line || 0, + labelEdge = axis.labelEdge, + justifyLabel = axis.justifyLabels && (isFirst || isLast), + justifyToPlot; + + // Hide it if it now overlaps the neighbour label + if (labelEdge[line] === UNDEFINED || pxPos + leftSide > labelEdge[line]) { + labelEdge[line] = pxPos + rightSide; + + } else if (!justifyLabel) { + show = false; + } + + if (justifyLabel) { + justifyToPlot = axis.justifyToPlot; + axisLeft = justifyToPlot ? axis.pos : 0; + axisRight = justifyToPlot ? axisLeft + axis.len : axis.chart.chartWidth; + + // Find the firsth neighbour on the same line + do { + index += (isFirst ? 1 : -1); + neighbour = axis.ticks[tickPositions[index]]; + } while (tickPositions[index] && (!neighbour || neighbour.label.line !== line)); + + neighbourEdge = neighbour && neighbour.label.xy && neighbour.label.xy.x + neighbour.getLabelSides()[isFirst ? 0 : 1]; + + if ((isFirst && !reversed) || (isLast && reversed)) { + // Is the label spilling out to the left of the plot area? + if (pxPos + leftSide < axisLeft) { + + // Align it to plot left + pxPos = axisLeft - leftSide; + + // Hide it if it now overlaps the neighbour label + if (neighbour && pxPos + rightSide > neighbourEdge) { + show = false; + } + } + + } else { + // Is the label spilling out to the right of the plot area? + if (pxPos + rightSide > axisRight) { + + // Align it to plot right + pxPos = axisRight - rightSide; + + // Hide it if it now overlaps the neighbour label + if (neighbour && pxPos + leftSide < neighbourEdge) { + show = false; + } + + } + } + + // Set the modified x position of the label + xy.x = pxPos; + } + return show; + }, + + /** + * Get the x and y position for ticks and labels + */ + getPosition: function (horiz, pos, tickmarkOffset, old) { + var axis = this.axis, + chart = axis.chart, + cHeight = (old && chart.oldChartHeight) || chart.chartHeight; + + return { + x: horiz ? + axis.translate(pos + tickmarkOffset, null, null, old) + axis.transB : + axis.left + axis.offset + (axis.opposite ? ((old && chart.oldChartWidth) || chart.chartWidth) - axis.right - axis.left : 0), + + y: horiz ? + cHeight - axis.bottom + axis.offset - (axis.opposite ? axis.height : 0) : + cHeight - axis.translate(pos + tickmarkOffset, null, null, old) - axis.transB + }; + + }, + + /** + * Get the x, y position of the tick label + */ + getLabelPosition: function (x, y, label, horiz, labelOptions, tickmarkOffset, index, step) { + var axis = this.axis, + transA = axis.transA, + reversed = axis.reversed, + staggerLines = axis.staggerLines, + baseline = axis.chart.renderer.fontMetrics(labelOptions.style.fontSize).b, + rotation = labelOptions.rotation; + + x = x + labelOptions.x - (tickmarkOffset && horiz ? + tickmarkOffset * transA * (reversed ? -1 : 1) : 0); + y = y + labelOptions.y - (tickmarkOffset && !horiz ? + tickmarkOffset * transA * (reversed ? 1 : -1) : 0); + + // Correct for rotation (#1764) + if (rotation && axis.side === 2) { + y -= baseline - baseline * mathCos(rotation * deg2rad); + } + + // Vertically centered + if (!defined(labelOptions.y) && !rotation) { // #1951 + y += baseline - label.getBBox().height / 2; + } + + // Correct for staggered labels + if (staggerLines) { + label.line = (index / (step || 1) % staggerLines); + y += label.line * (axis.labelOffset / staggerLines); + } + + return { + x: x, + y: y + }; + }, + + /** + * Extendible method to return the path of the marker + */ + getMarkPath: function (x, y, tickLength, tickWidth, horiz, renderer) { + return renderer.crispLine([ + M, + x, + y, + L, + x + (horiz ? 0 : -tickLength), + y + (horiz ? tickLength : 0) + ], tickWidth); + }, + + /** + * Put everything in place + * + * @param index {Number} + * @param old {Boolean} Use old coordinates to prepare an animation into new position + */ + render: function (index, old, opacity) { + var tick = this, + axis = tick.axis, + options = axis.options, + chart = axis.chart, + renderer = chart.renderer, + horiz = axis.horiz, + type = tick.type, + label = tick.label, + pos = tick.pos, + labelOptions = options.labels, + gridLine = tick.gridLine, + gridPrefix = type ? type + 'Grid' : 'grid', + tickPrefix = type ? type + 'Tick' : 'tick', + gridLineWidth = options[gridPrefix + 'LineWidth'], + gridLineColor = options[gridPrefix + 'LineColor'], + dashStyle = options[gridPrefix + 'LineDashStyle'], + tickLength = options[tickPrefix + 'Length'], + tickWidth = options[tickPrefix + 'Width'] || 0, + tickColor = options[tickPrefix + 'Color'], + tickPosition = options[tickPrefix + 'Position'], + gridLinePath, + mark = tick.mark, + markPath, + step = labelOptions.step, + attribs, + show = true, + tickmarkOffset = axis.tickmarkOffset, + xy = tick.getPosition(horiz, pos, tickmarkOffset, old), + x = xy.x, + y = xy.y, + reverseCrisp = ((horiz && x === axis.pos + axis.len) || (!horiz && y === axis.pos)) ? -1 : 1; // #1480, #1687 + + this.isActive = true; + + // create the grid line + if (gridLineWidth) { + gridLinePath = axis.getPlotLinePath(pos + tickmarkOffset, gridLineWidth * reverseCrisp, old, true); + + if (gridLine === UNDEFINED) { + attribs = { + stroke: gridLineColor, + 'stroke-width': gridLineWidth + }; + if (dashStyle) { + attribs.dashstyle = dashStyle; + } + if (!type) { + attribs.zIndex = 1; + } + if (old) { + attribs.opacity = 0; + } + tick.gridLine = gridLine = + gridLineWidth ? + renderer.path(gridLinePath) + .attr(attribs).add(axis.gridGroup) : + null; + } + + // If the parameter 'old' is set, the current call will be followed + // by another call, therefore do not do any animations this time + if (!old && gridLine && gridLinePath) { + gridLine[tick.isNew ? 'attr' : 'animate']({ + d: gridLinePath, + opacity: opacity + }); + } + } + + // create the tick mark + if (tickWidth && tickLength) { + + // negate the length + if (tickPosition === 'inside') { + tickLength = -tickLength; + } + if (axis.opposite) { + tickLength = -tickLength; + } + + markPath = tick.getMarkPath(x, y, tickLength, tickWidth * reverseCrisp, horiz, renderer); + if (mark) { // updating + mark.animate({ + d: markPath, + opacity: opacity + }); + } else { // first time + tick.mark = renderer.path( + markPath + ).attr({ + stroke: tickColor, + 'stroke-width': tickWidth, + opacity: opacity + }).add(axis.axisGroup); + } + } + + // the label is created on init - now move it into place + if (label && !isNaN(x)) { + label.xy = xy = tick.getLabelPosition(x, y, label, horiz, labelOptions, tickmarkOffset, index, step); + + // Apply show first and show last. If the tick is both first and last, it is + // a single centered tick, in which case we show the label anyway (#2100). + if ((tick.isFirst && !tick.isLast && !pick(options.showFirstLabel, 1)) || + (tick.isLast && !tick.isFirst && !pick(options.showLastLabel, 1))) { + show = false; + + // Handle label overflow and show or hide accordingly + } else if (!axis.isRadial && !labelOptions.step && !labelOptions.rotation && !old && opacity !== 0) { + show = tick.handleOverflow(index, xy); + } + + // apply step + if (step && index % step) { + // show those indices dividable by step + show = false; + } + + // Set the new position, and show or hide + if (show && !isNaN(xy.y)) { + xy.opacity = opacity; + label[tick.isNew ? 'attr' : 'animate'](xy); + tick.isNew = false; + } else { + label.attr('y', -9999); // #1338 + } + } + }, + + /** + * Destructor for the tick prototype + */ + destroy: function () { + destroyObjectProperties(this, this.axis); + } +}; + +/** + * The object wrapper for plot lines and plot bands + * @param {Object} options + */ +Highcharts.PlotLineOrBand = function (axis, options) { + this.axis = axis; + + if (options) { + this.options = options; + this.id = options.id; + } +}; + +Highcharts.PlotLineOrBand.prototype = { + + /** + * Render the plot line or plot band. If it is already existing, + * move it. + */ + render: function () { + var plotLine = this, + axis = plotLine.axis, + horiz = axis.horiz, + halfPointRange = (axis.pointRange || 0) / 2, + options = plotLine.options, + optionsLabel = options.label, + label = plotLine.label, + width = options.width, + to = options.to, + from = options.from, + isBand = defined(from) && defined(to), + value = options.value, + dashStyle = options.dashStyle, + svgElem = plotLine.svgElem, + path = [], + addEvent, + eventType, + xs, + ys, + x, + y, + color = options.color, + zIndex = options.zIndex, + events = options.events, + attribs = {}, + renderer = axis.chart.renderer; + + // logarithmic conversion + if (axis.isLog) { + from = log2lin(from); + to = log2lin(to); + value = log2lin(value); + } + + // plot line + if (width) { + path = axis.getPlotLinePath(value, width); + attribs = { + stroke: color, + 'stroke-width': width + }; + if (dashStyle) { + attribs.dashstyle = dashStyle; + } + } else if (isBand) { // plot band + + // keep within plot area + from = mathMax(from, axis.min - halfPointRange); + to = mathMin(to, axis.max + halfPointRange); + + path = axis.getPlotBandPath(from, to, options); + if (color) { + attribs.fill = color; + } + if (options.borderWidth) { + attribs.stroke = options.borderColor; + attribs['stroke-width'] = options.borderWidth; + } + } else { + return; + } + // zIndex + if (defined(zIndex)) { + attribs.zIndex = zIndex; + } + + // common for lines and bands + if (svgElem) { + if (path) { + svgElem.animate({ + d: path + }, null, svgElem.onGetPath); + } else { + svgElem.hide(); + svgElem.onGetPath = function () { + svgElem.show(); + }; + if (label) { + plotLine.label = label = label.destroy(); + } + } + } else if (path && path.length) { + plotLine.svgElem = svgElem = renderer.path(path) + .attr(attribs).add(); + + // events + if (events) { + addEvent = function (eventType) { + svgElem.on(eventType, function (e) { + events[eventType].apply(plotLine, [e]); + }); + }; + for (eventType in events) { + addEvent(eventType); + } + } + } + + // the plot band/line label + if (optionsLabel && defined(optionsLabel.text) && path && path.length && axis.width > 0 && axis.height > 0) { + // apply defaults + optionsLabel = merge({ + align: horiz && isBand && 'center', + x: horiz ? !isBand && 4 : 10, + verticalAlign : !horiz && isBand && 'middle', + y: horiz ? isBand ? 16 : 10 : isBand ? 6 : -4, + rotation: horiz && !isBand && 90 + }, optionsLabel); + + // add the SVG element + if (!label) { + attribs = { + align: optionsLabel.textAlign || optionsLabel.align, + rotation: optionsLabel.rotation + }; + if (defined(zIndex)) { + attribs.zIndex = zIndex; + } + plotLine.label = label = renderer.text( + optionsLabel.text, + 0, + 0, + optionsLabel.useHTML + ) + .attr(attribs) + .css(optionsLabel.style) + .add(); + } + + // get the bounding box and align the label + xs = [path[1], path[4], pick(path[6], path[1])]; + ys = [path[2], path[5], pick(path[7], path[2])]; + x = arrayMin(xs); + y = arrayMin(ys); + + label.align(optionsLabel, false, { + x: x, + y: y, + width: arrayMax(xs) - x, + height: arrayMax(ys) - y + }); + label.show(); + + } else if (label) { // move out of sight + label.hide(); + } + + // chainable + return plotLine; + }, + + /** + * Remove the plot line or band + */ + destroy: function () { + // remove it from the lookup + erase(this.axis.plotLinesAndBands, this); + + delete this.axis; + destroyObjectProperties(this); + } +}; + +/** + * Object with members for extending the Axis prototype + */ + +AxisPlotLineOrBandExtension = { + + /** + * Create the path for a plot band + */ + getPlotBandPath: function (from, to) { + var toPath = this.getPlotLinePath(to), + path = this.getPlotLinePath(from); + + if (path && toPath) { + path.push( + toPath[4], + toPath[5], + toPath[1], + toPath[2] + ); + } else { // outside the axis area + path = null; + } + + return path; + }, + + addPlotBand: function (options) { + this.addPlotBandOrLine(options, 'plotBands'); + }, + + addPlotLine: function (options) { + this.addPlotBandOrLine(options, 'plotLines'); + }, + + /** + * Add a plot band or plot line after render time + * + * @param options {Object} The plotBand or plotLine configuration object + */ + addPlotBandOrLine: function (options, coll) { + var obj = new Highcharts.PlotLineOrBand(this, options).render(), + userOptions = this.userOptions; + + if (obj) { // #2189 + // Add it to the user options for exporting and Axis.update + if (coll) { + userOptions[coll] = userOptions[coll] || []; + userOptions[coll].push(options); + } + this.plotLinesAndBands.push(obj); + } + + return obj; + }, + + /** + * Remove a plot band or plot line from the chart by id + * @param {Object} id + */ + removePlotBandOrLine: function (id) { + var plotLinesAndBands = this.plotLinesAndBands, + options = this.options, + userOptions = this.userOptions, + i = plotLinesAndBands.length; + while (i--) { + if (plotLinesAndBands[i].id === id) { + plotLinesAndBands[i].destroy(); + } + } + each([options.plotLines || [], userOptions.plotLines || [], options.plotBands || [], userOptions.plotBands || []], function (arr) { + i = arr.length; + while (i--) { + if (arr[i].id === id) { + erase(arr, arr[i]); + } + } + }); + } +}; + +/** + * Create a new axis object + * @param {Object} chart + * @param {Object} options + */ +function Axis() { + this.init.apply(this, arguments); +} + +Axis.prototype = { + + /** + * Default options for the X axis - the Y axis has extended defaults + */ + defaultOptions: { + // allowDecimals: null, + // alternateGridColor: null, + // categories: [], + 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: false, + gridLineColor: '#C0C0C0', + // gridLineDashStyle: 'solid', + // gridLineWidth: 0, + // reversed: false, + + labels: defaultLabelOptions, + // { step: null }, + lineColor: '#C0D0E0', + lineWidth: 1, + //linkedTo: null, + //max: undefined, + //min: undefined, + minPadding: 0.01, + maxPadding: 0.01, + //minRange: null, + minorGridLineColor: '#E0E0E0', + // minorGridLineDashStyle: null, + minorGridLineWidth: 1, + minorTickColor: '#A0A0A0', + //minorTickInterval: null, + minorTickLength: 2, + minorTickPosition: 'outside', // inside or outside + //minorTickWidth: 0, + //opposite: false, + //offset: 0, + //plotBands: [{ + // events: {}, + // zIndex: 1, + // labels: { align, x, verticalAlign, y, style, rotation, textAlign } + //}], + //plotLines: [{ + // events: {} + // dashStyle: {} + // zIndex: + // labels: { align, x, verticalAlign, y, style, rotation, textAlign } + //}], + //reversed: false, + // showFirstLabel: true, + // showLastLabel: true, + startOfWeek: 1, + startOnTick: false, + tickColor: '#C0D0E0', + //tickInterval: null, + tickLength: 10, + tickmarkPlacement: 'between', // on or between + tickPixelInterval: 100, + tickPosition: 'outside', + tickWidth: 1, + title: { + //text: null, + align: 'middle', // low, middle or high + //margin: 0 for horizontal, 10 for vertical axes, + //rotation: 0, + //side: 'outside', + style: { + color: '#707070' + } + //x: 0, + //y: 0 + }, + type: 'linear' // linear, logarithmic or datetime + }, + + /** + * This options set extends the defaultOptions for Y axes + */ + defaultYAxisOptions: { + endOnTick: true, + gridLineWidth: 1, + tickPixelInterval: 72, + showLastLabel: true, + labels: { + x: -8, + y: 3 + }, + lineWidth: 0, + maxPadding: 0.05, + minPadding: 0.05, + startOnTick: true, + tickWidth: 0, + title: { + rotation: 270, + text: 'Values' + }, + stackLabels: { + enabled: false, + //align: dynamic, + //y: dynamic, + //x: dynamic, + //verticalAlign: dynamic, + //textAlign: dynamic, + //rotation: 0, + formatter: function () { + return numberFormat(this.total, -1); + }, + style: defaultLabelOptions.style + } + }, + + /** + * These options extend the defaultOptions for left axes + */ + defaultLeftAxisOptions: { + labels: { + x: -15, + y: null + }, + title: { + rotation: 270 + } + }, + + /** + * These options extend the defaultOptions for right axes + */ + defaultRightAxisOptions: { + labels: { + x: 15, + y: null + }, + title: { + rotation: 90 + } + }, + + /** + * These options extend the defaultOptions for bottom axes + */ + defaultBottomAxisOptions: { + labels: { + x: 0, + y: 20 + // overflow: undefined, + // staggerLines: null + }, + title: { + rotation: 0 + } + }, + /** + * These options extend the defaultOptions for left axes + */ + defaultTopAxisOptions: { + labels: { + x: 0, + y: -15 + // overflow: undefined + // staggerLines: null + }, + title: { + rotation: 0 + } + }, + + /** + * Initialize the axis + */ + init: function (chart, userOptions) { + + + var isXAxis = userOptions.isX, + axis = this; + + // Flag, is the axis horizontal + axis.horiz = chart.inverted ? !isXAxis : isXAxis; + + // Flag, isXAxis + axis.isXAxis = isXAxis; + axis.coll = isXAxis ? 'xAxis' : 'yAxis'; + + axis.opposite = userOptions.opposite; // needed in setOptions + axis.side = userOptions.side || (axis.horiz ? + (axis.opposite ? 0 : 2) : // top : bottom + (axis.opposite ? 1 : 3)); // right : left + + axis.setOptions(userOptions); + + + var options = this.options, + type = options.type, + isDatetimeAxis = type === 'datetime'; + + axis.labelFormatter = options.labels.formatter || axis.defaultLabelFormatter; // can be overwritten by dynamic format + + + // Flag, stagger lines or not + axis.userOptions = userOptions; + + //axis.axisTitleMargin = UNDEFINED,// = options.title.margin, + axis.minPixelPadding = 0; + //axis.ignoreMinPadding = UNDEFINED; // can be set to true by a column or bar series + //axis.ignoreMaxPadding = UNDEFINED; + + axis.chart = chart; + axis.reversed = options.reversed; + axis.zoomEnabled = options.zoomEnabled !== false; + + // Initial categories + axis.categories = options.categories || type === 'category'; + axis.names = []; + + // Elements + //axis.axisGroup = UNDEFINED; + //axis.gridGroup = UNDEFINED; + //axis.axisTitle = UNDEFINED; + //axis.axisLine = UNDEFINED; + + // Shorthand types + axis.isLog = type === 'logarithmic'; + axis.isDatetimeAxis = isDatetimeAxis; + + // Flag, if axis is linked to another axis + axis.isLinked = defined(options.linkedTo); + // Linked axis. + //axis.linkedParent = UNDEFINED; + + // Tick positions + //axis.tickPositions = UNDEFINED; // array containing predefined positions + // Tick intervals + //axis.tickInterval = UNDEFINED; + //axis.minorTickInterval = UNDEFINED; + + axis.tickmarkOffset = (axis.categories && options.tickmarkPlacement === 'between') ? 0.5 : 0; + + // Major ticks + axis.ticks = {}; + axis.labelEdge = []; + // Minor ticks + axis.minorTicks = {}; + //axis.tickAmount = UNDEFINED; + + // List of plotLines/Bands + axis.plotLinesAndBands = []; + + // Alternate bands + axis.alternateBands = {}; + + // Axis metrics + //axis.left = UNDEFINED; + //axis.top = UNDEFINED; + //axis.width = UNDEFINED; + //axis.height = UNDEFINED; + //axis.bottom = UNDEFINED; + //axis.right = UNDEFINED; + //axis.transA = UNDEFINED; + //axis.transB = UNDEFINED; + //axis.oldTransA = UNDEFINED; + axis.len = 0; + //axis.oldMin = UNDEFINED; + //axis.oldMax = UNDEFINED; + //axis.oldUserMin = UNDEFINED; + //axis.oldUserMax = UNDEFINED; + //axis.oldAxisLength = UNDEFINED; + axis.minRange = axis.userMinRange = options.minRange || options.maxZoom; + axis.range = options.range; + axis.offset = options.offset || 0; + + + // Dictionary for stacks + axis.stacks = {}; + axis.oldStacks = {}; + + // Min and max in the data + //axis.dataMin = UNDEFINED, + //axis.dataMax = UNDEFINED, + + // The axis range + axis.max = null; + axis.min = null; + + // User set min and max + //axis.userMin = UNDEFINED, + //axis.userMax = UNDEFINED, + + // Crosshair options + axis.crosshair = pick(options.crosshair, splat(chart.options.tooltip.crosshairs)[isXAxis ? 0 : 1], false); + // Run Axis + + var eventType, + events = axis.options.events; + + // Register + if (inArray(axis, chart.axes) === -1) { // don't add it again on Axis.update() + if (isXAxis && !this.isColorAxis) { // #2713 + chart.axes.splice(chart.xAxis.length, 0, axis); + } else { + chart.axes.push(axis); + } + + chart[axis.coll].push(axis); + } + + axis.series = axis.series || []; // populated by Series + + // inverted charts have reversed xAxes as default + if (chart.inverted && isXAxis && axis.reversed === UNDEFINED) { + axis.reversed = true; + } + + axis.removePlotBand = axis.removePlotBandOrLine; + axis.removePlotLine = axis.removePlotBandOrLine; + + + // register event listeners + for (eventType in events) { + addEvent(axis, eventType, events[eventType]); + } + + // extend logarithmic axis + if (axis.isLog) { + axis.val2lin = log2lin; + axis.lin2val = lin2log; + } + }, + + /** + * Merge and set options + */ + setOptions: function (userOptions) { + this.options = merge( + this.defaultOptions, + this.isXAxis ? {} : this.defaultYAxisOptions, + [this.defaultTopAxisOptions, this.defaultRightAxisOptions, + this.defaultBottomAxisOptions, this.defaultLeftAxisOptions][this.side], + merge( + defaultOptions[this.coll], // if set in setOptions (#1053) + userOptions + ) + ); + }, + + /** + * The default label formatter. The context is a special config object for the label. + */ + defaultLabelFormatter: function () { + var axis = this.axis, + value = this.value, + categories = axis.categories, + dateTimeLabelFormat = this.dateTimeLabelFormat, + numericSymbols = defaultOptions.lang.numericSymbols, + i = numericSymbols && numericSymbols.length, + multi, + ret, + formatOption = axis.options.labels.format, + + // make sure the same symbol is added for all labels on a linear axis + numericSymbolDetector = axis.isLog ? value : axis.tickInterval; + + if (formatOption) { + ret = format(formatOption, this); + + } else if (categories) { + ret = value; + + } else if (dateTimeLabelFormat) { // datetime axis + ret = dateFormat(dateTimeLabelFormat, value); + + } else if (i && numericSymbolDetector >= 1000) { + // Decide whether we should add a numeric symbol like k (thousands) or M (millions). + // If we are to enable this in tooltip or other places as well, we can move this + // logic to the numberFormatter and enable it by a parameter. + while (i-- && ret === UNDEFINED) { + multi = Math.pow(1000, i + 1); + if (numericSymbolDetector >= multi && numericSymbols[i] !== null) { + ret = numberFormat(value / multi, -1) + numericSymbols[i]; + } + } + } + + if (ret === UNDEFINED) { + if (mathAbs(value) >= 10000) { // add thousands separators + ret = numberFormat(value, 0); + + } else { // small numbers + ret = numberFormat(value, -1, UNDEFINED, ''); // #2466 + } + } + + return ret; + }, + + /** + * Get the minimum and maximum for the series of each axis + */ + getSeriesExtremes: function () { + var axis = this, + chart = axis.chart; + + axis.hasVisibleSeries = false; + + // reset dataMin and dataMax in case we're redrawing + axis.dataMin = axis.dataMax = null; + + if (axis.buildStacks) { + axis.buildStacks(); + } + + // loop through this axis' series + each(axis.series, function (series) { + + if (series.visible || !chart.options.chart.ignoreHiddenSeries) { + + var seriesOptions = series.options, + xData, + threshold = seriesOptions.threshold, + seriesDataMin, + seriesDataMax; + + axis.hasVisibleSeries = true; + + // Validate threshold in logarithmic axes + if (axis.isLog && threshold <= 0) { + threshold = null; + } + + // Get dataMin and dataMax for X axes + if (axis.isXAxis) { + xData = series.xData; + if (xData.length) { + axis.dataMin = mathMin(pick(axis.dataMin, xData[0]), arrayMin(xData)); + axis.dataMax = mathMax(pick(axis.dataMax, xData[0]), arrayMax(xData)); + } + + // Get dataMin and dataMax for Y axes, as well as handle stacking and processed data + } else { + + // Get this particular series extremes + series.getExtremes(); + seriesDataMax = series.dataMax; + seriesDataMin = series.dataMin; + + // Get the dataMin and dataMax so far. If percentage is used, the min and max are + // always 0 and 100. If seriesDataMin and seriesDataMax is null, then series + // doesn't have active y data, we continue with nulls + if (defined(seriesDataMin) && defined(seriesDataMax)) { + axis.dataMin = mathMin(pick(axis.dataMin, seriesDataMin), seriesDataMin); + axis.dataMax = mathMax(pick(axis.dataMax, seriesDataMax), seriesDataMax); + } + + // Adjust to threshold + if (defined(threshold)) { + if (axis.dataMin >= threshold) { + axis.dataMin = threshold; + axis.ignoreMinPadding = true; + } else if (axis.dataMax < threshold) { + axis.dataMax = threshold; + axis.ignoreMaxPadding = true; + } + } + } + } + }); + }, + + /** + * Translate from axis value to pixel position on the chart, or back + * + */ + translate: function (val, backwards, cvsCoord, old, handleLog, pointPlacement) { + var axis = this, + sign = 1, + cvsOffset = 0, + localA = old ? axis.oldTransA : axis.transA, + localMin = old ? axis.oldMin : axis.min, + returnValue, + minPixelPadding = axis.minPixelPadding, + postTranslate = (axis.options.ordinal || (axis.isLog && handleLog)) && axis.lin2val; + + if (!localA) { + localA = axis.transA; + } + + // In vertical axes, the canvas coordinates start from 0 at the top like in + // SVG. + if (cvsCoord) { + sign *= -1; // canvas coordinates inverts the value + cvsOffset = axis.len; + } + + // Handle reversed axis + if (axis.reversed) { + sign *= -1; + cvsOffset -= sign * (axis.sector || axis.len); + } + + // From pixels to value + if (backwards) { // reverse translation + + val = val * sign + cvsOffset; + val -= minPixelPadding; + returnValue = val / localA + localMin; // from chart pixel to value + if (postTranslate) { // log and ordinal axes + returnValue = axis.lin2val(returnValue); + } + + // From value to pixels + } else { + if (postTranslate) { // log and ordinal axes + val = axis.val2lin(val); + } + if (pointPlacement === 'between') { + pointPlacement = 0.5; + } + returnValue = sign * (val - localMin) * localA + cvsOffset + (sign * minPixelPadding) + + (isNumber(pointPlacement) ? localA * pointPlacement * axis.pointRange : 0); + } + + return returnValue; + }, + + /** + * Utility method to translate an axis value to pixel position. + * @param {Number} value A value in terms of axis units + * @param {Boolean} paneCoordinates Whether to return the pixel coordinate relative to the chart + * or just the axis/pane itself. + */ + toPixels: function (value, paneCoordinates) { + return this.translate(value, false, !this.horiz, null, true) + (paneCoordinates ? 0 : this.pos); + }, + + /* + * Utility method to translate a pixel position in to an axis value + * @param {Number} pixel The pixel value coordinate + * @param {Boolean} paneCoordiantes Whether the input pixel is relative to the chart or just the + * axis/pane itself. + */ + toValue: function (pixel, paneCoordinates) { + return this.translate(pixel - (paneCoordinates ? 0 : this.pos), true, !this.horiz, null, true); + }, + + /** + * Create the path for a plot line that goes from the given value on + * this axis, across the plot to the opposite side + * @param {Number} value + * @param {Number} lineWidth Used for calculation crisp line + * @param {Number] old Use old coordinates (for resizing and rescaling) + */ + getPlotLinePath: function (value, lineWidth, old, force, translatedValue) { + var axis = this, + chart = axis.chart, + axisLeft = axis.left, + axisTop = axis.top, + x1, + y1, + x2, + y2, + cHeight = (old && chart.oldChartHeight) || chart.chartHeight, + cWidth = (old && chart.oldChartWidth) || chart.chartWidth, + skip, + transB = axis.transB; + + translatedValue = pick(translatedValue, axis.translate(value, null, null, old)); + x1 = x2 = mathRound(translatedValue + transB); + y1 = y2 = mathRound(cHeight - translatedValue - transB); + + if (isNaN(translatedValue)) { // no min or max + skip = true; + + } else if (axis.horiz) { + y1 = axisTop; + y2 = cHeight - axis.bottom; + if (x1 < axisLeft || x1 > axisLeft + axis.width) { + skip = true; + } + } else { + x1 = axisLeft; + x2 = cWidth - axis.right; + + if (y1 < axisTop || y1 > axisTop + axis.height) { + skip = true; + } + } + return skip && !force ? + null : + chart.renderer.crispLine([M, x1, y1, L, x2, y2], lineWidth || 1); + }, + + /** + * Set the tick positions of a linear axis to round values like whole tens or every five. + */ + getLinearTickPositions: function (tickInterval, min, max) { + var pos, + lastPos, + roundedMin = correctFloat(mathFloor(min / tickInterval) * tickInterval), + roundedMax = correctFloat(mathCeil(max / tickInterval) * tickInterval), + tickPositions = []; + + // For single points, add a tick regardless of the relative position (#2662) + if (min === max && isNumber(min)) { + return [min]; + } + + // Populate the intermediate values + pos = roundedMin; + while (pos <= roundedMax) { + + // Place the tick on the rounded value + tickPositions.push(pos); + + // Always add the raw tickInterval, not the corrected one. + pos = correctFloat(pos + tickInterval); + + // If the interval is not big enough in the current min - max range to actually increase + // the loop variable, we need to break out to prevent endless loop. Issue #619 + if (pos === lastPos) { + break; + } + + // Record the last value + lastPos = pos; + } + return tickPositions; + }, + + /** + * Return the minor tick positions. For logarithmic axes, reuse the same logic + * as for major ticks. + */ + getMinorTickPositions: function () { + var axis = this, + options = axis.options, + tickPositions = axis.tickPositions, + minorTickInterval = axis.minorTickInterval, + minorTickPositions = [], + pos, + i, + len; + + if (axis.isLog) { + len = tickPositions.length; + for (i = 1; i < len; i++) { + minorTickPositions = minorTickPositions.concat( + axis.getLogTickPositions(minorTickInterval, tickPositions[i - 1], tickPositions[i], true) + ); + } + } else if (axis.isDatetimeAxis && options.minorTickInterval === 'auto') { // #1314 + minorTickPositions = minorTickPositions.concat( + axis.getTimeTicks( + axis.normalizeTimeTickInterval(minorTickInterval), + axis.min, + axis.max, + options.startOfWeek + ) + ); + if (minorTickPositions[0] < axis.min) { + minorTickPositions.shift(); + } + } else { + for (pos = axis.min + (tickPositions[0] - axis.min) % minorTickInterval; pos <= axis.max; pos += minorTickInterval) { + minorTickPositions.push(pos); + } + } + return minorTickPositions; + }, + + /** + * Adjust the min and max for the minimum range. Keep in mind that the series data is + * not yet processed, so we don't have information on data cropping and grouping, or + * updated axis.pointRange or series.pointRange. The data can't be processed until + * we have finally established min and max. + */ + adjustForMinRange: function () { + var axis = this, + options = axis.options, + min = axis.min, + max = axis.max, + zoomOffset, + spaceAvailable = axis.dataMax - axis.dataMin >= axis.minRange, + closestDataRange, + i, + distance, + xData, + loopLength, + minArgs, + maxArgs; + + // Set the automatic minimum range based on the closest point distance + if (axis.isXAxis && axis.minRange === UNDEFINED && !axis.isLog) { + + if (defined(options.min) || defined(options.max)) { + axis.minRange = null; // don't do this again + + } else { + + // Find the closest distance between raw data points, as opposed to + // closestPointRange that applies to processed points (cropped and grouped) + each(axis.series, function (series) { + xData = series.xData; + loopLength = series.xIncrement ? 1 : xData.length - 1; + for (i = loopLength; i > 0; i--) { + distance = xData[i] - xData[i - 1]; + if (closestDataRange === UNDEFINED || distance < closestDataRange) { + closestDataRange = distance; + } + } + }); + axis.minRange = mathMin(closestDataRange * 5, axis.dataMax - axis.dataMin); + } + } + + // if minRange is exceeded, adjust + if (max - min < axis.minRange) { + var minRange = axis.minRange; + zoomOffset = (minRange - max + min) / 2; + + // if min and max options have been set, don't go beyond it + minArgs = [min - zoomOffset, pick(options.min, min - zoomOffset)]; + if (spaceAvailable) { // if space is available, stay within the data range + minArgs[2] = axis.dataMin; + } + min = arrayMax(minArgs); + + maxArgs = [min + minRange, pick(options.max, min + minRange)]; + if (spaceAvailable) { // if space is availabe, stay within the data range + maxArgs[2] = axis.dataMax; + } + + max = arrayMin(maxArgs); + + // now if the max is adjusted, adjust the min back + if (max - min < minRange) { + minArgs[0] = max - minRange; + minArgs[1] = pick(options.min, max - minRange); + min = arrayMax(minArgs); + } + } + + // Record modified extremes + axis.min = min; + axis.max = max; + }, + + /** + * Update translation information + */ + setAxisTranslation: function (saveOld) { + var axis = this, + range = axis.max - axis.min, + pointRange = axis.axisPointRange || 0, + closestPointRange, + minPointOffset = 0, + pointRangePadding = 0, + linkedParent = axis.linkedParent, + ordinalCorrection, + hasCategories = !!axis.categories, + transA = axis.transA; + + // Adjust translation for padding. Y axis with categories need to go through the same (#1784). + if (axis.isXAxis || hasCategories || pointRange) { + if (linkedParent) { + minPointOffset = linkedParent.minPointOffset; + pointRangePadding = linkedParent.pointRangePadding; + + } else { + each(axis.series, function (series) { + var seriesPointRange = hasCategories ? 1 : (axis.isXAxis ? series.pointRange : (axis.axisPointRange || 0)), // #2806 + pointPlacement = series.options.pointPlacement, + seriesClosestPointRange = series.closestPointRange; + + if (seriesPointRange > range) { // #1446 + seriesPointRange = 0; + } + pointRange = mathMax(pointRange, seriesPointRange); + + // minPointOffset is the value padding to the left of the axis in order to make + // room for points with a pointRange, typically columns. When the pointPlacement option + // is 'between' or 'on', this padding does not apply. + minPointOffset = mathMax( + minPointOffset, + isString(pointPlacement) ? 0 : seriesPointRange / 2 + ); + + // Determine the total padding needed to the length of the axis to make room for the + // pointRange. If the series' pointPlacement is 'on', no padding is added. + pointRangePadding = mathMax( + pointRangePadding, + pointPlacement === 'on' ? 0 : seriesPointRange + ); + + // Set the closestPointRange + if (!series.noSharedTooltip && defined(seriesClosestPointRange)) { + closestPointRange = defined(closestPointRange) ? + mathMin(closestPointRange, seriesClosestPointRange) : + seriesClosestPointRange; + } + }); + } + + // Record minPointOffset and pointRangePadding + ordinalCorrection = axis.ordinalSlope && closestPointRange ? axis.ordinalSlope / closestPointRange : 1; // #988, #1853 + axis.minPointOffset = minPointOffset = minPointOffset * ordinalCorrection; + axis.pointRangePadding = pointRangePadding = pointRangePadding * ordinalCorrection; + + // pointRange means the width reserved for each point, like in a column chart + axis.pointRange = mathMin(pointRange, range); + + // closestPointRange means the closest distance between points. In columns + // it is mostly equal to pointRange, but in lines pointRange is 0 while closestPointRange + // is some other value + axis.closestPointRange = closestPointRange; + } + + // Secondary values + if (saveOld) { + axis.oldTransA = transA; + } + axis.translationSlope = axis.transA = transA = axis.len / ((range + pointRangePadding) || 1); + axis.transB = axis.horiz ? axis.left : axis.bottom; // translation addend + axis.minPixelPadding = transA * minPointOffset; + }, + + /** + * Set the tick positions to round values and optionally extend the extremes + * to the nearest tick + */ + setTickPositions: function (secondPass) { + var axis = this, + chart = axis.chart, + options = axis.options, + isLog = axis.isLog, + isDatetimeAxis = axis.isDatetimeAxis, + isXAxis = axis.isXAxis, + isLinked = axis.isLinked, + tickPositioner = axis.options.tickPositioner, + maxPadding = options.maxPadding, + minPadding = options.minPadding, + length, + linkedParentExtremes, + tickIntervalOption = options.tickInterval, + minTickIntervalOption = options.minTickInterval, + tickPixelIntervalOption = options.tickPixelInterval, + tickPositions, + keepTwoTicksOnly, + categories = axis.categories; + + // linked axis gets the extremes from the parent axis + if (isLinked) { + axis.linkedParent = chart[axis.coll][options.linkedTo]; + linkedParentExtremes = axis.linkedParent.getExtremes(); + axis.min = pick(linkedParentExtremes.min, linkedParentExtremes.dataMin); + axis.max = pick(linkedParentExtremes.max, linkedParentExtremes.dataMax); + if (options.type !== axis.linkedParent.options.type) { + error(11, 1); // Can't link axes of different type + } + } else { // initial min and max from the extreme data values + axis.min = pick(axis.userMin, options.min, axis.dataMin); + axis.max = pick(axis.userMax, options.max, axis.dataMax); + } + + if (isLog) { + if (!secondPass && mathMin(axis.min, pick(axis.dataMin, axis.min)) <= 0) { // #978 + error(10, 1); // Can't plot negative values on log axis + } + axis.min = correctFloat(log2lin(axis.min)); // correctFloat cures #934 + axis.max = correctFloat(log2lin(axis.max)); + } + + // handle zoomed range + if (axis.range && defined(axis.max)) { + axis.userMin = axis.min = mathMax(axis.min, axis.max - axis.range); // #618 + axis.userMax = axis.max; + + axis.range = null; // don't use it when running setExtremes + } + + // Hook for adjusting this.min and this.max. Used by bubble series. + if (axis.beforePadding) { + axis.beforePadding(); + } + + // adjust min and max for the minimum range + axis.adjustForMinRange(); + + // Pad the values to get clear of the chart's edges. To avoid tickInterval taking the padding + // into account, we do this after computing tick interval (#1337). + if (!categories && !axis.axisPointRange && !axis.usePercentage && !isLinked && defined(axis.min) && defined(axis.max)) { + length = axis.max - axis.min; + if (length) { + if (!defined(options.min) && !defined(axis.userMin) && minPadding && (axis.dataMin < 0 || !axis.ignoreMinPadding)) { + axis.min -= length * minPadding; + } + if (!defined(options.max) && !defined(axis.userMax) && maxPadding && (axis.dataMax > 0 || !axis.ignoreMaxPadding)) { + axis.max += length * maxPadding; + } + } + } + + // Stay within floor and ceiling + if (isNumber(options.floor)) { + axis.min = mathMax(axis.min, options.floor); + } + if (isNumber(options.ceiling)) { + axis.max = mathMin(axis.max, options.ceiling); + } + + // get tickInterval + if (axis.min === axis.max || axis.min === undefined || axis.max === undefined) { + axis.tickInterval = 1; + } else if (isLinked && !tickIntervalOption && + tickPixelIntervalOption === axis.linkedParent.options.tickPixelInterval) { + axis.tickInterval = axis.linkedParent.tickInterval; + } else { + axis.tickInterval = pick( + tickIntervalOption, + categories ? // for categoried axis, 1 is default, for linear axis use tickPix + 1 : + // don't let it be more than the data range + (axis.max - axis.min) * tickPixelIntervalOption / mathMax(axis.len, tickPixelIntervalOption) + ); + // For squished axes, set only two ticks + if (!defined(tickIntervalOption) && axis.len < tickPixelIntervalOption && !this.isRadial && + !this.isLog && !categories && options.startOnTick && options.endOnTick) { + keepTwoTicksOnly = true; + axis.tickInterval /= 4; // tick extremes closer to the real values + } + } + + // Now we're finished detecting min and max, crop and group series data. This + // is in turn needed in order to find tick positions in ordinal axes. + if (isXAxis && !secondPass) { + each(axis.series, function (series) { + series.processData(axis.min !== axis.oldMin || axis.max !== axis.oldMax); + }); + } + + // set the translation factor used in translate function + axis.setAxisTranslation(true); + + // hook for ordinal axes and radial axes + if (axis.beforeSetTickPositions) { + axis.beforeSetTickPositions(); + } + + // hook for extensions, used in Highstock ordinal axes + if (axis.postProcessTickInterval) { + axis.tickInterval = axis.postProcessTickInterval(axis.tickInterval); + } + + // In column-like charts, don't cramp in more ticks than there are points (#1943) + if (axis.pointRange) { + axis.tickInterval = mathMax(axis.pointRange, axis.tickInterval); + } + + // Before normalizing the tick interval, handle minimum tick interval. This applies only if tickInterval is not defined. + if (!tickIntervalOption && axis.tickInterval < minTickIntervalOption) { + axis.tickInterval = minTickIntervalOption; + } + + // for linear axes, get magnitude and normalize the interval + if (!isDatetimeAxis && !isLog) { // linear + if (!tickIntervalOption) { + axis.tickInterval = normalizeTickInterval(axis.tickInterval, null, getMagnitude(axis.tickInterval), options); + } + } + + // get minorTickInterval + axis.minorTickInterval = options.minorTickInterval === 'auto' && axis.tickInterval ? + axis.tickInterval / 5 : options.minorTickInterval; + + // find the tick positions + axis.tickPositions = tickPositions = options.tickPositions ? + [].concat(options.tickPositions) : // Work on a copy (#1565) + (tickPositioner && tickPositioner.apply(axis, [axis.min, axis.max])); + if (!tickPositions) { + + // Too many ticks + if (!axis.ordinalPositions && (axis.max - axis.min) / axis.tickInterval > mathMax(2 * axis.len, 200)) { + error(19, true); + } + + if (isDatetimeAxis) { + tickPositions = axis.getTimeTicks( + axis.normalizeTimeTickInterval(axis.tickInterval, options.units), + axis.min, + axis.max, + options.startOfWeek, + axis.ordinalPositions, + axis.closestPointRange, + true + ); + } else if (isLog) { + tickPositions = axis.getLogTickPositions(axis.tickInterval, axis.min, axis.max); + } else { + tickPositions = axis.getLinearTickPositions(axis.tickInterval, axis.min, axis.max); + } + + if (keepTwoTicksOnly) { + tickPositions.splice(1, tickPositions.length - 2); + } + + axis.tickPositions = tickPositions; + } + + if (!isLinked) { + + // reset min/max or remove extremes based on start/end on tick + var roundedMin = tickPositions[0], + roundedMax = tickPositions[tickPositions.length - 1], + minPointOffset = axis.minPointOffset || 0, + singlePad; + + if (options.startOnTick) { + axis.min = roundedMin; + } else if (axis.min - minPointOffset > roundedMin) { + tickPositions.shift(); + } + + if (options.endOnTick) { + axis.max = roundedMax; + } else if (axis.max + minPointOffset < roundedMax) { + tickPositions.pop(); + } + + // When there is only one point, or all points have the same value on this axis, then min + // and max are equal and tickPositions.length is 0 or 1. In this case, add some padding + // in order to center the point, but leave it with one tick. #1337. + if (tickPositions.length === 1) { + singlePad = mathAbs(axis.max) > 10e12 ? 1 : 0.001; // The lowest possible number to avoid extra padding on columns (#2619, #2846) + axis.min -= singlePad; + axis.max += singlePad; + } + } + }, + + /** + * Set the max ticks of either the x and y axis collection + */ + setMaxTicks: function () { + + var chart = this.chart, + maxTicks = chart.maxTicks || {}, + tickPositions = this.tickPositions, + key = this._maxTicksKey = [this.coll, this.pos, this.len].join('-'); + + if (!this.isLinked && !this.isDatetimeAxis && tickPositions && tickPositions.length > (maxTicks[key] || 0) && this.options.alignTicks !== false) { + maxTicks[key] = tickPositions.length; + } + chart.maxTicks = maxTicks; + }, + + /** + * When using multiple axes, adjust the number of ticks to match the highest + * number of ticks in that group + */ + adjustTickAmount: function () { + var axis = this, + chart = axis.chart, + key = axis._maxTicksKey, + tickPositions = axis.tickPositions, + maxTicks = chart.maxTicks; + + if (maxTicks && maxTicks[key] && !axis.isDatetimeAxis && !axis.categories && !axis.isLinked && + axis.options.alignTicks !== false && this.min !== UNDEFINED) { + var oldTickAmount = axis.tickAmount, + calculatedTickAmount = tickPositions.length, + tickAmount; + + // set the axis-level tickAmount to use below + axis.tickAmount = tickAmount = maxTicks[key]; + + if (calculatedTickAmount < tickAmount) { + while (tickPositions.length < tickAmount) { + tickPositions.push(correctFloat( + tickPositions[tickPositions.length - 1] + axis.tickInterval + )); + } + axis.transA *= (calculatedTickAmount - 1) / (tickAmount - 1); + axis.max = tickPositions[tickPositions.length - 1]; + + } + if (defined(oldTickAmount) && tickAmount !== oldTickAmount) { + axis.isDirty = true; + } + } + }, + + /** + * Set the scale based on data min and max, user set min and max or options + * + */ + setScale: function () { + var axis = this, + stacks = axis.stacks, + type, + i, + isDirtyData, + isDirtyAxisLength; + + axis.oldMin = axis.min; + axis.oldMax = axis.max; + axis.oldAxisLength = axis.len; + + // set the new axisLength + axis.setAxisSize(); + //axisLength = horiz ? axisWidth : axisHeight; + isDirtyAxisLength = axis.len !== axis.oldAxisLength; + + // is there new data? + each(axis.series, function (series) { + if (series.isDirtyData || series.isDirty || + series.xAxis.isDirty) { // when x axis is dirty, we need new data extremes for y as well + isDirtyData = true; + } + }); + + // do we really need to go through all this? + if (isDirtyAxisLength || isDirtyData || axis.isLinked || axis.forceRedraw || + axis.userMin !== axis.oldUserMin || axis.userMax !== axis.oldUserMax) { + + // reset stacks + if (!axis.isXAxis) { + for (type in stacks) { + for (i in stacks[type]) { + stacks[type][i].total = null; + stacks[type][i].cum = 0; + } + } + } + + axis.forceRedraw = false; + + // get data extremes if needed + axis.getSeriesExtremes(); + + // get fixed positions based on tickInterval + axis.setTickPositions(); + + // record old values to decide whether a rescale is necessary later on (#540) + axis.oldUserMin = axis.userMin; + axis.oldUserMax = axis.userMax; + + // Mark as dirty if it is not already set to dirty and extremes have changed. #595. + if (!axis.isDirty) { + axis.isDirty = isDirtyAxisLength || axis.min !== axis.oldMin || axis.max !== axis.oldMax; + } + } else if (!axis.isXAxis) { + if (axis.oldStacks) { + stacks = axis.stacks = axis.oldStacks; + } + + // reset stacks + for (type in stacks) { + for (i in stacks[type]) { + stacks[type][i].cum = stacks[type][i].total; + } + } + } + + // Set the maximum tick amount + axis.setMaxTicks(); + }, + + /** + * Set the extremes and optionally redraw + * @param {Number} newMin + * @param {Number} newMax + * @param {Boolean} redraw + * @param {Boolean|Object} animation Whether to apply animation, and optionally animation + * configuration + * @param {Object} eventArguments + * + */ + setExtremes: function (newMin, newMax, redraw, animation, eventArguments) { + var axis = this, + chart = axis.chart; + + redraw = pick(redraw, true); // defaults to true + + // Extend the arguments with min and max + eventArguments = extend(eventArguments, { + min: newMin, + max: newMax + }); + + // Fire the event + fireEvent(axis, 'setExtremes', eventArguments, function () { // the default event handler + + axis.userMin = newMin; + axis.userMax = newMax; + axis.eventArgs = eventArguments; + + // Mark for running afterSetExtremes + axis.isDirtyExtremes = true; + + // redraw + if (redraw) { + chart.redraw(animation); + } + }); + }, + + /** + * Overridable method for zooming chart. Pulled out in a separate method to allow overriding + * in stock charts. + */ + zoom: function (newMin, newMax) { + var dataMin = this.dataMin, + dataMax = this.dataMax, + options = this.options; + + // Prevent pinch zooming out of range. Check for defined is for #1946. #1734. + if (!this.allowZoomOutside) { + if (defined(dataMin) && newMin <= mathMin(dataMin, pick(options.min, dataMin))) { + newMin = UNDEFINED; + } + if (defined(dataMax) && newMax >= mathMax(dataMax, pick(options.max, dataMax))) { + newMax = UNDEFINED; + } + } + + // In full view, displaying the reset zoom button is not required + this.displayBtn = newMin !== UNDEFINED || newMax !== UNDEFINED; + + // Do it + this.setExtremes( + newMin, + newMax, + false, + UNDEFINED, + { trigger: 'zoom' } + ); + return true; + }, + + /** + * Update the axis metrics + */ + setAxisSize: function () { + var chart = this.chart, + options = this.options, + offsetLeft = options.offsetLeft || 0, + offsetRight = options.offsetRight || 0, + horiz = this.horiz, + width = pick(options.width, chart.plotWidth - offsetLeft + offsetRight), + height = pick(options.height, chart.plotHeight), + top = pick(options.top, chart.plotTop), + left = pick(options.left, chart.plotLeft + offsetLeft), + percentRegex = /%$/; // docs + + // Check for percentage based input values + if (percentRegex.test(height)) { + height = parseInt(height, 10) / 100 * chart.plotHeight; + } + if (percentRegex.test(top)) { + top = parseInt(top, 10) / 100 * chart.plotHeight + chart.plotTop; + } + + // Expose basic values to use in Series object and navigator + this.left = left; + this.top = top; + this.width = width; + this.height = height; + this.bottom = chart.chartHeight - height - top; + this.right = chart.chartWidth - width - left; + + // Direction agnostic properties + this.len = mathMax(horiz ? width : height, 0); // mathMax fixes #905 + this.pos = horiz ? left : top; // distance from SVG origin + }, + + /** + * Get the actual axis extremes + */ + getExtremes: function () { + var axis = this, + isLog = axis.isLog; + + return { + min: isLog ? correctFloat(lin2log(axis.min)) : axis.min, + max: isLog ? correctFloat(lin2log(axis.max)) : axis.max, + dataMin: axis.dataMin, + dataMax: axis.dataMax, + userMin: axis.userMin, + userMax: axis.userMax + }; + }, + + /** + * Get the zero plane either based on zero or on the min or max value. + * Used in bar and area plots + */ + getThreshold: function (threshold) { + var axis = this, + isLog = axis.isLog; + + var realMin = isLog ? lin2log(axis.min) : axis.min, + realMax = isLog ? lin2log(axis.max) : axis.max; + + if (realMin > threshold || threshold === null) { + threshold = realMin; + } else if (realMax < threshold) { + threshold = realMax; + } + + return axis.translate(threshold, 0, 1, 0, 1); + }, + + /** + * Compute auto alignment for the axis label based on which side the axis is on + * and the given rotation for the label + */ + autoLabelAlign: function (rotation) { + var ret, + angle = (pick(rotation, 0) - (this.side * 90) + 720) % 360; + + if (angle > 15 && angle < 165) { + ret = 'right'; + } else if (angle > 195 && angle < 345) { + ret = 'left'; + } else { + ret = 'center'; + } + return ret; + }, + + /** + * Render the tick labels to a preliminary position to get their sizes + */ + getOffset: function () { + var axis = this, + chart = axis.chart, + renderer = chart.renderer, + options = axis.options, + tickPositions = axis.tickPositions, + ticks = axis.ticks, + horiz = axis.horiz, + side = axis.side, + invertedSide = chart.inverted ? [1, 0, 3, 2][side] : side, + hasData, + showAxis, + titleOffset = 0, + titleOffsetOption, + titleMargin = 0, + axisTitleOptions = options.title, + labelOptions = options.labels, + labelOffset = 0, // reset + axisOffset = chart.axisOffset, + clipOffset = chart.clipOffset, + directionFactor = [-1, 1, 1, -1][side], + n, + i, + autoStaggerLines = 1, + maxStaggerLines = pick(labelOptions.maxStaggerLines, 5), + sortedPositions, + lastRight, + overlap, + pos, + bBox, + x, + w, + lineNo, + lineHeightCorrection = side === 2 ? renderer.fontMetrics(labelOptions.style.fontSize).b : 0; + + // For reuse in Axis.render + axis.hasData = hasData = (axis.hasVisibleSeries || (defined(axis.min) && defined(axis.max) && !!tickPositions)); + axis.showAxis = showAxis = hasData || pick(options.showEmpty, true); + + // Set/reset staggerLines + axis.staggerLines = axis.horiz && labelOptions.staggerLines; + + // Create the axisGroup and gridGroup elements on first iteration + if (!axis.axisGroup) { + axis.gridGroup = renderer.g('grid') + .attr({ zIndex: options.gridZIndex || 1 }) + .add(); + axis.axisGroup = renderer.g('axis') + .attr({ zIndex: options.zIndex || 2 }) + .add(); + axis.labelGroup = renderer.g('axis-labels') + .attr({ zIndex: labelOptions.zIndex || 7 }) + .addClass(PREFIX + axis.coll.toLowerCase() + '-labels') + .add(); + } + + if (hasData || axis.isLinked) { + + // Set the explicit or automatic label alignment + axis.labelAlign = pick(labelOptions.align || axis.autoLabelAlign(labelOptions.rotation)); + + // Generate ticks + each(tickPositions, function (pos) { + if (!ticks[pos]) { + ticks[pos] = new Tick(axis, pos); + } else { + ticks[pos].addLabel(); // update labels depending on tick interval + } + }); + + // Handle automatic stagger lines + if (axis.horiz && !axis.staggerLines && maxStaggerLines && !labelOptions.rotation) { + sortedPositions = axis.reversed ? [].concat(tickPositions).reverse() : tickPositions; + while (autoStaggerLines < maxStaggerLines) { + lastRight = []; + overlap = false; + + for (i = 0; i < sortedPositions.length; i++) { + pos = sortedPositions[i]; + bBox = ticks[pos].label && ticks[pos].label.getBBox(); + w = bBox ? bBox.width : 0; + lineNo = i % autoStaggerLines; + + if (w) { + x = axis.translate(pos); // don't handle log + if (lastRight[lineNo] !== UNDEFINED && x < lastRight[lineNo]) { + overlap = true; + } + lastRight[lineNo] = x + w; + } + } + if (overlap) { + autoStaggerLines++; + } else { + break; + } + } + + if (autoStaggerLines > 1) { + axis.staggerLines = autoStaggerLines; + } + } + + + each(tickPositions, function (pos) { + // left side must be align: right and right side must have align: left for labels + if (side === 0 || side === 2 || { 1: 'left', 3: 'right' }[side] === axis.labelAlign) { + + // get the highest offset + labelOffset = mathMax( + ticks[pos].getLabelSize(), + labelOffset + ); + } + + }); + if (axis.staggerLines) { + labelOffset *= axis.staggerLines; + axis.labelOffset = labelOffset; + } + + + } else { // doesn't have data + for (n in ticks) { + ticks[n].destroy(); + delete ticks[n]; + } + } + + if (axisTitleOptions && axisTitleOptions.text && axisTitleOptions.enabled !== false) { + if (!axis.axisTitle) { + axis.axisTitle = renderer.text( + axisTitleOptions.text, + 0, + 0, + axisTitleOptions.useHTML + ) + .attr({ + zIndex: 7, + rotation: axisTitleOptions.rotation || 0, + align: + axisTitleOptions.textAlign || + { low: 'left', middle: 'center', high: 'right' }[axisTitleOptions.align] + }) + .addClass(PREFIX + this.coll.toLowerCase() + '-title') + .css(axisTitleOptions.style) + .add(axis.axisGroup); + axis.axisTitle.isNew = true; + } + + if (showAxis) { + titleOffset = axis.axisTitle.getBBox()[horiz ? 'height' : 'width']; + titleMargin = pick(axisTitleOptions.margin, horiz ? 5 : 10); + titleOffsetOption = axisTitleOptions.offset; + } + + // hide or show the title depending on whether showEmpty is set + axis.axisTitle[showAxis ? 'show' : 'hide'](); + } + + // handle automatic or user set offset + axis.offset = directionFactor * pick(options.offset, axisOffset[side]); + + axis.axisTitleMargin = + pick(titleOffsetOption, + labelOffset + titleMargin + + (labelOffset && (directionFactor * options.labels[horiz ? 'y' : 'x'] - lineHeightCorrection)) + ); + + axisOffset[side] = mathMax( + axisOffset[side], + axis.axisTitleMargin + titleOffset + directionFactor * axis.offset + ); + clipOffset[invertedSide] = mathMax(clipOffset[invertedSide], mathFloor(options.lineWidth / 2) * 2); + }, + + /** + * Get the path for the axis line + */ + getLinePath: function (lineWidth) { + var chart = this.chart, + opposite = this.opposite, + offset = this.offset, + horiz = this.horiz, + lineLeft = this.left + (opposite ? this.width : 0) + offset, + lineTop = chart.chartHeight - this.bottom - (opposite ? this.height : 0) + offset; + + if (opposite) { + lineWidth *= -1; // crispify the other way - #1480, #1687 + } + + return chart.renderer.crispLine([ + M, + horiz ? + this.left : + lineLeft, + horiz ? + lineTop : + this.top, + L, + horiz ? + chart.chartWidth - this.right : + lineLeft, + horiz ? + lineTop : + chart.chartHeight - this.bottom + ], lineWidth); + }, + + /** + * Position the title + */ + getTitlePosition: function () { + // compute anchor points for each of the title align options + var horiz = this.horiz, + axisLeft = this.left, + axisTop = this.top, + axisLength = this.len, + axisTitleOptions = this.options.title, + margin = horiz ? axisLeft : axisTop, + opposite = this.opposite, + offset = this.offset, + fontSize = pInt(axisTitleOptions.style.fontSize || 12), + + // the position in the length direction of the axis + alongAxis = { + low: margin + (horiz ? 0 : axisLength), + middle: margin + axisLength / 2, + high: margin + (horiz ? axisLength : 0) + }[axisTitleOptions.align], + + // the position in the perpendicular direction of the axis + offAxis = (horiz ? axisTop + this.height : axisLeft) + + (horiz ? 1 : -1) * // horizontal axis reverses the margin + (opposite ? -1 : 1) * // so does opposite axes + this.axisTitleMargin + + (this.side === 2 ? fontSize : 0); + + return { + x: horiz ? + alongAxis : + offAxis + (opposite ? this.width : 0) + offset + + (axisTitleOptions.x || 0), // x + y: horiz ? + offAxis - (opposite ? this.height : 0) + offset : + alongAxis + (axisTitleOptions.y || 0) // y + }; + }, + + /** + * Render the axis + */ + render: function () { + var axis = this, + horiz = axis.horiz, + reversed = axis.reversed, + chart = axis.chart, + renderer = chart.renderer, + options = axis.options, + isLog = axis.isLog, + isLinked = axis.isLinked, + tickPositions = axis.tickPositions, + sortedPositions, + axisTitle = axis.axisTitle, + ticks = axis.ticks, + minorTicks = axis.minorTicks, + alternateBands = axis.alternateBands, + stackLabelOptions = options.stackLabels, + alternateGridColor = options.alternateGridColor, + tickmarkOffset = axis.tickmarkOffset, + lineWidth = options.lineWidth, + linePath, + hasRendered = chart.hasRendered, + slideInTicks = hasRendered && defined(axis.oldMin) && !isNaN(axis.oldMin), + hasData = axis.hasData, + showAxis = axis.showAxis, + from, + overflow = options.labels.overflow, + justifyLabels = axis.justifyLabels = horiz && overflow !== false, + to; + + // Reset + axis.labelEdge.length = 0; + axis.justifyToPlot = overflow === 'justify'; + + // Mark all elements inActive before we go over and mark the active ones + each([ticks, minorTicks, alternateBands], function (coll) { + var pos; + for (pos in coll) { + coll[pos].isActive = false; + } + }); + + // If the series has data draw the ticks. Else only the line and title + if (hasData || isLinked) { + + // minor ticks + if (axis.minorTickInterval && !axis.categories) { + each(axis.getMinorTickPositions(), function (pos) { + if (!minorTicks[pos]) { + minorTicks[pos] = new Tick(axis, pos, 'minor'); + } + + // render new ticks in old position + if (slideInTicks && minorTicks[pos].isNew) { + minorTicks[pos].render(null, true); + } + + minorTicks[pos].render(null, false, 1); + }); + } + + // Major ticks. Pull out the first item and render it last so that + // we can get the position of the neighbour label. #808. + if (tickPositions.length) { // #1300 + sortedPositions = tickPositions.slice(); + if ((horiz && reversed) || (!horiz && !reversed)) { + sortedPositions.reverse(); + } + if (justifyLabels) { + sortedPositions = sortedPositions.slice(1).concat([sortedPositions[0]]); + } + each(sortedPositions, function (pos, i) { + + // Reorganize the indices + if (justifyLabels) { + i = (i === sortedPositions.length - 1) ? 0 : i + 1; + } + + // linked axes need an extra check to find out if + if (!isLinked || (pos >= axis.min && pos <= axis.max)) { + + if (!ticks[pos]) { + ticks[pos] = new Tick(axis, pos); + } + + // render new ticks in old position + if (slideInTicks && ticks[pos].isNew) { + ticks[pos].render(i, true, 0.1); + } + + ticks[pos].render(i, false, 1); + } + + }); + // In a categorized axis, the tick marks are displayed between labels. So + // we need to add a tick mark and grid line at the left edge of the X axis. + if (tickmarkOffset && axis.min === 0) { + if (!ticks[-1]) { + ticks[-1] = new Tick(axis, -1, null, true); + } + ticks[-1].render(-1); + } + + } + + // alternate grid color + if (alternateGridColor) { + each(tickPositions, function (pos, i) { + if (i % 2 === 0 && pos < axis.max) { + if (!alternateBands[pos]) { + alternateBands[pos] = new Highcharts.PlotLineOrBand(axis); + } + from = pos + tickmarkOffset; // #949 + to = tickPositions[i + 1] !== UNDEFINED ? tickPositions[i + 1] + tickmarkOffset : axis.max; + alternateBands[pos].options = { + from: isLog ? lin2log(from) : from, + to: isLog ? lin2log(to) : to, + color: alternateGridColor + }; + alternateBands[pos].render(); + alternateBands[pos].isActive = true; + } + }); + } + + // custom plot lines and bands + if (!axis._addedPlotLB) { // only first time + each((options.plotLines || []).concat(options.plotBands || []), function (plotLineOptions) { + axis.addPlotBandOrLine(plotLineOptions); + }); + axis._addedPlotLB = true; + } + + } // end if hasData + + // Remove inactive ticks + each([ticks, minorTicks, alternateBands], function (coll) { + var pos, + i, + forDestruction = [], + delay = globalAnimation ? globalAnimation.duration || 500 : 0, + destroyInactiveItems = function () { + i = forDestruction.length; + while (i--) { + // When resizing rapidly, the same items may be destroyed in different timeouts, + // or the may be reactivated + if (coll[forDestruction[i]] && !coll[forDestruction[i]].isActive) { + coll[forDestruction[i]].destroy(); + delete coll[forDestruction[i]]; + } + } + + }; + + for (pos in coll) { + + if (!coll[pos].isActive) { + // Render to zero opacity + coll[pos].render(pos, false, 0); + coll[pos].isActive = false; + forDestruction.push(pos); + } + } + + // When the objects are finished fading out, destroy them + if (coll === alternateBands || !chart.hasRendered || !delay) { + destroyInactiveItems(); + } else if (delay) { + setTimeout(destroyInactiveItems, delay); + } + }); + + // Static items. As the axis group is cleared on subsequent calls + // to render, these items are added outside the group. + // axis line + if (lineWidth) { + linePath = axis.getLinePath(lineWidth); + if (!axis.axisLine) { + axis.axisLine = renderer.path(linePath) + .attr({ + stroke: options.lineColor, + 'stroke-width': lineWidth, + zIndex: 7 + }) + .add(axis.axisGroup); + } else { + axis.axisLine.animate({ d: linePath }); + } + + // show or hide the line depending on options.showEmpty + axis.axisLine[showAxis ? 'show' : 'hide'](); + } + + if (axisTitle && showAxis) { + + axisTitle[axisTitle.isNew ? 'attr' : 'animate']( + axis.getTitlePosition() + ); + axisTitle.isNew = false; + } + + // Stacked totals: + if (stackLabelOptions && stackLabelOptions.enabled) { + axis.renderStackTotals(); + } + // End stacked totals + + axis.isDirty = false; + }, + + /** + * Redraw the axis to reflect changes in the data or axis extremes + */ + redraw: function () { + var axis = this, + chart = axis.chart, + pointer = chart.pointer; + + // hide tooltip and hover states + if (pointer) { + pointer.reset(true); + } + + // render the axis + axis.render(); + + // move plot lines and bands + each(axis.plotLinesAndBands, function (plotLine) { + plotLine.render(); + }); + + // mark associated series as dirty and ready for redraw + each(axis.series, function (series) { + series.isDirty = true; + }); + + }, + + /** + * Destroys an Axis instance. + */ + destroy: function (keepEvents) { + var axis = this, + stacks = axis.stacks, + stackKey, + plotLinesAndBands = axis.plotLinesAndBands, + i; + + // Remove the events + if (!keepEvents) { + removeEvent(axis); + } + + // Destroy each stack total + for (stackKey in stacks) { + destroyObjectProperties(stacks[stackKey]); + + stacks[stackKey] = null; + } + + // Destroy collections + each([axis.ticks, axis.minorTicks, axis.alternateBands], function (coll) { + destroyObjectProperties(coll); + }); + i = plotLinesAndBands.length; + while (i--) { // #1975 + plotLinesAndBands[i].destroy(); + } + + // Destroy local variables + each(['stackTotalGroup', 'axisLine', 'axisTitle', 'axisGroup', 'cross', 'gridGroup', 'labelGroup'], function (prop) { + if (axis[prop]) { + axis[prop] = axis[prop].destroy(); + } + }); + + // Destroy crosshair + if (this.cross) { + this.cross.destroy(); + } + }, + + /** + * Draw the crosshair + */ + drawCrosshair: function (e, point) { + if (!this.crosshair) { return; }// Do not draw crosshairs if you don't have too. + + if ((defined(point) || !pick(this.crosshair.snap, true)) === false) { + this.hideCrosshair(); + return; + } + + var path, + options = this.crosshair, + animation = options.animation, + pos; + + // Get the path + if (!pick(options.snap, true)) { + pos = (this.horiz ? e.chartX - this.pos : this.len - e.chartY + this.pos); + } else if (defined(point)) { + /*jslint eqeq: true*/ + pos = (this.chart.inverted != this.horiz) ? point.plotX : this.len - point.plotY; + /*jslint eqeq: false*/ + } + + if (this.isRadial) { + path = this.getPlotLinePath(this.isXAxis ? point.x : pick(point.stackY, point.y)); + } else { + path = this.getPlotLinePath(null, null, null, null, pos); + } + + if (path === null) { + this.hideCrosshair(); + return; + } + + // Draw the cross + if (this.cross) { + this.cross + .attr({ visibility: VISIBLE })[animation ? 'animate' : 'attr']({ d: path }, animation); + } else { + var attribs = { + 'stroke-width': options.width || 1, + stroke: options.color || '#C0C0C0', + zIndex: options.zIndex || 2 + }; + if (options.dashStyle) { + attribs.dashstyle = options.dashStyle; + } + this.cross = this.chart.renderer.path(path).attr(attribs).add(); + } + }, + + /** + * Hide the crosshair. + */ + hideCrosshair: function () { + if (this.cross) { + this.cross.hide(); + } + } +}; // end Axis + +extend(Axis.prototype, AxisPlotLineOrBandExtension); + +/** + * Set the tick positions to a time unit that makes sense, for example + * on the first of each month or on every Monday. Return an array + * with the time positions. Used in datetime axes as well as for grouping + * data on a datetime axis. + * + * @param {Object} normalizedInterval The interval in axis values (ms) and the count + * @param {Number} min The minimum in axis values + * @param {Number} max The maximum in axis values + * @param {Number} startOfWeek + */ +Axis.prototype.getTimeTicks = function (normalizedInterval, min, max, startOfWeek) { + var tickPositions = [], + i, + higherRanks = {}, + useUTC = defaultOptions.global.useUTC, + minYear, // used in months and years as a basis for Date.UTC() + minDate = new Date(min - timezoneOffset), + interval = normalizedInterval.unitRange, + count = normalizedInterval.count; + + if (defined(min)) { // #1300 + if (interval >= timeUnits[SECOND]) { // second + minDate.setMilliseconds(0); + minDate.setSeconds(interval >= timeUnits[MINUTE] ? 0 : + count * mathFloor(minDate.getSeconds() / count)); + } + + if (interval >= timeUnits[MINUTE]) { // minute + minDate[setMinutes](interval >= timeUnits[HOUR] ? 0 : + count * mathFloor(minDate[getMinutes]() / count)); + } + + if (interval >= timeUnits[HOUR]) { // hour + minDate[setHours](interval >= timeUnits[DAY] ? 0 : + count * mathFloor(minDate[getHours]() / count)); + } + + if (interval >= timeUnits[DAY]) { // day + minDate[setDate](interval >= timeUnits[MONTH] ? 1 : + count * mathFloor(minDate[getDate]() / count)); + } + + if (interval >= timeUnits[MONTH]) { // month + minDate[setMonth](interval >= timeUnits[YEAR] ? 0 : + count * mathFloor(minDate[getMonth]() / count)); + minYear = minDate[getFullYear](); + } + + if (interval >= timeUnits[YEAR]) { // year + minYear -= minYear % count; + minDate[setFullYear](minYear); + } + + // week is a special case that runs outside the hierarchy + if (interval === timeUnits[WEEK]) { + // get start of current week, independent of count + minDate[setDate](minDate[getDate]() - minDate[getDay]() + + pick(startOfWeek, 1)); + } + + + // get tick positions + i = 1; + if (timezoneOffset) { + minDate = new Date(minDate.getTime() + timezoneOffset); + } + minYear = minDate[getFullYear](); + var time = minDate.getTime(), + minMonth = minDate[getMonth](), + minDateDate = minDate[getDate](), + localTimezoneOffset = useUTC ? + timezoneOffset : + (24 * 3600 * 1000 + minDate.getTimezoneOffset() * 60 * 1000) % (24 * 3600 * 1000); // #950 + + // iterate and add tick positions at appropriate values + while (time < max) { + tickPositions.push(time); + + // if the interval is years, use Date.UTC to increase years + if (interval === timeUnits[YEAR]) { + time = makeTime(minYear + i * count, 0); + + // if the interval is months, use Date.UTC to increase months + } else if (interval === timeUnits[MONTH]) { + time = makeTime(minYear, minMonth + i * count); + + // if we're using global time, the interval is not fixed as it jumps + // one hour at the DST crossover + } else if (!useUTC && (interval === timeUnits[DAY] || interval === timeUnits[WEEK])) { + time = makeTime(minYear, minMonth, minDateDate + + i * count * (interval === timeUnits[DAY] ? 1 : 7)); + + // else, the interval is fixed and we use simple addition + } else { + time += interval * count; + } + + i++; + } + + // push the last time + tickPositions.push(time); + + + // mark new days if the time is dividible by day (#1649, #1760) + each(grep(tickPositions, function (time) { + return interval <= timeUnits[HOUR] && time % timeUnits[DAY] === localTimezoneOffset; + }), function (time) { + higherRanks[time] = DAY; + }); + } + + + // record information on the chosen unit - for dynamic label formatter + tickPositions.info = extend(normalizedInterval, { + higherRanks: higherRanks, + totalRange: interval * count + }); + + return tickPositions; +}; + +/** + * Get a normalized tick interval for dates. Returns a configuration object with + * unit range (interval), count and name. Used to prepare data for getTimeTicks. + * Previously this logic was part of getTimeTicks, but as getTimeTicks now runs + * of segments in stock charts, the normalizing logic was extracted in order to + * prevent it for running over again for each segment having the same interval. + * #662, #697. + */ +Axis.prototype.normalizeTimeTickInterval = function (tickInterval, unitsOption) { + var units = unitsOption || [[ + MILLISECOND, // unit name + [1, 2, 5, 10, 20, 25, 50, 100, 200, 500] // allowed multiples + ], [ + 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 + ]], + unit = units[units.length - 1], // default unit is years + interval = timeUnits[unit[0]], + multiples = unit[1], + count, + i; + + // loop through the units to find the one that best fits the tickInterval + for (i = 0; i < units.length; i++) { + unit = units[i]; + interval = timeUnits[unit[0]]; + multiples = unit[1]; + + + if (units[i + 1]) { + // lessThan is in the middle between the highest multiple and the next unit. + var lessThan = (interval * multiples[multiples.length - 1] + + timeUnits[units[i + 1][0]]) / 2; + + // break and keep the current unit + if (tickInterval <= lessThan) { + break; + } + } + } + + // prevent 2.5 years intervals, though 25, 250 etc. are allowed + if (interval === timeUnits[YEAR] && tickInterval < 5 * interval) { + multiples = [1, 2, 5]; + } + + // get the count + count = normalizeTickInterval( + tickInterval / interval, + multiples, + unit[0] === YEAR ? mathMax(getMagnitude(tickInterval / interval), 1) : 1 // #1913, #2360 + ); + + return { + unitRange: interval, + count: count, + unitName: unit[0] + }; +};/** + * Methods defined on the Axis prototype + */ + +/** + * Set the tick positions of a logarithmic axis + */ +Axis.prototype.getLogTickPositions = function (interval, min, max, minor) { + var axis = this, + options = axis.options, + axisLength = axis.len, + // Since we use this method for both major and minor ticks, + // use a local variable and return the result + positions = []; + + // Reset + if (!minor) { + axis._minorAutoInterval = null; + } + + // First case: All ticks fall on whole logarithms: 1, 10, 100 etc. + if (interval >= 0.5) { + interval = mathRound(interval); + positions = axis.getLinearTickPositions(interval, min, max); + + // Second case: We need intermediary ticks. For example + // 1, 2, 4, 6, 8, 10, 20, 40 etc. + } else if (interval >= 0.08) { + var roundedMin = mathFloor(min), + intermediate, + i, + j, + len, + pos, + lastPos, + break2; + + if (interval > 0.3) { + intermediate = [1, 2, 4]; + } else if (interval > 0.15) { // 0.2 equals five minor ticks per 1, 10, 100 etc + intermediate = [1, 2, 4, 6, 8]; + } else { // 0.1 equals ten minor ticks per 1, 10, 100 etc + intermediate = [1, 2, 3, 4, 5, 6, 7, 8, 9]; + } + + for (i = roundedMin; i < max + 1 && !break2; i++) { + len = intermediate.length; + for (j = 0; j < len && !break2; j++) { + pos = log2lin(lin2log(i) * intermediate[j]); + + if (pos > min && (!minor || lastPos <= max)) { // #1670 + positions.push(lastPos); + } + + if (lastPos > max) { + break2 = true; + } + lastPos = pos; + } + } + + // Third case: We are so deep in between whole logarithmic values that + // we might as well handle the tick positions like a linear axis. For + // example 1.01, 1.02, 1.03, 1.04. + } else { + var realMin = lin2log(min), + realMax = lin2log(max), + tickIntervalOption = options[minor ? 'minorTickInterval' : 'tickInterval'], + filteredTickIntervalOption = tickIntervalOption === 'auto' ? null : tickIntervalOption, + tickPixelIntervalOption = options.tickPixelInterval / (minor ? 5 : 1), + totalPixelLength = minor ? axisLength / axis.tickPositions.length : axisLength; + + interval = pick( + filteredTickIntervalOption, + axis._minorAutoInterval, + (realMax - realMin) * tickPixelIntervalOption / (totalPixelLength || 1) + ); + + interval = normalizeTickInterval( + interval, + null, + getMagnitude(interval) + ); + + positions = map(axis.getLinearTickPositions( + interval, + realMin, + realMax + ), log2lin); + + if (!minor) { + axis._minorAutoInterval = interval / 5; + } + } + + // Set the axis-level tickInterval variable + if (!minor) { + axis.tickInterval = interval; + } + return positions; +};/** + * The tooltip object + * @param {Object} chart The chart instance + * @param {Object} options Tooltip options + */ +var Tooltip = Highcharts.Tooltip = function () { + this.init.apply(this, arguments); +}; + +Tooltip.prototype = { + + init: function (chart, options) { + + var borderWidth = options.borderWidth, + style = options.style, + padding = pInt(style.padding); + + // Save the chart and options + this.chart = chart; + this.options = options; + + // Keep track of the current series + //this.currentSeries = UNDEFINED; + + // List of crosshairs + this.crosshairs = []; + + // Current values of x and y when animating + this.now = { x: 0, y: 0 }; + + // The tooltip is initially hidden + this.isHidden = true; + + + // create the label + this.label = chart.renderer.label('', 0, 0, options.shape || 'callout', null, null, options.useHTML, null, 'tooltip') + .attr({ + padding: padding, + fill: options.backgroundColor, + 'stroke-width': borderWidth, + r: options.borderRadius, + zIndex: 8 + }) + .css(style) + .css({ padding: 0 }) // Remove it from VML, the padding is applied as an attribute instead (#1117) + .add() + .attr({ y: -9999 }); // #2301, #2657 + + // When using canVG the shadow shows up as a gray circle + // even if the tooltip is hidden. + if (!useCanVG) { + this.label.shadow(options.shadow); + } + + // Public property for getting the shared state. + this.shared = options.shared; + }, + + /** + * Destroy the tooltip and its elements. + */ + destroy: function () { + // Destroy and clear local variables + if (this.label) { + this.label = this.label.destroy(); + } + clearTimeout(this.hideTimer); + clearTimeout(this.tooltipTimeout); + }, + + /** + * Provide a soft movement for the tooltip + * + * @param {Number} x + * @param {Number} y + * @private + */ + move: function (x, y, anchorX, anchorY) { + var tooltip = this, + now = tooltip.now, + animate = tooltip.options.animation !== false && !tooltip.isHidden, + skipAnchor = tooltip.followPointer || tooltip.len > 1; + + // get intermediate values for animation + extend(now, { + x: animate ? (2 * now.x + x) / 3 : x, + y: animate ? (now.y + y) / 2 : y, + anchorX: skipAnchor ? UNDEFINED : animate ? (2 * now.anchorX + anchorX) / 3 : anchorX, + anchorY: skipAnchor ? UNDEFINED : animate ? (now.anchorY + anchorY) / 2 : anchorY + }); + + // move to the intermediate value + tooltip.label.attr(now); + + + // run on next tick of the mouse tracker + if (animate && (mathAbs(x - now.x) > 1 || mathAbs(y - now.y) > 1)) { + + // never allow two timeouts + clearTimeout(this.tooltipTimeout); + + // set the fixed interval ticking for the smooth tooltip + this.tooltipTimeout = setTimeout(function () { + // The interval function may still be running during destroy, so check that the chart is really there before calling. + if (tooltip) { + tooltip.move(x, y, anchorX, anchorY); + } + }, 32); + + } + }, + + /** + * Hide the tooltip + */ + hide: function () { + var tooltip = this, + hoverPoints; + + clearTimeout(this.hideTimer); // disallow duplicate timers (#1728, #1766) + if (!this.isHidden) { + hoverPoints = this.chart.hoverPoints; + + this.hideTimer = setTimeout(function () { + tooltip.label.fadeOut(); + tooltip.isHidden = true; + }, pick(this.options.hideDelay, 500)); + + // hide previous hoverPoints and set new + if (hoverPoints) { + each(hoverPoints, function (point) { + point.setState(); + }); + } + + this.chart.hoverPoints = null; + } + }, + + /** + * Extendable method to get the anchor position of the tooltip + * from a point or set of points + */ + getAnchor: function (points, mouseEvent) { + var ret, + chart = this.chart, + inverted = chart.inverted, + plotTop = chart.plotTop, + plotX = 0, + plotY = 0, + yAxis; + + points = splat(points); + + // Pie uses a special tooltipPos + ret = points[0].tooltipPos; + + // When tooltip follows mouse, relate the position to the mouse + if (this.followPointer && mouseEvent) { + if (mouseEvent.chartX === UNDEFINED) { + mouseEvent = chart.pointer.normalize(mouseEvent); + } + ret = [ + mouseEvent.chartX - chart.plotLeft, + mouseEvent.chartY - plotTop + ]; + } + // When shared, use the average position + if (!ret) { + each(points, function (point) { + yAxis = point.series.yAxis; + plotX += point.plotX; + plotY += (point.plotLow ? (point.plotLow + point.plotHigh) / 2 : point.plotY) + + (!inverted && yAxis ? yAxis.top - plotTop : 0); // #1151 + }); + + plotX /= points.length; + plotY /= points.length; + + ret = [ + inverted ? chart.plotWidth - plotY : plotX, + this.shared && !inverted && points.length > 1 && mouseEvent ? + mouseEvent.chartY - plotTop : // place shared tooltip next to the mouse (#424) + inverted ? chart.plotHeight - plotX : plotY + ]; + } + + return map(ret, mathRound); + }, + + /** + * Place the tooltip in a chart without spilling over + * and not covering the point it self. + */ + getPosition: function (boxWidth, boxHeight, point) { + + var chart = this.chart, + distance = this.distance, + ret = {}, + swapped, + first = ['y', chart.chartHeight, boxHeight, point.plotY + chart.plotTop], + second = ['x', chart.chartWidth, boxWidth, point.plotX + chart.plotLeft], + // The far side is right or bottom + preferFarSide = point.ttBelow || (chart.inverted && !point.negative) || (!chart.inverted && point.negative), + /** + * Handle the preferred dimension. When the preferred dimension is tooltip + * on top or bottom of the point, it will look for space there. + */ + firstDimension = function (dim, outerSize, innerSize, point) { + var roomLeft = innerSize < point - distance, + roomRight = point + distance + innerSize < outerSize, + alignedLeft = point - distance - innerSize, + alignedRight = point + distance; + + if (preferFarSide && roomRight) { + ret[dim] = alignedRight; + } else if (!preferFarSide && roomLeft) { + ret[dim] = alignedLeft; + } else if (roomLeft) { + ret[dim] = alignedLeft; + } else if (roomRight) { + ret[dim] = alignedRight; + } else { + return false; + } + }, + /** + * Handle the secondary dimension. If the preferred dimension is tooltip + * on top or bottom of the point, the second dimension is to align the tooltip + * above the point, trying to align center but allowing left or right + * align within the chart box. + */ + secondDimension = function (dim, outerSize, innerSize, point) { + // Too close to the edge, return false and swap dimensions + if (point < distance || point > outerSize - distance) { + return false; + + // Align left/top + } else if (point < innerSize / 2) { + ret[dim] = 1; + // Align right/bottom + } else if (point > outerSize - innerSize / 2) { + ret[dim] = outerSize - innerSize - 2; + // Align center + } else { + ret[dim] = point - innerSize / 2; + } + }, + /** + * Swap the dimensions + */ + swap = function (count) { + var temp = first; + first = second; + second = temp; + swapped = count; + }, + run = function () { + if (firstDimension.apply(0, first) !== false) { + if (secondDimension.apply(0, second) === false && !swapped) { + swap(true); + run(); + } + } else if (!swapped) { + swap(true); + run(); + } else { + ret.x = ret.y = 0; + } + }; + + // Under these conditions, prefer the tooltip on the side of the point + if (chart.inverted || this.len > 1) { + swap(); + } + run(); + + return ret; + + }, + + /** + * In case no user defined formatter is given, this will be used. Note that the context + * here is an object holding point, series, x, y etc. + */ + defaultFormatter: function (tooltip) { + var items = this.points || splat(this), + series = items[0].series, + s; + + // build the header + s = [tooltip.tooltipHeaderFormatter(items[0])]; + + // build the values + each(items, function (item) { + series = item.series; + s.push((series.tooltipFormatter && series.tooltipFormatter(item)) || + item.point.tooltipFormatter(series.tooltipOptions.pointFormat)); + }); + + // footer + s.push(tooltip.options.footerFormat || ''); + + return s.join(''); + }, + + /** + * Refresh the tooltip's text and position. + * @param {Object} point + */ + refresh: function (point, mouseEvent) { + var tooltip = this, + chart = tooltip.chart, + label = tooltip.label, + options = tooltip.options, + x, + y, + anchor, + textConfig = {}, + text, + pointConfig = [], + formatter = options.formatter || tooltip.defaultFormatter, + hoverPoints = chart.hoverPoints, + borderColor, + shared = tooltip.shared, + currentSeries; + + clearTimeout(this.hideTimer); + + // get the reference point coordinates (pie charts use tooltipPos) + tooltip.followPointer = splat(point)[0].series.tooltipOptions.followPointer; + anchor = tooltip.getAnchor(point, mouseEvent); + x = anchor[0]; + y = anchor[1]; + + // shared tooltip, array is sent over + if (shared && !(point.series && point.series.noSharedTooltip)) { + + // hide previous hoverPoints and set new + + chart.hoverPoints = point; + if (hoverPoints) { + each(hoverPoints, function (point) { + point.setState(); + }); + } + + each(point, function (item) { + item.setState(HOVER_STATE); + + pointConfig.push(item.getLabelConfig()); + }); + + textConfig = { + x: point[0].category, + y: point[0].y + }; + textConfig.points = pointConfig; + this.len = pointConfig.length; + point = point[0]; + + // single point tooltip + } else { + textConfig = point.getLabelConfig(); + } + text = formatter.call(textConfig, tooltip); + + // register the current series + currentSeries = point.series; + this.distance = pick(currentSeries.tooltipOptions.distance, 16); + + // update the inner HTML + if (text === false) { + this.hide(); + } else { + + // show it + if (tooltip.isHidden) { + stop(label); + label.attr('opacity', 1).show(); + } + + // update text + label.attr({ + text: text + }); + + // set the stroke color of the box + borderColor = options.borderColor || point.color || currentSeries.color || '#606060'; + label.attr({ + stroke: borderColor + }); + + tooltip.updatePosition({ plotX: x, plotY: y, negative: point.negative, ttBelow: point.ttBelow }); + + this.isHidden = false; + } + fireEvent(chart, 'tooltipRefresh', { + text: text, + x: x + chart.plotLeft, + y: y + chart.plotTop, + borderColor: borderColor + }); + }, + + /** + * Find the new position and perform the move + */ + updatePosition: function (point) { + var chart = this.chart, + label = this.label, + pos = (this.options.positioner || this.getPosition).call( + this, + label.width, + label.height, + point + ); + + // do the move + this.move( + mathRound(pos.x), + mathRound(pos.y), + point.plotX + chart.plotLeft, + point.plotY + chart.plotTop + ); + }, + + + /** + * Format the header of the tooltip + */ + tooltipHeaderFormatter: function (point) { + var series = point.series, + tooltipOptions = series.tooltipOptions, + dateTimeLabelFormats = tooltipOptions.dateTimeLabelFormats, + xDateFormat = tooltipOptions.xDateFormat, + xAxis = series.xAxis, + isDateTime = xAxis && xAxis.options.type === 'datetime' && isNumber(point.key), + headerFormat = tooltipOptions.headerFormat, + closestPointRange = xAxis && xAxis.closestPointRange, + n; + + // Guess the best date format based on the closest point distance (#568) + if (isDateTime && !xDateFormat) { + if (closestPointRange) { + for (n in timeUnits) { + if (timeUnits[n] >= closestPointRange || + // If the point is placed every day at 23:59, we need to show + // the minutes as well. This logic only works for time units less than + // a day, since all higher time units are dividable by those. #2637. + (timeUnits[n] <= timeUnits[DAY] && point.key % timeUnits[n] > 0)) { + xDateFormat = dateTimeLabelFormats[n]; + break; + } + } + } else { + xDateFormat = dateTimeLabelFormats.day; + } + + xDateFormat = xDateFormat || dateTimeLabelFormats.year; // #2546, 2581 + + } + + // Insert the header date format if any + if (isDateTime && xDateFormat) { + headerFormat = headerFormat.replace('{point.key}', '{point.key:' + xDateFormat + '}'); + } + + return format(headerFormat, { + point: point, + series: series + }); + } +}; + +var hoverChartIndex; + +// Global flag for touch support +hasTouch = doc.documentElement.ontouchstart !== UNDEFINED; + +/** + * The mouse tracker object. All methods starting with "on" are primary DOM event handlers. + * Subsequent methods should be named differently from what they are doing. + * @param {Object} chart The Chart instance + * @param {Object} options The root options object + */ +var Pointer = Highcharts.Pointer = function (chart, options) { + this.init(chart, options); +}; + +Pointer.prototype = { + /** + * Initialize Pointer + */ + init: function (chart, options) { + + var chartOptions = options.chart, + chartEvents = chartOptions.events, + zoomType = useCanVG ? '' : chartOptions.zoomType, + inverted = chart.inverted, + zoomX, + zoomY; + + // Store references + this.options = options; + this.chart = chart; + + // Zoom status + this.zoomX = zoomX = /x/.test(zoomType); + this.zoomY = zoomY = /y/.test(zoomType); + this.zoomHor = (zoomX && !inverted) || (zoomY && inverted); + this.zoomVert = (zoomY && !inverted) || (zoomX && inverted); + this.hasZoom = zoomX || zoomY; + + // Do we need to handle click on a touch device? + this.runChartClick = chartEvents && !!chartEvents.click; + + this.pinchDown = []; + this.lastValidTouch = {}; + + if (Highcharts.Tooltip && options.tooltip.enabled) { + chart.tooltip = new Tooltip(chart, options.tooltip); + this.followTouchMove = options.tooltip.followTouchMove; + } + + this.setDOMEvents(); + }, + + /** + * Add crossbrowser support for chartX and chartY + * @param {Object} e The event object in standard browsers + */ + normalize: function (e, chartPosition) { + var chartX, + chartY, + ePos; + + // common IE normalizing + e = e || window.event; + + // Framework specific normalizing (#1165) + e = washMouseEvent(e); + + // More IE normalizing, needs to go after washMouseEvent + if (!e.target) { + e.target = e.srcElement; + } + + // iOS (#2757) + ePos = e.touches ? (e.touches.length ? e.touches.item(0) : e.changedTouches[0]) : e; + + // Get mouse position + if (!chartPosition) { + this.chartPosition = chartPosition = offset(this.chart.container); + } + + // chartX and chartY + if (ePos.pageX === UNDEFINED) { // IE < 9. #886. + chartX = mathMax(e.x, e.clientX - chartPosition.left); // #2005, #2129: the second case is + // for IE10 quirks mode within framesets + chartY = e.y; + } else { + chartX = ePos.pageX - chartPosition.left; + chartY = ePos.pageY - chartPosition.top; + } + + return extend(e, { + chartX: mathRound(chartX), + chartY: mathRound(chartY) + }); + }, + + /** + * Get the click position in terms of axis values. + * + * @param {Object} e A pointer event + */ + getCoordinates: function (e) { + var coordinates = { + xAxis: [], + yAxis: [] + }; + + each(this.chart.axes, function (axis) { + coordinates[axis.isXAxis ? 'xAxis' : 'yAxis'].push({ + axis: axis, + value: axis.toValue(e[axis.horiz ? 'chartX' : 'chartY']) + }); + }); + return coordinates; + }, + + /** + * Return the index in the tooltipPoints array, corresponding to pixel position in + * the plot area. + */ + getIndex: function (e) { + var chart = this.chart; + return chart.inverted ? + chart.plotHeight + chart.plotTop - e.chartY : + e.chartX - chart.plotLeft; + }, + + /** + * With line type charts with a single tracker, get the point closest to the mouse. + * Run Point.onMouseOver and display tooltip for the point or points. + */ + runPointActions: function (e) { + var pointer = this, + chart = pointer.chart, + series = chart.series, + tooltip = chart.tooltip, + followPointer, + point, + points, + hoverPoint = chart.hoverPoint, + hoverSeries = chart.hoverSeries, + i, + j, + distance = chart.chartWidth, + index = pointer.getIndex(e), + anchor; + + // shared tooltip + if (tooltip && pointer.options.tooltip.shared && !(hoverSeries && hoverSeries.noSharedTooltip)) { + points = []; + + // loop over all series and find the ones with points closest to the mouse + i = series.length; + for (j = 0; j < i; j++) { + if (series[j].visible && + series[j].options.enableMouseTracking !== false && + !series[j].noSharedTooltip && series[j].singularTooltips !== true && series[j].tooltipPoints.length) { + point = series[j].tooltipPoints[index]; + if (point && point.series) { // not a dummy point, #1544 + point._dist = mathAbs(index - point.clientX); + distance = mathMin(distance, point._dist); + points.push(point); + } + } + } + // remove furthest points + i = points.length; + while (i--) { + if (points[i]._dist > distance) { + points.splice(i, 1); + } + } + // refresh the tooltip if necessary + if (points.length && (points[0].clientX !== pointer.hoverX)) { + tooltip.refresh(points, e); + pointer.hoverX = points[0].clientX; + } + } + + // Separate tooltip and general mouse events + followPointer = hoverSeries && hoverSeries.tooltipOptions.followPointer; + if (hoverSeries && hoverSeries.tracker && !followPointer) { // #2584, #2830 + + // get the point + point = hoverSeries.tooltipPoints[index]; + + // a new point is hovered, refresh the tooltip + if (point && point !== hoverPoint) { + + // trigger the events + point.onMouseOver(e); + + } + + } else if (tooltip && followPointer && !tooltip.isHidden) { + anchor = tooltip.getAnchor([{}], e); + tooltip.updatePosition({ plotX: anchor[0], plotY: anchor[1] }); + } + + // Start the event listener to pick up the tooltip + if (tooltip && !pointer._onDocumentMouseMove) { + pointer._onDocumentMouseMove = function (e) { + if (charts[hoverChartIndex]) { + charts[hoverChartIndex].pointer.onDocumentMouseMove(e); + } + }; + addEvent(doc, 'mousemove', pointer._onDocumentMouseMove); + } + + // Draw independent crosshairs + each(chart.axes, function (axis) { + axis.drawCrosshair(e, pick(point, hoverPoint)); + }); + }, + + + + /** + * Reset the tracking by hiding the tooltip, the hover series state and the hover point + * + * @param allowMove {Boolean} Instead of destroying the tooltip altogether, allow moving it if possible + */ + reset: function (allowMove) { + var pointer = this, + chart = pointer.chart, + hoverSeries = chart.hoverSeries, + hoverPoint = chart.hoverPoint, + tooltip = chart.tooltip, + tooltipPoints = tooltip && tooltip.shared ? chart.hoverPoints : hoverPoint; + + // Narrow in allowMove + allowMove = allowMove && tooltip && tooltipPoints; + + // Check if the points have moved outside the plot area, #1003 + if (allowMove && splat(tooltipPoints)[0].plotX === UNDEFINED) { + allowMove = false; + } + + // Just move the tooltip, #349 + if (allowMove) { + tooltip.refresh(tooltipPoints); + if (hoverPoint) { // #2500 + hoverPoint.setState(hoverPoint.state, true); + } + + // Full reset + } else { + + if (hoverPoint) { + hoverPoint.onMouseOut(); + } + + if (hoverSeries) { + hoverSeries.onMouseOut(); + } + + if (tooltip) { + tooltip.hide(); + } + + if (pointer._onDocumentMouseMove) { + removeEvent(doc, 'mousemove', pointer._onDocumentMouseMove); + pointer._onDocumentMouseMove = null; + } + + // Remove crosshairs + each(chart.axes, function (axis) { + axis.hideCrosshair(); + }); + + pointer.hoverX = null; + + } + }, + + /** + * Scale series groups to a certain scale and translation + */ + scaleGroups: function (attribs, clip) { + + var chart = this.chart, + seriesAttribs; + + // Scale each series + each(chart.series, function (series) { + seriesAttribs = attribs || series.getPlotBox(); // #1701 + if (series.xAxis && series.xAxis.zoomEnabled) { + series.group.attr(seriesAttribs); + if (series.markerGroup) { + series.markerGroup.attr(seriesAttribs); + series.markerGroup.clip(clip ? chart.clipRect : null); + } + if (series.dataLabelsGroup) { + series.dataLabelsGroup.attr(seriesAttribs); + } + } + }); + + // Clip + chart.clipRect.attr(clip || chart.clipBox); + }, + + /** + * Start a drag operation + */ + dragStart: function (e) { + var chart = this.chart; + + // Record the start position + chart.mouseIsDown = e.type; + chart.cancelClick = false; + chart.mouseDownX = this.mouseDownX = e.chartX; + chart.mouseDownY = this.mouseDownY = e.chartY; + }, + + /** + * Perform a drag operation in response to a mousemove event while the mouse is down + */ + drag: function (e) { + + var chart = this.chart, + chartOptions = chart.options.chart, + chartX = e.chartX, + chartY = e.chartY, + zoomHor = this.zoomHor, + zoomVert = this.zoomVert, + plotLeft = chart.plotLeft, + plotTop = chart.plotTop, + plotWidth = chart.plotWidth, + plotHeight = chart.plotHeight, + clickedInside, + size, + mouseDownX = this.mouseDownX, + mouseDownY = this.mouseDownY; + + // If the mouse is outside the plot area, adjust to cooordinates + // inside to prevent the selection marker from going outside + if (chartX < plotLeft) { + chartX = plotLeft; + } else if (chartX > plotLeft + plotWidth) { + chartX = plotLeft + plotWidth; + } + + if (chartY < plotTop) { + chartY = plotTop; + } else if (chartY > plotTop + plotHeight) { + chartY = plotTop + plotHeight; + } + + // determine if the mouse has moved more than 10px + this.hasDragged = Math.sqrt( + Math.pow(mouseDownX - chartX, 2) + + Math.pow(mouseDownY - chartY, 2) + ); + + if (this.hasDragged > 10) { + clickedInside = chart.isInsidePlot(mouseDownX - plotLeft, mouseDownY - plotTop); + + // make a selection + if (chart.hasCartesianSeries && (this.zoomX || this.zoomY) && clickedInside) { + if (!this.selectionMarker) { + this.selectionMarker = chart.renderer.rect( + plotLeft, + plotTop, + zoomHor ? 1 : plotWidth, + zoomVert ? 1 : plotHeight, + 0 + ) + .attr({ + fill: chartOptions.selectionMarkerFill || 'rgba(69,114,167,0.25)', + zIndex: 7 + }) + .add(); + } + } + + // adjust the width of the selection marker + if (this.selectionMarker && zoomHor) { + size = chartX - mouseDownX; + this.selectionMarker.attr({ + width: mathAbs(size), + x: (size > 0 ? 0 : size) + mouseDownX + }); + } + // adjust the height of the selection marker + if (this.selectionMarker && zoomVert) { + size = chartY - mouseDownY; + this.selectionMarker.attr({ + height: mathAbs(size), + y: (size > 0 ? 0 : size) + mouseDownY + }); + } + + // panning + if (clickedInside && !this.selectionMarker && chartOptions.panning) { + chart.pan(e, chartOptions.panning); + } + } + }, + + /** + * On mouse up or touch end across the entire document, drop the selection. + */ + drop: function (e) { + var chart = this.chart, + hasPinched = this.hasPinched; + + if (this.selectionMarker) { + var selectionData = { + xAxis: [], + yAxis: [], + originalEvent: e.originalEvent || e + }, + selectionBox = this.selectionMarker, + selectionLeft = selectionBox.attr ? selectionBox.attr('x') : selectionBox.x, + selectionTop = selectionBox.attr ? selectionBox.attr('y') : selectionBox.y, + selectionWidth = selectionBox.attr ? selectionBox.attr('width') : selectionBox.width, + selectionHeight = selectionBox.attr ? selectionBox.attr('height') : selectionBox.height, + runZoom; + + // a selection has been made + if (this.hasDragged || hasPinched) { + + // record each axis' min and max + each(chart.axes, function (axis) { + if (axis.zoomEnabled) { + var horiz = axis.horiz, + selectionMin = axis.toValue((horiz ? selectionLeft : selectionTop)), + selectionMax = axis.toValue((horiz ? selectionLeft + selectionWidth : selectionTop + selectionHeight)); + + if (!isNaN(selectionMin) && !isNaN(selectionMax)) { // #859 + selectionData[axis.coll].push({ + axis: axis, + min: mathMin(selectionMin, selectionMax), // for reversed axes, + max: mathMax(selectionMin, selectionMax) + }); + runZoom = true; + } + } + }); + if (runZoom) { + fireEvent(chart, 'selection', selectionData, function (args) { + chart.zoom(extend(args, hasPinched ? { animation: false } : null)); + }); + } + + } + this.selectionMarker = this.selectionMarker.destroy(); + + // Reset scaling preview + if (hasPinched) { + this.scaleGroups(); + } + } + + // Reset all + if (chart) { // it may be destroyed on mouse up - #877 + css(chart.container, { cursor: chart._cursor }); + chart.cancelClick = this.hasDragged > 10; // #370 + chart.mouseIsDown = this.hasDragged = this.hasPinched = false; + this.pinchDown = []; + } + }, + + onContainerMouseDown: function (e) { + + e = this.normalize(e); + + // issue #295, dragging not always working in Firefox + if (e.preventDefault) { + e.preventDefault(); + } + + this.dragStart(e); + }, + + + + onDocumentMouseUp: function (e) { + if (charts[hoverChartIndex]) { + charts[hoverChartIndex].pointer.drop(e); + } + }, + + /** + * Special handler for mouse move that will hide the tooltip when the mouse leaves the plotarea. + * Issue #149 workaround. The mouseleave event does not always fire. + */ + onDocumentMouseMove: function (e) { + var chart = this.chart, + chartPosition = this.chartPosition, + hoverSeries = chart.hoverSeries; + + e = this.normalize(e, chartPosition); + + // If we're outside, hide the tooltip + if (chartPosition && hoverSeries && !this.inClass(e.target, 'highcharts-tracker') && + !chart.isInsidePlot(e.chartX - chart.plotLeft, e.chartY - chart.plotTop)) { + this.reset(); + } + }, + + /** + * When mouse leaves the container, hide the tooltip. + */ + onContainerMouseLeave: function () { + var chart = charts[hoverChartIndex]; + if (chart) { + chart.pointer.reset(); + chart.pointer.chartPosition = null; // also reset the chart position, used in #149 fix + } + }, + + // The mousemove, touchmove and touchstart event handler + onContainerMouseMove: function (e) { + + var chart = this.chart; + + hoverChartIndex = chart.index; + + // normalize + e = this.normalize(e); + + if (chart.mouseIsDown === 'mousedown') { + this.drag(e); + } + + // Show the tooltip and run mouse over events (#977) + if ((this.inClass(e.target, 'highcharts-tracker') || + chart.isInsidePlot(e.chartX - chart.plotLeft, e.chartY - chart.plotTop)) && !chart.openMenu) { + this.runPointActions(e); + } + }, + + /** + * Utility to detect whether an element has, or has a parent with, a specific + * class name. Used on detection of tracker objects and on deciding whether + * hovering the tooltip should cause the active series to mouse out. + */ + inClass: function (element, className) { + var elemClassName; + while (element) { + elemClassName = attr(element, 'class'); + if (elemClassName) { + if (elemClassName.indexOf(className) !== -1) { + return true; + } else if (elemClassName.indexOf(PREFIX + 'container') !== -1) { + return false; + } + } + element = element.parentNode; + } + }, + + onTrackerMouseOut: function (e) { + var series = this.chart.hoverSeries, + relatedTarget = e.relatedTarget || e.toElement, + relatedSeries = relatedTarget && relatedTarget.point && relatedTarget.point.series; // #2499 + + if (series && !series.options.stickyTracking && !this.inClass(relatedTarget, PREFIX + 'tooltip') && + relatedSeries !== series) { + series.onMouseOut(); + } + }, + + onContainerClick: function (e) { + var chart = this.chart, + hoverPoint = chart.hoverPoint, + plotLeft = chart.plotLeft, + plotTop = chart.plotTop; + + e = this.normalize(e); + e.cancelBubble = true; // IE specific + + if (!chart.cancelClick) { + + // On tracker click, fire the series and point events. #783, #1583 + if (hoverPoint && this.inClass(e.target, PREFIX + 'tracker')) { + + // the series click event + fireEvent(hoverPoint.series, 'click', extend(e, { + point: hoverPoint + })); + + // the point click event + if (chart.hoverPoint) { // it may be destroyed (#1844) + hoverPoint.firePointEvent('click', e); + } + + // When clicking outside a tracker, fire a chart event + } else { + extend(e, this.getCoordinates(e)); + + // fire a click event in the chart + if (chart.isInsidePlot(e.chartX - plotLeft, e.chartY - plotTop)) { + fireEvent(chart, 'click', e); + } + } + + + } + }, + + /** + * Set the JS DOM events on the container and document. This method should contain + * a one-to-one assignment between methods and their handlers. Any advanced logic should + * be moved to the handler reflecting the event's name. + */ + setDOMEvents: function () { + + var pointer = this, + container = pointer.chart.container; + + container.onmousedown = function (e) { + pointer.onContainerMouseDown(e); + }; + container.onmousemove = function (e) { + pointer.onContainerMouseMove(e); + }; + container.onclick = function (e) { + pointer.onContainerClick(e); + }; + addEvent(container, 'mouseleave', pointer.onContainerMouseLeave); + if (chartCount === 1) { + addEvent(doc, 'mouseup', pointer.onDocumentMouseUp); + } + if (hasTouch) { + container.ontouchstart = function (e) { + pointer.onContainerTouchStart(e); + }; + container.ontouchmove = function (e) { + pointer.onContainerTouchMove(e); + }; + if (chartCount === 1) { + addEvent(doc, 'touchend', pointer.onDocumentTouchEnd); + } + } + + }, + + /** + * Destroys the Pointer object and disconnects DOM events. + */ + destroy: function () { + var prop; + + removeEvent(this.chart.container, 'mouseleave', this.onContainerMouseLeave); + if (!chartCount) { + removeEvent(doc, 'mouseup', this.onDocumentMouseUp); + removeEvent(doc, 'touchend', this.onDocumentTouchEnd); + } + + // memory and CPU leak + clearInterval(this.tooltipTimeout); + + for (prop in this) { + this[prop] = null; + } + } +}; + + +/* Support for touch devices */ +extend(Highcharts.Pointer.prototype, { + + /** + * Run translation operations + */ + pinchTranslate: function (pinchDown, touches, transform, selectionMarker, clip, lastValidTouch) { + if (this.zoomHor || this.pinchHor) { + this.pinchTranslateDirection(true, pinchDown, touches, transform, selectionMarker, clip, lastValidTouch); + } + if (this.zoomVert || this.pinchVert) { + this.pinchTranslateDirection(false, pinchDown, touches, transform, selectionMarker, clip, lastValidTouch); + } + }, + + /** + * Run translation operations for each direction (horizontal and vertical) independently + */ + pinchTranslateDirection: function (horiz, pinchDown, touches, transform, selectionMarker, clip, lastValidTouch, forcedScale) { + var chart = this.chart, + xy = horiz ? 'x' : 'y', + XY = horiz ? 'X' : 'Y', + sChartXY = 'chart' + XY, + wh = horiz ? 'width' : 'height', + plotLeftTop = chart['plot' + (horiz ? 'Left' : 'Top')], + selectionWH, + selectionXY, + clipXY, + scale = forcedScale || 1, + inverted = chart.inverted, + bounds = chart.bounds[horiz ? 'h' : 'v'], + singleTouch = pinchDown.length === 1, + touch0Start = pinchDown[0][sChartXY], + touch0Now = touches[0][sChartXY], + touch1Start = !singleTouch && pinchDown[1][sChartXY], + touch1Now = !singleTouch && touches[1][sChartXY], + outOfBounds, + transformScale, + scaleKey, + setScale = function () { + if (!singleTouch && mathAbs(touch0Start - touch1Start) > 20) { // Don't zoom if fingers are too close on this axis + scale = forcedScale || mathAbs(touch0Now - touch1Now) / mathAbs(touch0Start - touch1Start); + } + + clipXY = ((plotLeftTop - touch0Now) / scale) + touch0Start; + selectionWH = chart['plot' + (horiz ? 'Width' : 'Height')] / scale; + }; + + // Set the scale, first pass + setScale(); + + selectionXY = clipXY; // the clip position (x or y) is altered if out of bounds, the selection position is not + + // Out of bounds + if (selectionXY < bounds.min) { + selectionXY = bounds.min; + outOfBounds = true; + } else if (selectionXY + selectionWH > bounds.max) { + selectionXY = bounds.max - selectionWH; + outOfBounds = true; + } + + // Is the chart dragged off its bounds, determined by dataMin and dataMax? + if (outOfBounds) { + + // Modify the touchNow position in order to create an elastic drag movement. This indicates + // to the user that the chart is responsive but can't be dragged further. + touch0Now -= 0.8 * (touch0Now - lastValidTouch[xy][0]); + if (!singleTouch) { + touch1Now -= 0.8 * (touch1Now - lastValidTouch[xy][1]); + } + + // Set the scale, second pass to adapt to the modified touchNow positions + setScale(); + + } else { + lastValidTouch[xy] = [touch0Now, touch1Now]; + } + + // Set geometry for clipping, selection and transformation + if (!inverted) { // TODO: implement clipping for inverted charts + clip[xy] = clipXY - plotLeftTop; + clip[wh] = selectionWH; + } + scaleKey = inverted ? (horiz ? 'scaleY' : 'scaleX') : 'scale' + XY; + transformScale = inverted ? 1 / scale : scale; + + selectionMarker[wh] = selectionWH; + selectionMarker[xy] = selectionXY; + transform[scaleKey] = scale; + transform['translate' + XY] = (transformScale * plotLeftTop) + (touch0Now - (transformScale * touch0Start)); + }, + + /** + * Handle touch events with two touches + */ + pinch: function (e) { + + var self = this, + chart = self.chart, + pinchDown = self.pinchDown, + followTouchMove = self.followTouchMove, + touches = e.touches, + touchesLength = touches.length, + lastValidTouch = self.lastValidTouch, + hasZoom = self.hasZoom, + selectionMarker = self.selectionMarker, + transform = {}, + fireClickEvent = touchesLength === 1 && ((self.inClass(e.target, PREFIX + 'tracker') && + chart.runTrackerClick) || chart.runChartClick), + clip = {}; + + // On touch devices, only proceed to trigger click if a handler is defined + if ((hasZoom || followTouchMove) && !fireClickEvent) { + e.preventDefault(); + } + + // Normalize each touch + map(touches, function (e) { + return self.normalize(e); + }); + + // Register the touch start position + if (e.type === 'touchstart') { + each(touches, function (e, i) { + pinchDown[i] = { chartX: e.chartX, chartY: e.chartY }; + }); + lastValidTouch.x = [pinchDown[0].chartX, pinchDown[1] && pinchDown[1].chartX]; + lastValidTouch.y = [pinchDown[0].chartY, pinchDown[1] && pinchDown[1].chartY]; + + // Identify the data bounds in pixels + each(chart.axes, function (axis) { + if (axis.zoomEnabled) { + var bounds = chart.bounds[axis.horiz ? 'h' : 'v'], + minPixelPadding = axis.minPixelPadding, + min = axis.toPixels(axis.dataMin), + max = axis.toPixels(axis.dataMax), + absMin = mathMin(min, max), + absMax = mathMax(min, max); + + // Store the bounds for use in the touchmove handler + bounds.min = mathMin(axis.pos, absMin - minPixelPadding); + bounds.max = mathMax(axis.pos + axis.len, absMax + minPixelPadding); + } + }); + + // Event type is touchmove, handle panning and pinching + } else if (pinchDown.length) { // can be 0 when releasing, if touchend fires first + + + // Set the marker + if (!selectionMarker) { + self.selectionMarker = selectionMarker = extend({ + destroy: noop + }, chart.plotBox); + } + + self.pinchTranslate(pinchDown, touches, transform, selectionMarker, clip, lastValidTouch); + + self.hasPinched = hasZoom; + + // Scale and translate the groups to provide visual feedback during pinching + self.scaleGroups(transform, clip); + + // Optionally move the tooltip on touchmove + if (!hasZoom && followTouchMove && touchesLength === 1) { + this.runPointActions(self.normalize(e)); + } + } + }, + + onContainerTouchStart: function (e) { + var chart = this.chart; + + hoverChartIndex = chart.index; + + if (e.touches.length === 1) { + + e = this.normalize(e); + + if (chart.isInsidePlot(e.chartX - chart.plotLeft, e.chartY - chart.plotTop)) { + + // Run mouse events and display tooltip etc + this.runPointActions(e); + + this.pinch(e); + + } else { + // Hide the tooltip on touching outside the plot area (#1203) + this.reset(); + } + + } else if (e.touches.length === 2) { + this.pinch(e); + } + }, + + onContainerTouchMove: function (e) { + if (e.touches.length === 1 || e.touches.length === 2) { + this.pinch(e); + } + }, + + onDocumentTouchEnd: function (e) { + if (charts[hoverChartIndex]) { + charts[hoverChartIndex].pointer.drop(e); + } + } + +}); +if (win.PointerEvent || win.MSPointerEvent) { + + // The touches object keeps track of the points being touched at all times + var touches = {}, + hasPointerEvent = !!win.PointerEvent, + getWebkitTouches = function () { + var key, fake = []; + fake.item = function (i) { return this[i]; }; + for (key in touches) { + if (touches.hasOwnProperty(key)) { + fake.push({ + pageX: touches[key].pageX, + pageY: touches[key].pageY, + target: touches[key].target + }); + } + } + return fake; + }, + translateMSPointer = function (e, method, wktype, callback) { + var p; + e = e.originalEvent || e; + if ((e.pointerType === 'touch' || e.pointerType === e.MSPOINTER_TYPE_TOUCH) && charts[hoverChartIndex]) { + callback(e); + p = charts[hoverChartIndex].pointer; + p[method]({ + type: wktype, + target: e.currentTarget, + preventDefault: noop, + touches: getWebkitTouches() + }); + } + }; + + /** + * Extend the Pointer prototype with methods for each event handler and more + */ + extend(Pointer.prototype, { + onContainerPointerDown: function (e) { + translateMSPointer(e, 'onContainerTouchStart', 'touchstart', function (e) { + touches[e.pointerId] = { pageX: e.pageX, pageY: e.pageY, target: e.currentTarget }; + }); + }, + onContainerPointerMove: function (e) { + translateMSPointer(e, 'onContainerTouchMove', 'touchmove', function (e) { + touches[e.pointerId] = { pageX: e.pageX, pageY: e.pageY }; + if (!touches[e.pointerId].target) { + touches[e.pointerId].target = e.currentTarget; + } + }); + }, + onDocumentPointerUp: function (e) { + translateMSPointer(e, 'onContainerTouchEnd', 'touchend', function (e) { + delete touches[e.pointerId]; + }); + }, + + /** + * Add or remove the MS Pointer specific events + */ + batchMSEvents: function (fn) { + fn(this.chart.container, hasPointerEvent ? 'pointerdown' : 'MSPointerDown', this.onContainerPointerDown); + fn(this.chart.container, hasPointerEvent ? 'pointermove' : 'MSPointerMove', this.onContainerPointerMove); + fn(doc, hasPointerEvent ? 'pointerup' : 'MSPointerUp', this.onDocumentPointerUp); + } + }); + + // Disable default IE actions for pinch and such on chart element + wrap(Pointer.prototype, 'init', function (proceed, chart, options) { + proceed.call(this, chart, options); + if (this.hasZoom || this.followTouchMove) { + css(chart.container, { + '-ms-touch-action': NONE, + 'touch-action': NONE + }); + } + }); + + // Add IE specific touch events to chart + wrap(Pointer.prototype, 'setDOMEvents', function (proceed) { + proceed.apply(this); + if (this.hasZoom || this.followTouchMove) { + this.batchMSEvents(addEvent); + } + }); + // Destroy MS events also + wrap(Pointer.prototype, 'destroy', function (proceed) { + this.batchMSEvents(removeEvent); + proceed.call(this); + }); +} +/** + * The overview of the chart's series + */ +var Legend = Highcharts.Legend = function (chart, options) { + this.init(chart, options); +}; + +Legend.prototype = { + + /** + * Initialize the legend + */ + init: function (chart, options) { + + var legend = this, + itemStyle = options.itemStyle, + padding = pick(options.padding, 8), + itemMarginTop = options.itemMarginTop || 0; + + this.options = options; + + if (!options.enabled) { + return; + } + + legend.baseline = pInt(itemStyle.fontSize) + 3 + itemMarginTop; // used in Series prototype + legend.itemStyle = itemStyle; + legend.itemHiddenStyle = merge(itemStyle, options.itemHiddenStyle); + legend.itemMarginTop = itemMarginTop; + legend.padding = padding; + legend.initialItemX = padding; + legend.initialItemY = padding - 5; // 5 is the number of pixels above the text + legend.maxItemWidth = 0; + legend.chart = chart; + legend.itemHeight = 0; + legend.lastLineHeight = 0; + legend.symbolWidth = pick(options.symbolWidth, 16); + legend.pages = []; + + + // Render it + legend.render(); + + // move checkboxes + addEvent(legend.chart, 'endResize', function () { + legend.positionCheckboxes(); + }); + + }, + + /** + * Set the colors for the legend item + * @param {Object} item A Series or Point instance + * @param {Object} visible Dimmed or colored + */ + colorizeItem: function (item, visible) { + var legend = this, + options = legend.options, + legendItem = item.legendItem, + legendLine = item.legendLine, + legendSymbol = item.legendSymbol, + hiddenColor = legend.itemHiddenStyle.color, + textColor = visible ? options.itemStyle.color : hiddenColor, + symbolColor = visible ? (item.legendColor || item.color || '#CCC') : hiddenColor, + markerOptions = item.options && item.options.marker, + symbolAttr = { fill: symbolColor }, + key, + val; + + if (legendItem) { + legendItem.css({ fill: textColor, color: textColor }); // color for #1553, oldIE + } + if (legendLine) { + legendLine.attr({ stroke: symbolColor }); + } + + if (legendSymbol) { + + // Apply marker options + if (markerOptions && legendSymbol.isMarker) { // #585 + symbolAttr.stroke = symbolColor; + markerOptions = item.convertAttribs(markerOptions); + for (key in markerOptions) { + val = markerOptions[key]; + if (val !== UNDEFINED) { + symbolAttr[key] = val; + } + } + } + + legendSymbol.attr(symbolAttr); + } + }, + + /** + * Position the legend item + * @param {Object} item A Series or Point instance + */ + positionItem: function (item) { + var legend = this, + options = legend.options, + symbolPadding = options.symbolPadding, + ltr = !options.rtl, + legendItemPos = item._legendItemPos, + itemX = legendItemPos[0], + itemY = legendItemPos[1], + checkbox = item.checkbox; + + if (item.legendGroup) { + item.legendGroup.translate( + ltr ? itemX : legend.legendWidth - itemX - 2 * symbolPadding - 4, + itemY + ); + } + + if (checkbox) { + checkbox.x = itemX; + checkbox.y = itemY; + } + }, + + /** + * Destroy a single legend item + * @param {Object} item The series or point + */ + destroyItem: function (item) { + var checkbox = item.checkbox; + + // destroy SVG elements + each(['legendItem', 'legendLine', 'legendSymbol', 'legendGroup'], function (key) { + if (item[key]) { + item[key] = item[key].destroy(); + } + }); + + if (checkbox) { + discardElement(item.checkbox); + } + }, + + /** + * Destroys the legend. + */ + destroy: function () { + var legend = this, + legendGroup = legend.group, + box = legend.box; + + if (box) { + legend.box = box.destroy(); + } + + if (legendGroup) { + legend.group = legendGroup.destroy(); + } + }, + + /** + * Position the checkboxes after the width is determined + */ + positionCheckboxes: function (scrollOffset) { + var alignAttr = this.group.alignAttr, + translateY, + clipHeight = this.clipHeight || this.legendHeight; + + if (alignAttr) { + translateY = alignAttr.translateY; + each(this.allItems, function (item) { + var checkbox = item.checkbox, + top; + + if (checkbox) { + top = (translateY + checkbox.y + (scrollOffset || 0) + 3); + css(checkbox, { + left: (alignAttr.translateX + item.checkboxOffset + checkbox.x - 20) + PX, + top: top + PX, + display: top > translateY - 6 && top < translateY + clipHeight - 6 ? '' : NONE + }); + } + }); + } + }, + + /** + * Render the legend title on top of the legend + */ + renderTitle: function () { + var options = this.options, + padding = this.padding, + titleOptions = options.title, + titleHeight = 0, + bBox; + + if (titleOptions.text) { + if (!this.title) { + this.title = this.chart.renderer.label(titleOptions.text, padding - 3, padding - 4, null, null, null, null, null, 'legend-title') + .attr({ zIndex: 1 }) + .css(titleOptions.style) + .add(this.group); + } + bBox = this.title.getBBox(); + titleHeight = bBox.height; + this.offsetWidth = bBox.width; // #1717 + this.contentGroup.attr({ translateY: titleHeight }); + } + this.titleHeight = titleHeight; + }, + + /** + * Render a single specific legend item + * @param {Object} item A series or point + */ + renderItem: function (item) { + var legend = this, + chart = legend.chart, + renderer = chart.renderer, + options = legend.options, + horizontal = options.layout === 'horizontal', + symbolWidth = legend.symbolWidth, + symbolPadding = options.symbolPadding, + itemStyle = legend.itemStyle, + itemHiddenStyle = legend.itemHiddenStyle, + padding = legend.padding, + itemDistance = horizontal ? pick(options.itemDistance, 20) : 0, // docs + ltr = !options.rtl, + itemHeight, + widthOption = options.width, + itemMarginBottom = options.itemMarginBottom || 0, + itemMarginTop = legend.itemMarginTop, + initialItemX = legend.initialItemX, + bBox, + itemWidth, + li = item.legendItem, + series = item.series && item.series.drawLegendSymbol ? item.series : item, + seriesOptions = series.options, + showCheckbox = legend.createCheckboxForItem && seriesOptions && seriesOptions.showCheckbox, + useHTML = options.useHTML; + + if (!li) { // generate it once, later move it + + // Generate the group box + // A group to hold the symbol and text. Text is to be appended in Legend class. + item.legendGroup = renderer.g('legend-item') + .attr({ zIndex: 1 }) + .add(legend.scrollGroup); + + // Draw the legend symbol inside the group box + series.drawLegendSymbol(legend, item); + + // Generate the list item text and add it to the group + item.legendItem = li = renderer.text( + options.labelFormat ? format(options.labelFormat, item) : options.labelFormatter.call(item), + ltr ? symbolWidth + symbolPadding : -symbolPadding, + legend.baseline, + useHTML + ) + .css(merge(item.visible ? itemStyle : itemHiddenStyle)) // merge to prevent modifying original (#1021) + .attr({ + align: ltr ? 'left' : 'right', + zIndex: 2 + }) + .add(item.legendGroup); + + if (legend.setItemEvents) { + legend.setItemEvents(item, li, useHTML, itemStyle, itemHiddenStyle); + } + + // Colorize the items + legend.colorizeItem(item, item.visible); + + // add the HTML checkbox on top + if (showCheckbox) { + legend.createCheckboxForItem(item); + } + } + + // calculate the positions for the next line + bBox = li.getBBox(); + + itemWidth = item.checkboxOffset = + options.itemWidth || + item.legendItemWidth || + symbolWidth + symbolPadding + bBox.width + itemDistance + (showCheckbox ? 20 : 0); + legend.itemHeight = itemHeight = mathRound(item.legendItemHeight || bBox.height); + + // if the item exceeds the width, start a new line + if (horizontal && legend.itemX - initialItemX + itemWidth > + (widthOption || (chart.chartWidth - 2 * padding - initialItemX - options.x))) { + legend.itemX = initialItemX; + legend.itemY += itemMarginTop + legend.lastLineHeight + itemMarginBottom; + legend.lastLineHeight = 0; // reset for next line + } + + // If the item exceeds the height, start a new column + /*if (!horizontal && legend.itemY + options.y + itemHeight > chart.chartHeight - spacingTop - spacingBottom) { + legend.itemY = legend.initialItemY; + legend.itemX += legend.maxItemWidth; + legend.maxItemWidth = 0; + }*/ + + // Set the edge positions + legend.maxItemWidth = mathMax(legend.maxItemWidth, itemWidth); + legend.lastItemY = itemMarginTop + legend.itemY + itemMarginBottom; + legend.lastLineHeight = mathMax(itemHeight, legend.lastLineHeight); // #915 + + // cache the position of the newly generated or reordered items + item._legendItemPos = [legend.itemX, legend.itemY]; + + // advance + if (horizontal) { + legend.itemX += itemWidth; + + } else { + legend.itemY += itemMarginTop + itemHeight + itemMarginBottom; + legend.lastLineHeight = itemHeight; + } + + // the width of the widest item + legend.offsetWidth = widthOption || mathMax( + (horizontal ? legend.itemX - initialItemX - itemDistance : itemWidth) + padding, + legend.offsetWidth + ); + }, + + /** + * Get all items, which is one item per series for normal series and one item per point + * for pie series. + */ + getAllItems: function () { + var allItems = []; + each(this.chart.series, function (series) { + var seriesOptions = series.options; + + // Handle showInLegend. If the series is linked to another series, defaults to false. + if (!pick(seriesOptions.showInLegend, !defined(seriesOptions.linkedTo) ? UNDEFINED : false, true)) { + return; + } + + // use points or series for the legend item depending on legendType + allItems = allItems.concat( + series.legendItems || + (seriesOptions.legendType === 'point' ? + series.data : + series) + ); + }); + return allItems; + }, + + /** + * Render the legend. This method can be called both before and after + * chart.render. If called after, it will only rearrange items instead + * of creating new ones. + */ + render: function () { + var legend = this, + chart = legend.chart, + renderer = chart.renderer, + legendGroup = legend.group, + allItems, + display, + legendWidth, + legendHeight, + box = legend.box, + options = legend.options, + padding = legend.padding, + legendBorderWidth = options.borderWidth, + legendBackgroundColor = options.backgroundColor; + + legend.itemX = legend.initialItemX; + legend.itemY = legend.initialItemY; + legend.offsetWidth = 0; + legend.lastItemY = 0; + + if (!legendGroup) { + legend.group = legendGroup = renderer.g('legend') + .attr({ zIndex: 7 }) + .add(); + legend.contentGroup = renderer.g() + .attr({ zIndex: 1 }) // above background + .add(legendGroup); + legend.scrollGroup = renderer.g() + .add(legend.contentGroup); + } + + legend.renderTitle(); + + // add each series or point + allItems = legend.getAllItems(); + + // sort by legendIndex + stableSort(allItems, function (a, b) { + return ((a.options && a.options.legendIndex) || 0) - ((b.options && b.options.legendIndex) || 0); + }); + + // reversed legend + if (options.reversed) { + allItems.reverse(); + } + + legend.allItems = allItems; + legend.display = display = !!allItems.length; + + // render the items + each(allItems, function (item) { + legend.renderItem(item); + }); + + // Draw the border + legendWidth = options.width || legend.offsetWidth; + legendHeight = legend.lastItemY + legend.lastLineHeight + legend.titleHeight; + + + legendHeight = legend.handleOverflow(legendHeight); + + if (legendBorderWidth || legendBackgroundColor) { + legendWidth += padding; + legendHeight += padding; + + if (!box) { + legend.box = box = renderer.rect( + 0, + 0, + legendWidth, + legendHeight, + options.borderRadius, + legendBorderWidth || 0 + ).attr({ + stroke: options.borderColor, + 'stroke-width': legendBorderWidth || 0, + fill: legendBackgroundColor || NONE + }) + .add(legendGroup) + .shadow(options.shadow); + box.isNew = true; + + } else if (legendWidth > 0 && legendHeight > 0) { + box[box.isNew ? 'attr' : 'animate']( + box.crisp({ width: legendWidth, height: legendHeight }) + ); + box.isNew = false; + } + + // hide the border if no items + box[display ? 'show' : 'hide'](); + } + + legend.legendWidth = legendWidth; + legend.legendHeight = legendHeight; + + // Now that the legend width and height are established, put the items in the + // final position + each(allItems, function (item) { + legend.positionItem(item); + }); + + // 1.x compatibility: positioning based on style + /*var props = ['left', 'right', 'top', 'bottom'], + prop, + i = 4; + while (i--) { + prop = props[i]; + if (options.style[prop] && options.style[prop] !== 'auto') { + options[i < 2 ? 'align' : 'verticalAlign'] = prop; + options[i < 2 ? 'x' : 'y'] = pInt(options.style[prop]) * (i % 2 ? -1 : 1); + } + }*/ + + if (display) { + legendGroup.align(extend({ + width: legendWidth, + height: legendHeight + }, options), true, 'spacingBox'); + } + + if (!chart.isResizing) { + this.positionCheckboxes(); + } + }, + + /** + * Set up the overflow handling by adding navigation with up and down arrows below the + * legend. + */ + handleOverflow: function (legendHeight) { + var legend = this, + chart = this.chart, + renderer = chart.renderer, + options = this.options, + optionsY = options.y, + alignTop = options.verticalAlign === 'top', + spaceHeight = chart.spacingBox.height + (alignTop ? -optionsY : optionsY) - this.padding, + maxHeight = options.maxHeight, + clipHeight, + clipRect = this.clipRect, + navOptions = options.navigation, + animation = pick(navOptions.animation, true), + arrowSize = navOptions.arrowSize || 12, + nav = this.nav, + pages = this.pages, + lastY, + allItems = this.allItems; + + // Adjust the height + if (options.layout === 'horizontal') { + spaceHeight /= 2; + } + if (maxHeight) { + spaceHeight = mathMin(spaceHeight, maxHeight); + } + + // Reset the legend height and adjust the clipping rectangle + pages.length = 0; + if (legendHeight > spaceHeight && !options.useHTML) { + + this.clipHeight = clipHeight = spaceHeight - 20 - this.titleHeight - this.padding; + this.currentPage = pick(this.currentPage, 1); + this.fullHeight = legendHeight; + + // Fill pages with Y positions so that the top of each a legend item defines + // the scroll top for each page (#2098) + each(allItems, function (item, i) { + var y = item._legendItemPos[1], + h = mathRound(item.legendItem.getBBox().height), + len = pages.length; + + if (!len || (y - pages[len - 1] > clipHeight && (lastY || y) !== pages[len - 1])) { + pages.push(lastY || y); + len++; + } + + if (i === allItems.length - 1 && y + h - pages[len - 1] > clipHeight) { + pages.push(y); + } + if (y !== lastY) { + lastY = y; + } + }); + + // Only apply clipping if needed. Clipping causes blurred legend in PDF export (#1787) + if (!clipRect) { + clipRect = legend.clipRect = renderer.clipRect(0, this.padding, 9999, 0); + legend.contentGroup.clip(clipRect); + } + clipRect.attr({ + height: clipHeight + }); + + // Add navigation elements + if (!nav) { + this.nav = nav = renderer.g().attr({ zIndex: 1 }).add(this.group); + this.up = renderer.symbol('triangle', 0, 0, arrowSize, arrowSize) + .on('click', function () { + legend.scroll(-1, animation); + }) + .add(nav); + this.pager = renderer.text('', 15, 10) + .css(navOptions.style) + .add(nav); + this.down = renderer.symbol('triangle-down', 0, 0, arrowSize, arrowSize) + .on('click', function () { + legend.scroll(1, animation); + }) + .add(nav); + } + + // Set initial position + legend.scroll(0); + + legendHeight = spaceHeight; + + } else if (nav) { + clipRect.attr({ + height: chart.chartHeight + }); + nav.hide(); + this.scrollGroup.attr({ + translateY: 1 + }); + this.clipHeight = 0; // #1379 + } + + return legendHeight; + }, + + /** + * Scroll the legend by a number of pages + * @param {Object} scrollBy + * @param {Object} animation + */ + scroll: function (scrollBy, animation) { + var pages = this.pages, + pageCount = pages.length, + currentPage = this.currentPage + scrollBy, + clipHeight = this.clipHeight, + navOptions = this.options.navigation, + activeColor = navOptions.activeColor, + inactiveColor = navOptions.inactiveColor, + pager = this.pager, + padding = this.padding, + scrollOffset; + + // When resizing while looking at the last page + if (currentPage > pageCount) { + currentPage = pageCount; + } + + if (currentPage > 0) { + + if (animation !== UNDEFINED) { + setAnimation(animation, this.chart); + } + + this.nav.attr({ + translateX: padding, + translateY: clipHeight + this.padding + 7 + this.titleHeight, + visibility: VISIBLE + }); + this.up.attr({ + fill: currentPage === 1 ? inactiveColor : activeColor + }) + .css({ + cursor: currentPage === 1 ? 'default' : 'pointer' + }); + pager.attr({ + text: currentPage + '/' + pageCount + }); + this.down.attr({ + x: 18 + this.pager.getBBox().width, // adjust to text width + fill: currentPage === pageCount ? inactiveColor : activeColor + }) + .css({ + cursor: currentPage === pageCount ? 'default' : 'pointer' + }); + + scrollOffset = -pages[currentPage - 1] + this.initialItemY; + + this.scrollGroup.animate({ + translateY: scrollOffset + }); + + this.currentPage = currentPage; + this.positionCheckboxes(scrollOffset); + } + + } + +}; + +/* + * LegendSymbolMixin + */ + +var LegendSymbolMixin = Highcharts.LegendSymbolMixin = { + + /** + * Get the series' symbol in the legend + * + * @param {Object} legend The legend object + * @param {Object} item The series (this) or point + */ + drawRectangle: function (legend, item) { + var symbolHeight = legend.options.symbolHeight || 12; + + item.legendSymbol = this.chart.renderer.rect( + 0, + legend.baseline - 5 - (symbolHeight / 2), + legend.symbolWidth, + symbolHeight, + legend.options.symbolRadius || 0 + ).attr({ + zIndex: 3 + }).add(item.legendGroup); + + }, + + /** + * Get the series' symbol in the legend. This method should be overridable to create custom + * symbols through Highcharts.seriesTypes[type].prototype.drawLegendSymbols. + * + * @param {Object} legend The legend object + */ + drawLineMarker: function (legend) { + + var options = this.options, + markerOptions = options.marker, + radius, + legendOptions = legend.options, + legendSymbol, + symbolWidth = legend.symbolWidth, + renderer = this.chart.renderer, + legendItemGroup = this.legendGroup, + verticalCenter = legend.baseline - mathRound(renderer.fontMetrics(legendOptions.itemStyle.fontSize).b * 0.3), + attr; + + // Draw the line + if (options.lineWidth) { + attr = { + 'stroke-width': options.lineWidth + }; + if (options.dashStyle) { + attr.dashstyle = options.dashStyle; + } + this.legendLine = renderer.path([ + M, + 0, + verticalCenter, + L, + symbolWidth, + verticalCenter + ]) + .attr(attr) + .add(legendItemGroup); + } + + // Draw the marker + if (markerOptions && markerOptions.enabled !== false) { + radius = markerOptions.radius; + this.legendSymbol = legendSymbol = renderer.symbol( + this.symbol, + (symbolWidth / 2) - radius, + verticalCenter - radius, + 2 * radius, + 2 * radius + ) + .add(legendItemGroup); + legendSymbol.isMarker = true; + } + } +}; + +// Workaround for #2030, horizontal legend items not displaying in IE11 Preview, +// and for #2580, a similar drawing flaw in Firefox 26. +// TODO: Explore if there's a general cause for this. The problem may be related +// to nested group elements, as the legend item texts are within 4 group elements. +if (/Trident\/7\.0/.test(userAgent) || isFirefox) { + wrap(Legend.prototype, 'positionItem', function (proceed, item) { + var legend = this, + runPositionItem = function () { // If chart destroyed in sync, this is undefined (#2030) + if (item._legendItemPos) { + proceed.call(legend, item); + } + }; + + // Do it now, for export and to get checkbox placement + runPositionItem(); + + // Do it after to work around the core issue + setTimeout(runPositionItem); + }); +} +/** + * The chart class + * @param {Object} options + * @param {Function} callback Function to run when the chart has loaded + */ +function Chart() { + this.init.apply(this, arguments); +} + +Chart.prototype = { + + /** + * Initialize the chart + */ + init: function (userOptions, callback) { + + // Handle regular options + var options, + seriesOptions = userOptions.series; // skip merging data points to increase performance + + userOptions.series = null; + options = merge(defaultOptions, userOptions); // do the merge + options.series = userOptions.series = seriesOptions; // set back the series data + this.userOptions = userOptions; + + var optionsChart = options.chart; + + // Create margin & spacing array + this.margin = this.splashArray('margin', optionsChart); + this.spacing = this.splashArray('spacing', optionsChart); + + var chartEvents = optionsChart.events; + + //this.runChartClick = chartEvents && !!chartEvents.click; + this.bounds = { h: {}, v: {} }; // Pixel data bounds for touch zoom + + this.callback = callback; + this.isResizing = 0; + this.options = options; + //chartTitleOptions = UNDEFINED; + //chartSubtitleOptions = UNDEFINED; + + this.axes = []; + this.series = []; + this.hasCartesianSeries = optionsChart.showAxes; + //this.axisOffset = UNDEFINED; + //this.maxTicks = UNDEFINED; // handle the greatest amount of ticks on grouped axes + //this.inverted = UNDEFINED; + //this.loadingShown = UNDEFINED; + //this.container = UNDEFINED; + //this.chartWidth = UNDEFINED; + //this.chartHeight = UNDEFINED; + //this.marginRight = UNDEFINED; + //this.marginBottom = UNDEFINED; + //this.containerWidth = UNDEFINED; + //this.containerHeight = UNDEFINED; + //this.oldChartWidth = UNDEFINED; + //this.oldChartHeight = UNDEFINED; + + //this.renderTo = UNDEFINED; + //this.renderToClone = UNDEFINED; + + //this.spacingBox = UNDEFINED + + //this.legend = UNDEFINED; + + // Elements + //this.chartBackground = UNDEFINED; + //this.plotBackground = UNDEFINED; + //this.plotBGImage = UNDEFINED; + //this.plotBorder = UNDEFINED; + //this.loadingDiv = UNDEFINED; + //this.loadingSpan = UNDEFINED; + + var chart = this, + eventType; + + // Add the chart to the global lookup + chart.index = charts.length; + charts.push(chart); + chartCount++; + + // Set up auto resize + if (optionsChart.reflow !== false) { + addEvent(chart, 'load', function () { + chart.initReflow(); + }); + } + + // Chart event handlers + if (chartEvents) { + for (eventType in chartEvents) { + addEvent(chart, eventType, chartEvents[eventType]); + } + } + + chart.xAxis = []; + chart.yAxis = []; + + // Expose methods and variables + chart.animation = useCanVG ? false : pick(optionsChart.animation, true); + chart.pointCount = 0; + chart.counters = new ChartCounters(); + + chart.firstRender(); + }, + + /** + * Initialize an individual series, called internally before render time + */ + initSeries: function (options) { + var chart = this, + optionsChart = chart.options.chart, + type = options.type || optionsChart.type || optionsChart.defaultSeriesType, + series, + constr = seriesTypes[type]; + + // No such series type + if (!constr) { + error(17, true); + } + + series = new constr(); + series.init(this, options); + return series; + }, + + /** + * Check whether a given point is within the plot area + * + * @param {Number} plotX Pixel x relative to the plot area + * @param {Number} plotY Pixel y relative to the plot area + * @param {Boolean} inverted Whether the chart is inverted + */ + isInsidePlot: function (plotX, plotY, inverted) { + var x = inverted ? plotY : plotX, + y = inverted ? plotX : plotY; + + return x >= 0 && + x <= this.plotWidth && + y >= 0 && + y <= this.plotHeight; + }, + + /** + * Adjust all axes tick amounts + */ + adjustTickAmounts: function () { + if (this.options.chart.alignTicks !== false) { + each(this.axes, function (axis) { + axis.adjustTickAmount(); + }); + } + this.maxTicks = null; + }, + + /** + * Redraw legend, axes or series based on updated data + * + * @param {Boolean|Object} animation Whether to apply animation, and optionally animation + * configuration + */ + redraw: function (animation) { + var chart = this, + axes = chart.axes, + series = chart.series, + pointer = chart.pointer, + legend = chart.legend, + redrawLegend = chart.isDirtyLegend, + hasStackedSeries, + hasDirtyStacks, + isDirtyBox = chart.isDirtyBox, // todo: check if it has actually changed? + seriesLength = series.length, + i = seriesLength, + serie, + renderer = chart.renderer, + isHiddenChart = renderer.isHidden(), + afterRedraw = []; + + setAnimation(animation, chart); + + if (isHiddenChart) { + chart.cloneRenderTo(); + } + + // Adjust title layout (reflow multiline text) + chart.layOutTitles(); + + // link stacked series + while (i--) { + serie = series[i]; + + if (serie.options.stacking) { + hasStackedSeries = true; + + if (serie.isDirty) { + hasDirtyStacks = true; + break; + } + } + } + if (hasDirtyStacks) { // mark others as dirty + i = seriesLength; + while (i--) { + serie = series[i]; + if (serie.options.stacking) { + serie.isDirty = true; + } + } + } + + // handle updated data in the series + each(series, function (serie) { + if (serie.isDirty) { // prepare the data so axis can read it + if (serie.options.legendType === 'point') { + redrawLegend = true; + } + } + }); + + // handle added or removed series + if (redrawLegend && legend.options.enabled) { // series or pie points are added or removed + // draw legend graphics + legend.render(); + + chart.isDirtyLegend = false; + } + + // reset stacks + if (hasStackedSeries) { + chart.getStacks(); + } + + + if (chart.hasCartesianSeries) { + if (!chart.isResizing) { + + // reset maxTicks + chart.maxTicks = null; + + // set axes scales + each(axes, function (axis) { + axis.setScale(); + }); + } + + chart.adjustTickAmounts(); + chart.getMargins(); + + // If one axis is dirty, all axes must be redrawn (#792, #2169) + each(axes, function (axis) { + if (axis.isDirty) { + isDirtyBox = true; + } + }); + + // redraw axes + each(axes, function (axis) { + + // Fire 'afterSetExtremes' only if extremes are set + if (axis.isDirtyExtremes) { // #821 + axis.isDirtyExtremes = false; + afterRedraw.push(function () { // prevent a recursive call to chart.redraw() (#1119) + fireEvent(axis, 'afterSetExtremes', extend(axis.eventArgs, axis.getExtremes())); // #747, #751 + delete axis.eventArgs; + }); + } + + if (isDirtyBox || hasStackedSeries) { + axis.redraw(); + } + }); + + + } + // the plot areas size has changed + if (isDirtyBox) { + chart.drawChartBox(); + } + + + // redraw affected series + each(series, function (serie) { + if (serie.isDirty && serie.visible && + (!serie.isCartesian || serie.xAxis)) { // issue #153 + serie.redraw(); + } + }); + + // move tooltip or reset + if (pointer) { + pointer.reset(true); + } + + // redraw if canvas + renderer.draw(); + + // fire the event + fireEvent(chart, 'redraw'); // jQuery breaks this when calling it from addEvent. Overwrites chart.redraw + + if (isHiddenChart) { + chart.cloneRenderTo(true); + } + + // Fire callbacks that are put on hold until after the redraw + each(afterRedraw, function (callback) { + callback.call(); + }); + }, + + /** + * Get an axis, series or point object by id. + * @param id {String} The id as given in the configuration options + */ + get: function (id) { + var chart = this, + axes = chart.axes, + series = chart.series; + + var i, + j, + points; + + // search axes + for (i = 0; i < axes.length; i++) { + if (axes[i].options.id === id) { + return axes[i]; + } + } + + // search series + for (i = 0; i < series.length; i++) { + if (series[i].options.id === id) { + return series[i]; + } + } + + // search points + for (i = 0; i < series.length; i++) { + points = series[i].points || []; + for (j = 0; j < points.length; j++) { + if (points[j].id === id) { + return points[j]; + } + } + } + return null; + }, + + /** + * Create the Axis instances based on the config options + */ + getAxes: function () { + var chart = this, + options = this.options, + xAxisOptions = options.xAxis = splat(options.xAxis || {}), + yAxisOptions = options.yAxis = splat(options.yAxis || {}), + optionsArray, + axis; + + // make sure the options are arrays and add some members + each(xAxisOptions, function (axis, i) { + axis.index = i; + axis.isX = true; + }); + + each(yAxisOptions, function (axis, i) { + axis.index = i; + }); + + // concatenate all axis options into one array + optionsArray = xAxisOptions.concat(yAxisOptions); + + each(optionsArray, function (axisOptions) { + axis = new Axis(chart, axisOptions); + }); + + chart.adjustTickAmounts(); + }, + + + /** + * Get the currently selected points from all series + */ + getSelectedPoints: function () { + var points = []; + each(this.series, function (serie) { + points = points.concat(grep(serie.points || [], function (point) { + return point.selected; + })); + }); + return points; + }, + + /** + * Get the currently selected series + */ + getSelectedSeries: function () { + return grep(this.series, function (serie) { + return serie.selected; + }); + }, + + /** + * Generate stacks for each series and calculate stacks total values + */ + getStacks: function () { + var chart = this; + + // reset stacks for each yAxis + each(chart.yAxis, function (axis) { + if (axis.stacks && axis.hasVisibleSeries) { + axis.oldStacks = axis.stacks; + } + }); + + each(chart.series, function (series) { + if (series.options.stacking && (series.visible === true || chart.options.chart.ignoreHiddenSeries === false)) { + series.stackKey = series.type + pick(series.options.stack, ''); + } + }); + }, + + /** + * Show the title and subtitle of the chart + * + * @param titleOptions {Object} New title options + * @param subtitleOptions {Object} New subtitle options + * + */ + setTitle: function (titleOptions, subtitleOptions, redraw) { + var chart = this, + options = chart.options, + chartTitleOptions, + chartSubtitleOptions; + + chartTitleOptions = options.title = merge(options.title, titleOptions); + chartSubtitleOptions = options.subtitle = merge(options.subtitle, subtitleOptions); + + // add title and subtitle + each([ + ['title', titleOptions, chartTitleOptions], + ['subtitle', subtitleOptions, chartSubtitleOptions] + ], function (arr) { + var name = arr[0], + title = chart[name], + titleOptions = arr[1], + chartTitleOptions = arr[2]; + + if (title && titleOptions) { + chart[name] = title = title.destroy(); // remove old + } + + if (chartTitleOptions && chartTitleOptions.text && !title) { + chart[name] = chart.renderer.text( + chartTitleOptions.text, + 0, + 0, + chartTitleOptions.useHTML + ) + .attr({ + align: chartTitleOptions.align, + 'class': PREFIX + name, + zIndex: chartTitleOptions.zIndex || 4 + }) + .css(chartTitleOptions.style) + .add(); + } + }); + chart.layOutTitles(redraw); + }, + + /** + * Lay out the chart titles and cache the full offset height for use in getMargins + */ + layOutTitles: function (redraw) { + var titleOffset = 0, + title = this.title, + subtitle = this.subtitle, + options = this.options, + titleOptions = options.title, + subtitleOptions = options.subtitle, + requiresDirtyBox, + autoWidth = this.spacingBox.width - 44; // 44 makes room for default context button + + if (title) { + title + .css({ width: (titleOptions.width || autoWidth) + PX }) + .align(extend({ y: 15 }, titleOptions), false, 'spacingBox'); + + if (!titleOptions.floating && !titleOptions.verticalAlign) { + titleOffset = title.getBBox().height; + } + } + if (subtitle) { + subtitle + .css({ width: (subtitleOptions.width || autoWidth) + PX }) + .align(extend({ y: titleOffset + titleOptions.margin }, subtitleOptions), false, 'spacingBox'); + + if (!subtitleOptions.floating && !subtitleOptions.verticalAlign) { + titleOffset = mathCeil(titleOffset + subtitle.getBBox().height); + } + } + + requiresDirtyBox = this.titleOffset !== titleOffset; + this.titleOffset = titleOffset; // used in getMargins + + if (!this.isDirtyBox && requiresDirtyBox) { + this.isDirtyBox = requiresDirtyBox; + // Redraw if necessary (#2719, #2744) + if (this.hasRendered && pick(redraw, true) && this.isDirtyBox) { + this.redraw(); + } + } + }, + + /** + * Get chart width and height according to options and container size + */ + getChartSize: function () { + var chart = this, + optionsChart = chart.options.chart, + widthOption = optionsChart.width, + heightOption = optionsChart.height, + renderTo = chart.renderToClone || chart.renderTo; + + // get inner width and height from jQuery (#824) + if (!defined(widthOption)) { + chart.containerWidth = adapterRun(renderTo, 'width'); + } + if (!defined(heightOption)) { + chart.containerHeight = adapterRun(renderTo, 'height'); + } + + chart.chartWidth = mathMax(0, widthOption || chart.containerWidth || 600); // #1393, 1460 + chart.chartHeight = mathMax(0, pick(heightOption, + // the offsetHeight of an empty container is 0 in standard browsers, but 19 in IE7: + chart.containerHeight > 19 ? chart.containerHeight : 400)); + }, + + /** + * Create a clone of the chart's renderTo div and place it outside the viewport to allow + * size computation on chart.render and chart.redraw + */ + cloneRenderTo: function (revert) { + var clone = this.renderToClone, + container = this.container; + + // Destroy the clone and bring the container back to the real renderTo div + if (revert) { + if (clone) { + this.renderTo.appendChild(container); + discardElement(clone); + delete this.renderToClone; + } + + // Set up the clone + } else { + if (container && container.parentNode === this.renderTo) { + this.renderTo.removeChild(container); // do not clone this + } + this.renderToClone = clone = this.renderTo.cloneNode(0); + css(clone, { + position: ABSOLUTE, + top: '-9999px', + display: 'block' // #833 + }); + if (clone.style.setProperty) { // #2631 + clone.style.setProperty('display', 'block', 'important'); + } + doc.body.appendChild(clone); + if (container) { + clone.appendChild(container); + } + } + }, + + /** + * Get the containing element, determine the size and create the inner container + * div to hold the chart + */ + getContainer: function () { + var chart = this, + container, + optionsChart = chart.options.chart, + chartWidth, + chartHeight, + renderTo, + indexAttrName = 'data-highcharts-chart', + oldChartIndex, + containerId; + + chart.renderTo = renderTo = optionsChart.renderTo; + containerId = PREFIX + idCounter++; + + if (isString(renderTo)) { + chart.renderTo = renderTo = doc.getElementById(renderTo); + } + + // Display an error if the renderTo is wrong + if (!renderTo) { + error(13, true); + } + + // If the container already holds a chart, destroy it. The check for hasRendered is there + // because web pages that are saved to disk from the browser, will preserve the data-highcharts-chart + // attribute and the SVG contents, but not an interactive chart. So in this case, + // charts[oldChartIndex] will point to the wrong chart if any (#2609). + oldChartIndex = pInt(attr(renderTo, indexAttrName)); + if (!isNaN(oldChartIndex) && charts[oldChartIndex] && charts[oldChartIndex].hasRendered) { + charts[oldChartIndex].destroy(); + } + + // Make a reference to the chart from the div + attr(renderTo, indexAttrName, chart.index); + + // remove previous chart + renderTo.innerHTML = ''; + + // If the container doesn't have an offsetWidth, it has or is a child of a node + // that has display:none. We need to temporarily move it out to a visible + // state to determine the size, else the legend and tooltips won't render + // properly. The allowClone option is used in sparklines as a micro optimization, + // saving about 1-2 ms each chart. + if (!optionsChart.skipClone && !renderTo.offsetWidth) { + chart.cloneRenderTo(); + } + + // get the width and height + chart.getChartSize(); + chartWidth = chart.chartWidth; + chartHeight = chart.chartHeight; + + // create the inner container + chart.container = container = createElement(DIV, { + className: PREFIX + 'container' + + (optionsChart.className ? ' ' + optionsChart.className : ''), + id: containerId + }, extend({ + position: RELATIVE, + overflow: HIDDEN, // needed for context menu (avoid scrollbars) and + // content overflow in IE + width: chartWidth + PX, + height: chartHeight + PX, + textAlign: 'left', + lineHeight: 'normal', // #427 + zIndex: 0, // #1072 + '-webkit-tap-highlight-color': 'rgba(0,0,0,0)' + }, optionsChart.style), + chart.renderToClone || renderTo + ); + + // cache the cursor (#1650) + chart._cursor = container.style.cursor; + + // Initialize the renderer + chart.renderer = + optionsChart.forExport ? // force SVG, used for SVG export + new SVGRenderer(container, chartWidth, chartHeight, optionsChart.style, true) : + new Renderer(container, chartWidth, chartHeight, optionsChart.style); + + if (useCanVG) { + // If we need canvg library, extend and configure the renderer + // to get the tracker for translating mouse events + chart.renderer.create(chart, container, chartWidth, chartHeight); + } + }, + + /** + * Calculate margins by rendering axis labels in a preliminary position. Title, + * subtitle and legend have already been rendered at this stage, but will be + * moved into their final positions + */ + getMargins: function () { + var chart = this, + spacing = chart.spacing, + axisOffset, + legend = chart.legend, + margin = chart.margin, + legendOptions = chart.options.legend, + legendMargin = pick(legendOptions.margin, 20), + legendX = legendOptions.x, + legendY = legendOptions.y, + align = legendOptions.align, + verticalAlign = legendOptions.verticalAlign, + titleOffset = chart.titleOffset; + + chart.resetMargins(); + axisOffset = chart.axisOffset; + + // Adjust for title and subtitle + if (titleOffset && !defined(margin[0])) { + chart.plotTop = mathMax(chart.plotTop, titleOffset + chart.options.title.margin + spacing[0]); + } + + // Adjust for legend + if (legend.display && !legendOptions.floating) { + if (align === 'right') { // horizontal alignment handled first + if (!defined(margin[1])) { + chart.marginRight = mathMax( + chart.marginRight, + legend.legendWidth - legendX + legendMargin + spacing[1] + ); + } + } else if (align === 'left') { + if (!defined(margin[3])) { + chart.plotLeft = mathMax( + chart.plotLeft, + legend.legendWidth + legendX + legendMargin + spacing[3] + ); + } + + } else if (verticalAlign === 'top') { + if (!defined(margin[0])) { + chart.plotTop = mathMax( + chart.plotTop, + legend.legendHeight + legendY + legendMargin + spacing[0] + ); + } + + } else if (verticalAlign === 'bottom') { + if (!defined(margin[2])) { + chart.marginBottom = mathMax( + chart.marginBottom, + legend.legendHeight - legendY + legendMargin + spacing[2] + ); + } + } + } + + // adjust for scroller + if (chart.extraBottomMargin) { + chart.marginBottom += chart.extraBottomMargin; + } + if (chart.extraTopMargin) { + chart.plotTop += chart.extraTopMargin; + } + + // pre-render axes to get labels offset width + if (chart.hasCartesianSeries) { + each(chart.axes, function (axis) { + axis.getOffset(); + }); + } + + if (!defined(margin[3])) { + chart.plotLeft += axisOffset[3]; + } + if (!defined(margin[0])) { + chart.plotTop += axisOffset[0]; + } + if (!defined(margin[2])) { + chart.marginBottom += axisOffset[2]; + } + if (!defined(margin[1])) { + chart.marginRight += axisOffset[1]; + } + + chart.setChartSize(); + + }, + + /** + * Resize the chart to its container if size is not explicitly set + */ + reflow: function (e) { + var chart = this, + optionsChart = chart.options.chart, + renderTo = chart.renderTo, + width = optionsChart.width || adapterRun(renderTo, 'width'), + height = optionsChart.height || adapterRun(renderTo, 'height'), + target = e ? e.target : win, // #805 - MooTools doesn't supply e + doReflow = function () { + if (chart.container) { // It may have been destroyed in the meantime (#1257) + chart.setSize(width, height, false); + chart.hasUserSize = null; + } + }; + + // Width and height checks for display:none. Target is doc in IE8 and Opera, + // win in Firefox, Chrome and IE9. + if (!chart.hasUserSize && width && height && (target === win || target === doc)) { + if (width !== chart.containerWidth || height !== chart.containerHeight) { + clearTimeout(chart.reflowTimeout); + if (e) { // Called from window.resize + chart.reflowTimeout = setTimeout(doReflow, 100); + } else { // Called directly (#2224) + doReflow(); + } + } + chart.containerWidth = width; + chart.containerHeight = height; + } + }, + + /** + * Add the event handlers necessary for auto resizing + */ + initReflow: function () { + var chart = this, + reflow = function (e) { + chart.reflow(e); + }; + + + addEvent(win, 'resize', reflow); + addEvent(chart, 'destroy', function () { + removeEvent(win, 'resize', reflow); + }); + }, + + /** + * Resize the chart to a given width and height + * @param {Number} width + * @param {Number} height + * @param {Object|Boolean} animation + */ + setSize: function (width, height, animation) { + var chart = this, + chartWidth, + chartHeight, + fireEndResize; + + // Handle the isResizing counter + chart.isResizing += 1; + fireEndResize = function () { + if (chart) { + fireEvent(chart, 'endResize', null, function () { + chart.isResizing -= 1; + }); + } + }; + + // set the animation for the current process + setAnimation(animation, chart); + + chart.oldChartHeight = chart.chartHeight; + chart.oldChartWidth = chart.chartWidth; + if (defined(width)) { + chart.chartWidth = chartWidth = mathMax(0, mathRound(width)); + chart.hasUserSize = !!chartWidth; + } + if (defined(height)) { + chart.chartHeight = chartHeight = mathMax(0, mathRound(height)); + } + + // Resize the container with the global animation applied if enabled (#2503) + (globalAnimation ? animate : css)(chart.container, { + width: chartWidth + PX, + height: chartHeight + PX + }, globalAnimation); + + chart.setChartSize(true); + chart.renderer.setSize(chartWidth, chartHeight, animation); + + // handle axes + chart.maxTicks = null; + each(chart.axes, function (axis) { + axis.isDirty = true; + axis.setScale(); + }); + + // make sure non-cartesian series are also handled + each(chart.series, function (serie) { + serie.isDirty = true; + }); + + chart.isDirtyLegend = true; // force legend redraw + chart.isDirtyBox = true; // force redraw of plot and chart border + + chart.layOutTitles(); // #2857 + chart.getMargins(); + + chart.redraw(animation); + + + chart.oldChartHeight = null; + fireEvent(chart, 'resize'); + + // fire endResize and set isResizing back + // If animation is disabled, fire without delay + if (globalAnimation === false) { + fireEndResize(); + } else { // else set a timeout with the animation duration + setTimeout(fireEndResize, (globalAnimation && globalAnimation.duration) || 500); + } + }, + + /** + * Set the public chart properties. This is done before and after the pre-render + * to determine margin sizes + */ + setChartSize: function (skipAxes) { + var chart = this, + inverted = chart.inverted, + renderer = chart.renderer, + chartWidth = chart.chartWidth, + chartHeight = chart.chartHeight, + optionsChart = chart.options.chart, + spacing = chart.spacing, + clipOffset = chart.clipOffset, + clipX, + clipY, + plotLeft, + plotTop, + plotWidth, + plotHeight, + plotBorderWidth; + + chart.plotLeft = plotLeft = mathRound(chart.plotLeft); + chart.plotTop = plotTop = mathRound(chart.plotTop); + chart.plotWidth = plotWidth = mathMax(0, mathRound(chartWidth - plotLeft - chart.marginRight)); + chart.plotHeight = plotHeight = mathMax(0, mathRound(chartHeight - plotTop - chart.marginBottom)); + + chart.plotSizeX = inverted ? plotHeight : plotWidth; + chart.plotSizeY = inverted ? plotWidth : plotHeight; + + chart.plotBorderWidth = optionsChart.plotBorderWidth || 0; + + // Set boxes used for alignment + chart.spacingBox = renderer.spacingBox = { + x: spacing[3], + y: spacing[0], + width: chartWidth - spacing[3] - spacing[1], + height: chartHeight - spacing[0] - spacing[2] + }; + chart.plotBox = renderer.plotBox = { + x: plotLeft, + y: plotTop, + width: plotWidth, + height: plotHeight + }; + + plotBorderWidth = 2 * mathFloor(chart.plotBorderWidth / 2); + clipX = mathCeil(mathMax(plotBorderWidth, clipOffset[3]) / 2); + clipY = mathCeil(mathMax(plotBorderWidth, clipOffset[0]) / 2); + chart.clipBox = { + x: clipX, + y: clipY, + width: mathFloor(chart.plotSizeX - mathMax(plotBorderWidth, clipOffset[1]) / 2 - clipX), + height: mathFloor(chart.plotSizeY - mathMax(plotBorderWidth, clipOffset[2]) / 2 - clipY) + }; + + if (!skipAxes) { + each(chart.axes, function (axis) { + axis.setAxisSize(); + axis.setAxisTranslation(); + }); + } + }, + + /** + * Initial margins before auto size margins are applied + */ + resetMargins: function () { + var chart = this, + spacing = chart.spacing, + margin = chart.margin; + + chart.plotTop = pick(margin[0], spacing[0]); + chart.marginRight = pick(margin[1], spacing[1]); + chart.marginBottom = pick(margin[2], spacing[2]); + chart.plotLeft = pick(margin[3], spacing[3]); + chart.axisOffset = [0, 0, 0, 0]; // top, right, bottom, left + chart.clipOffset = [0, 0, 0, 0]; + }, + + /** + * Draw the borders and backgrounds for chart and plot area + */ + drawChartBox: function () { + var chart = this, + optionsChart = chart.options.chart, + renderer = chart.renderer, + chartWidth = chart.chartWidth, + chartHeight = chart.chartHeight, + chartBackground = chart.chartBackground, + plotBackground = chart.plotBackground, + plotBorder = chart.plotBorder, + plotBGImage = chart.plotBGImage, + chartBorderWidth = optionsChart.borderWidth || 0, + chartBackgroundColor = optionsChart.backgroundColor, + plotBackgroundColor = optionsChart.plotBackgroundColor, + plotBackgroundImage = optionsChart.plotBackgroundImage, + plotBorderWidth = optionsChart.plotBorderWidth || 0, + mgn, + bgAttr, + plotLeft = chart.plotLeft, + plotTop = chart.plotTop, + plotWidth = chart.plotWidth, + plotHeight = chart.plotHeight, + plotBox = chart.plotBox, + clipRect = chart.clipRect, + clipBox = chart.clipBox; + + // Chart area + mgn = chartBorderWidth + (optionsChart.shadow ? 8 : 0); + + if (chartBorderWidth || chartBackgroundColor) { + if (!chartBackground) { + + bgAttr = { + fill: chartBackgroundColor || NONE + }; + if (chartBorderWidth) { // #980 + bgAttr.stroke = optionsChart.borderColor; + bgAttr['stroke-width'] = chartBorderWidth; + } + chart.chartBackground = renderer.rect(mgn / 2, mgn / 2, chartWidth - mgn, chartHeight - mgn, + optionsChart.borderRadius, chartBorderWidth) + .attr(bgAttr) + .addClass(PREFIX + 'background') + .add() + .shadow(optionsChart.shadow); + + } else { // resize + chartBackground.animate( + chartBackground.crisp({ width: chartWidth - mgn, height: chartHeight - mgn }) + ); + } + } + + + // Plot background + if (plotBackgroundColor) { + if (!plotBackground) { + chart.plotBackground = renderer.rect(plotLeft, plotTop, plotWidth, plotHeight, 0) + .attr({ + fill: plotBackgroundColor + }) + .add() + .shadow(optionsChart.plotShadow); + } else { + plotBackground.animate(plotBox); + } + } + if (plotBackgroundImage) { + if (!plotBGImage) { + chart.plotBGImage = renderer.image(plotBackgroundImage, plotLeft, plotTop, plotWidth, plotHeight) + .add(); + } else { + plotBGImage.animate(plotBox); + } + } + + // Plot clip + if (!clipRect) { + chart.clipRect = renderer.clipRect(clipBox); + } else { + clipRect.animate({ + width: clipBox.width, + height: clipBox.height + }); + } + + // Plot area border + if (plotBorderWidth) { + if (!plotBorder) { + chart.plotBorder = renderer.rect(plotLeft, plotTop, plotWidth, plotHeight, 0, -plotBorderWidth) + .attr({ + stroke: optionsChart.plotBorderColor, + 'stroke-width': plotBorderWidth, + fill: NONE, + zIndex: 1 + }) + .add(); + } else { + plotBorder.animate( + plotBorder.crisp({ x: plotLeft, y: plotTop, width: plotWidth, height: plotHeight }) + ); + } + } + + // reset + chart.isDirtyBox = false; + }, + + /** + * Detect whether a certain chart property is needed based on inspecting its options + * and series. This mainly applies to the chart.invert property, and in extensions to + * the chart.angular and chart.polar properties. + */ + propFromSeries: function () { + var chart = this, + optionsChart = chart.options.chart, + klass, + seriesOptions = chart.options.series, + i, + value; + + + each(['inverted', 'angular', 'polar'], function (key) { + + // The default series type's class + klass = seriesTypes[optionsChart.type || optionsChart.defaultSeriesType]; + + // Get the value from available chart-wide properties + value = ( + chart[key] || // 1. it is set before + optionsChart[key] || // 2. it is set in the options + (klass && klass.prototype[key]) // 3. it's default series class requires it + ); + + // 4. Check if any the chart's series require it + i = seriesOptions && seriesOptions.length; + while (!value && i--) { + klass = seriesTypes[seriesOptions[i].type]; + if (klass && klass.prototype[key]) { + value = true; + } + } + + // Set the chart property + chart[key] = value; + }); + + }, + + /** + * Link two or more series together. This is done initially from Chart.render, + * and after Chart.addSeries and Series.remove. + */ + linkSeries: function () { + var chart = this, + chartSeries = chart.series; + + // Reset links + each(chartSeries, function (series) { + series.linkedSeries.length = 0; + }); + + // Apply new links + each(chartSeries, function (series) { + var linkedTo = series.options.linkedTo; + if (isString(linkedTo)) { + if (linkedTo === ':previous') { + linkedTo = chart.series[series.index - 1]; + } else { + linkedTo = chart.get(linkedTo); + } + if (linkedTo) { + linkedTo.linkedSeries.push(series); + series.linkedParent = linkedTo; + } + } + }); + }, + + /** + * Render series for the chart + */ + renderSeries: function () { + each(this.series, function (serie) { + serie.translate(); + if (serie.setTooltipPoints) { + serie.setTooltipPoints(); + } + serie.render(); + }); + }, + + /** + * Render all graphics for the chart + */ + render: function () { + var chart = this, + axes = chart.axes, + renderer = chart.renderer, + options = chart.options; + + var labels = options.labels, + credits = options.credits, + creditsHref; + + // Title + chart.setTitle(); + + + // Legend + chart.legend = new Legend(chart, options.legend); + + chart.getStacks(); // render stacks + + // Get margins by pre-rendering axes + // set axes scales + each(axes, function (axis) { + axis.setScale(); + }); + + chart.getMargins(); + + chart.maxTicks = null; // reset for second pass + each(axes, function (axis) { + axis.setTickPositions(true); // update to reflect the new margins + axis.setMaxTicks(); + }); + chart.adjustTickAmounts(); + chart.getMargins(); // second pass to check for new labels + + + // Draw the borders and backgrounds + chart.drawChartBox(); + + + // Axes + if (chart.hasCartesianSeries) { + each(axes, function (axis) { + axis.render(); + }); + } + + // The series + if (!chart.seriesGroup) { + chart.seriesGroup = renderer.g('series-group') + .attr({ zIndex: 3 }) + .add(); + } + chart.renderSeries(); + + // Labels + if (labels.items) { + each(labels.items, function (label) { + var style = extend(labels.style, label.style), + x = pInt(style.left) + chart.plotLeft, + y = pInt(style.top) + chart.plotTop + 12; + + // delete to prevent rewriting in IE + delete style.left; + delete style.top; + + renderer.text( + label.html, + x, + y + ) + .attr({ zIndex: 2 }) + .css(style) + .add(); + + }); + } + + // Credits + if (credits.enabled && !chart.credits) { + creditsHref = credits.href; + chart.credits = renderer.text( + credits.text, + 0, + 0 + ) + .on('click', function () { + if (creditsHref) { + location.href = creditsHref; + } + }) + .attr({ + align: credits.position.align, + zIndex: 8 + }) + .css(credits.style) + .add() + .align(credits.position); + } + + // Set flag + chart.hasRendered = true; + + }, + + /** + * Clean up memory usage + */ + destroy: function () { + var chart = this, + axes = chart.axes, + series = chart.series, + container = chart.container, + i, + parentNode = container && container.parentNode; + + // fire the chart.destoy event + fireEvent(chart, 'destroy'); + + // Delete the chart from charts lookup array + charts[chart.index] = UNDEFINED; + chartCount--; + chart.renderTo.removeAttribute('data-highcharts-chart'); + + // remove events + removeEvent(chart); + + // ==== Destroy collections: + // Destroy axes + i = axes.length; + while (i--) { + axes[i] = axes[i].destroy(); + } + + // Destroy each series + i = series.length; + while (i--) { + series[i] = series[i].destroy(); + } + + // ==== Destroy chart properties: + each(['title', 'subtitle', 'chartBackground', 'plotBackground', 'plotBGImage', + 'plotBorder', 'seriesGroup', 'clipRect', 'credits', 'pointer', 'scroller', + 'rangeSelector', 'legend', 'resetZoomButton', 'tooltip', 'renderer'], function (name) { + var prop = chart[name]; + + if (prop && prop.destroy) { + chart[name] = prop.destroy(); + } + }); + + // remove container and all SVG + if (container) { // can break in IE when destroyed before finished loading + container.innerHTML = ''; + removeEvent(container); + if (parentNode) { + discardElement(container); + } + + } + + // clean it all up + for (i in chart) { + delete chart[i]; + } + + }, + + + /** + * VML namespaces can't be added until after complete. Listening + * for Perini's doScroll hack is not enough. + */ + isReadyToRender: function () { + var chart = this; + + // Note: in spite of JSLint's complaints, win == win.top is required + /*jslint eqeq: true*/ + if ((!hasSVG && (win == win.top && doc.readyState !== 'complete')) || (useCanVG && !win.canvg)) { + /*jslint eqeq: false*/ + if (useCanVG) { + // Delay rendering until canvg library is downloaded and ready + CanVGController.push(function () { chart.firstRender(); }, chart.options.global.canvasToolsURL); + } else { + doc.attachEvent('onreadystatechange', function () { + doc.detachEvent('onreadystatechange', chart.firstRender); + if (doc.readyState === 'complete') { + chart.firstRender(); + } + }); + } + return false; + } + return true; + }, + + /** + * Prepare for first rendering after all data are loaded + */ + firstRender: function () { + var chart = this, + options = chart.options, + callback = chart.callback; + + // Check whether the chart is ready to render + if (!chart.isReadyToRender()) { + return; + } + + // Create the container + chart.getContainer(); + + // Run an early event after the container and renderer are established + fireEvent(chart, 'init'); + + + chart.resetMargins(); + chart.setChartSize(); + + // Set the common chart properties (mainly invert) from the given series + chart.propFromSeries(); + + // get axes + chart.getAxes(); + + // Initialize the series + each(options.series || [], function (serieOptions) { + chart.initSeries(serieOptions); + }); + + chart.linkSeries(); + + // Run an event after axes and series are initialized, but before render. At this stage, + // the series data is indexed and cached in the xData and yData arrays, so we can access + // those before rendering. Used in Highstock. + fireEvent(chart, 'beforeRender'); + + // depends on inverted and on margins being set + if (Highcharts.Pointer) { + chart.pointer = new Pointer(chart, options); + } + + chart.render(); + + // add canvas + chart.renderer.draw(); + // run callbacks + if (callback) { + callback.apply(chart, [chart]); + } + each(chart.callbacks, function (fn) { + fn.apply(chart, [chart]); + }); + + + // If the chart was rendered outside the top container, put it back in + chart.cloneRenderTo(true); + + fireEvent(chart, 'load'); + + }, + + /** + * Creates arrays for spacing and margin from given options. + */ + splashArray: function (target, options) { + var oVar = options[target], + tArray = isObject(oVar) ? oVar : [oVar, oVar, oVar, oVar]; + + return [pick(options[target + 'Top'], tArray[0]), + pick(options[target + 'Right'], tArray[1]), + pick(options[target + 'Bottom'], tArray[2]), + pick(options[target + 'Left'], tArray[3])]; + } +}; // end Chart + +// Hook for exporting module +Chart.prototype.callbacks = []; + +var CenteredSeriesMixin = Highcharts.CenteredSeriesMixin = { + /** + * Get the center of the pie based on the size and center options relative to the + * plot area. Borrowed by the polar and gauge series types. + */ + getCenter: function () { + + var options = this.options, + chart = this.chart, + slicingRoom = 2 * (options.slicedOffset || 0), + handleSlicingRoom, + plotWidth = chart.plotWidth - 2 * slicingRoom, + plotHeight = chart.plotHeight - 2 * slicingRoom, + centerOption = options.center, + positions = [pick(centerOption[0], '50%'), pick(centerOption[1], '50%'), options.size || '100%', options.innerSize || 0], + smallestSize = mathMin(plotWidth, plotHeight), + isPercent; + + return map(positions, function (length, i) { + isPercent = /%$/.test(length); + handleSlicingRoom = i < 2 || (i === 2 && isPercent); + return (isPercent ? + // i == 0: centerX, relative to width + // i == 1: centerY, relative to height + // i == 2: size, relative to smallestSize + // i == 4: innerSize, relative to smallestSize + [plotWidth, plotHeight, smallestSize, smallestSize][i] * + pInt(length) / 100 : + length) + (handleSlicingRoom ? slicingRoom : 0); + }); + } +}; + +/** + * The Point object and prototype. Inheritable and used as base for PiePoint + */ +var Point = function () {}; +Point.prototype = { + + /** + * Initialize the point + * @param {Object} series The series object containing this point + * @param {Object} options The data in either number, array or object format + */ + init: function (series, options, x) { + + var point = this, + colors; + point.series = series; + point.applyOptions(options, x); + point.pointAttr = {}; + + if (series.options.colorByPoint) { + colors = series.options.colors || series.chart.options.colors; + point.color = point.color || colors[series.colorCounter++]; + // loop back to zero + if (series.colorCounter === colors.length) { + series.colorCounter = 0; + } + } + + series.chart.pointCount++; + return point; + }, + /** + * Apply the options containing the x and y data and possible some extra properties. + * This is called on point init or from point.update. + * + * @param {Object} options + */ + applyOptions: function (options, x) { + var point = this, + series = point.series, + pointValKey = series.pointValKey; + + options = Point.prototype.optionsToObject.call(this, options); + + // copy options directly to point + extend(point, options); + point.options = point.options ? extend(point.options, options) : options; + + // For higher dimension series types. For instance, for ranges, point.y is mapped to point.low. + if (pointValKey) { + point.y = point[pointValKey]; + } + + // If no x is set by now, get auto incremented value. All points must have an + // x value, however the y value can be null to create a gap in the series + if (point.x === UNDEFINED && series) { + point.x = x === UNDEFINED ? series.autoIncrement() : x; + } + + return point; + }, + + /** + * Transform number or array configs into objects + */ + optionsToObject: function (options) { + var ret = {}, + series = this.series, + pointArrayMap = series.pointArrayMap || ['y'], + valueCount = pointArrayMap.length, + firstItemType, + i = 0, + j = 0; + + if (typeof options === 'number' || options === null) { + ret[pointArrayMap[0]] = options; + + } else if (isArray(options)) { + // with leading x value + if (options.length > valueCount) { + firstItemType = typeof options[0]; + if (firstItemType === 'string') { + ret.name = options[0]; + } else if (firstItemType === 'number') { + ret.x = options[0]; + } + i++; + } + while (j < valueCount) { + ret[pointArrayMap[j++]] = options[i++]; + } + } else if (typeof options === 'object') { + ret = options; + + // This is the fastest way to detect if there are individual point dataLabels that need + // to be considered in drawDataLabels. These can only occur in object configs. + if (options.dataLabels) { + series._hasPointLabels = true; + } + + // Same approach as above for markers + if (options.marker) { + series._hasPointMarkers = true; + } + } + return ret; + }, + + /** + * Destroy a point to clear memory. Its reference still stays in series.data. + */ + destroy: function () { + var point = this, + series = point.series, + chart = series.chart, + hoverPoints = chart.hoverPoints, + prop; + + chart.pointCount--; + + if (hoverPoints) { + point.setState(); + erase(hoverPoints, point); + if (!hoverPoints.length) { + chart.hoverPoints = null; + } + + } + if (point === chart.hoverPoint) { + point.onMouseOut(); + } + + // remove all events + if (point.graphic || point.dataLabel) { // removeEvent and destroyElements are performance expensive + removeEvent(point); + point.destroyElements(); + } + + if (point.legendItem) { // pies have legend items + chart.legend.destroyItem(point); + } + + for (prop in point) { + point[prop] = null; + } + + + }, + + /** + * Destroy SVG elements associated with the point + */ + destroyElements: function () { + var point = this, + props = ['graphic', 'dataLabel', 'dataLabelUpper', 'group', 'connector', 'shadowGroup'], + prop, + i = 6; + while (i--) { + prop = props[i]; + if (point[prop]) { + point[prop] = point[prop].destroy(); + } + } + }, + + /** + * Return the configuration hash needed for the data label and tooltip formatters + */ + getLabelConfig: function () { + var point = this; + return { + x: point.category, + y: point.y, + key: point.name || point.category, + series: point.series, + point: point, + percentage: point.percentage, + total: point.total || point.stackTotal + }; + }, + + /** + * Extendable method for formatting each point's tooltip line + * + * @return {String} A string to be concatenated in to the common tooltip text + */ + tooltipFormatter: function (pointFormat) { + + // Insert options for valueDecimals, valuePrefix, and valueSuffix + var series = this.series, + seriesTooltipOptions = series.tooltipOptions, + valueDecimals = pick(seriesTooltipOptions.valueDecimals, ''), + valuePrefix = seriesTooltipOptions.valuePrefix || '', + valueSuffix = seriesTooltipOptions.valueSuffix || ''; + + // Loop over the point array map and replace unformatted values with sprintf formatting markup + each(series.pointArrayMap || ['y'], function (key) { + key = '{point.' + key; // without the closing bracket + if (valuePrefix || valueSuffix) { + pointFormat = pointFormat.replace(key + '}', valuePrefix + key + '}' + valueSuffix); + } + pointFormat = pointFormat.replace(key + '}', key + ':,.' + valueDecimals + 'f}'); + }); + + return format(pointFormat, { + point: this, + series: this.series + }); + }, + + /** + * Fire an event on the Point object. Must not be renamed to fireEvent, as this + * causes a name clash in MooTools + * @param {String} eventType + * @param {Object} eventArgs Additional event arguments + * @param {Function} defaultFunction Default event handler + */ + firePointEvent: function (eventType, eventArgs, defaultFunction) { + var point = this, + series = this.series, + seriesOptions = series.options; + + // load event handlers on demand to save time on mouseover/out + if (seriesOptions.point.events[eventType] || (point.options && point.options.events && point.options.events[eventType])) { + this.importEvents(); + } + + // add default handler if in selection mode + if (eventType === 'click' && seriesOptions.allowPointSelect) { + defaultFunction = function (event) { + // Control key is for Windows, meta (= Cmd key) for Mac, Shift for Opera + point.select(null, event.ctrlKey || event.metaKey || event.shiftKey); + }; + } + + fireEvent(this, eventType, eventArgs, defaultFunction); + } +};/** + * @classDescription The base function which all other series types inherit from. The data in the series is stored + * in various arrays. + * + * - First, series.options.data contains all the original config options for + * each point whether added by options or methods like series.addPoint. + * - Next, series.data contains those values converted to points, but in case the series data length + * exceeds the cropThreshold, or if the data is grouped, series.data doesn't contain all the points. It + * only contains the points that have been created on demand. + * - Then there's series.points that contains all currently visible point objects. In case of cropping, + * the cropped-away points are not part of this array. The series.points array starts at series.cropStart + * compared to series.data and series.options.data. If however the series data is grouped, these can't + * be correlated one to one. + * - series.xData and series.processedXData contain clean x values, equivalent to series.data and series.points. + * - series.yData and series.processedYData contain clean x values, equivalent to series.data and series.points. + * + * @param {Object} chart + * @param {Object} options + */ +var Series = function () {}; + +Series.prototype = { + + isCartesian: true, + type: 'line', + pointClass: Point, + sorted: true, // requires the data to be sorted + requireSorting: true, + pointAttrToOptions: { // mapping between SVG attributes and the corresponding options + stroke: 'lineColor', + 'stroke-width': 'lineWidth', + fill: 'fillColor', + r: 'radius' + }, + axisTypes: ['xAxis', 'yAxis'], + colorCounter: 0, + parallelArrays: ['x', 'y'], // each point's x and y values are stored in this.xData and this.yData + init: function (chart, options) { + var series = this, + eventType, + events, + chartSeries = chart.series, + sortByIndex = function (a, b) { + return pick(a.options.index, a._i) - pick(b.options.index, b._i); + }; + + series.chart = chart; + series.options = options = series.setOptions(options); // merge with plotOptions + series.linkedSeries = []; + + // bind the axes + series.bindAxes(); + + // set some variables + extend(series, { + name: options.name, + state: NORMAL_STATE, + pointAttr: {}, + visible: options.visible !== false, // true by default + selected: options.selected === true // false by default + }); + + // special + if (useCanVG) { + options.animation = false; + } + + // register event listeners + events = options.events; + for (eventType in events) { + addEvent(series, eventType, events[eventType]); + } + if ( + (events && events.click) || + (options.point && options.point.events && options.point.events.click) || + options.allowPointSelect + ) { + chart.runTrackerClick = true; + } + + series.getColor(); + series.getSymbol(); + + // Set the data + each(series.parallelArrays, function (key) { + series[key + 'Data'] = []; + }); + series.setData(options.data, false); + + // Mark cartesian + if (series.isCartesian) { + chart.hasCartesianSeries = true; + } + + // Register it in the chart + chartSeries.push(series); + series._i = chartSeries.length - 1; + + // Sort series according to index option (#248, #1123, #2456) + stableSort(chartSeries, sortByIndex); + if (this.yAxis) { + stableSort(this.yAxis.series, sortByIndex); + } + + each(chartSeries, function (series, i) { + series.index = i; + series.name = series.name || 'Series ' + (i + 1); + }); + + }, + + /** + * Set the xAxis and yAxis properties of cartesian series, and register the series + * in the axis.series array + */ + bindAxes: function () { + var series = this, + seriesOptions = series.options, + chart = series.chart, + axisOptions; + + each(series.axisTypes || [], function (AXIS) { // repeat for xAxis and yAxis + + each(chart[AXIS], function (axis) { // loop through the chart's axis objects + axisOptions = axis.options; + + // apply if the series xAxis or yAxis option mathches the number of the + // axis, or if undefined, use the first axis + if ((seriesOptions[AXIS] === axisOptions.index) || + (seriesOptions[AXIS] !== UNDEFINED && seriesOptions[AXIS] === axisOptions.id) || + (seriesOptions[AXIS] === UNDEFINED && axisOptions.index === 0)) { + + // register this series in the axis.series lookup + axis.series.push(series); + + // set this series.xAxis or series.yAxis reference + series[AXIS] = axis; + + // mark dirty for redraw + axis.isDirty = true; + } + }); + + // The series needs an X and an Y axis + if (!series[AXIS] && series.optionalAxis !== AXIS) { + error(18, true); + } + + }); + }, + + /** + * For simple series types like line and column, the data values are held in arrays like + * xData and yData for quick lookup to find extremes and more. For multidimensional series + * like bubble and map, this can be extended with arrays like zData and valueData by + * adding to the series.parallelArrays array. + */ + updateParallelArrays: function (point, i) { + var series = point.series, + args = arguments, + fn = typeof i === 'number' ? + // Insert the value in the given position + function (key) { + var val = key === 'y' && series.toYData ? series.toYData(point) : point[key]; + series[key + 'Data'][i] = val; + } : + // Apply the method specified in i with the following arguments as arguments + function (key) { + Array.prototype[i].apply(series[key + 'Data'], Array.prototype.slice.call(args, 2)); + }; + + each(series.parallelArrays, fn); + }, + + /** + * Return an auto incremented x value based on the pointStart and pointInterval options. + * This is only used if an x value is not given for the point that calls autoIncrement. + */ + autoIncrement: function () { + var series = this, + options = series.options, + xIncrement = series.xIncrement; + + xIncrement = pick(xIncrement, options.pointStart, 0); + + series.pointInterval = pick(series.pointInterval, options.pointInterval, 1); + + series.xIncrement = xIncrement + series.pointInterval; + return xIncrement; + }, + + /** + * Divide the series data into segments divided by null values. + */ + getSegments: function () { + var series = this, + lastNull = -1, + segments = [], + i, + points = series.points, + pointsLength = points.length; + + if (pointsLength) { // no action required for [] + + // if connect nulls, just remove null points + if (series.options.connectNulls) { + i = pointsLength; + while (i--) { + if (points[i].y === null) { + points.splice(i, 1); + } + } + if (points.length) { + segments = [points]; + } + + // else, split on null points + } else { + each(points, function (point, i) { + if (point.y === null) { + if (i > lastNull + 1) { + segments.push(points.slice(lastNull + 1, i)); + } + lastNull = i; + } else if (i === pointsLength - 1) { // last value + segments.push(points.slice(lastNull + 1, i + 1)); + } + }); + } + } + + // register it + series.segments = segments; + }, + + /** + * Set the series options by merging from the options tree + * @param {Object} itemOptions + */ + setOptions: function (itemOptions) { + var chart = this.chart, + chartOptions = chart.options, + plotOptions = chartOptions.plotOptions, + userOptions = chart.userOptions || {}, + userPlotOptions = userOptions.plotOptions || {}, + typeOptions = plotOptions[this.type], + options; + + this.userOptions = itemOptions; + + options = merge( + typeOptions, + plotOptions.series, + itemOptions + ); + + // The tooltip options are merged between global and series specific options + this.tooltipOptions = merge( + defaultOptions.tooltip, + defaultOptions.plotOptions[this.type].tooltip, + userOptions.tooltip, + userPlotOptions.series && userPlotOptions.series.tooltip, + userPlotOptions[this.type] && userPlotOptions[this.type].tooltip, + itemOptions.tooltip + ); + + // Delete marker object if not allowed (#1125) + if (typeOptions.marker === null) { + delete options.marker; + } + + return options; + + }, + /** + * Get the series' color + */ + getColor: function () { + var options = this.options, + userOptions = this.userOptions, + defaultColors = this.chart.options.colors, + counters = this.chart.counters, + color, + colorIndex; + + color = options.color || defaultPlotOptions[this.type].color; + + if (!color && !options.colorByPoint) { + if (defined(userOptions._colorIndex)) { // after Series.update() + colorIndex = userOptions._colorIndex; + } else { + userOptions._colorIndex = counters.color; + colorIndex = counters.color++; + } + color = defaultColors[colorIndex]; + } + + this.color = color; + counters.wrapColor(defaultColors.length); + }, + /** + * Get the series' symbol + */ + getSymbol: function () { + var series = this, + userOptions = series.userOptions, + seriesMarkerOption = series.options.marker, + chart = series.chart, + defaultSymbols = chart.options.symbols, + counters = chart.counters, + symbolIndex; + + series.symbol = seriesMarkerOption.symbol; + if (!series.symbol) { + if (defined(userOptions._symbolIndex)) { // after Series.update() + symbolIndex = userOptions._symbolIndex; + } else { + userOptions._symbolIndex = counters.symbol; + symbolIndex = counters.symbol++; + } + series.symbol = defaultSymbols[symbolIndex]; + } + + // don't substract radius in image symbols (#604) + if (/^url/.test(series.symbol)) { + seriesMarkerOption.radius = 0; + } + counters.wrapSymbol(defaultSymbols.length); + }, + + drawLegendSymbol: LegendSymbolMixin.drawLineMarker, + + /** + * Replace the series data with a new set of data + * @param {Object} data + * @param {Object} redraw + */ + setData: function (data, redraw, animation, updatePoints) { + var series = this, + oldData = series.points, + oldDataLength = (oldData && oldData.length) || 0, + dataLength, + options = series.options, + chart = series.chart, + firstPoint = null, + xAxis = series.xAxis, + hasCategories = xAxis && !!xAxis.categories, + tooltipPoints = series.tooltipPoints, + i, + turboThreshold = options.turboThreshold, + pt, + xData = this.xData, + yData = this.yData, + pointArrayMap = series.pointArrayMap, + valueCount = pointArrayMap && pointArrayMap.length; + + data = data || []; + dataLength = data.length; + redraw = pick(redraw, true); + + // If the point count is the same as is was, just run Point.update which is + // cheaper, allows animation, and keeps references to points. + if (updatePoints !== false && dataLength && oldDataLength === dataLength && !series.cropped && !series.hasGroupedData) { + each(data, function (point, i) { + oldData[i].update(point, false); + }); + + } else { + + // Reset properties + series.xIncrement = null; + series.pointRange = hasCategories ? 1 : options.pointRange; + + series.colorCounter = 0; // for series with colorByPoint (#1547) + + // Update parallel arrays + each(this.parallelArrays, function (key) { + series[key + 'Data'].length = 0; + }); + + // In turbo mode, only one- or twodimensional arrays of numbers are allowed. The + // first value is tested, and we assume that all the rest are defined the same + // way. Although the 'for' loops are similar, they are repeated inside each + // if-else conditional for max performance. + if (turboThreshold && dataLength > turboThreshold) { + + // find the first non-null point + i = 0; + while (firstPoint === null && i < dataLength) { + firstPoint = data[i]; + i++; + } + + + if (isNumber(firstPoint)) { // assume all points are numbers + var x = pick(options.pointStart, 0), + pointInterval = pick(options.pointInterval, 1); + + for (i = 0; i < dataLength; i++) { + xData[i] = x; + yData[i] = data[i]; + x += pointInterval; + } + series.xIncrement = x; + } else if (isArray(firstPoint)) { // assume all points are arrays + if (valueCount) { // [x, low, high] or [x, o, h, l, c] + for (i = 0; i < dataLength; i++) { + pt = data[i]; + xData[i] = pt[0]; + yData[i] = pt.slice(1, valueCount + 1); + } + } else { // [x, y] + for (i = 0; i < dataLength; i++) { + pt = data[i]; + xData[i] = pt[0]; + yData[i] = pt[1]; + } + } + } else { + error(12); // Highcharts expects configs to be numbers or arrays in turbo mode + } + } else { + for (i = 0; i < dataLength; i++) { + if (data[i] !== UNDEFINED) { // stray commas in oldIE + pt = { series: series }; + series.pointClass.prototype.applyOptions.apply(pt, [data[i]]); + series.updateParallelArrays(pt, i); + if (hasCategories && pt.name) { + xAxis.names[pt.x] = pt.name; // #2046 + } + } + } + } + + // Forgetting to cast strings to numbers is a common caveat when handling CSV or JSON + if (isString(yData[0])) { + error(14, true); + } + + series.data = []; + series.options.data = data; + //series.zData = zData; + + // destroy old points + i = oldDataLength; + while (i--) { + if (oldData[i] && oldData[i].destroy) { + oldData[i].destroy(); + } + } + if (tooltipPoints) { // #2594 + tooltipPoints.length = 0; + } + + // reset minRange (#878) + if (xAxis) { + xAxis.minRange = xAxis.userMinRange; + } + + // redraw + series.isDirty = series.isDirtyData = chart.isDirtyBox = true; + animation = false; + } + + if (redraw) { + chart.redraw(animation); + } + }, + + /** + * Process the data by cropping away unused data points if the series is longer + * than the crop threshold. This saves computing time for lage series. + */ + processData: function (force) { + var series = this, + processedXData = series.xData, // copied during slice operation below + processedYData = series.yData, + dataLength = processedXData.length, + croppedData, + cropStart = 0, + cropped, + distance, + closestPointRange, + xAxis = series.xAxis, + i, // loop variable + options = series.options, + cropThreshold = options.cropThreshold, + activePointCount = 0, + isCartesian = series.isCartesian, + min, + max; + + // If the series data or axes haven't changed, don't go through this. Return false to pass + // the message on to override methods like in data grouping. + if (isCartesian && !series.isDirty && !xAxis.isDirty && !series.yAxis.isDirty && !force) { + return false; + } + + + // optionally filter out points outside the plot area + if (isCartesian && series.sorted && (!cropThreshold || dataLength > cropThreshold || series.forceCrop)) { + + min = xAxis.min; + max = xAxis.max; + + // it's outside current extremes + if (processedXData[dataLength - 1] < min || processedXData[0] > max) { + processedXData = []; + processedYData = []; + + // only crop if it's actually spilling out + } else if (processedXData[0] < min || processedXData[dataLength - 1] > max) { + croppedData = this.cropData(series.xData, series.yData, min, max); + processedXData = croppedData.xData; + processedYData = croppedData.yData; + cropStart = croppedData.start; + cropped = true; + activePointCount = processedXData.length; + } + } + + + // Find the closest distance between processed points + for (i = processedXData.length - 1; i >= 0; i--) { + distance = processedXData[i] - processedXData[i - 1]; + + if (!cropped && processedXData[i] > min && processedXData[i] < max) { + activePointCount++; + } + if (distance > 0 && (closestPointRange === UNDEFINED || distance < closestPointRange)) { + closestPointRange = distance; + + // Unsorted data is not supported by the line tooltip, as well as data grouping and + // navigation in Stock charts (#725) and width calculation of columns (#1900) + } else if (distance < 0 && series.requireSorting) { + error(15); + } + } + + // Record the properties + series.cropped = cropped; // undefined or true + series.cropStart = cropStart; + series.processedXData = processedXData; + series.processedYData = processedYData; + series.activePointCount = activePointCount; + + if (options.pointRange === null) { // null means auto, as for columns, candlesticks and OHLC + series.pointRange = closestPointRange || 1; + } + series.closestPointRange = closestPointRange; + + }, + + /** + * Iterate over xData and crop values between min and max. Returns object containing crop start/end + * cropped xData with corresponding part of yData, dataMin and dataMax within the cropped range + */ + cropData: function (xData, yData, min, max) { + var dataLength = xData.length, + cropStart = 0, + cropEnd = dataLength, + cropShoulder = pick(this.cropShoulder, 1), // line-type series need one point outside + i; + + // iterate up to find slice start + for (i = 0; i < dataLength; i++) { + if (xData[i] >= min) { + cropStart = mathMax(0, i - cropShoulder); + break; + } + } + + // proceed to find slice end + for (; i < dataLength; i++) { + if (xData[i] > max) { + cropEnd = i + cropShoulder; + break; + } + } + + return { + xData: xData.slice(cropStart, cropEnd), + yData: yData.slice(cropStart, cropEnd), + start: cropStart, + end: cropEnd + }; + }, + + + /** + * Generate the data point after the data has been processed by cropping away + * unused points and optionally grouped in Highcharts Stock. + */ + generatePoints: function () { + var series = this, + options = series.options, + dataOptions = options.data, + data = series.data, + dataLength, + processedXData = series.processedXData, + processedYData = series.processedYData, + pointClass = series.pointClass, + processedDataLength = processedXData.length, + cropStart = series.cropStart || 0, + cursor, + hasGroupedData = series.hasGroupedData, + point, + points = [], + i; + + if (!data && !hasGroupedData) { + var arr = []; + arr.length = dataOptions.length; + data = series.data = arr; + } + + for (i = 0; i < processedDataLength; i++) { + cursor = cropStart + i; + if (!hasGroupedData) { + if (data[cursor]) { + point = data[cursor]; + } else if (dataOptions[cursor] !== UNDEFINED) { // #970 + data[cursor] = point = (new pointClass()).init(series, dataOptions[cursor], processedXData[i]); + } + points[i] = point; + } else { + // splat the y data in case of ohlc data array + points[i] = (new pointClass()).init(series, [processedXData[i]].concat(splat(processedYData[i]))); + } + } + + // Hide cropped-away points - this only runs when the number of points is above cropThreshold, or when + // swithching view from non-grouped data to grouped data (#637) + if (data && (processedDataLength !== (dataLength = data.length) || hasGroupedData)) { + for (i = 0; i < dataLength; i++) { + if (i === cropStart && !hasGroupedData) { // when has grouped data, clear all points + i += processedDataLength; + } + if (data[i]) { + data[i].destroyElements(); + data[i].plotX = UNDEFINED; // #1003 + } + } + } + + series.data = data; + series.points = points; + }, + + /** + * Calculate Y extremes for visible data + */ + getExtremes: function (yData) { + var xAxis = this.xAxis, + yAxis = this.yAxis, + xData = this.processedXData, + yDataLength, + activeYData = [], + activeCounter = 0, + xExtremes = xAxis.getExtremes(), // #2117, need to compensate for log X axis + xMin = xExtremes.min, + xMax = xExtremes.max, + validValue, + withinRange, + dataMin, + dataMax, + x, + y, + i, + j; + + yData = yData || this.stackedYData || this.processedYData; + yDataLength = yData.length; + + for (i = 0; i < yDataLength; i++) { + + x = xData[i]; + y = yData[i]; + + // For points within the visible range, including the first point outside the + // visible range, consider y extremes + validValue = y !== null && y !== UNDEFINED && (!yAxis.isLog || (y.length || y > 0)); + withinRange = this.getExtremesFromAll || this.cropped || ((xData[i + 1] || x) >= xMin && + (xData[i - 1] || x) <= xMax); + + if (validValue && withinRange) { + + j = y.length; + if (j) { // array, like ohlc or range data + while (j--) { + if (y[j] !== null) { + activeYData[activeCounter++] = y[j]; + } + } + } else { + activeYData[activeCounter++] = y; + } + } + } + this.dataMin = pick(dataMin, arrayMin(activeYData)); + this.dataMax = pick(dataMax, arrayMax(activeYData)); + }, + + /** + * Translate data points from raw data values to chart specific positioning data + * needed later in drawPoints, drawGraph and drawTracker. + */ + translate: function () { + if (!this.processedXData) { // hidden series + this.processData(); + } + this.generatePoints(); + var series = this, + options = series.options, + stacking = options.stacking, + xAxis = series.xAxis, + categories = xAxis.categories, + yAxis = series.yAxis, + points = series.points, + dataLength = points.length, + hasModifyValue = !!series.modifyValue, + i, + pointPlacement = options.pointPlacement, + dynamicallyPlaced = pointPlacement === 'between' || isNumber(pointPlacement), + threshold = options.threshold; + + // Translate each point + for (i = 0; i < dataLength; i++) { + var point = points[i], + xValue = point.x, + yValue = point.y, + yBottom = point.low, + stack = stacking && yAxis.stacks[(series.negStacks && yValue < threshold ? '-' : '') + series.stackKey], + pointStack, + stackValues; + + // Discard disallowed y values for log axes + if (yAxis.isLog && yValue <= 0) { + point.y = yValue = null; + } + + // Get the plotX translation + point.plotX = xAxis.translate(xValue, 0, 0, 0, 1, pointPlacement, this.type === 'flags'); // Math.round fixes #591 + + + // Calculate the bottom y value for stacked series + if (stacking && series.visible && stack && stack[xValue]) { + + pointStack = stack[xValue]; + stackValues = pointStack.points[series.index + ',' + i]; + yBottom = stackValues[0]; + yValue = stackValues[1]; + + if (yBottom === 0) { + yBottom = pick(threshold, yAxis.min); + } + if (yAxis.isLog && yBottom <= 0) { // #1200, #1232 + yBottom = null; + } + + point.total = point.stackTotal = pointStack.total; + point.percentage = pointStack.total && (point.y / pointStack.total * 100); + point.stackY = yValue; + + // Place the stack label + pointStack.setOffset(series.pointXOffset || 0, series.barW || 0); + + } + + // Set translated yBottom or remove it + point.yBottom = defined(yBottom) ? + yAxis.translate(yBottom, 0, 1, 0, 1) : + null; + + // general hook, used for Highstock compare mode + if (hasModifyValue) { + yValue = series.modifyValue(yValue, point); + } + + // Set the the plotY value, reset it for redraws + point.plotY = (typeof yValue === 'number' && yValue !== Infinity) ? + //mathRound(yAxis.translate(yValue, 0, 1, 0, 1) * 10) / 10 : // Math.round fixes #591 + yAxis.translate(yValue, 0, 1, 0, 1) : + UNDEFINED; + + // Set client related positions for mouse tracking + point.clientX = dynamicallyPlaced ? xAxis.translate(xValue, 0, 0, 0, 1) : point.plotX; // #1514 + + point.negative = point.y < (threshold || 0); + + // some API data + point.category = categories && categories[point.x] !== UNDEFINED ? + categories[point.x] : point.x; + + } + + // now that we have the cropped data, build the segments + series.getSegments(); + }, + + /** + * Animate in the series + */ + animate: function (init) { + var series = this, + chart = series.chart, + renderer = chart.renderer, + clipRect, + markerClipRect, + animation = series.options.animation, + clipBox = series.clipBox || chart.clipBox, + inverted = chart.inverted, + sharedClipKey; + + // Animation option is set to true + if (animation && !isObject(animation)) { + animation = defaultPlotOptions[series.type].animation; + } + sharedClipKey = ['_sharedClip', animation.duration, animation.easing, clipBox.height].join(','); + + // Initialize the animation. Set up the clipping rectangle. + if (init) { + + // If a clipping rectangle with the same properties is currently present in the chart, use that. + clipRect = chart[sharedClipKey]; + markerClipRect = chart[sharedClipKey + 'm']; + if (!clipRect) { + chart[sharedClipKey] = clipRect = renderer.clipRect( + extend(clipBox, { width: 0 }) + ); + + chart[sharedClipKey + 'm'] = markerClipRect = renderer.clipRect( + -99, // include the width of the first marker + inverted ? -chart.plotLeft : -chart.plotTop, + 99, + inverted ? chart.chartWidth : chart.chartHeight + ); + } + series.group.clip(clipRect); + series.markerGroup.clip(markerClipRect); + series.sharedClipKey = sharedClipKey; + + // Run the animation + } else { + clipRect = chart[sharedClipKey]; + if (clipRect) { + clipRect.animate({ + width: chart.plotSizeX + }, animation); + } + if (chart[sharedClipKey + 'm']) { + chart[sharedClipKey + 'm'].animate({ + width: chart.plotSizeX + 99 + }, animation); + } + + // Delete this function to allow it only once + series.animate = null; + + } + }, + + /** + * This runs after animation to land on the final plot clipping + */ + afterAnimate: function () { + var chart = this.chart, + sharedClipKey = this.sharedClipKey, + group = this.group, + clipBox = this.clipBox; + + if (group && this.options.clip !== false) { + if (!sharedClipKey || !clipBox) { + group.clip(clipBox ? chart.renderer.clipRect(clipBox) : chart.clipRect); + } + this.markerGroup.clip(); // no clip + } + + fireEvent(this, 'afterAnimate'); + + // Remove the shared clipping rectancgle when all series are shown + setTimeout(function () { + if (sharedClipKey && chart[sharedClipKey]) { + if (!clipBox) { + chart[sharedClipKey] = chart[sharedClipKey].destroy(); + } + if (chart[sharedClipKey + 'm']) { + chart[sharedClipKey + 'm'] = chart[sharedClipKey + 'm'].destroy(); + } + } + }, 100); + }, + + /** + * Draw the markers + */ + drawPoints: function () { + var series = this, + pointAttr, + points = series.points, + chart = series.chart, + plotX, + plotY, + i, + point, + radius, + symbol, + isImage, + graphic, + options = series.options, + seriesMarkerOptions = options.marker, + seriesPointAttr = series.pointAttr[''], + pointMarkerOptions, + enabled, + isInside, + markerGroup = series.markerGroup, + globallyEnabled = pick( + seriesMarkerOptions.enabled, + series.activePointCount < (0.5 * series.xAxis.len / seriesMarkerOptions.radius) + ); + + if (seriesMarkerOptions.enabled !== false || series._hasPointMarkers) { + + i = points.length; + while (i--) { + point = points[i]; + plotX = mathFloor(point.plotX); // #1843 + plotY = point.plotY; + graphic = point.graphic; + pointMarkerOptions = point.marker || {}; + enabled = (globallyEnabled && pointMarkerOptions.enabled === UNDEFINED) || pointMarkerOptions.enabled; + isInside = chart.isInsidePlot(mathRound(plotX), plotY, chart.inverted); // #1858 + + // only draw the point if y is defined + if (enabled && plotY !== UNDEFINED && !isNaN(plotY) && point.y !== null) { + + // shortcuts + pointAttr = point.pointAttr[point.selected ? SELECT_STATE : NORMAL_STATE] || seriesPointAttr; + radius = pointAttr.r; + symbol = pick(pointMarkerOptions.symbol, series.symbol); + isImage = symbol.indexOf('url') === 0; + + if (graphic) { // update + graphic[isInside ? 'show' : 'hide'](true) // Since the marker group isn't clipped, each individual marker must be toggled + .animate(extend({ + x: plotX - radius, + y: plotY - radius + }, graphic.symbolName ? { // don't apply to image symbols #507 + width: 2 * radius, + height: 2 * radius + } : {})); + } else if (isInside && (radius > 0 || isImage)) { + point.graphic = graphic = chart.renderer.symbol( + symbol, + plotX - radius, + plotY - radius, + 2 * radius, + 2 * radius + ) + .attr(pointAttr) + .add(markerGroup); + } + + } else if (graphic) { + point.graphic = graphic.destroy(); // #1269 + } + } + } + + }, + + /** + * Convert state properties from API naming conventions to SVG attributes + * + * @param {Object} options API options object + * @param {Object} base1 SVG attribute object to inherit from + * @param {Object} base2 Second level SVG attribute object to inherit from + */ + convertAttribs: function (options, base1, base2, base3) { + var conversion = this.pointAttrToOptions, + attr, + option, + obj = {}; + + options = options || {}; + base1 = base1 || {}; + base2 = base2 || {}; + base3 = base3 || {}; + + for (attr in conversion) { + option = conversion[attr]; + obj[attr] = pick(options[option], base1[attr], base2[attr], base3[attr]); + } + return obj; + }, + + /** + * Get the state attributes. Each series type has its own set of attributes + * that are allowed to change on a point's state change. Series wide attributes are stored for + * all series, and additionally point specific attributes are stored for all + * points with individual marker options. If such options are not defined for the point, + * a reference to the series wide attributes is stored in point.pointAttr. + */ + getAttribs: function () { + var series = this, + seriesOptions = series.options, + normalOptions = defaultPlotOptions[series.type].marker ? seriesOptions.marker : seriesOptions, + stateOptions = normalOptions.states, + stateOptionsHover = stateOptions[HOVER_STATE], + pointStateOptionsHover, + seriesColor = series.color, + normalDefaults = { + stroke: seriesColor, + fill: seriesColor + }, + points = series.points || [], // #927 + i, + point, + seriesPointAttr = [], + pointAttr, + pointAttrToOptions = series.pointAttrToOptions, + hasPointSpecificOptions = series.hasPointSpecificOptions, + negativeColor = seriesOptions.negativeColor, + defaultLineColor = normalOptions.lineColor, + defaultFillColor = normalOptions.fillColor, + turboThreshold = seriesOptions.turboThreshold, + attr, + key; + + // series type specific modifications + if (seriesOptions.marker) { // line, spline, area, areaspline, scatter + + // if no hover radius is given, default to normal radius + 2 + stateOptionsHover.radius = stateOptionsHover.radius || normalOptions.radius + 2; + stateOptionsHover.lineWidth = stateOptionsHover.lineWidth || normalOptions.lineWidth + 1; + + } else { // column, bar, pie + + // if no hover color is given, brighten the normal color + stateOptionsHover.color = stateOptionsHover.color || + Color(stateOptionsHover.color || seriesColor) + .brighten(stateOptionsHover.brightness).get(); + } + + // general point attributes for the series normal state + seriesPointAttr[NORMAL_STATE] = series.convertAttribs(normalOptions, normalDefaults); + + // HOVER_STATE and SELECT_STATE states inherit from normal state except the default radius + each([HOVER_STATE, SELECT_STATE], function (state) { + seriesPointAttr[state] = + series.convertAttribs(stateOptions[state], seriesPointAttr[NORMAL_STATE]); + }); + + // set it + series.pointAttr = seriesPointAttr; + + + // Generate the point-specific attribute collections if specific point + // options are given. If not, create a referance to the series wide point + // attributes + i = points.length; + if (!turboThreshold || i < turboThreshold || hasPointSpecificOptions) { + while (i--) { + point = points[i]; + normalOptions = (point.options && point.options.marker) || point.options; + if (normalOptions && normalOptions.enabled === false) { + normalOptions.radius = 0; + } + + if (point.negative && negativeColor) { + point.color = point.fillColor = negativeColor; + } + + hasPointSpecificOptions = seriesOptions.colorByPoint || point.color; // #868 + + // check if the point has specific visual options + if (point.options) { + for (key in pointAttrToOptions) { + if (defined(normalOptions[pointAttrToOptions[key]])) { + hasPointSpecificOptions = true; + } + } + } + + // a specific marker config object is defined for the individual point: + // create it's own attribute collection + if (hasPointSpecificOptions) { + normalOptions = normalOptions || {}; + pointAttr = []; + stateOptions = normalOptions.states || {}; // reassign for individual point + pointStateOptionsHover = stateOptions[HOVER_STATE] = stateOptions[HOVER_STATE] || {}; + + // Handle colors for column and pies + if (!seriesOptions.marker) { // column, bar, point + // If no hover color is given, brighten the normal color. #1619, #2579 + pointStateOptionsHover.color = pointStateOptionsHover.color || (!point.options.color && stateOptionsHover.color) || + Color(point.color) + .brighten(pointStateOptionsHover.brightness || stateOptionsHover.brightness) + .get(); + } + + // normal point state inherits series wide normal state + attr = { color: point.color }; // #868 + if (!defaultFillColor) { // Individual point color or negative color markers (#2219) + attr.fillColor = point.color; + } + if (!defaultLineColor) { + attr.lineColor = point.color; // Bubbles take point color, line markers use white + } + pointAttr[NORMAL_STATE] = series.convertAttribs(extend(attr, normalOptions), seriesPointAttr[NORMAL_STATE]); + + // inherit from point normal and series hover + pointAttr[HOVER_STATE] = series.convertAttribs( + stateOptions[HOVER_STATE], + seriesPointAttr[HOVER_STATE], + pointAttr[NORMAL_STATE] + ); + + // inherit from point normal and series hover + pointAttr[SELECT_STATE] = series.convertAttribs( + stateOptions[SELECT_STATE], + seriesPointAttr[SELECT_STATE], + pointAttr[NORMAL_STATE] + ); + + + // no marker config object is created: copy a reference to the series-wide + // attribute collection + } else { + pointAttr = seriesPointAttr; + } + + point.pointAttr = pointAttr; + } + } + }, + + /** + * Clear DOM objects and free up memory + */ + destroy: function () { + var series = this, + chart = series.chart, + issue134 = /AppleWebKit\/533/.test(userAgent), + destroy, + i, + data = series.data || [], + point, + prop, + axis; + + // add event hook + fireEvent(series, 'destroy'); + + // remove all events + removeEvent(series); + + // erase from axes + each(series.axisTypes || [], function (AXIS) { + axis = series[AXIS]; + if (axis) { + erase(axis.series, series); + axis.isDirty = axis.forceRedraw = true; + } + }); + + // remove legend items + if (series.legendItem) { + series.chart.legend.destroyItem(series); + } + + // destroy all points with their elements + i = data.length; + while (i--) { + point = data[i]; + if (point && point.destroy) { + point.destroy(); + } + } + series.points = null; + + // Clear the animation timeout if we are destroying the series during initial animation + clearTimeout(series.animationTimeout); + + // destroy all SVGElements associated to the series + each(['area', 'graph', 'dataLabelsGroup', 'group', 'markerGroup', 'tracker', + 'graphNeg', 'areaNeg', 'posClip', 'negClip'], function (prop) { + if (series[prop]) { + + // issue 134 workaround + destroy = issue134 && prop === 'group' ? + 'hide' : + 'destroy'; + + series[prop][destroy](); + } + }); + + // remove from hoverSeries + if (chart.hoverSeries === series) { + chart.hoverSeries = null; + } + erase(chart.series, series); + + // clear all members + for (prop in series) { + delete series[prop]; + } + }, + + /** + * Return the graph path of a segment + */ + getSegmentPath: function (segment) { + var series = this, + segmentPath = [], + step = series.options.step; + + // build the segment line + each(segment, function (point, i) { + + var plotX = point.plotX, + plotY = point.plotY, + lastPoint; + + if (series.getPointSpline) { // generate the spline as defined in the SplineSeries object + segmentPath.push.apply(segmentPath, series.getPointSpline(segment, point, i)); + + } else { + + // moveTo or lineTo + segmentPath.push(i ? L : M); + + // step line? + if (step && i) { + lastPoint = segment[i - 1]; + if (step === 'right') { + segmentPath.push( + lastPoint.plotX, + plotY + ); + + } else if (step === 'center') { + segmentPath.push( + (lastPoint.plotX + plotX) / 2, + lastPoint.plotY, + (lastPoint.plotX + plotX) / 2, + plotY + ); + + } else { + segmentPath.push( + plotX, + lastPoint.plotY + ); + } + } + + // normal line to next point + segmentPath.push( + point.plotX, + point.plotY + ); + } + }); + + return segmentPath; + }, + + /** + * Get the graph path + */ + getGraphPath: function () { + var series = this, + graphPath = [], + segmentPath, + singlePoints = []; // used in drawTracker + + // Divide into segments and build graph and area paths + each(series.segments, function (segment) { + + segmentPath = series.getSegmentPath(segment); + + // add the segment to the graph, or a single point for tracking + if (segment.length > 1) { + graphPath = graphPath.concat(segmentPath); + } else { + singlePoints.push(segment[0]); + } + }); + + // Record it for use in drawGraph and drawTracker, and return graphPath + series.singlePoints = singlePoints; + series.graphPath = graphPath; + + return graphPath; + + }, + + /** + * Draw the actual graph + */ + drawGraph: function () { + var series = this, + options = this.options, + props = [['graph', options.lineColor || this.color]], + lineWidth = options.lineWidth, + dashStyle = options.dashStyle, + roundCap = options.linecap !== 'square', + graphPath = this.getGraphPath(), + negativeColor = options.negativeColor; + + if (negativeColor) { + props.push(['graphNeg', negativeColor]); + } + + // draw the graph + each(props, function (prop, i) { + var graphKey = prop[0], + graph = series[graphKey], + attribs; + + if (graph) { + stop(graph); // cancel running animations, #459 + graph.animate({ d: graphPath }); + + } else if (lineWidth && graphPath.length) { // #1487 + attribs = { + stroke: prop[1], + 'stroke-width': lineWidth, + fill: NONE, + zIndex: 1 // #1069 + }; + if (dashStyle) { + attribs.dashstyle = dashStyle; + } else if (roundCap) { + attribs['stroke-linecap'] = attribs['stroke-linejoin'] = 'round'; + } + + series[graphKey] = series.chart.renderer.path(graphPath) + .attr(attribs) + .add(series.group) + .shadow(!i && options.shadow); + } + }); + }, + + /** + * Clip the graphs into the positive and negative coloured graphs + */ + clipNeg: function () { + var options = this.options, + chart = this.chart, + renderer = chart.renderer, + negativeColor = options.negativeColor || options.negativeFillColor, + translatedThreshold, + posAttr, + negAttr, + graph = this.graph, + area = this.area, + posClip = this.posClip, + negClip = this.negClip, + chartWidth = chart.chartWidth, + chartHeight = chart.chartHeight, + chartSizeMax = mathMax(chartWidth, chartHeight), + yAxis = this.yAxis, + above, + below; + + if (negativeColor && (graph || area)) { + translatedThreshold = mathRound(yAxis.toPixels(options.threshold || 0, true)); + if (translatedThreshold < 0) { + chartSizeMax -= translatedThreshold; // #2534 + } + above = { + x: 0, + y: 0, + width: chartSizeMax, + height: translatedThreshold + }; + below = { + x: 0, + y: translatedThreshold, + width: chartSizeMax, + height: chartSizeMax + }; + + if (chart.inverted) { + + above.height = below.y = chart.plotWidth - translatedThreshold; + if (renderer.isVML) { + above = { + x: chart.plotWidth - translatedThreshold - chart.plotLeft, + y: 0, + width: chartWidth, + height: chartHeight + }; + below = { + x: translatedThreshold + chart.plotLeft - chartWidth, + y: 0, + width: chart.plotLeft + translatedThreshold, + height: chartWidth + }; + } + } + + if (yAxis.reversed) { + posAttr = below; + negAttr = above; + } else { + posAttr = above; + negAttr = below; + } + + if (posClip) { // update + posClip.animate(posAttr); + negClip.animate(negAttr); + } else { + + this.posClip = posClip = renderer.clipRect(posAttr); + this.negClip = negClip = renderer.clipRect(negAttr); + + if (graph && this.graphNeg) { + graph.clip(posClip); + this.graphNeg.clip(negClip); + } + + if (area) { + area.clip(posClip); + this.areaNeg.clip(negClip); + } + } + } + }, + + /** + * Initialize and perform group inversion on series.group and series.markerGroup + */ + invertGroups: function () { + var series = this, + chart = series.chart; + + // Pie, go away (#1736) + if (!series.xAxis) { + return; + } + + // A fixed size is needed for inversion to work + function setInvert() { + var size = { + width: series.yAxis.len, + height: series.xAxis.len + }; + + each(['group', 'markerGroup'], function (groupName) { + if (series[groupName]) { + series[groupName].attr(size).invert(); + } + }); + } + + addEvent(chart, 'resize', setInvert); // do it on resize + addEvent(series, 'destroy', function () { + removeEvent(chart, 'resize', setInvert); + }); + + // Do it now + setInvert(); // do it now + + // On subsequent render and redraw, just do setInvert without setting up events again + series.invertGroups = setInvert; + }, + + /** + * General abstraction for creating plot groups like series.group, series.dataLabelsGroup and + * series.markerGroup. On subsequent calls, the group will only be adjusted to the updated plot size. + */ + plotGroup: function (prop, name, visibility, zIndex, parent) { + var group = this[prop], + isNew = !group; + + // Generate it on first call + if (isNew) { + this[prop] = group = this.chart.renderer.g(name) + .attr({ + visibility: visibility, + zIndex: zIndex || 0.1 // IE8 needs this + }) + .add(parent); + } + // Place it on first and subsequent (redraw) calls + group[isNew ? 'attr' : 'animate'](this.getPlotBox()); + return group; + }, + + /** + * Get the translation and scale for the plot area of this series + */ + getPlotBox: function () { + var chart = this.chart, + xAxis = this.xAxis, + yAxis = this.yAxis; + + // Swap axes for inverted (#2339) + if (chart.inverted) { + xAxis = yAxis; + yAxis = this.xAxis; + } + return { + translateX: xAxis ? xAxis.left : chart.plotLeft, + translateY: yAxis ? yAxis.top : chart.plotTop, + scaleX: 1, // #1623 + scaleY: 1 + }; + }, + + /** + * Render the graph and markers + */ + render: function () { + var series = this, + chart = series.chart, + group, + options = series.options, + animation = options.animation, + // Animation doesn't work in IE8 quirks when the group div is hidden, + // and looks bad in other oldIE + animDuration = (animation && !!series.animate && chart.renderer.isSVG && pick(animation.duration, 500)) || 0, + visibility = series.visible ? VISIBLE : HIDDEN, + zIndex = options.zIndex, + hasRendered = series.hasRendered, + chartSeriesGroup = chart.seriesGroup; + + // the group + group = series.plotGroup( + 'group', + 'series', + visibility, + zIndex, + chartSeriesGroup + ); + + series.markerGroup = series.plotGroup( + 'markerGroup', + 'markers', + visibility, + zIndex, + chartSeriesGroup + ); + + // initiate the animation + if (animDuration) { + series.animate(true); + } + + // cache attributes for shapes + series.getAttribs(); + + // SVGRenderer needs to know this before drawing elements (#1089, #1795) + group.inverted = series.isCartesian ? chart.inverted : false; + + // draw the graph if any + if (series.drawGraph) { + series.drawGraph(); + series.clipNeg(); + } + + // draw the data labels (inn pies they go before the points) + if (series.drawDataLabels) { + series.drawDataLabels(); + } + + // draw the points + if (series.visible) { + series.drawPoints(); + } + + + // draw the mouse tracking area + if (series.drawTracker && series.options.enableMouseTracking !== false) { + series.drawTracker(); + } + + // Handle inverted series and tracker groups + if (chart.inverted) { + series.invertGroups(); + } + + // Initial clipping, must be defined after inverting groups for VML + if (options.clip !== false && !series.sharedClipKey && !hasRendered) { + group.clip(chart.clipRect); + } + + // Run the animation + if (animDuration) { + series.animate(); + } + + // Call the afterAnimate function on animation complete (but don't overwrite the animation.complete option + // which should be available to the user). + if (!hasRendered) { + if (animDuration) { + series.animationTimeout = setTimeout(function () { + series.afterAnimate(); + }, animDuration); + } else { + series.afterAnimate(); + } + } + + series.isDirty = series.isDirtyData = false; // means data is in accordance with what you see + // (See #322) series.isDirty = series.isDirtyData = false; // means data is in accordance with what you see + series.hasRendered = true; + }, + + /** + * Redraw the series after an update in the axes. + */ + redraw: function () { + var series = this, + chart = series.chart, + wasDirtyData = series.isDirtyData, // cache it here as it is set to false in render, but used after + group = series.group, + xAxis = series.xAxis, + yAxis = series.yAxis; + + // reposition on resize + if (group) { + if (chart.inverted) { + group.attr({ + width: chart.plotWidth, + height: chart.plotHeight + }); + } + + group.animate({ + translateX: pick(xAxis && xAxis.left, chart.plotLeft), + translateY: pick(yAxis && yAxis.top, chart.plotTop) + }); + } + + series.translate(); + if (series.setTooltipPoints) { + series.setTooltipPoints(true); + } + series.render(); + + if (wasDirtyData) { + fireEvent(series, 'updatedData'); + } + } +}; // end Series prototype + +/** + * The class for stack items + */ +function StackItem(axis, options, isNegative, x, stackOption) { + + var inverted = axis.chart.inverted; + + this.axis = axis; + + // Tells if the stack is negative + this.isNegative = isNegative; + + // Save the options to be able to style the label + this.options = options; + + // Save the x value to be able to position the label later + this.x = x; + + // Initialize total value + this.total = null; + + // This will keep each points' extremes stored by series.index and point index + this.points = {}; + + // Save the stack option on the series configuration object, and whether to treat it as percent + this.stack = stackOption; + + // The align options and text align varies on whether the stack is negative and + // if the chart is inverted or not. + // First test the user supplied value, then use the dynamic. + this.alignOptions = { + align: options.align || (inverted ? (isNegative ? 'left' : 'right') : 'center'), + verticalAlign: options.verticalAlign || (inverted ? 'middle' : (isNegative ? 'bottom' : 'top')), + y: pick(options.y, inverted ? 4 : (isNegative ? 14 : -6)), + x: pick(options.x, inverted ? (isNegative ? -6 : 6) : 0) + }; + + this.textAlign = options.textAlign || (inverted ? (isNegative ? 'right' : 'left') : 'center'); +} + +StackItem.prototype = { + destroy: function () { + destroyObjectProperties(this, this.axis); + }, + + /** + * Renders the stack total label and adds it to the stack label group. + */ + render: function (group) { + var options = this.options, + formatOption = options.format, + str = formatOption ? + format(formatOption, this) : + options.formatter.call(this); // format the text in the label + + // Change the text to reflect the new total and set visibility to hidden in case the serie is hidden + if (this.label) { + this.label.attr({text: str, visibility: HIDDEN}); + // Create new label + } else { + this.label = + this.axis.chart.renderer.text(str, null, null, options.useHTML) // dummy positions, actual position updated with setOffset method in columnseries + .css(options.style) // apply style + .attr({ + align: this.textAlign, // fix the text-anchor + rotation: options.rotation, // rotation + visibility: HIDDEN // hidden until setOffset is called + }) + .add(group); // add to the labels-group + } + }, + + /** + * Sets the offset that the stack has from the x value and repositions the label. + */ + setOffset: function (xOffset, xWidth) { + var stackItem = this, + axis = stackItem.axis, + chart = axis.chart, + inverted = chart.inverted, + neg = this.isNegative, // special treatment is needed for negative stacks + y = axis.translate(axis.usePercentage ? 100 : this.total, 0, 0, 0, 1), // stack value translated mapped to chart coordinates + yZero = axis.translate(0), // stack origin + h = mathAbs(y - yZero), // stack height + x = chart.xAxis[0].translate(this.x) + xOffset, // stack x position + plotHeight = chart.plotHeight, + stackBox = { // this is the box for the complete stack + x: inverted ? (neg ? y : y - h) : x, + y: inverted ? plotHeight - x - xWidth : (neg ? (plotHeight - y - h) : plotHeight - y), + width: inverted ? h : xWidth, + height: inverted ? xWidth : h + }, + label = this.label, + alignAttr; + + if (label) { + label.align(this.alignOptions, null, stackBox); // align the label to the box + + // Set visibility (#678) + alignAttr = label.alignAttr; + label[this.options.crop === false || chart.isInsidePlot(alignAttr.x, alignAttr.y) ? 'show' : 'hide'](true); + } + } +}; + + +// Stacking methods defined on the Axis prototype + +/** + * Build the stacks from top down + */ +Axis.prototype.buildStacks = function () { + var series = this.series, + reversedStacks = pick(this.options.reversedStacks, true), + i = series.length; + if (!this.isXAxis) { + this.usePercentage = false; + while (i--) { + series[reversedStacks ? i : series.length - i - 1].setStackedPoints(); + } + // Loop up again to compute percent stack + if (this.usePercentage) { + for (i = 0; i < series.length; i++) { + series[i].setPercentStacks(); + } + } + } +}; + +Axis.prototype.renderStackTotals = function () { + var axis = this, + chart = axis.chart, + renderer = chart.renderer, + stacks = axis.stacks, + stackKey, + oneStack, + stackCategory, + stackTotalGroup = axis.stackTotalGroup; + + // Create a separate group for the stack total labels + if (!stackTotalGroup) { + axis.stackTotalGroup = stackTotalGroup = + renderer.g('stack-labels') + .attr({ + visibility: VISIBLE, + zIndex: 6 + }) + .add(); + } + + // plotLeft/Top will change when y axis gets wider so we need to translate the + // stackTotalGroup at every render call. See bug #506 and #516 + stackTotalGroup.translate(chart.plotLeft, chart.plotTop); + + // Render each stack total + for (stackKey in stacks) { + oneStack = stacks[stackKey]; + for (stackCategory in oneStack) { + oneStack[stackCategory].render(stackTotalGroup); + } + } +}; + + +// Stacking methods defnied for Series prototype + +/** + * Adds series' points value to corresponding stack + */ +Series.prototype.setStackedPoints = function () { + if (!this.options.stacking || (this.visible !== true && this.chart.options.chart.ignoreHiddenSeries !== false)) { + return; + } + + var series = this, + xData = series.processedXData, + yData = series.processedYData, + stackedYData = [], + yDataLength = yData.length, + seriesOptions = series.options, + threshold = seriesOptions.threshold, + stackOption = seriesOptions.stack, + stacking = seriesOptions.stacking, + stackKey = series.stackKey, + negKey = '-' + stackKey, + negStacks = series.negStacks, + yAxis = series.yAxis, + stacks = yAxis.stacks, + oldStacks = yAxis.oldStacks, + isNegative, + stack, + other, + key, + pointKey, + i, + x, + y; + + // loop over the non-null y values and read them into a local array + for (i = 0; i < yDataLength; i++) { + x = xData[i]; + y = yData[i]; + pointKey = series.index + ',' + i; + + // Read stacked values into a stack based on the x value, + // the sign of y and the stack key. Stacking is also handled for null values (#739) + isNegative = negStacks && y < threshold; + key = isNegative ? negKey : stackKey; + + // Create empty object for this stack if it doesn't exist yet + if (!stacks[key]) { + stacks[key] = {}; + } + + // Initialize StackItem for this x + if (!stacks[key][x]) { + if (oldStacks[key] && oldStacks[key][x]) { + stacks[key][x] = oldStacks[key][x]; + stacks[key][x].total = null; + } else { + stacks[key][x] = new StackItem(yAxis, yAxis.options.stackLabels, isNegative, x, stackOption); + } + } + + // If the StackItem doesn't exist, create it first + stack = stacks[key][x]; + stack.points[pointKey] = [stack.cum || 0]; + + // Add value to the stack total + if (stacking === 'percent') { + + // Percent stacked column, totals are the same for the positive and negative stacks + other = isNegative ? stackKey : negKey; + if (negStacks && stacks[other] && stacks[other][x]) { + other = stacks[other][x]; + stack.total = other.total = mathMax(other.total, stack.total) + mathAbs(y) || 0; + + // Percent stacked areas + } else { + stack.total = correctFloat(stack.total + (mathAbs(y) || 0)); + } + } else { + stack.total = correctFloat(stack.total + (y || 0)); + } + + stack.cum = (stack.cum || 0) + (y || 0); + + stack.points[pointKey].push(stack.cum); + stackedYData[i] = stack.cum; + + } + + if (stacking === 'percent') { + yAxis.usePercentage = true; + } + + this.stackedYData = stackedYData; // To be used in getExtremes + + // Reset old stacks + yAxis.oldStacks = {}; +}; + +/** + * Iterate over all stacks and compute the absolute values to percent + */ +Series.prototype.setPercentStacks = function () { + var series = this, + stackKey = series.stackKey, + stacks = series.yAxis.stacks, + processedXData = series.processedXData; + + each([stackKey, '-' + stackKey], function (key) { + var i = processedXData.length, + x, + stack, + pointExtremes, + totalFactor; + + while (i--) { + x = processedXData[i]; + stack = stacks[key] && stacks[key][x]; + pointExtremes = stack && stack.points[series.index + ',' + i]; + if (pointExtremes) { + totalFactor = stack.total ? 100 / stack.total : 0; + pointExtremes[0] = correctFloat(pointExtremes[0] * totalFactor); // Y bottom value + pointExtremes[1] = correctFloat(pointExtremes[1] * totalFactor); // Y value + series.stackedYData[i] = pointExtremes[1]; + } + } + }); +}; + +// Extend the Chart prototype for dynamic methods +extend(Chart.prototype, { + + /** + * Add a series dynamically after time + * + * @param {Object} options The config options + * @param {Boolean} redraw Whether to redraw the chart after adding. Defaults to true. + * @param {Boolean|Object} animation Whether to apply animation, and optionally animation + * configuration + * + * @return {Object} series The newly created series object + */ + addSeries: function (options, redraw, animation) { + var series, + chart = this; + + if (options) { + redraw = pick(redraw, true); // defaults to true + + fireEvent(chart, 'addSeries', { options: options }, function () { + series = chart.initSeries(options); + + chart.isDirtyLegend = true; // the series array is out of sync with the display + chart.linkSeries(); + if (redraw) { + chart.redraw(animation); + } + }); + } + + return series; + }, + + /** + * Add an axis to the chart + * @param {Object} options The axis option + * @param {Boolean} isX Whether it is an X axis or a value axis + */ + addAxis: function (options, isX, redraw, animation) { + var key = isX ? 'xAxis' : 'yAxis', + chartOptions = this.options, + axis; + + /*jslint unused: false*/ + axis = new Axis(this, merge(options, { + index: this[key].length, + isX: isX + })); + /*jslint unused: true*/ + + // Push the new axis options to the chart options + chartOptions[key] = splat(chartOptions[key] || {}); + chartOptions[key].push(options); + + if (pick(redraw, true)) { + this.redraw(animation); + } + }, + + /** + * Dim the chart and show a loading text or symbol + * @param {String} str An optional text to show in the loading label instead of the default one + */ + showLoading: function (str) { + var chart = this, + options = chart.options, + loadingDiv = chart.loadingDiv; + + var loadingOptions = options.loading; + + // create the layer at the first call + if (!loadingDiv) { + chart.loadingDiv = loadingDiv = createElement(DIV, { + className: PREFIX + 'loading' + }, extend(loadingOptions.style, { + zIndex: 10, + display: NONE + }), chart.container); + + chart.loadingSpan = createElement( + 'span', + null, + loadingOptions.labelStyle, + loadingDiv + ); + + } + + // update text + chart.loadingSpan.innerHTML = str || options.lang.loading; + + // show it + if (!chart.loadingShown) { + css(loadingDiv, { + opacity: 0, + display: '', + left: chart.plotLeft + PX, + top: chart.plotTop + PX, + width: chart.plotWidth + PX, + height: chart.plotHeight + PX + }); + animate(loadingDiv, { + opacity: loadingOptions.style.opacity + }, { + duration: loadingOptions.showDuration || 0 + }); + chart.loadingShown = true; + } + }, + + /** + * Hide the loading layer + */ + hideLoading: function () { + var options = this.options, + loadingDiv = this.loadingDiv; + + if (loadingDiv) { + animate(loadingDiv, { + opacity: 0 + }, { + duration: options.loading.hideDuration || 100, + complete: function () { + css(loadingDiv, { display: NONE }); + } + }); + } + this.loadingShown = false; + } +}); + +// extend the Point prototype for dynamic methods +extend(Point.prototype, { + /** + * Update the point with new options (typically x/y data) and optionally redraw the series. + * + * @param {Object} options Point options as defined in the series.data array + * @param {Boolean} redraw Whether to redraw the chart or wait for an explicit call + * @param {Boolean|Object} animation Whether to apply animation, and optionally animation + * configuration + * + */ + update: function (options, redraw, animation) { + var point = this, + series = point.series, + graphic = point.graphic, + i, + data = series.data, + chart = series.chart, + seriesOptions = series.options; + + redraw = pick(redraw, true); + + // fire the event with a default handler of doing the update + point.firePointEvent('update', { options: options }, function () { + + point.applyOptions(options); + + // update visuals + if (isObject(options)) { + series.getAttribs(); + if (graphic) { + if (options && options.marker && options.marker.symbol) { + point.graphic = graphic.destroy(); + } else { + graphic.attr(point.pointAttr[point.state || '']); + } + } + if (options && options.dataLabels && point.dataLabel) { // #2468 + point.dataLabel = point.dataLabel.destroy(); + } + } + + // record changes in the parallel arrays + i = inArray(point, data); + series.updateParallelArrays(point, i); + + seriesOptions.data[i] = point.options; + + // redraw + series.isDirty = series.isDirtyData = true; + if (!series.fixedBox && series.hasCartesianSeries) { // #1906, #2320 + chart.isDirtyBox = true; + } + + if (seriesOptions.legendType === 'point') { // #1831, #1885 + chart.legend.destroyItem(point); + } + if (redraw) { + chart.redraw(animation); + } + }); + }, + + /** + * Remove a point and optionally redraw the series and if necessary the axes + * @param {Boolean} redraw Whether to redraw the chart or wait for an explicit call + * @param {Boolean|Object} animation Whether to apply animation, and optionally animation + * configuration + */ + remove: function (redraw, animation) { + var point = this, + series = point.series, + points = series.points, + chart = series.chart, + i, + data = series.data; + + setAnimation(animation, chart); + redraw = pick(redraw, true); + + // fire the event with a default handler of removing the point + point.firePointEvent('remove', null, function () { + + // splice all the parallel arrays + i = inArray(point, data); + if (data.length === points.length) { + points.splice(i, 1); + } + data.splice(i, 1); + series.options.data.splice(i, 1); + series.updateParallelArrays(point, 'splice', i, 1); + + point.destroy(); + + // redraw + series.isDirty = true; + series.isDirtyData = true; + if (redraw) { + chart.redraw(); + } + }); + } +}); + +// Extend the series prototype for dynamic methods +extend(Series.prototype, { + /** + * Add a point dynamically after chart load time + * @param {Object} options Point options as given in series.data + * @param {Boolean} redraw Whether to redraw the chart or wait for an explicit call + * @param {Boolean} shift If shift is true, a point is shifted off the start + * of the series as one is appended to the end. + * @param {Boolean|Object} animation Whether to apply animation, and optionally animation + * configuration + */ + addPoint: function (options, redraw, shift, animation) { + var series = this, + seriesOptions = series.options, + data = series.data, + graph = series.graph, + area = series.area, + chart = series.chart, + names = series.xAxis && series.xAxis.names, + currentShift = (graph && graph.shift) || 0, + dataOptions = seriesOptions.data, + point, + isInTheMiddle, + xData = series.xData, + x, + i; + + setAnimation(animation, chart); + + // Make graph animate sideways + if (shift) { + each([graph, area, series.graphNeg, series.areaNeg], function (shape) { + if (shape) { + shape.shift = currentShift + 1; + } + }); + } + if (area) { + area.isArea = true; // needed in animation, both with and without shift + } + + // Optional redraw, defaults to true + redraw = pick(redraw, true); + + // Get options and push the point to xData, yData and series.options. In series.generatePoints + // the Point instance will be created on demand and pushed to the series.data array. + point = { series: series }; + series.pointClass.prototype.applyOptions.apply(point, [options]); + x = point.x; + + // Get the insertion point + i = xData.length; + if (series.requireSorting && x < xData[i - 1]) { + isInTheMiddle = true; + while (i && xData[i - 1] > x) { + i--; + } + } + + series.updateParallelArrays(point, 'splice', i, 0, 0); // insert undefined item + series.updateParallelArrays(point, i); // update it + + if (names) { + names[x] = point.name; + } + dataOptions.splice(i, 0, options); + + if (isInTheMiddle) { + series.data.splice(i, 0, null); + series.processData(); + } + + // Generate points to be added to the legend (#1329) + if (seriesOptions.legendType === 'point') { + series.generatePoints(); + } + + // Shift the first point off the parallel arrays + // todo: consider series.removePoint(i) method + if (shift) { + if (data[0] && data[0].remove) { + data[0].remove(false); + } else { + data.shift(); + series.updateParallelArrays(point, 'shift'); + + dataOptions.shift(); + } + } + + // redraw + series.isDirty = true; + series.isDirtyData = true; + if (redraw) { + series.getAttribs(); // #1937 + chart.redraw(); + } + }, + + /** + * Remove a series and optionally redraw the chart + * + * @param {Boolean} redraw Whether to redraw the chart or wait for an explicit call + * @param {Boolean|Object} animation Whether to apply animation, and optionally animation + * configuration + */ + + remove: function (redraw, animation) { + var series = this, + chart = series.chart; + redraw = pick(redraw, true); + + if (!series.isRemoving) { /* prevent triggering native event in jQuery + (calling the remove function from the remove event) */ + series.isRemoving = true; + + // fire the event with a default handler of removing the point + fireEvent(series, 'remove', null, function () { + + + // destroy elements + series.destroy(); + + + // redraw + chart.isDirtyLegend = chart.isDirtyBox = true; + chart.linkSeries(); + + if (redraw) { + chart.redraw(animation); + } + }); + + } + series.isRemoving = false; + }, + + /** + * Update the series with a new set of options + */ + update: function (newOptions, redraw) { + var chart = this.chart, + // must use user options when changing type because this.options is merged + // in with type specific plotOptions + oldOptions = this.userOptions, + oldType = this.type, + proto = seriesTypes[oldType].prototype, + n; + + // Do the merge, with some forced options + newOptions = merge(oldOptions, { + animation: false, + index: this.index, + pointStart: this.xData[0] // when updating after addPoint + }, { data: this.options.data }, newOptions); + + // Destroy the series and reinsert methods from the type prototype + this.remove(false); + for (n in proto) { // Overwrite series-type specific methods (#2270) + if (proto.hasOwnProperty(n)) { + this[n] = UNDEFINED; + } + } + extend(this, seriesTypes[newOptions.type || oldType].prototype); + + + this.init(chart, newOptions); + if (pick(redraw, true)) { + chart.redraw(false); + } + } +}); + +// Extend the Axis.prototype for dynamic methods +extend(Axis.prototype, { + + /** + * Update the axis with a new options structure + */ + update: function (newOptions, redraw) { + var chart = this.chart; + + newOptions = chart.options[this.coll][this.options.index] = merge(this.userOptions, newOptions); + + this.destroy(true); + this._addedPlotLB = UNDEFINED; // #1611, #2887 + + this.init(chart, extend(newOptions, { events: UNDEFINED })); + + chart.isDirtyBox = true; + if (pick(redraw, true)) { + chart.redraw(); + } + }, + + /** + * Remove the axis from the chart + */ + remove: function (redraw) { + var chart = this.chart, + key = this.coll, // xAxis or yAxis + axisSeries = this.series, + i = axisSeries.length; + + // Remove associated series (#2687) + while (i--) { + if (axisSeries[i]) { + axisSeries[i].remove(false); + } + } + + // Remove the axis + erase(chart.axes, this); + erase(chart[key], this); + chart.options[key].splice(this.options.index, 1); + each(chart[key], function (axis, i) { // Re-index, #1706 + axis.options.index = i; + }); + this.destroy(); + chart.isDirtyBox = true; + + if (pick(redraw, true)) { + chart.redraw(); + } + }, + + /** + * Update the axis title by options + */ + setTitle: function (newTitleOptions, redraw) { + this.update({ title: newTitleOptions }, redraw); + }, + + /** + * Set new axis categories and optionally redraw + * @param {Array} categories + * @param {Boolean} redraw + */ + setCategories: function (categories, redraw) { + this.update({ categories: categories }, redraw); + } + +}); + + +/** + * LineSeries object + */ +var LineSeries = extendClass(Series); +seriesTypes.line = LineSeries; + +/** + * Set the default options for area + */ +defaultPlotOptions.area = merge(defaultSeriesOptions, { + threshold: 0 + // trackByArea: false, + // lineColor: null, // overrides color, but lets fillColor be unaltered + // fillOpacity: 0.75, + // fillColor: null +}); + +/** + * AreaSeries object + */ +var AreaSeries = extendClass(Series, { + type: 'area', + /** + * For stacks, don't split segments on null values. Instead, draw null values with + * no marker. Also insert dummy points for any X position that exists in other series + * in the stack. + */ + getSegments: function () { + var segments = [], + segment = [], + keys = [], + xAxis = this.xAxis, + yAxis = this.yAxis, + stack = yAxis.stacks[this.stackKey], + pointMap = {}, + plotX, + plotY, + points = this.points, + connectNulls = this.options.connectNulls, + val, + i, + x; + + if (this.options.stacking && !this.cropped) { // cropped causes artefacts in Stock, and perf issue + // Create a map where we can quickly look up the points by their X value. + for (i = 0; i < points.length; i++) { + pointMap[points[i].x] = points[i]; + } + + // Sort the keys (#1651) + for (x in stack) { + if (stack[x].total !== null) { // nulled after switching between grouping and not (#1651, #2336) + keys.push(+x); + } + } + keys.sort(function (a, b) { + return a - b; + }); + + each(keys, function (x) { + if (connectNulls && (!pointMap[x] || pointMap[x].y === null)) { // #1836 + return; + + // The point exists, push it to the segment + } else if (pointMap[x]) { + segment.push(pointMap[x]); + + // There is no point for this X value in this series, so we + // insert a dummy point in order for the areas to be drawn + // correctly. + } else { + plotX = xAxis.translate(x); + val = stack[x].percent ? (stack[x].total ? stack[x].cum * 100 / stack[x].total : 0) : stack[x].cum; // #1991 + plotY = yAxis.toPixels(val, true); + segment.push({ + y: null, + plotX: plotX, + clientX: plotX, + plotY: plotY, + yBottom: plotY, + onMouseOver: noop + }); + } + }); + + if (segment.length) { + segments.push(segment); + } + + } else { + Series.prototype.getSegments.call(this); + segments = this.segments; + } + + this.segments = segments; + }, + + /** + * Extend the base Series getSegmentPath method by adding the path for the area. + * This path is pushed to the series.areaPath property. + */ + getSegmentPath: function (segment) { + + var segmentPath = Series.prototype.getSegmentPath.call(this, segment), // call base method + areaSegmentPath = [].concat(segmentPath), // work on a copy for the area path + i, + options = this.options, + segLength = segmentPath.length, + translatedThreshold = this.yAxis.getThreshold(options.threshold), // #2181 + yBottom; + + if (segLength === 3) { // for animation from 1 to two points + areaSegmentPath.push(L, segmentPath[1], segmentPath[2]); + } + if (options.stacking && !this.closedStacks) { + + // Follow stack back. Todo: implement areaspline. A general solution could be to + // reverse the entire graphPath of the previous series, though may be hard with + // splines and with series with different extremes + for (i = segment.length - 1; i >= 0; i--) { + + yBottom = pick(segment[i].yBottom, translatedThreshold); + + // step line? + if (i < segment.length - 1 && options.step) { + areaSegmentPath.push(segment[i + 1].plotX, yBottom); + } + + areaSegmentPath.push(segment[i].plotX, yBottom); + } + + } else { // follow zero line back + this.closeSegment(areaSegmentPath, segment, translatedThreshold); + } + this.areaPath = this.areaPath.concat(areaSegmentPath); + return segmentPath; + }, + + /** + * Extendable method to close the segment path of an area. This is overridden in polar + * charts. + */ + closeSegment: function (path, segment, translatedThreshold) { + path.push( + L, + segment[segment.length - 1].plotX, + translatedThreshold, + L, + segment[0].plotX, + translatedThreshold + ); + }, + + /** + * Draw the graph and the underlying area. This method calls the Series base + * function and adds the area. The areaPath is calculated in the getSegmentPath + * method called from Series.prototype.drawGraph. + */ + drawGraph: function () { + + // Define or reset areaPath + this.areaPath = []; + + // Call the base method + Series.prototype.drawGraph.apply(this); + + // Define local variables + var series = this, + areaPath = this.areaPath, + options = this.options, + negativeColor = options.negativeColor, + negativeFillColor = options.negativeFillColor, + props = [['area', this.color, options.fillColor]]; // area name, main color, fill color + + if (negativeColor || negativeFillColor) { + props.push(['areaNeg', negativeColor, negativeFillColor]); + } + + each(props, function (prop) { + var areaKey = prop[0], + area = series[areaKey]; + + // Create or update the area + if (area) { // update + area.animate({ d: areaPath }); + + } else { // create + series[areaKey] = series.chart.renderer.path(areaPath) + .attr({ + fill: pick( + prop[2], + Color(prop[1]).setOpacity(pick(options.fillOpacity, 0.75)).get() + ), + zIndex: 0 // #1069 + }).add(series.group); + } + }); + }, + + drawLegendSymbol: LegendSymbolMixin.drawRectangle +}); + +seriesTypes.area = AreaSeries; +/** + * Set the default options for spline + */ +defaultPlotOptions.spline = merge(defaultSeriesOptions); + +/** + * SplineSeries object + */ +var SplineSeries = extendClass(Series, { + type: 'spline', + + /** + * Get the spline segment from a given point's previous neighbour to the given point + */ + getPointSpline: function (segment, point, i) { + var smoothing = 1.5, // 1 means control points midway between points, 2 means 1/3 from the point, 3 is 1/4 etc + denom = smoothing + 1, + plotX = point.plotX, + plotY = point.plotY, + lastPoint = segment[i - 1], + nextPoint = segment[i + 1], + leftContX, + leftContY, + rightContX, + rightContY, + ret; + + // find control points + if (lastPoint && nextPoint) { + + var lastX = lastPoint.plotX, + lastY = lastPoint.plotY, + nextX = nextPoint.plotX, + nextY = nextPoint.plotY, + correction; + + leftContX = (smoothing * plotX + lastX) / denom; + leftContY = (smoothing * plotY + lastY) / denom; + rightContX = (smoothing * plotX + nextX) / denom; + rightContY = (smoothing * plotY + nextY) / denom; + + // have the two control points make a straight line through main point + correction = ((rightContY - leftContY) * (rightContX - plotX)) / + (rightContX - leftContX) + plotY - rightContY; + + leftContY += correction; + rightContY += correction; + + // to prevent false extremes, check that control points are between + // neighbouring points' y values + if (leftContY > lastY && leftContY > plotY) { + leftContY = mathMax(lastY, plotY); + rightContY = 2 * plotY - leftContY; // mirror of left control point + } else if (leftContY < lastY && leftContY < plotY) { + leftContY = mathMin(lastY, plotY); + rightContY = 2 * plotY - leftContY; + } + if (rightContY > nextY && rightContY > plotY) { + rightContY = mathMax(nextY, plotY); + leftContY = 2 * plotY - rightContY; + } else if (rightContY < nextY && rightContY < plotY) { + rightContY = mathMin(nextY, plotY); + leftContY = 2 * plotY - rightContY; + } + + // record for drawing in next point + point.rightContX = rightContX; + point.rightContY = rightContY; + + } + + // Visualize control points for debugging + /* + if (leftContX) { + this.chart.renderer.circle(leftContX + this.chart.plotLeft, leftContY + this.chart.plotTop, 2) + .attr({ + stroke: 'red', + 'stroke-width': 1, + fill: 'none' + }) + .add(); + this.chart.renderer.path(['M', leftContX + this.chart.plotLeft, leftContY + this.chart.plotTop, + 'L', plotX + this.chart.plotLeft, plotY + this.chart.plotTop]) + .attr({ + stroke: 'red', + 'stroke-width': 1 + }) + .add(); + this.chart.renderer.circle(rightContX + this.chart.plotLeft, rightContY + this.chart.plotTop, 2) + .attr({ + stroke: 'green', + 'stroke-width': 1, + fill: 'none' + }) + .add(); + this.chart.renderer.path(['M', rightContX + this.chart.plotLeft, rightContY + this.chart.plotTop, + 'L', plotX + this.chart.plotLeft, plotY + this.chart.plotTop]) + .attr({ + stroke: 'green', + 'stroke-width': 1 + }) + .add(); + } + */ + + // moveTo or lineTo + if (!i) { + ret = [M, plotX, plotY]; + } else { // curve from last point to this + ret = [ + 'C', + lastPoint.rightContX || lastPoint.plotX, + lastPoint.rightContY || lastPoint.plotY, + leftContX || plotX, + leftContY || plotY, + plotX, + plotY + ]; + lastPoint.rightContX = lastPoint.rightContY = null; // reset for updating series later + } + return ret; + } +}); +seriesTypes.spline = SplineSeries; + +/** + * Set the default options for areaspline + */ +defaultPlotOptions.areaspline = merge(defaultPlotOptions.area); + +/** + * AreaSplineSeries object + */ +var areaProto = AreaSeries.prototype, + AreaSplineSeries = extendClass(SplineSeries, { + type: 'areaspline', + closedStacks: true, // instead of following the previous graph back, follow the threshold back + + // Mix in methods from the area series + getSegmentPath: areaProto.getSegmentPath, + closeSegment: areaProto.closeSegment, + drawGraph: areaProto.drawGraph, + drawLegendSymbol: LegendSymbolMixin.drawRectangle + }); + +seriesTypes.areaspline = AreaSplineSeries; + +/** + * Set the default options for column + */ +defaultPlotOptions.column = merge(defaultSeriesOptions, { + borderColor: '#FFFFFF', + //borderWidth: 1, + borderRadius: 0, + //colorByPoint: undefined, + groupPadding: 0.2, + //grouping: true, + marker: null, // point options are specified in the base options + pointPadding: 0.1, + //pointWidth: null, + minPointLength: 0, + cropThreshold: 50, // when there are more points, they will not animate out of the chart on xAxis.setExtremes + pointRange: null, // null means auto, meaning 1 in a categorized axis and least distance between points if not categories + states: { + hover: { + brightness: 0.1, + shadow: false, + halo: false + }, + select: { + color: '#C0C0C0', + borderColor: '#000000', + shadow: false + } + }, + dataLabels: { + align: null, // auto + verticalAlign: null, // auto + y: null + }, + stickyTracking: false, + tooltip: { + distance: 6 + }, + threshold: 0 +}); + +/** + * ColumnSeries object + */ +var ColumnSeries = extendClass(Series, { + type: 'column', + pointAttrToOptions: { // mapping between SVG attributes and the corresponding options + stroke: 'borderColor', + fill: 'color', + r: 'borderRadius' + }, + cropShoulder: 0, + trackerGroups: ['group', 'dataLabelsGroup'], + negStacks: true, // use separate negative stacks, unlike area stacks where a negative + // point is substracted from previous (#1910) + + /** + * Initialize the series + */ + init: function () { + Series.prototype.init.apply(this, arguments); + + var series = this, + chart = series.chart; + + // if the series is added dynamically, force redraw of other + // series affected by a new column + if (chart.hasRendered) { + each(chart.series, function (otherSeries) { + if (otherSeries.type === series.type) { + otherSeries.isDirty = true; + } + }); + } + }, + + /** + * Return the width and x offset of the columns adjusted for grouping, groupPadding, pointPadding, + * pointWidth etc. + */ + getColumnMetrics: function () { + + var series = this, + options = series.options, + xAxis = series.xAxis, + yAxis = series.yAxis, + reversedXAxis = xAxis.reversed, + stackKey, + stackGroups = {}, + columnIndex, + columnCount = 0; + + // Get the total number of column type series. + // This is called on every series. Consider moving this logic to a + // chart.orderStacks() function and call it on init, addSeries and removeSeries + if (options.grouping === false) { + columnCount = 1; + } else { + each(series.chart.series, function (otherSeries) { + var otherOptions = otherSeries.options, + otherYAxis = otherSeries.yAxis; + if (otherSeries.type === series.type && otherSeries.visible && + yAxis.len === otherYAxis.len && yAxis.pos === otherYAxis.pos) { // #642, #2086 + if (otherOptions.stacking) { + stackKey = otherSeries.stackKey; + if (stackGroups[stackKey] === UNDEFINED) { + stackGroups[stackKey] = columnCount++; + } + columnIndex = stackGroups[stackKey]; + } else if (otherOptions.grouping !== false) { // #1162 + columnIndex = columnCount++; + } + otherSeries.columnIndex = columnIndex; + } + }); + } + + var categoryWidth = mathMin( + mathAbs(xAxis.transA) * (xAxis.ordinalSlope || options.pointRange || xAxis.closestPointRange || xAxis.tickInterval || 1), // #2610 + xAxis.len // #1535 + ), + groupPadding = categoryWidth * options.groupPadding, + groupWidth = categoryWidth - 2 * groupPadding, + pointOffsetWidth = groupWidth / columnCount, + optionPointWidth = options.pointWidth, + pointPadding = defined(optionPointWidth) ? (pointOffsetWidth - optionPointWidth) / 2 : + pointOffsetWidth * options.pointPadding, + pointWidth = pick(optionPointWidth, pointOffsetWidth - 2 * pointPadding), // exact point width, used in polar charts + colIndex = (reversedXAxis ? + columnCount - (series.columnIndex || 0) : // #1251 + series.columnIndex) || 0, + pointXOffset = pointPadding + (groupPadding + colIndex * + pointOffsetWidth - (categoryWidth / 2)) * + (reversedXAxis ? -1 : 1); + + // Save it for reading in linked series (Error bars particularly) + return (series.columnMetrics = { + width: pointWidth, + offset: pointXOffset + }); + + }, + + /** + * Translate each point to the plot area coordinate system and find shape positions + */ + translate: function () { + var series = this, + chart = series.chart, + options = series.options, + borderWidth = series.borderWidth = pick( + options.borderWidth, + series.activePointCount > 0.5 * series.xAxis.len ? 0 : 1 + ), + yAxis = series.yAxis, + threshold = options.threshold, + translatedThreshold = series.translatedThreshold = yAxis.getThreshold(threshold), + minPointLength = pick(options.minPointLength, 5), + metrics = series.getColumnMetrics(), + pointWidth = metrics.width, + seriesBarW = series.barW = mathCeil(mathMax(pointWidth, 1 + 2 * borderWidth)), // rounded and postprocessed for border width + pointXOffset = series.pointXOffset = metrics.offset, + xCrisp = -(borderWidth % 2 ? 0.5 : 0), + yCrisp = borderWidth % 2 ? 0.5 : 1; + + if (chart.renderer.isVML && chart.inverted) { + yCrisp += 1; + } + + Series.prototype.translate.apply(series); + + // record the new values + each(series.points, function (point) { + var yBottom = pick(point.yBottom, translatedThreshold), + plotY = mathMin(mathMax(-999 - yBottom, point.plotY), yAxis.len + 999 + yBottom), // Don't draw too far outside plot area (#1303, #2241) + barX = point.plotX + pointXOffset, + barW = seriesBarW, + barY = mathMin(plotY, yBottom), + right, + bottom, + fromTop, + fromLeft, + barH = mathMax(plotY, yBottom) - barY; + + // Handle options.minPointLength + if (mathAbs(barH) < minPointLength) { + if (minPointLength) { + barH = minPointLength; + barY = + mathRound(mathAbs(barY - translatedThreshold) > minPointLength ? // stacked + yBottom - minPointLength : // keep position + translatedThreshold - (yAxis.translate(point.y, 0, 1, 0, 1) <= translatedThreshold ? minPointLength : 0)); // use exact yAxis.translation (#1485) + } + } + + // Cache for access in polar + point.barX = barX; + point.pointWidth = pointWidth; + + // Fix the tooltip on center of grouped columns (#1216) + point.tooltipPos = chart.inverted ? [yAxis.len - plotY, series.xAxis.len - barX - barW / 2] : [barX + barW / 2, plotY]; + + // Round off to obtain crisp edges + fromLeft = mathAbs(barX) < 0.5; + right = mathRound(barX + barW) + xCrisp; + barX = mathRound(barX) + xCrisp; + barW = right - barX; + + fromTop = mathAbs(barY) < 0.5; + bottom = mathRound(barY + barH) + yCrisp; + barY = mathRound(barY) + yCrisp; + barH = bottom - barY; + + // Top and left edges are exceptions + if (fromLeft) { + barX += 1; + barW -= 1; + } + if (fromTop) { + barY -= 1; + barH += 1; + } + + // Register shape type and arguments to be used in drawPoints + point.shapeType = 'rect'; + point.shapeArgs = { + x: barX, + y: barY, + width: barW, + height: barH + }; + }); + + }, + + getSymbol: noop, + + /** + * Use a solid rectangle like the area series types + */ + drawLegendSymbol: LegendSymbolMixin.drawRectangle, + + + /** + * Columns have no graph + */ + drawGraph: noop, + + /** + * Draw the columns. For bars, the series.group is rotated, so the same coordinates + * apply for columns and bars. This method is inherited by scatter series. + * + */ + drawPoints: function () { + var series = this, + chart = this.chart, + options = series.options, + renderer = chart.renderer, + animationLimit = options.animationLimit || 250, + shapeArgs, + pointAttr, + borderAttr; + + // draw the columns + each(series.points, function (point) { + var plotY = point.plotY, + graphic = point.graphic; + + if (plotY !== UNDEFINED && !isNaN(plotY) && point.y !== null) { + shapeArgs = point.shapeArgs; + borderAttr = defined(series.borderWidth) ? { + 'stroke-width': series.borderWidth + } : {}; + pointAttr = point.pointAttr[point.selected ? SELECT_STATE : NORMAL_STATE] || series.pointAttr[NORMAL_STATE]; + if (graphic) { // update + stop(graphic); + graphic.attr(borderAttr)[chart.pointCount < animationLimit ? 'animate' : 'attr'](merge(shapeArgs)); + + } else { + point.graphic = graphic = renderer[point.shapeType](shapeArgs) + .attr(pointAttr) + .attr(borderAttr) + .add(series.group) + .shadow(options.shadow, null, options.stacking && !options.borderRadius); + } + + } else if (graphic) { + point.graphic = graphic.destroy(); // #1269 + } + }); + }, + + /** + * Animate the column heights one by one from zero + * @param {Boolean} init Whether to initialize the animation or run it + */ + animate: function (init) { + var series = this, + yAxis = this.yAxis, + options = series.options, + inverted = this.chart.inverted, + attr = {}, + translatedThreshold; + + if (hasSVG) { // VML is too slow anyway + if (init) { + attr.scaleY = 0.001; + translatedThreshold = mathMin(yAxis.pos + yAxis.len, mathMax(yAxis.pos, yAxis.toPixels(options.threshold))); + if (inverted) { + attr.translateX = translatedThreshold - yAxis.len; + } else { + attr.translateY = translatedThreshold; + } + series.group.attr(attr); + + } else { // run the animation + + attr.scaleY = 1; + attr[inverted ? 'translateX' : 'translateY'] = yAxis.pos; + series.group.animate(attr, series.options.animation); + + // delete this function to allow it only once + series.animate = null; + } + } + }, + + /** + * Remove this series from the chart + */ + remove: function () { + var series = this, + chart = series.chart; + + // column and bar series affects other series of the same type + // as they are either stacked or grouped + if (chart.hasRendered) { + each(chart.series, function (otherSeries) { + if (otherSeries.type === series.type) { + otherSeries.isDirty = true; + } + }); + } + + Series.prototype.remove.apply(series, arguments); + } +}); +seriesTypes.column = ColumnSeries; +/** + * Set the default options for bar + */ +defaultPlotOptions.bar = merge(defaultPlotOptions.column); +/** + * The Bar series class + */ +var BarSeries = extendClass(ColumnSeries, { + type: 'bar', + inverted: true +}); +seriesTypes.bar = BarSeries; + +/** + * Set the default options for scatter + */ +defaultPlotOptions.scatter = merge(defaultSeriesOptions, { + lineWidth: 0, + tooltip: { + headerFormat: '\u25CF {series.name}
', // docs + pointFormat: 'x: {point.x}
y: {point.y}
' + }, + stickyTracking: false +}); + +/** + * The scatter series class + */ +var ScatterSeries = extendClass(Series, { + type: 'scatter', + sorted: false, + requireSorting: false, + noSharedTooltip: true, + trackerGroups: ['markerGroup'], + takeOrdinalPosition: false, // #2342 + singularTooltips: true, + drawGraph: function () { + if (this.options.lineWidth) { + Series.prototype.drawGraph.call(this); + } + } +}); + +seriesTypes.scatter = ScatterSeries; + +/** + * Set the default options for pie + */ +defaultPlotOptions.pie = merge(defaultSeriesOptions, { + borderColor: '#FFFFFF', + borderWidth: 1, + center: [null, null], + clip: false, + colorByPoint: true, // always true for pies + dataLabels: { + // align: null, + // connectorWidth: 1, + // connectorColor: point.color, + // connectorPadding: 5, + distance: 30, + enabled: true, + formatter: function () { + return this.point.name; + } + // softConnector: true, + //y: 0 + }, + ignoreHiddenPoint: true, + //innerSize: 0, + legendType: 'point', + marker: null, // point options are specified in the base options + size: null, + showInLegend: false, + slicedOffset: 10, + states: { + hover: { + brightness: 0.1, + shadow: false + } + }, + stickyTracking: false, + tooltip: { + followPointer: true + } +}); + +/** + * Extended point object for pies + */ +var PiePoint = extendClass(Point, { + /** + * Initiate the pie slice + */ + init: function () { + + Point.prototype.init.apply(this, arguments); + + var point = this, + toggleSlice; + + // Disallow negative values (#1530) + if (point.y < 0) { + point.y = null; + } + + //visible: options.visible !== false, + extend(point, { + visible: point.visible !== false, + name: pick(point.name, 'Slice') + }); + + // add event listener for select + toggleSlice = function (e) { + point.slice(e.type === 'select'); + }; + addEvent(point, 'select', toggleSlice); + addEvent(point, 'unselect', toggleSlice); + + return point; + }, + + /** + * Toggle the visibility of the pie slice + * @param {Boolean} vis Whether to show the slice or not. If undefined, the + * visibility is toggled + */ + setVisible: function (vis) { + var point = this, + series = point.series, + chart = series.chart; + + // if called without an argument, toggle visibility + point.visible = point.options.visible = vis = vis === UNDEFINED ? !point.visible : vis; + series.options.data[inArray(point, series.data)] = point.options; // update userOptions.data + + // Show and hide associated elements + each(['graphic', 'dataLabel', 'connector', 'shadowGroup'], function (key) { + if (point[key]) { + point[key][vis ? 'show' : 'hide'](true); + } + }); + + if (point.legendItem) { + chart.legend.colorizeItem(point, vis); + } + + // Handle ignore hidden slices + if (!series.isDirty && series.options.ignoreHiddenPoint) { + series.isDirty = true; + chart.redraw(); + } + }, + + /** + * Set or toggle whether the slice is cut out from the pie + * @param {Boolean} sliced When undefined, the slice state is toggled + * @param {Boolean} redraw Whether to redraw the chart. True by default. + */ + slice: function (sliced, redraw, animation) { + var point = this, + series = point.series, + chart = series.chart, + translation; + + setAnimation(animation, chart); + + // redraw is true by default + redraw = pick(redraw, true); + + // if called without an argument, toggle + point.sliced = point.options.sliced = sliced = defined(sliced) ? sliced : !point.sliced; + series.options.data[inArray(point, series.data)] = point.options; // update userOptions.data + + translation = sliced ? point.slicedTranslation : { + translateX: 0, + translateY: 0 + }; + + point.graphic.animate(translation); + + if (point.shadowGroup) { + point.shadowGroup.animate(translation); + } + + }, + + haloPath: function (size) { + var shapeArgs = this.shapeArgs, + chart = this.series.chart; + + return this.series.chart.renderer.symbols.arc(chart.plotLeft + shapeArgs.x, chart.plotTop + shapeArgs.y, shapeArgs.r + size, shapeArgs.r + size, { + innerR: this.shapeArgs.r, + start: shapeArgs.start, + end: shapeArgs.end + }); + } +}); + +/** + * The Pie series class + */ +var PieSeries = { + type: 'pie', + isCartesian: false, + pointClass: PiePoint, + requireSorting: false, + noSharedTooltip: true, + trackerGroups: ['group', 'dataLabelsGroup'], + axisTypes: [], + pointAttrToOptions: { // mapping between SVG attributes and the corresponding options + stroke: 'borderColor', + 'stroke-width': 'borderWidth', + fill: 'color' + }, + singularTooltips: true, + + /** + * Pies have one color each point + */ + getColor: noop, + + /** + * Animate the pies in + */ + animate: function (init) { + var series = this, + points = series.points, + startAngleRad = series.startAngleRad; + + if (!init) { + each(points, function (point) { + var graphic = point.graphic, + args = point.shapeArgs; + + if (graphic) { + // start values + graphic.attr({ + r: series.center[3] / 2, // animate from inner radius (#779) + start: startAngleRad, + end: startAngleRad + }); + + // animate + graphic.animate({ + r: args.r, + start: args.start, + end: args.end + }, series.options.animation); + } + }); + + // delete this function to allow it only once + series.animate = null; + } + }, + + /** + * Extend the basic setData method by running processData and generatePoints immediately, + * in order to access the points from the legend. + */ + setData: function (data, redraw, animation, updatePoints) { + Series.prototype.setData.call(this, data, false, animation, updatePoints); + this.processData(); + this.generatePoints(); + if (pick(redraw, true)) { + this.chart.redraw(animation); + } + }, + + /** + * Extend the generatePoints method by adding total and percentage properties to each point + */ + generatePoints: function () { + var i, + total = 0, + points, + len, + point, + ignoreHiddenPoint = this.options.ignoreHiddenPoint; + + Series.prototype.generatePoints.call(this); + + // Populate local vars + points = this.points; + len = points.length; + + // Get the total sum + for (i = 0; i < len; i++) { + point = points[i]; + total += (ignoreHiddenPoint && !point.visible) ? 0 : point.y; + } + this.total = total; + + // Set each point's properties + for (i = 0; i < len; i++) { + point = points[i]; + point.percentage = total > 0 ? (point.y / total) * 100 : 0; + point.total = total; + } + + }, + + /** + * Do translation for pie slices + */ + translate: function (positions) { + this.generatePoints(); + + var series = this, + cumulative = 0, + precision = 1000, // issue #172 + options = series.options, + slicedOffset = options.slicedOffset, + connectorOffset = slicedOffset + options.borderWidth, + start, + end, + angle, + startAngle = options.startAngle || 0, + startAngleRad = series.startAngleRad = mathPI / 180 * (startAngle - 90), + endAngleRad = series.endAngleRad = mathPI / 180 * ((pick(options.endAngle, startAngle + 360)) - 90), + circ = endAngleRad - startAngleRad, //2 * mathPI, + points = series.points, + radiusX, // the x component of the radius vector for a given point + radiusY, + labelDistance = options.dataLabels.distance, + ignoreHiddenPoint = options.ignoreHiddenPoint, + i, + len = points.length, + point; + + // Get positions - either an integer or a percentage string must be given. + // If positions are passed as a parameter, we're in a recursive loop for adjusting + // space for data labels. + if (!positions) { + series.center = positions = series.getCenter(); + } + + // utility for getting the x value from a given y, used for anticollision logic in data labels + series.getX = function (y, left) { + + angle = math.asin(mathMin((y - positions[1]) / (positions[2] / 2 + labelDistance), 1)); + + return positions[0] + + (left ? -1 : 1) * + (mathCos(angle) * (positions[2] / 2 + labelDistance)); + }; + + // Calculate the geometry for each point + for (i = 0; i < len; i++) { + + point = points[i]; + + // set start and end angle + start = startAngleRad + (cumulative * circ); + if (!ignoreHiddenPoint || point.visible) { + cumulative += point.percentage / 100; + } + end = startAngleRad + (cumulative * circ); + + // set the shape + point.shapeType = 'arc'; + point.shapeArgs = { + x: positions[0], + y: positions[1], + r: positions[2] / 2, + innerR: positions[3] / 2, + start: mathRound(start * precision) / precision, + end: mathRound(end * precision) / precision + }; + + // The angle must stay within -90 and 270 (#2645) + angle = (end + start) / 2; + if (angle > 1.5 * mathPI) { + angle -= 2 * mathPI; + } else if (angle < -mathPI / 2) { + angle += 2 * mathPI; + } + + // Center for the sliced out slice + point.slicedTranslation = { + translateX: mathRound(mathCos(angle) * slicedOffset), + translateY: mathRound(mathSin(angle) * slicedOffset) + }; + + // set the anchor point for tooltips + radiusX = mathCos(angle) * positions[2] / 2; + radiusY = mathSin(angle) * positions[2] / 2; + point.tooltipPos = [ + positions[0] + radiusX * 0.7, + positions[1] + radiusY * 0.7 + ]; + + point.half = angle < -mathPI / 2 || angle > mathPI / 2 ? 1 : 0; + point.angle = angle; + + // set the anchor point for data labels + connectorOffset = mathMin(connectorOffset, labelDistance / 2); // #1678 + point.labelPos = [ + positions[0] + radiusX + mathCos(angle) * labelDistance, // first break of connector + positions[1] + radiusY + mathSin(angle) * labelDistance, // a/a + positions[0] + radiusX + mathCos(angle) * connectorOffset, // second break, right outside pie + positions[1] + radiusY + mathSin(angle) * connectorOffset, // a/a + positions[0] + radiusX, // landing point for connector + positions[1] + radiusY, // a/a + labelDistance < 0 ? // alignment + 'center' : + point.half ? 'right' : 'left', // alignment + angle // center angle + ]; + + } + }, + + drawGraph: null, + + /** + * Draw the data points + */ + drawPoints: function () { + var series = this, + chart = series.chart, + renderer = chart.renderer, + groupTranslation, + //center, + graphic, + //group, + shadow = series.options.shadow, + shadowGroup, + shapeArgs; + + if (shadow && !series.shadowGroup) { + series.shadowGroup = renderer.g('shadow') + .add(series.group); + } + + // draw the slices + each(series.points, function (point) { + graphic = point.graphic; + shapeArgs = point.shapeArgs; + shadowGroup = point.shadowGroup; + + // put the shadow behind all points + if (shadow && !shadowGroup) { + shadowGroup = point.shadowGroup = renderer.g('shadow') + .add(series.shadowGroup); + } + + // if the point is sliced, use special translation, else use plot area traslation + groupTranslation = point.sliced ? point.slicedTranslation : { + translateX: 0, + translateY: 0 + }; + + //group.translate(groupTranslation[0], groupTranslation[1]); + if (shadowGroup) { + shadowGroup.attr(groupTranslation); + } + + // draw the slice + if (graphic) { + graphic.animate(extend(shapeArgs, groupTranslation)); + } else { + point.graphic = graphic = renderer[point.shapeType](shapeArgs) + .setRadialReference(series.center) + .attr( + point.pointAttr[point.selected ? SELECT_STATE : NORMAL_STATE] + ) + .attr({ + 'stroke-linejoin': 'round' + //zIndex: 1 // #2722 (reversed) + }) + .attr(groupTranslation) + .add(series.group) + .shadow(shadow, shadowGroup); + } + + // detect point specific visibility (#2430) + if (point.visible !== undefined) { + point.setVisible(point.visible); + } + + }); + + }, + + /** + * Utility for sorting data labels + */ + sortByAngle: function (points, sign) { + points.sort(function (a, b) { + return a.angle !== undefined && (b.angle - a.angle) * sign; + }); + }, + + /** + * Use a simple symbol from LegendSymbolMixin + */ + drawLegendSymbol: LegendSymbolMixin.drawRectangle, + + /** + * Use the getCenter method from drawLegendSymbol + */ + getCenter: CenteredSeriesMixin.getCenter, + + /** + * Pies don't have point marker symbols + */ + getSymbol: noop + +}; +PieSeries = extendClass(Series, PieSeries); +seriesTypes.pie = PieSeries; + +/** + * Draw the data labels + */ +Series.prototype.drawDataLabels = function () { + + var series = this, + seriesOptions = series.options, + cursor = seriesOptions.cursor, + options = seriesOptions.dataLabels, + points = series.points, + pointOptions, + generalOptions, + str, + dataLabelsGroup; + + if (options.enabled || series._hasPointLabels) { + + // Process default alignment of data labels for columns + if (series.dlProcessOptions) { + series.dlProcessOptions(options); + } + + // Create a separate group for the data labels to avoid rotation + dataLabelsGroup = series.plotGroup( + 'dataLabelsGroup', + 'data-labels', + HIDDEN, + options.zIndex || 6 + ); + + if (!series.hasRendered && pick(options.defer, true)) { + dataLabelsGroup.attr({ opacity: 0 }); + addEvent(series, 'afterAnimate', function () { + series.dataLabelsGroup.show()[seriesOptions.animation ? 'animate' : 'attr']({ opacity: 1 }, { duration: 200 }); + }); + } + + // Make the labels for each point + generalOptions = options; + each(points, function (point) { + + var enabled, + dataLabel = point.dataLabel, + labelConfig, + attr, + name, + rotation, + connector = point.connector, + isNew = true; + + // Determine if each data label is enabled + pointOptions = point.options && point.options.dataLabels; + enabled = pick(pointOptions && pointOptions.enabled, generalOptions.enabled); // #2282 + + + // If the point is outside the plot area, destroy it. #678, #820 + if (dataLabel && !enabled) { + point.dataLabel = dataLabel.destroy(); + + // Individual labels are disabled if the are explicitly disabled + // in the point options, or if they fall outside the plot area. + } else if (enabled) { + + // Create individual options structure that can be extended without + // affecting others + options = merge(generalOptions, pointOptions); + + rotation = options.rotation; + + // Get the string + labelConfig = point.getLabelConfig(); + str = options.format ? + format(options.format, labelConfig) : + options.formatter.call(labelConfig, options); + + // Determine the color + options.style.color = pick(options.color, options.style.color, series.color, 'black'); + + + // update existing label + if (dataLabel) { + + if (defined(str)) { + dataLabel + .attr({ + text: str + }); + isNew = false; + + } else { // #1437 - the label is shown conditionally + point.dataLabel = dataLabel = dataLabel.destroy(); + if (connector) { + point.connector = connector.destroy(); + } + } + + // create new label + } else if (defined(str)) { + attr = { + //align: align, + fill: options.backgroundColor, + stroke: options.borderColor, + 'stroke-width': options.borderWidth, + r: options.borderRadius || 0, + rotation: rotation, + padding: options.padding, + zIndex: 1 + }; + // Remove unused attributes (#947) + for (name in attr) { + if (attr[name] === UNDEFINED) { + delete attr[name]; + } + } + + dataLabel = point.dataLabel = series.chart.renderer[rotation ? 'text' : 'label']( // labels don't support rotation + str, + 0, + -999, + null, + null, + null, + options.useHTML + ) + .attr(attr) + .css(extend(options.style, cursor && { cursor: cursor })) + .add(dataLabelsGroup) + .shadow(options.shadow); + + } + + if (dataLabel) { + // Now the data label is created and placed at 0,0, so we need to align it + series.alignDataLabel(point, dataLabel, options, null, isNew); + } + } + }); + } +}; + +/** + * Align each individual data label + */ +Series.prototype.alignDataLabel = function (point, dataLabel, options, alignTo, isNew) { + var chart = this.chart, + inverted = chart.inverted, + plotX = pick(point.plotX, -999), + plotY = pick(point.plotY, -999), + bBox = dataLabel.getBBox(), + // Math.round for rounding errors (#2683), alignTo to allow column labels (#2700) + visible = this.visible && (point.series.forceDL || chart.isInsidePlot(plotX, mathRound(plotY), inverted) || + (alignTo && chart.isInsidePlot(plotX, inverted ? alignTo.x + 1 : alignTo.y + alignTo.height - 1, inverted))), + alignAttr; // the final position; + + if (visible) { + + // The alignment box is a singular point + alignTo = extend({ + x: inverted ? chart.plotWidth - plotY : plotX, + y: mathRound(inverted ? chart.plotHeight - plotX : plotY), + width: 0, + height: 0 + }, alignTo); + + // Add the text size for alignment calculation + extend(options, { + width: bBox.width, + height: bBox.height + }); + + // Allow a hook for changing alignment in the last moment, then do the alignment + if (options.rotation) { // Fancy box alignment isn't supported for rotated text + alignAttr = { + align: options.align, + x: alignTo.x + options.x + alignTo.width / 2, + y: alignTo.y + options.y + alignTo.height / 2 + }; + dataLabel[isNew ? 'attr' : 'animate'](alignAttr); + } else { + dataLabel.align(options, null, alignTo); + alignAttr = dataLabel.alignAttr; + + // Handle justify or crop + if (pick(options.overflow, 'justify') === 'justify') { + this.justifyDataLabel(dataLabel, options, alignAttr, bBox, alignTo, isNew); + + } else if (pick(options.crop, true)) { + // Now check that the data label is within the plot area + visible = chart.isInsidePlot(alignAttr.x, alignAttr.y) && chart.isInsidePlot(alignAttr.x + bBox.width, alignAttr.y + bBox.height); + + } + } + } + + // Show or hide based on the final aligned position + if (!visible) { + dataLabel.attr({ y: -999 }); + dataLabel.placed = false; // don't animate back in + } + +}; + +/** + * If data labels fall partly outside the plot area, align them back in, in a way that + * doesn't hide the point. + */ +Series.prototype.justifyDataLabel = function (dataLabel, options, alignAttr, bBox, alignTo, isNew) { + var chart = this.chart, + align = options.align, + verticalAlign = options.verticalAlign, + off, + justified; + + // Off left + off = alignAttr.x; + if (off < 0) { + if (align === 'right') { + options.align = 'left'; + } else { + options.x = -off; + } + justified = true; + } + + // Off right + off = alignAttr.x + bBox.width; + if (off > chart.plotWidth) { + if (align === 'left') { + options.align = 'right'; + } else { + options.x = chart.plotWidth - off; + } + justified = true; + } + + // Off top + off = alignAttr.y; + if (off < 0) { + if (verticalAlign === 'bottom') { + options.verticalAlign = 'top'; + } else { + options.y = -off; + } + justified = true; + } + + // Off bottom + off = alignAttr.y + bBox.height; + if (off > chart.plotHeight) { + if (verticalAlign === 'top') { + options.verticalAlign = 'bottom'; + } else { + options.y = chart.plotHeight - off; + } + justified = true; + } + + if (justified) { + dataLabel.placed = !isNew; + dataLabel.align(options, null, alignTo); + } +}; + +/** + * Override the base drawDataLabels method by pie specific functionality + */ +if (seriesTypes.pie) { + seriesTypes.pie.prototype.drawDataLabels = function () { + var series = this, + data = series.data, + point, + chart = series.chart, + options = series.options.dataLabels, + connectorPadding = pick(options.connectorPadding, 10), + connectorWidth = pick(options.connectorWidth, 1), + plotWidth = chart.plotWidth, + plotHeight = chart.plotHeight, + connector, + connectorPath, + softConnector = pick(options.softConnector, true), + distanceOption = options.distance, + seriesCenter = series.center, + radius = seriesCenter[2] / 2, + centerY = seriesCenter[1], + outside = distanceOption > 0, + dataLabel, + dataLabelWidth, + labelPos, + labelHeight, + halves = [// divide the points into right and left halves for anti collision + [], // right + [] // left + ], + x, + y, + visibility, + rankArr, + i, + j, + overflow = [0, 0, 0, 0], // top, right, bottom, left + sort = function (a, b) { + return b.y - a.y; + }; + + // get out if not enabled + if (!series.visible || (!options.enabled && !series._hasPointLabels)) { + return; + } + + // run parent method + Series.prototype.drawDataLabels.apply(series); + + // arrange points for detection collision + each(data, function (point) { + if (point.dataLabel && point.visible) { // #407, #2510 + halves[point.half].push(point); + } + }); + + // assume equal label heights + i = 0; + while (!labelHeight && data[i]) { // #1569 + labelHeight = data[i] && data[i].dataLabel && (data[i].dataLabel.getBBox().height || 21); // 21 is for #968 + i++; + } + + /* Loop over the points in each half, starting from the top and bottom + * of the pie to detect overlapping labels. + */ + i = 2; + while (i--) { + + var slots = [], + slotsLength, + usedSlots = [], + points = halves[i], + pos, + length = points.length, + slotIndex; + + // Sort by angle + series.sortByAngle(points, i - 0.5); + + // Only do anti-collision when we are outside the pie and have connectors (#856) + if (distanceOption > 0) { + + // build the slots + for (pos = centerY - radius - distanceOption; pos <= centerY + radius + distanceOption; pos += labelHeight) { + slots.push(pos); + + // visualize the slot + /* + var slotX = series.getX(pos, i) + chart.plotLeft - (i ? 100 : 0), + slotY = pos + chart.plotTop; + if (!isNaN(slotX)) { + chart.renderer.rect(slotX, slotY - 7, 100, labelHeight, 1) + .attr({ + 'stroke-width': 1, + stroke: 'silver' + }) + .add(); + chart.renderer.text('Slot '+ (slots.length - 1), slotX, slotY + 4) + .attr({ + fill: 'silver' + }).add(); + } + */ + } + slotsLength = slots.length; + + // if there are more values than available slots, remove lowest values + if (length > slotsLength) { + // create an array for sorting and ranking the points within each quarter + rankArr = [].concat(points); + rankArr.sort(sort); + j = length; + while (j--) { + rankArr[j].rank = j; + } + j = length; + while (j--) { + if (points[j].rank >= slotsLength) { + points.splice(j, 1); + } + } + length = points.length; + } + + // The label goes to the nearest open slot, but not closer to the edge than + // the label's index. + for (j = 0; j < length; j++) { + + point = points[j]; + labelPos = point.labelPos; + + var closest = 9999, + distance, + slotI; + + // find the closest slot index + for (slotI = 0; slotI < slotsLength; slotI++) { + distance = mathAbs(slots[slotI] - labelPos[1]); + if (distance < closest) { + closest = distance; + slotIndex = slotI; + } + } + + // if that slot index is closer to the edges of the slots, move it + // to the closest appropriate slot + if (slotIndex < j && slots[j] !== null) { // cluster at the top + slotIndex = j; + } else if (slotsLength < length - j + slotIndex && slots[j] !== null) { // cluster at the bottom + slotIndex = slotsLength - length + j; + while (slots[slotIndex] === null) { // make sure it is not taken + slotIndex++; + } + } else { + // Slot is taken, find next free slot below. In the next run, the next slice will find the + // slot above these, because it is the closest one + while (slots[slotIndex] === null) { // make sure it is not taken + slotIndex++; + } + } + + usedSlots.push({ i: slotIndex, y: slots[slotIndex] }); + slots[slotIndex] = null; // mark as taken + } + // sort them in order to fill in from the top + usedSlots.sort(sort); + } + + // now the used slots are sorted, fill them up sequentially + for (j = 0; j < length; j++) { + + var slot, naturalY; + + point = points[j]; + labelPos = point.labelPos; + dataLabel = point.dataLabel; + visibility = point.visible === false ? HIDDEN : VISIBLE; + naturalY = labelPos[1]; + + if (distanceOption > 0) { + slot = usedSlots.pop(); + slotIndex = slot.i; + + // if the slot next to currrent slot is free, the y value is allowed + // to fall back to the natural position + y = slot.y; + if ((naturalY > y && slots[slotIndex + 1] !== null) || + (naturalY < y && slots[slotIndex - 1] !== null)) { + y = naturalY; + } + + } else { + y = naturalY; + } + + // get the x - use the natural x position for first and last slot, to prevent the top + // and botton slice connectors from touching each other on either side + x = options.justify ? + seriesCenter[0] + (i ? -1 : 1) * (radius + distanceOption) : + series.getX(slotIndex === 0 || slotIndex === slots.length - 1 ? naturalY : y, i); + + + // Record the placement and visibility + dataLabel._attr = { + visibility: visibility, + align: labelPos[6] + }; + dataLabel._pos = { + x: x + options.x + + ({ left: connectorPadding, right: -connectorPadding }[labelPos[6]] || 0), + y: y + options.y - 10 // 10 is for the baseline (label vs text) + }; + dataLabel.connX = x; + dataLabel.connY = y; + + + // Detect overflowing data labels + if (this.options.size === null) { + dataLabelWidth = dataLabel.width; + // Overflow left + if (x - dataLabelWidth < connectorPadding) { + overflow[3] = mathMax(mathRound(dataLabelWidth - x + connectorPadding), overflow[3]); + + // Overflow right + } else if (x + dataLabelWidth > plotWidth - connectorPadding) { + overflow[1] = mathMax(mathRound(x + dataLabelWidth - plotWidth + connectorPadding), overflow[1]); + } + + // Overflow top + if (y - labelHeight / 2 < 0) { + overflow[0] = mathMax(mathRound(-y + labelHeight / 2), overflow[0]); + + // Overflow left + } else if (y + labelHeight / 2 > plotHeight) { + overflow[2] = mathMax(mathRound(y + labelHeight / 2 - plotHeight), overflow[2]); + } + } + } // for each point + } // for each half + + // Do not apply the final placement and draw the connectors until we have verified + // that labels are not spilling over. + if (arrayMax(overflow) === 0 || this.verifyDataLabelOverflow(overflow)) { + + // Place the labels in the final position + this.placeDataLabels(); + + // Draw the connectors + if (outside && connectorWidth) { + each(this.points, function (point) { + connector = point.connector; + labelPos = point.labelPos; + dataLabel = point.dataLabel; + + if (dataLabel && dataLabel._pos) { + visibility = dataLabel._attr.visibility; + x = dataLabel.connX; + y = dataLabel.connY; + connectorPath = softConnector ? [ + M, + x + (labelPos[6] === 'left' ? 5 : -5), y, // end of the string at the label + 'C', + x, y, // first break, next to the label + 2 * labelPos[2] - labelPos[4], 2 * labelPos[3] - labelPos[5], + labelPos[2], labelPos[3], // second break + L, + labelPos[4], labelPos[5] // base + ] : [ + M, + x + (labelPos[6] === 'left' ? 5 : -5), y, // end of the string at the label + L, + labelPos[2], labelPos[3], // second break + L, + labelPos[4], labelPos[5] // base + ]; + + if (connector) { + connector.animate({ d: connectorPath }); + connector.attr('visibility', visibility); + + } else { + point.connector = connector = series.chart.renderer.path(connectorPath).attr({ + 'stroke-width': connectorWidth, + stroke: options.connectorColor || point.color || '#606060', + visibility: visibility + //zIndex: 0 // #2722 (reversed) + }) + .add(series.dataLabelsGroup); + } + } else if (connector) { + point.connector = connector.destroy(); + } + }); + } + } + }; + /** + * Perform the final placement of the data labels after we have verified that they + * fall within the plot area. + */ + seriesTypes.pie.prototype.placeDataLabels = function () { + each(this.points, function (point) { + var dataLabel = point.dataLabel, + _pos; + + if (dataLabel) { + _pos = dataLabel._pos; + if (_pos) { + dataLabel.attr(dataLabel._attr); + dataLabel[dataLabel.moved ? 'animate' : 'attr'](_pos); + dataLabel.moved = true; + } else if (dataLabel) { + dataLabel.attr({ y: -999 }); + } + } + }); + }; + + seriesTypes.pie.prototype.alignDataLabel = noop; + + /** + * Verify whether the data labels are allowed to draw, or we should run more translation and data + * label positioning to keep them inside the plot area. Returns true when data labels are ready + * to draw. + */ + seriesTypes.pie.prototype.verifyDataLabelOverflow = function (overflow) { + + var center = this.center, + options = this.options, + centerOption = options.center, + minSize = options.minSize || 80, + newSize = minSize, + ret; + + // Handle horizontal size and center + if (centerOption[0] !== null) { // Fixed center + newSize = mathMax(center[2] - mathMax(overflow[1], overflow[3]), minSize); + + } else { // Auto center + newSize = mathMax( + center[2] - overflow[1] - overflow[3], // horizontal overflow + minSize + ); + center[0] += (overflow[3] - overflow[1]) / 2; // horizontal center + } + + // Handle vertical size and center + if (centerOption[1] !== null) { // Fixed center + newSize = mathMax(mathMin(newSize, center[2] - mathMax(overflow[0], overflow[2])), minSize); + + } else { // Auto center + newSize = mathMax( + mathMin( + newSize, + center[2] - overflow[0] - overflow[2] // vertical overflow + ), + minSize + ); + center[1] += (overflow[0] - overflow[2]) / 2; // vertical center + } + + // If the size must be decreased, we need to run translate and drawDataLabels again + if (newSize < center[2]) { + center[2] = newSize; + this.translate(center); + each(this.points, function (point) { + if (point.dataLabel) { + point.dataLabel._pos = null; // reset + } + }); + + if (this.drawDataLabels) { + this.drawDataLabels(); + } + // Else, return true to indicate that the pie and its labels is within the plot area + } else { + ret = true; + } + return ret; + }; +} + +if (seriesTypes.column) { + + /** + * Override the basic data label alignment by adjusting for the position of the column + */ + seriesTypes.column.prototype.alignDataLabel = function (point, dataLabel, options, alignTo, isNew) { + var chart = this.chart, + inverted = chart.inverted, + dlBox = point.dlBox || point.shapeArgs, // data label box for alignment + below = point.below || (point.plotY > pick(this.translatedThreshold, chart.plotSizeY)), + inside = pick(options.inside, !!this.options.stacking); // draw it inside the box? + + // Align to the column itself, or the top of it + if (dlBox) { // Area range uses this method but not alignTo + alignTo = merge(dlBox); + + if (inverted) { + alignTo = { + x: chart.plotWidth - alignTo.y - alignTo.height, + y: chart.plotHeight - alignTo.x - alignTo.width, + width: alignTo.height, + height: alignTo.width + }; + } + + // Compute the alignment box + if (!inside) { + if (inverted) { + alignTo.x += below ? 0 : alignTo.width; + alignTo.width = 0; + } else { + alignTo.y += below ? alignTo.height : 0; + alignTo.height = 0; + } + } + } + + + // When alignment is undefined (typically columns and bars), display the individual + // point below or above the point depending on the threshold + options.align = pick( + options.align, + !inverted || inside ? 'center' : below ? 'right' : 'left' + ); + options.verticalAlign = pick( + options.verticalAlign, + inverted || inside ? 'middle' : below ? 'top' : 'bottom' + ); + + // Call the parent method + Series.prototype.alignDataLabel.call(this, point, dataLabel, options, alignTo, isNew); + }; +} + + + +/** + * TrackerMixin for points and graphs + */ + +var TrackerMixin = Highcharts.TrackerMixin = { + + drawTrackerPoint: function () { + var series = this, + chart = series.chart, + pointer = chart.pointer, + cursor = series.options.cursor, + css = cursor && { cursor: cursor }, + onMouseOver = function (e) { + var target = e.target, + point; + + if (chart.hoverSeries !== series) { + series.onMouseOver(); + } + + while (target && !point) { + point = target.point; + target = target.parentNode; + } + + if (point !== UNDEFINED && point !== chart.hoverPoint) { // undefined on graph in scatterchart + point.onMouseOver(e); + } + }; + + // Add reference to the point + each(series.points, function (point) { + if (point.graphic) { + point.graphic.element.point = point; + } + if (point.dataLabel) { + point.dataLabel.element.point = point; + } + }); + + // Add the event listeners, we need to do this only once + if (!series._hasTracking) { + each(series.trackerGroups, function (key) { + if (series[key]) { // we don't always have dataLabelsGroup + series[key] + .addClass(PREFIX + 'tracker') + .on('mouseover', onMouseOver) + .on('mouseout', function (e) { pointer.onTrackerMouseOut(e); }) + .css(css); + if (hasTouch) { + series[key].on('touchstart', onMouseOver); + } + } + }); + series._hasTracking = true; + } + }, + + /** + * Draw the tracker object that sits above all data labels and markers to + * track mouse events on the graph or points. For the line type charts + * the tracker uses the same graphPath, but with a greater stroke width + * for better control. + */ + drawTrackerGraph: function () { + var series = this, + options = series.options, + trackByArea = options.trackByArea, + trackerPath = [].concat(trackByArea ? series.areaPath : series.graphPath), + trackerPathLength = trackerPath.length, + chart = series.chart, + pointer = chart.pointer, + renderer = chart.renderer, + snap = chart.options.tooltip.snap, + tracker = series.tracker, + cursor = options.cursor, + css = cursor && { cursor: cursor }, + singlePoints = series.singlePoints, + singlePoint, + i, + onMouseOver = function () { + if (chart.hoverSeries !== series) { + series.onMouseOver(); + } + }, + /* + * Empirical lowest possible opacities for TRACKER_FILL for an element to stay invisible but clickable + * IE6: 0.002 + * IE7: 0.002 + * IE8: 0.002 + * IE9: 0.00000000001 (unlimited) + * IE10: 0.0001 (exporting only) + * FF: 0.00000000001 (unlimited) + * Chrome: 0.000001 + * Safari: 0.000001 + * Opera: 0.00000000001 (unlimited) + */ + TRACKER_FILL = 'rgba(192,192,192,' + (hasSVG ? 0.0001 : 0.002) + ')'; + + // Extend end points. A better way would be to use round linecaps, + // but those are not clickable in VML. + if (trackerPathLength && !trackByArea) { + i = trackerPathLength + 1; + while (i--) { + if (trackerPath[i] === M) { // extend left side + trackerPath.splice(i + 1, 0, trackerPath[i + 1] - snap, trackerPath[i + 2], L); + } + if ((i && trackerPath[i] === M) || i === trackerPathLength) { // extend right side + trackerPath.splice(i, 0, L, trackerPath[i - 2] + snap, trackerPath[i - 1]); + } + } + } + + // handle single points + for (i = 0; i < singlePoints.length; i++) { + singlePoint = singlePoints[i]; + trackerPath.push(M, singlePoint.plotX - snap, singlePoint.plotY, + L, singlePoint.plotX + snap, singlePoint.plotY); + } + + // draw the tracker + if (tracker) { + tracker.attr({ d: trackerPath }); + } else { // create + + series.tracker = renderer.path(trackerPath) + .attr({ + 'stroke-linejoin': 'round', // #1225 + visibility: series.visible ? VISIBLE : HIDDEN, + stroke: TRACKER_FILL, + fill: trackByArea ? TRACKER_FILL : NONE, + 'stroke-width' : options.lineWidth + (trackByArea ? 0 : 2 * snap), + zIndex: 2 + }) + .add(series.group); + + // The tracker is added to the series group, which is clipped, but is covered + // by the marker group. So the marker group also needs to capture events. + each([series.tracker, series.markerGroup], function (tracker) { + tracker.addClass(PREFIX + 'tracker') + .on('mouseover', onMouseOver) + .on('mouseout', function (e) { pointer.onTrackerMouseOut(e); }) + .css(css); + + if (hasTouch) { + tracker.on('touchstart', onMouseOver); + } + }); + } + } +}; +/* End TrackerMixin */ + + +/** + * Add tracking event listener to the series group, so the point graphics + * themselves act as trackers + */ + +if (seriesTypes.column) { + ColumnSeries.prototype.drawTracker = TrackerMixin.drawTrackerPoint; +} + +if (seriesTypes.pie) { + seriesTypes.pie.prototype.drawTracker = TrackerMixin.drawTrackerPoint; +} + +if (seriesTypes.scatter) { + ScatterSeries.prototype.drawTracker = TrackerMixin.drawTrackerPoint; +} + +/* + * Extend Legend for item events + */ +extend(Legend.prototype, { + + setItemEvents: function (item, legendItem, useHTML, itemStyle, itemHiddenStyle) { + var legend = this; + // Set the events on the item group, or in case of useHTML, the item itself (#1249) + (useHTML ? legendItem : item.legendGroup).on('mouseover', function () { + item.setState(HOVER_STATE); + legendItem.css(legend.options.itemHoverStyle); + }) + .on('mouseout', function () { + legendItem.css(item.visible ? itemStyle : itemHiddenStyle); + item.setState(); + }) + .on('click', function (event) { + var strLegendItemClick = 'legendItemClick', + fnLegendItemClick = function () { + item.setVisible(); + }; + + // Pass over the click/touch event. #4. + event = { + browserEvent: event + }; + + // click the name or symbol + if (item.firePointEvent) { // point + item.firePointEvent(strLegendItemClick, event, fnLegendItemClick); + } else { + fireEvent(item, strLegendItemClick, event, fnLegendItemClick); + } + }); + }, + + createCheckboxForItem: function (item) { + var legend = this; + + item.checkbox = createElement('input', { + type: 'checkbox', + checked: item.selected, + defaultChecked: item.selected // required by IE7 + }, legend.options.itemCheckboxStyle, legend.chart.container); + + addEvent(item.checkbox, 'click', function (event) { + var target = event.target; + fireEvent(item, 'checkboxClick', { + checked: target.checked + }, + function () { + item.select(); + } + ); + }); + } +}); + +/* + * Add pointer cursor to legend itemstyle in defaultOptions + */ +defaultOptions.legend.itemStyle.cursor = 'pointer'; + + +/* + * Extend the Chart object with interaction + */ + +extend(Chart.prototype, { + /** + * Display the zoom button + */ + showResetZoom: function () { + var chart = this, + lang = defaultOptions.lang, + btnOptions = chart.options.chart.resetZoomButton, + theme = btnOptions.theme, + states = theme.states, + alignTo = btnOptions.relativeTo === 'chart' ? null : 'plotBox'; + + this.resetZoomButton = chart.renderer.button(lang.resetZoom, null, null, function () { chart.zoomOut(); }, theme, states && states.hover) + .attr({ + align: btnOptions.position.align, + title: lang.resetZoomTitle + }) + .add() + .align(btnOptions.position, false, alignTo); + + }, + + /** + * Zoom out to 1:1 + */ + zoomOut: function () { + var chart = this; + fireEvent(chart, 'selection', { resetSelection: true }, function () { + chart.zoom(); + }); + }, + + /** + * Zoom into a given portion of the chart given by axis coordinates + * @param {Object} event + */ + zoom: function (event) { + var chart = this, + hasZoomed, + pointer = chart.pointer, + displayButton = false, + resetZoomButton; + + // If zoom is called with no arguments, reset the axes + if (!event || event.resetSelection) { + each(chart.axes, function (axis) { + hasZoomed = axis.zoom(); + }); + } else { // else, zoom in on all axes + each(event.xAxis.concat(event.yAxis), function (axisData) { + var axis = axisData.axis, + isXAxis = axis.isXAxis; + + // don't zoom more than minRange + if (pointer[isXAxis ? 'zoomX' : 'zoomY'] || pointer[isXAxis ? 'pinchX' : 'pinchY']) { + hasZoomed = axis.zoom(axisData.min, axisData.max); + if (axis.displayBtn) { + displayButton = true; + } + } + }); + } + + // Show or hide the Reset zoom button + resetZoomButton = chart.resetZoomButton; + if (displayButton && !resetZoomButton) { + chart.showResetZoom(); + } else if (!displayButton && isObject(resetZoomButton)) { + chart.resetZoomButton = resetZoomButton.destroy(); + } + + + // Redraw + if (hasZoomed) { + chart.redraw( + pick(chart.options.chart.animation, event && event.animation, chart.pointCount < 100) // animation + ); + } + }, + + /** + * Pan the chart by dragging the mouse across the pane. This function is called + * on mouse move, and the distance to pan is computed from chartX compared to + * the first chartX position in the dragging operation. + */ + pan: function (e, panning) { + + var chart = this, + hoverPoints = chart.hoverPoints, + doRedraw; + + // remove active points for shared tooltip + if (hoverPoints) { + each(hoverPoints, function (point) { + point.setState(); + }); + } + + each(panning === 'xy' ? [1, 0] : [1], function (isX) { // xy is used in maps + var mousePos = e[isX ? 'chartX' : 'chartY'], + axis = chart[isX ? 'xAxis' : 'yAxis'][0], + startPos = chart[isX ? 'mouseDownX' : 'mouseDownY'], + halfPointRange = (axis.pointRange || 0) / 2, + extremes = axis.getExtremes(), + newMin = axis.toValue(startPos - mousePos, true) + halfPointRange, + newMax = axis.toValue(startPos + chart[isX ? 'plotWidth' : 'plotHeight'] - mousePos, true) - halfPointRange; + + if (axis.series.length && newMin > mathMin(extremes.dataMin, extremes.min) && newMax < mathMax(extremes.dataMax, extremes.max)) { + axis.setExtremes(newMin, newMax, false, false, { trigger: 'pan' }); + doRedraw = true; + } + + chart[isX ? 'mouseDownX' : 'mouseDownY'] = mousePos; // set new reference for next run + }); + + if (doRedraw) { + chart.redraw(false); + } + css(chart.container, { cursor: 'move' }); + } +}); + +/* + * Extend the Point object with interaction + */ +extend(Point.prototype, { + /** + * Toggle the selection status of a point + * @param {Boolean} selected Whether to select or unselect the point. + * @param {Boolean} accumulate Whether to add to the previous selection. By default, + * this happens if the control key (Cmd on Mac) was pressed during clicking. + */ + select: function (selected, accumulate) { + var point = this, + series = point.series, + chart = series.chart; + + selected = pick(selected, !point.selected); + + // fire the event with the defalut handler + point.firePointEvent(selected ? 'select' : 'unselect', { accumulate: accumulate }, function () { + point.selected = point.options.selected = selected; + series.options.data[inArray(point, series.data)] = point.options; + + point.setState(selected && SELECT_STATE); + + // unselect all other points unless Ctrl or Cmd + click + if (!accumulate) { + each(chart.getSelectedPoints(), function (loopPoint) { + if (loopPoint.selected && loopPoint !== point) { + loopPoint.selected = loopPoint.options.selected = false; + series.options.data[inArray(loopPoint, series.data)] = loopPoint.options; + loopPoint.setState(NORMAL_STATE); + loopPoint.firePointEvent('unselect'); + } + }); + } + }); + }, + + /** + * Runs on mouse over the point + */ + onMouseOver: function (e) { + var point = this, + series = point.series, + chart = series.chart, + tooltip = chart.tooltip, + hoverPoint = chart.hoverPoint; + + // set normal state to previous series + if (hoverPoint && hoverPoint !== point) { + hoverPoint.onMouseOut(); + } + + // trigger the event + point.firePointEvent('mouseOver'); + + // update the tooltip + if (tooltip && (!tooltip.shared || series.noSharedTooltip)) { + tooltip.refresh(point, e); + } + + // hover this + point.setState(HOVER_STATE); + chart.hoverPoint = point; + }, + + /** + * Runs on mouse out from the point + */ + onMouseOut: function () { + var chart = this.series.chart, + hoverPoints = chart.hoverPoints; + + if (!hoverPoints || inArray(this, hoverPoints) === -1) { // #887 + this.firePointEvent('mouseOut'); + + this.setState(); + chart.hoverPoint = null; + } + }, + + /** + * Import events from the series' and point's options. Only do it on + * demand, to save processing time on hovering. + */ + importEvents: function () { + if (!this.hasImportedEvents) { + var point = this, + options = merge(point.series.options.point, point.options), + events = options.events, + eventType; + + point.events = events; + + for (eventType in events) { + addEvent(point, eventType, events[eventType]); + } + this.hasImportedEvents = true; + + } + }, + + /** + * Set the point's state + * @param {String} state + */ + setState: function (state, move) { + var point = this, + plotX = point.plotX, + plotY = point.plotY, + series = point.series, + stateOptions = series.options.states, + markerOptions = defaultPlotOptions[series.type].marker && series.options.marker, + normalDisabled = markerOptions && !markerOptions.enabled, + markerStateOptions = markerOptions && markerOptions.states[state], + stateDisabled = markerStateOptions && markerStateOptions.enabled === false, + stateMarkerGraphic = series.stateMarkerGraphic, + pointMarker = point.marker || {}, + chart = series.chart, + radius, + halo = series.halo, + haloOptions, + newSymbol, + pointAttr; + + state = state || NORMAL_STATE; // empty string + pointAttr = point.pointAttr[state] || series.pointAttr[state]; + + if ( + // already has this state + (state === point.state && !move) || + // selected points don't respond to hover + (point.selected && state !== SELECT_STATE) || + // series' state options is disabled + (stateOptions[state] && stateOptions[state].enabled === false) || + // general point marker's state options is disabled + (state && (stateDisabled || (normalDisabled && markerStateOptions.enabled === false))) || + // individual point marker's state options is disabled + (state && pointMarker.states && pointMarker.states[state] && pointMarker.states[state].enabled === false) // #1610 + + ) { + return; + } + + // apply hover styles to the existing point + if (point.graphic) { + radius = markerOptions && point.graphic.symbolName && pointAttr.r; + point.graphic.attr(merge( + pointAttr, + radius ? { // new symbol attributes (#507, #612) + x: plotX - radius, + y: plotY - radius, + width: 2 * radius, + height: 2 * radius + } : {} + )); + + // Zooming in from a range with no markers to a range with markers + if (stateMarkerGraphic) { + stateMarkerGraphic.hide(); + } + } else { + // if a graphic is not applied to each point in the normal state, create a shared + // graphic for the hover state + if (state && markerStateOptions) { + radius = markerStateOptions.radius; + newSymbol = pointMarker.symbol || series.symbol; + + // If the point has another symbol than the previous one, throw away the + // state marker graphic and force a new one (#1459) + if (stateMarkerGraphic && stateMarkerGraphic.currentSymbol !== newSymbol) { + stateMarkerGraphic = stateMarkerGraphic.destroy(); + } + + // Add a new state marker graphic + if (!stateMarkerGraphic) { + if (newSymbol) { + series.stateMarkerGraphic = stateMarkerGraphic = chart.renderer.symbol( + newSymbol, + plotX - radius, + plotY - radius, + 2 * radius, + 2 * radius + ) + .attr(pointAttr) + .add(series.markerGroup); + stateMarkerGraphic.currentSymbol = newSymbol; + } + + // Move the existing graphic + } else { + stateMarkerGraphic[move ? 'animate' : 'attr']({ // #1054 + x: plotX - radius, + y: plotY - radius + }); + } + } + + if (stateMarkerGraphic) { + stateMarkerGraphic[state && chart.isInsidePlot(plotX, plotY, chart.inverted) ? 'show' : 'hide'](); // #2450 + } + } + + // Show me your halo + haloOptions = stateOptions[state] && stateOptions[state].halo; + if (haloOptions && haloOptions.size) { + if (!halo) { + series.halo = halo = chart.renderer.path() + .add(series.seriesGroup); + } + halo.attr(extend({ + fill: Color(point.color || series.color).setOpacity(haloOptions.opacity).get() + }, haloOptions.attributes))[move ? 'animate' : 'attr']({ + d: point.haloPath(haloOptions.size) + }); + } else if (halo) { + halo.attr({ d: [] }); + } + + point.state = state; + }, + + haloPath: function (size) { + var series = this.series, + chart = series.chart, + plotBox = series.getPlotBox(), + inverted = chart.inverted; + + return chart.renderer.symbols.circle( + plotBox.translateX + (inverted ? series.yAxis.len - this.plotY : this.plotX) - size, + plotBox.translateY + (inverted ? series.xAxis.len - this.plotX : this.plotY) - size, + size * 2, + size * 2 + ); + } +}); + +/* + * Extend the Series object with interaction + */ + +extend(Series.prototype, { + /** + * Series mouse over handler + */ + onMouseOver: function () { + var series = this, + chart = series.chart, + hoverSeries = chart.hoverSeries; + + // set normal state to previous series + if (hoverSeries && hoverSeries !== series) { + hoverSeries.onMouseOut(); + } + + // trigger the event, but to save processing time, + // only if defined + if (series.options.events.mouseOver) { + fireEvent(series, 'mouseOver'); + } + + // hover this + series.setState(HOVER_STATE); + chart.hoverSeries = series; + }, + + /** + * Series mouse out handler + */ + onMouseOut: function () { + // trigger the event only if listeners exist + var series = this, + options = series.options, + chart = series.chart, + tooltip = chart.tooltip, + hoverPoint = chart.hoverPoint; + + // trigger mouse out on the point, which must be in this series + if (hoverPoint) { + hoverPoint.onMouseOut(); + } + + // fire the mouse out event + if (series && options.events.mouseOut) { + fireEvent(series, 'mouseOut'); + } + + + // hide the tooltip + if (tooltip && !options.stickyTracking && (!tooltip.shared || series.noSharedTooltip)) { + tooltip.hide(); + } + + // set normal state + series.setState(); + chart.hoverSeries = null; + }, + + /** + * Set the state of the graph + */ + setState: function (state) { + var series = this, + options = series.options, + graph = series.graph, + graphNeg = series.graphNeg, + stateOptions = options.states, + lineWidth = options.lineWidth, + attribs; + + state = state || NORMAL_STATE; + + if (series.state !== state) { + series.state = state; + + if (stateOptions[state] && stateOptions[state].enabled === false) { + return; + } + + if (state) { + lineWidth = stateOptions[state].lineWidth || lineWidth + 1; + } + + if (graph && !graph.dashstyle) { // hover is turned off for dashed lines in VML + attribs = { + 'stroke-width': lineWidth + }; + // use attr because animate will cause any other animation on the graph to stop + graph.attr(attribs); + if (graphNeg) { + graphNeg.attr(attribs); + } + } + } + }, + + /** + * Set the visibility of the graph + * + * @param vis {Boolean} True to show the series, false to hide. If UNDEFINED, + * the visibility is toggled. + */ + setVisible: function (vis, redraw) { + var series = this, + chart = series.chart, + legendItem = series.legendItem, + showOrHide, + ignoreHiddenSeries = chart.options.chart.ignoreHiddenSeries, + oldVisibility = series.visible; + + // if called without an argument, toggle visibility + series.visible = vis = series.userOptions.visible = vis === UNDEFINED ? !oldVisibility : vis; + showOrHide = vis ? 'show' : 'hide'; + + // show or hide elements + each(['group', 'dataLabelsGroup', 'markerGroup', 'tracker'], function (key) { + if (series[key]) { + series[key][showOrHide](); + } + }); + + + // hide tooltip (#1361) + if (chart.hoverSeries === series) { + series.onMouseOut(); + } + + + if (legendItem) { + chart.legend.colorizeItem(series, vis); + } + + + // rescale or adapt to resized chart + series.isDirty = true; + // in a stack, all other series are affected + if (series.options.stacking) { + each(chart.series, function (otherSeries) { + if (otherSeries.options.stacking && otherSeries.visible) { + otherSeries.isDirty = true; + } + }); + } + + // show or hide linked series + each(series.linkedSeries, function (otherSeries) { + otherSeries.setVisible(vis, false); + }); + + if (ignoreHiddenSeries) { + chart.isDirtyBox = true; + } + if (redraw !== false) { + chart.redraw(); + } + + fireEvent(series, showOrHide); + }, + + /** + * Memorize tooltip texts and positions + */ + setTooltipPoints: function (renew) { + var series = this, + points = [], + pointsLength, + low, + high, + xAxis = series.xAxis, + xExtremes = xAxis && xAxis.getExtremes(), + axisLength = xAxis ? (xAxis.tooltipLen || xAxis.len) : series.chart.plotSizeX, // tooltipLen and tooltipPosName used in polar + point, + pointX, + nextPoint, + i, + tooltipPoints = []; // a lookup array for each pixel in the x dimension + + // don't waste resources if tracker is disabled + if (series.options.enableMouseTracking === false || series.singularTooltips) { + return; + } + + // renew + if (renew) { + series.tooltipPoints = null; + } + + // concat segments to overcome null values + each(series.segments || series.points, function (segment) { + points = points.concat(segment); + }); + + // Reverse the points in case the X axis is reversed + if (xAxis && xAxis.reversed) { + points = points.reverse(); + } + + // Polar needs additional shaping + if (series.orderTooltipPoints) { + series.orderTooltipPoints(points); + } + + // Assign each pixel position to the nearest point + pointsLength = points.length; + for (i = 0; i < pointsLength; i++) { + point = points[i]; + pointX = point.x; + if (pointX >= xExtremes.min && pointX <= xExtremes.max) { // #1149 + nextPoint = points[i + 1]; + + // Set this range's low to the last range's high plus one + low = high === UNDEFINED ? 0 : high + 1; + // Now find the new high + high = points[i + 1] ? + mathMin(mathMax(0, mathFloor( // #2070 + (point.clientX + (nextPoint ? (nextPoint.wrappedClientX || nextPoint.clientX) : axisLength)) / 2 + )), axisLength) : + axisLength; + + while (low >= 0 && low <= high) { + tooltipPoints[low++] = point; + } + } + } + series.tooltipPoints = tooltipPoints; + }, + + /** + * Show the graph + */ + show: function () { + this.setVisible(true); + }, + + /** + * Hide the graph + */ + hide: function () { + this.setVisible(false); + }, + + + /** + * Set the selected state of the graph + * + * @param selected {Boolean} True to select the series, false to unselect. If + * UNDEFINED, the selection state is toggled. + */ + select: function (selected) { + var series = this; + // if called without an argument, toggle + series.selected = selected = (selected === UNDEFINED) ? !series.selected : selected; + + if (series.checkbox) { + series.checkbox.checked = selected; + } + + fireEvent(series, selected ? 'select' : 'unselect'); + }, + + drawTracker: TrackerMixin.drawTrackerGraph +}); +// global variables +extend(Highcharts, { + + // Constructors + Axis: Axis, + Chart: Chart, + Color: Color, + Point: Point, + Tick: Tick, + Renderer: Renderer, + Series: Series, + SVGElement: SVGElement, + SVGRenderer: SVGRenderer, + + // Various + arrayMin: arrayMin, + arrayMax: arrayMax, + charts: charts, + dateFormat: dateFormat, + format: format, + pathAnim: pathAnim, + getOptions: getOptions, + hasBidiBug: hasBidiBug, + isTouchDevice: isTouchDevice, + numberFormat: numberFormat, + seriesTypes: seriesTypes, + setOptions: setOptions, + addEvent: addEvent, + removeEvent: removeEvent, + createElement: createElement, + discardElement: discardElement, + css: css, + each: each, + extend: extend, + map: map, + merge: merge, + pick: pick, + splat: splat, + extendClass: extendClass, + pInt: pInt, + wrap: wrap, + svg: hasSVG, + canvas: useCanVG, + vml: !hasSVG && !useCanVG, + product: PRODUCT, + version: VERSION +}); + +}()); diff --git a/ABC_Score/assets/javascripts/jquery.filtertable.min.js b/ABC_Score/assets/javascripts/jquery.filtertable.min.js new file mode 100755 index 00000000..b3d4d392 --- /dev/null +++ b/ABC_Score/assets/javascripts/jquery.filtertable.min.js @@ -0,0 +1,13 @@ +/** + * jquery.filterTable + * + * This plugin will add a search filter to tables. When typing in the filter, + * any rows that do not contain the filter will be hidden. + * + * Utilizes bindWithDelay() if available. https://github.com/bgrins/bindWithDelay + * + * @version v1.5.6 + * @author Sunny Walker, swalker@hawaii.edu + * @license MIT + */ +!function($){var e=$.fn.jquery.split("."),t=parseFloat(e[0]),n=parseFloat(e[1]);2>t&&8>n?($.expr[":"].filterTableFind=function(e,t,n){return $(e).text().toUpperCase().indexOf(n[3].toUpperCase().replace(/"""/g,'"').replace(/"\\"/g,"\\"))>=0},$.expr[":"].filterTableFindAny=function(e,t,n){var i=n[3].split(/[\s,]/),r=[];return $.each(i,function(e,t){var n=t.replace(/^\s+|\s$/g,"");n&&r.push(n)}),r.length?function(e){var t=!1;return $.each(r,function(n,i){return $(e).text().toUpperCase().indexOf(i.toUpperCase().replace(/"""/g,'"').replace(/"\\"/g,"\\"))>=0?(t=!0,!1):void 0}),t}:!1},$.expr[":"].filterTableFindAll=function(e,t,n){var i=n[3].split(/[\s,]/),r=[];return $.each(i,function(e,t){var n=t.replace(/^\s+|\s$/g,"");n&&r.push(n)}),r.length?function(e){var t=0;return $.each(r,function(n,i){$(e).text().toUpperCase().indexOf(i.toUpperCase().replace(/"""/g,'"').replace(/"\\"/g,"\\"))>=0&&t++}),t===r.length}:!1}):($.expr[":"].filterTableFind=jQuery.expr.createPseudo(function(e){return function(t){return $(t).text().toUpperCase().indexOf(e.toUpperCase().replace(/"""/g,'"').replace(/"\\"/g,"\\"))>=0}}),$.expr[":"].filterTableFindAny=jQuery.expr.createPseudo(function(e){var t=e.split(/[\s,]/),n=[];return $.each(t,function(e,t){var i=t.replace(/^\s+|\s$/g,"");i&&n.push(i)}),n.length?function(e){var t=!1;return $.each(n,function(n,i){return $(e).text().toUpperCase().indexOf(i.toUpperCase().replace(/"""/g,'"').replace(/"\\"/g,"\\"))>=0?(t=!0,!1):void 0}),t}:!1}),$.expr[":"].filterTableFindAll=jQuery.expr.createPseudo(function(e){var t=e.split(/[\s,]/),n=[];return $.each(t,function(e,t){var i=t.replace(/^\s+|\s$/g,"");i&&n.push(i)}),n.length?function(e){var t=0;return $.each(n,function(n,i){$(e).text().toUpperCase().indexOf(i.toUpperCase().replace(/"""/g,'"').replace(/"\\"/g,"\\"))>=0&&t++}),t===n.length}:!1})),$.fn.filterTable=function(e){var t={autofocus:!1,callback:null,containerClass:"filter-table",containerTag:"p",filterExpression:"filterTableFind",hideTFootOnFilter:!1,highlightClass:"alt",ignoreClass:"",ignoreColumns:[],inputSelector:null,inputName:"",inputType:"search",label:"Filter:",minChars:1,minRows:8,placeholder:"search this table",preventReturnKey:!0,quickList:[],quickListClass:"quick",quickListGroupTag:"",quickListTag:"a",visibleClass:"visible"},n=function(e){return e.replace(/&/g,"&").replace(/"/g,""").replace(//g,">")},i=$.extend({},t,e),r=function(e,t){var n=e.find("tbody");if(""===t||t.length0&&(0===i.minRows||i.minRows>0&&t.find("tr").length>=i.minRows)&&!e.prev().hasClass(i.containerClass)&&(i.inputSelector&&1===$(i.inputSelector).length?(s=$(i.inputSelector),a=s.parent(),o=!1):(a=$("<"+i.containerTag+" />"),""!==i.containerClass&&a.addClass(i.containerClass),a.prepend(i.label+" "),s=$(''),i.preventReturnKey&&s.on("keydown",function(e){return 13===(e.keyCode||e.which)?(e.preventDefault(),!1):void 0})),i.autofocus&&s.attr("autofocus",!0),$.fn.bindWithDelay?s.bindWithDelay("keyup",function(){r(e,$(this).val())},200):s.bind("keyup",function(){r(e,$(this).val())}),s.bind("click search input paste blur",function(){r(e,$(this).val())}),o&&a.append(s),i.quickList.length>0&&(l=i.quickListGroupTag?$("<"+i.quickListGroupTag+" />"):a,$.each(i.quickList,function(e,t){var r=$("<"+i.quickListTag+' class="'+i.quickListClass+'" />');r.text(n(t)),"A"===r[0].nodeName&&r.attr("href","#"),r.bind("click",function(e){e.preventDefault(),s.val(t).focus().trigger("click")}),l.append(r)}),l!==a&&a.append(l)),o&&e.before(a))})}}(jQuery); \ No newline at end of file diff --git a/ABC_Score/assets/javascripts/jquery.min.js b/ABC_Score/assets/javascripts/jquery.min.js new file mode 100755 index 00000000..4c5be4c0 --- /dev/null +++ b/ABC_Score/assets/javascripts/jquery.min.js @@ -0,0 +1,4 @@ +/*! jQuery v3.1.1 | (c) jQuery Foundation | jquery.org/license */ +!function(a,b){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=a.document?b(a,!0):function(a){if(!a.document)throw new Error("jQuery requires a window with a document");return b(a)}:b(a)}("undefined"!=typeof window?window:this,function(a,b){"use strict";var c=[],d=a.document,e=Object.getPrototypeOf,f=c.slice,g=c.concat,h=c.push,i=c.indexOf,j={},k=j.toString,l=j.hasOwnProperty,m=l.toString,n=m.call(Object),o={};function p(a,b){b=b||d;var c=b.createElement("script");c.text=a,b.head.appendChild(c).parentNode.removeChild(c)}var q="3.1.1",r=function(a,b){return new r.fn.init(a,b)},s=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,t=/^-ms-/,u=/-([a-z])/g,v=function(a,b){return b.toUpperCase()};r.fn=r.prototype={jquery:q,constructor:r,length:0,toArray:function(){return f.call(this)},get:function(a){return null==a?f.call(this):a<0?this[a+this.length]:this[a]},pushStack:function(a){var b=r.merge(this.constructor(),a);return b.prevObject=this,b},each:function(a){return r.each(this,a)},map:function(a){return this.pushStack(r.map(this,function(b,c){return a.call(b,c,b)}))},slice:function(){return this.pushStack(f.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(a){var b=this.length,c=+a+(a<0?b:0);return this.pushStack(c>=0&&c0&&b-1 in a)}var x=function(a){var b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u="sizzle"+1*new Date,v=a.document,w=0,x=0,y=ha(),z=ha(),A=ha(),B=function(a,b){return a===b&&(l=!0),0},C={}.hasOwnProperty,D=[],E=D.pop,F=D.push,G=D.push,H=D.slice,I=function(a,b){for(var c=0,d=a.length;c+~]|"+K+")"+K+"*"),S=new RegExp("="+K+"*([^\\]'\"]*?)"+K+"*\\]","g"),T=new RegExp(N),U=new RegExp("^"+L+"$"),V={ID:new RegExp("^#("+L+")"),CLASS:new RegExp("^\\.("+L+")"),TAG:new RegExp("^("+L+"|[*])"),ATTR:new RegExp("^"+M),PSEUDO:new RegExp("^"+N),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+K+"*(even|odd|(([+-]|)(\\d*)n|)"+K+"*(?:([+-]|)"+K+"*(\\d+)|))"+K+"*\\)|)","i"),bool:new RegExp("^(?:"+J+")$","i"),needsContext:new RegExp("^"+K+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+K+"*((?:-\\d)?\\d*)"+K+"*\\)|)(?=[^-]|$)","i")},W=/^(?:input|select|textarea|button)$/i,X=/^h\d$/i,Y=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,$=/[+~]/,_=new RegExp("\\\\([\\da-f]{1,6}"+K+"?|("+K+")|.)","ig"),aa=function(a,b,c){var d="0x"+b-65536;return d!==d||c?b:d<0?String.fromCharCode(d+65536):String.fromCharCode(d>>10|55296,1023&d|56320)},ba=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,ca=function(a,b){return b?"\0"===a?"\ufffd":a.slice(0,-1)+"\\"+a.charCodeAt(a.length-1).toString(16)+" ":"\\"+a},da=function(){m()},ea=ta(function(a){return a.disabled===!0&&("form"in a||"label"in a)},{dir:"parentNode",next:"legend"});try{G.apply(D=H.call(v.childNodes),v.childNodes),D[v.childNodes.length].nodeType}catch(fa){G={apply:D.length?function(a,b){F.apply(a,H.call(b))}:function(a,b){var c=a.length,d=0;while(a[c++]=b[d++]);a.length=c-1}}}function ga(a,b,d,e){var f,h,j,k,l,o,r,s=b&&b.ownerDocument,w=b?b.nodeType:9;if(d=d||[],"string"!=typeof a||!a||1!==w&&9!==w&&11!==w)return d;if(!e&&((b?b.ownerDocument||b:v)!==n&&m(b),b=b||n,p)){if(11!==w&&(l=Z.exec(a)))if(f=l[1]){if(9===w){if(!(j=b.getElementById(f)))return d;if(j.id===f)return d.push(j),d}else if(s&&(j=s.getElementById(f))&&t(b,j)&&j.id===f)return d.push(j),d}else{if(l[2])return G.apply(d,b.getElementsByTagName(a)),d;if((f=l[3])&&c.getElementsByClassName&&b.getElementsByClassName)return G.apply(d,b.getElementsByClassName(f)),d}if(c.qsa&&!A[a+" "]&&(!q||!q.test(a))){if(1!==w)s=b,r=a;else if("object"!==b.nodeName.toLowerCase()){(k=b.getAttribute("id"))?k=k.replace(ba,ca):b.setAttribute("id",k=u),o=g(a),h=o.length;while(h--)o[h]="#"+k+" "+sa(o[h]);r=o.join(","),s=$.test(a)&&qa(b.parentNode)||b}if(r)try{return G.apply(d,s.querySelectorAll(r)),d}catch(x){}finally{k===u&&b.removeAttribute("id")}}}return i(a.replace(P,"$1"),b,d,e)}function ha(){var a=[];function b(c,e){return a.push(c+" ")>d.cacheLength&&delete b[a.shift()],b[c+" "]=e}return b}function ia(a){return a[u]=!0,a}function ja(a){var b=n.createElement("fieldset");try{return!!a(b)}catch(c){return!1}finally{b.parentNode&&b.parentNode.removeChild(b),b=null}}function ka(a,b){var c=a.split("|"),e=c.length;while(e--)d.attrHandle[c[e]]=b}function la(a,b){var c=b&&a,d=c&&1===a.nodeType&&1===b.nodeType&&a.sourceIndex-b.sourceIndex;if(d)return d;if(c)while(c=c.nextSibling)if(c===b)return-1;return a?1:-1}function ma(a){return function(b){var c=b.nodeName.toLowerCase();return"input"===c&&b.type===a}}function na(a){return function(b){var c=b.nodeName.toLowerCase();return("input"===c||"button"===c)&&b.type===a}}function oa(a){return function(b){return"form"in b?b.parentNode&&b.disabled===!1?"label"in b?"label"in b.parentNode?b.parentNode.disabled===a:b.disabled===a:b.isDisabled===a||b.isDisabled!==!a&&ea(b)===a:b.disabled===a:"label"in b&&b.disabled===a}}function pa(a){return ia(function(b){return b=+b,ia(function(c,d){var e,f=a([],c.length,b),g=f.length;while(g--)c[e=f[g]]&&(c[e]=!(d[e]=c[e]))})})}function qa(a){return a&&"undefined"!=typeof a.getElementsByTagName&&a}c=ga.support={},f=ga.isXML=function(a){var b=a&&(a.ownerDocument||a).documentElement;return!!b&&"HTML"!==b.nodeName},m=ga.setDocument=function(a){var b,e,g=a?a.ownerDocument||a:v;return g!==n&&9===g.nodeType&&g.documentElement?(n=g,o=n.documentElement,p=!f(n),v!==n&&(e=n.defaultView)&&e.top!==e&&(e.addEventListener?e.addEventListener("unload",da,!1):e.attachEvent&&e.attachEvent("onunload",da)),c.attributes=ja(function(a){return a.className="i",!a.getAttribute("className")}),c.getElementsByTagName=ja(function(a){return a.appendChild(n.createComment("")),!a.getElementsByTagName("*").length}),c.getElementsByClassName=Y.test(n.getElementsByClassName),c.getById=ja(function(a){return o.appendChild(a).id=u,!n.getElementsByName||!n.getElementsByName(u).length}),c.getById?(d.filter.ID=function(a){var b=a.replace(_,aa);return function(a){return a.getAttribute("id")===b}},d.find.ID=function(a,b){if("undefined"!=typeof b.getElementById&&p){var c=b.getElementById(a);return c?[c]:[]}}):(d.filter.ID=function(a){var b=a.replace(_,aa);return function(a){var c="undefined"!=typeof a.getAttributeNode&&a.getAttributeNode("id");return c&&c.value===b}},d.find.ID=function(a,b){if("undefined"!=typeof b.getElementById&&p){var c,d,e,f=b.getElementById(a);if(f){if(c=f.getAttributeNode("id"),c&&c.value===a)return[f];e=b.getElementsByName(a),d=0;while(f=e[d++])if(c=f.getAttributeNode("id"),c&&c.value===a)return[f]}return[]}}),d.find.TAG=c.getElementsByTagName?function(a,b){return"undefined"!=typeof b.getElementsByTagName?b.getElementsByTagName(a):c.qsa?b.querySelectorAll(a):void 0}:function(a,b){var c,d=[],e=0,f=b.getElementsByTagName(a);if("*"===a){while(c=f[e++])1===c.nodeType&&d.push(c);return d}return f},d.find.CLASS=c.getElementsByClassName&&function(a,b){if("undefined"!=typeof b.getElementsByClassName&&p)return b.getElementsByClassName(a)},r=[],q=[],(c.qsa=Y.test(n.querySelectorAll))&&(ja(function(a){o.appendChild(a).innerHTML="
",a.querySelectorAll("[msallowcapture^='']").length&&q.push("[*^$]="+K+"*(?:''|\"\")"),a.querySelectorAll("[selected]").length||q.push("\\["+K+"*(?:value|"+J+")"),a.querySelectorAll("[id~="+u+"-]").length||q.push("~="),a.querySelectorAll(":checked").length||q.push(":checked"),a.querySelectorAll("a#"+u+"+*").length||q.push(".#.+[+~]")}),ja(function(a){a.innerHTML="";var b=n.createElement("input");b.setAttribute("type","hidden"),a.appendChild(b).setAttribute("name","D"),a.querySelectorAll("[name=d]").length&&q.push("name"+K+"*[*^$|!~]?="),2!==a.querySelectorAll(":enabled").length&&q.push(":enabled",":disabled"),o.appendChild(a).disabled=!0,2!==a.querySelectorAll(":disabled").length&&q.push(":enabled",":disabled"),a.querySelectorAll("*,:x"),q.push(",.*:")})),(c.matchesSelector=Y.test(s=o.matches||o.webkitMatchesSelector||o.mozMatchesSelector||o.oMatchesSelector||o.msMatchesSelector))&&ja(function(a){c.disconnectedMatch=s.call(a,"*"),s.call(a,"[s!='']:x"),r.push("!=",N)}),q=q.length&&new RegExp(q.join("|")),r=r.length&&new RegExp(r.join("|")),b=Y.test(o.compareDocumentPosition),t=b||Y.test(o.contains)?function(a,b){var c=9===a.nodeType?a.documentElement:a,d=b&&b.parentNode;return a===d||!(!d||1!==d.nodeType||!(c.contains?c.contains(d):a.compareDocumentPosition&&16&a.compareDocumentPosition(d)))}:function(a,b){if(b)while(b=b.parentNode)if(b===a)return!0;return!1},B=b?function(a,b){if(a===b)return l=!0,0;var d=!a.compareDocumentPosition-!b.compareDocumentPosition;return d?d:(d=(a.ownerDocument||a)===(b.ownerDocument||b)?a.compareDocumentPosition(b):1,1&d||!c.sortDetached&&b.compareDocumentPosition(a)===d?a===n||a.ownerDocument===v&&t(v,a)?-1:b===n||b.ownerDocument===v&&t(v,b)?1:k?I(k,a)-I(k,b):0:4&d?-1:1)}:function(a,b){if(a===b)return l=!0,0;var c,d=0,e=a.parentNode,f=b.parentNode,g=[a],h=[b];if(!e||!f)return a===n?-1:b===n?1:e?-1:f?1:k?I(k,a)-I(k,b):0;if(e===f)return la(a,b);c=a;while(c=c.parentNode)g.unshift(c);c=b;while(c=c.parentNode)h.unshift(c);while(g[d]===h[d])d++;return d?la(g[d],h[d]):g[d]===v?-1:h[d]===v?1:0},n):n},ga.matches=function(a,b){return ga(a,null,null,b)},ga.matchesSelector=function(a,b){if((a.ownerDocument||a)!==n&&m(a),b=b.replace(S,"='$1']"),c.matchesSelector&&p&&!A[b+" "]&&(!r||!r.test(b))&&(!q||!q.test(b)))try{var d=s.call(a,b);if(d||c.disconnectedMatch||a.document&&11!==a.document.nodeType)return d}catch(e){}return ga(b,n,null,[a]).length>0},ga.contains=function(a,b){return(a.ownerDocument||a)!==n&&m(a),t(a,b)},ga.attr=function(a,b){(a.ownerDocument||a)!==n&&m(a);var e=d.attrHandle[b.toLowerCase()],f=e&&C.call(d.attrHandle,b.toLowerCase())?e(a,b,!p):void 0;return void 0!==f?f:c.attributes||!p?a.getAttribute(b):(f=a.getAttributeNode(b))&&f.specified?f.value:null},ga.escape=function(a){return(a+"").replace(ba,ca)},ga.error=function(a){throw new Error("Syntax error, unrecognized expression: "+a)},ga.uniqueSort=function(a){var b,d=[],e=0,f=0;if(l=!c.detectDuplicates,k=!c.sortStable&&a.slice(0),a.sort(B),l){while(b=a[f++])b===a[f]&&(e=d.push(f));while(e--)a.splice(d[e],1)}return k=null,a},e=ga.getText=function(a){var b,c="",d=0,f=a.nodeType;if(f){if(1===f||9===f||11===f){if("string"==typeof a.textContent)return a.textContent;for(a=a.firstChild;a;a=a.nextSibling)c+=e(a)}else if(3===f||4===f)return a.nodeValue}else while(b=a[d++])c+=e(b);return c},d=ga.selectors={cacheLength:50,createPseudo:ia,match:V,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(a){return a[1]=a[1].replace(_,aa),a[3]=(a[3]||a[4]||a[5]||"").replace(_,aa),"~="===a[2]&&(a[3]=" "+a[3]+" "),a.slice(0,4)},CHILD:function(a){return a[1]=a[1].toLowerCase(),"nth"===a[1].slice(0,3)?(a[3]||ga.error(a[0]),a[4]=+(a[4]?a[5]+(a[6]||1):2*("even"===a[3]||"odd"===a[3])),a[5]=+(a[7]+a[8]||"odd"===a[3])):a[3]&&ga.error(a[0]),a},PSEUDO:function(a){var b,c=!a[6]&&a[2];return V.CHILD.test(a[0])?null:(a[3]?a[2]=a[4]||a[5]||"":c&&T.test(c)&&(b=g(c,!0))&&(b=c.indexOf(")",c.length-b)-c.length)&&(a[0]=a[0].slice(0,b),a[2]=c.slice(0,b)),a.slice(0,3))}},filter:{TAG:function(a){var b=a.replace(_,aa).toLowerCase();return"*"===a?function(){return!0}:function(a){return a.nodeName&&a.nodeName.toLowerCase()===b}},CLASS:function(a){var b=y[a+" "];return b||(b=new RegExp("(^|"+K+")"+a+"("+K+"|$)"))&&y(a,function(a){return b.test("string"==typeof a.className&&a.className||"undefined"!=typeof a.getAttribute&&a.getAttribute("class")||"")})},ATTR:function(a,b,c){return function(d){var e=ga.attr(d,a);return null==e?"!="===b:!b||(e+="","="===b?e===c:"!="===b?e!==c:"^="===b?c&&0===e.indexOf(c):"*="===b?c&&e.indexOf(c)>-1:"$="===b?c&&e.slice(-c.length)===c:"~="===b?(" "+e.replace(O," ")+" ").indexOf(c)>-1:"|="===b&&(e===c||e.slice(0,c.length+1)===c+"-"))}},CHILD:function(a,b,c,d,e){var f="nth"!==a.slice(0,3),g="last"!==a.slice(-4),h="of-type"===b;return 1===d&&0===e?function(a){return!!a.parentNode}:function(b,c,i){var j,k,l,m,n,o,p=f!==g?"nextSibling":"previousSibling",q=b.parentNode,r=h&&b.nodeName.toLowerCase(),s=!i&&!h,t=!1;if(q){if(f){while(p){m=b;while(m=m[p])if(h?m.nodeName.toLowerCase()===r:1===m.nodeType)return!1;o=p="only"===a&&!o&&"nextSibling"}return!0}if(o=[g?q.firstChild:q.lastChild],g&&s){m=q,l=m[u]||(m[u]={}),k=l[m.uniqueID]||(l[m.uniqueID]={}),j=k[a]||[],n=j[0]===w&&j[1],t=n&&j[2],m=n&&q.childNodes[n];while(m=++n&&m&&m[p]||(t=n=0)||o.pop())if(1===m.nodeType&&++t&&m===b){k[a]=[w,n,t];break}}else if(s&&(m=b,l=m[u]||(m[u]={}),k=l[m.uniqueID]||(l[m.uniqueID]={}),j=k[a]||[],n=j[0]===w&&j[1],t=n),t===!1)while(m=++n&&m&&m[p]||(t=n=0)||o.pop())if((h?m.nodeName.toLowerCase()===r:1===m.nodeType)&&++t&&(s&&(l=m[u]||(m[u]={}),k=l[m.uniqueID]||(l[m.uniqueID]={}),k[a]=[w,t]),m===b))break;return t-=e,t===d||t%d===0&&t/d>=0}}},PSEUDO:function(a,b){var c,e=d.pseudos[a]||d.setFilters[a.toLowerCase()]||ga.error("unsupported pseudo: "+a);return e[u]?e(b):e.length>1?(c=[a,a,"",b],d.setFilters.hasOwnProperty(a.toLowerCase())?ia(function(a,c){var d,f=e(a,b),g=f.length;while(g--)d=I(a,f[g]),a[d]=!(c[d]=f[g])}):function(a){return e(a,0,c)}):e}},pseudos:{not:ia(function(a){var b=[],c=[],d=h(a.replace(P,"$1"));return d[u]?ia(function(a,b,c,e){var f,g=d(a,null,e,[]),h=a.length;while(h--)(f=g[h])&&(a[h]=!(b[h]=f))}):function(a,e,f){return b[0]=a,d(b,null,f,c),b[0]=null,!c.pop()}}),has:ia(function(a){return function(b){return ga(a,b).length>0}}),contains:ia(function(a){return a=a.replace(_,aa),function(b){return(b.textContent||b.innerText||e(b)).indexOf(a)>-1}}),lang:ia(function(a){return U.test(a||"")||ga.error("unsupported lang: "+a),a=a.replace(_,aa).toLowerCase(),function(b){var c;do if(c=p?b.lang:b.getAttribute("xml:lang")||b.getAttribute("lang"))return c=c.toLowerCase(),c===a||0===c.indexOf(a+"-");while((b=b.parentNode)&&1===b.nodeType);return!1}}),target:function(b){var c=a.location&&a.location.hash;return c&&c.slice(1)===b.id},root:function(a){return a===o},focus:function(a){return a===n.activeElement&&(!n.hasFocus||n.hasFocus())&&!!(a.type||a.href||~a.tabIndex)},enabled:oa(!1),disabled:oa(!0),checked:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&!!a.checked||"option"===b&&!!a.selected},selected:function(a){return a.parentNode&&a.parentNode.selectedIndex,a.selected===!0},empty:function(a){for(a=a.firstChild;a;a=a.nextSibling)if(a.nodeType<6)return!1;return!0},parent:function(a){return!d.pseudos.empty(a)},header:function(a){return X.test(a.nodeName)},input:function(a){return W.test(a.nodeName)},button:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&"button"===a.type||"button"===b},text:function(a){var b;return"input"===a.nodeName.toLowerCase()&&"text"===a.type&&(null==(b=a.getAttribute("type"))||"text"===b.toLowerCase())},first:pa(function(){return[0]}),last:pa(function(a,b){return[b-1]}),eq:pa(function(a,b,c){return[c<0?c+b:c]}),even:pa(function(a,b){for(var c=0;c=0;)a.push(d);return a}),gt:pa(function(a,b,c){for(var d=c<0?c+b:c;++d1?function(b,c,d){var e=a.length;while(e--)if(!a[e](b,c,d))return!1;return!0}:a[0]}function va(a,b,c){for(var d=0,e=b.length;d-1&&(f[j]=!(g[j]=l))}}else r=wa(r===g?r.splice(o,r.length):r),e?e(null,g,r,i):G.apply(g,r)})}function ya(a){for(var b,c,e,f=a.length,g=d.relative[a[0].type],h=g||d.relative[" "],i=g?1:0,k=ta(function(a){return a===b},h,!0),l=ta(function(a){return I(b,a)>-1},h,!0),m=[function(a,c,d){var e=!g&&(d||c!==j)||((b=c).nodeType?k(a,c,d):l(a,c,d));return b=null,e}];i1&&ua(m),i>1&&sa(a.slice(0,i-1).concat({value:" "===a[i-2].type?"*":""})).replace(P,"$1"),c,i0,e=a.length>0,f=function(f,g,h,i,k){var l,o,q,r=0,s="0",t=f&&[],u=[],v=j,x=f||e&&d.find.TAG("*",k),y=w+=null==v?1:Math.random()||.1,z=x.length;for(k&&(j=g===n||g||k);s!==z&&null!=(l=x[s]);s++){if(e&&l){o=0,g||l.ownerDocument===n||(m(l),h=!p);while(q=a[o++])if(q(l,g||n,h)){i.push(l);break}k&&(w=y)}c&&((l=!q&&l)&&r--,f&&t.push(l))}if(r+=s,c&&s!==r){o=0;while(q=b[o++])q(t,u,g,h);if(f){if(r>0)while(s--)t[s]||u[s]||(u[s]=E.call(i));u=wa(u)}G.apply(i,u),k&&!f&&u.length>0&&r+b.length>1&&ga.uniqueSort(i)}return k&&(w=y,j=v),t};return c?ia(f):f}return h=ga.compile=function(a,b){var c,d=[],e=[],f=A[a+" "];if(!f){b||(b=g(a)),c=b.length;while(c--)f=ya(b[c]),f[u]?d.push(f):e.push(f);f=A(a,za(e,d)),f.selector=a}return f},i=ga.select=function(a,b,c,e){var f,i,j,k,l,m="function"==typeof a&&a,n=!e&&g(a=m.selector||a);if(c=c||[],1===n.length){if(i=n[0]=n[0].slice(0),i.length>2&&"ID"===(j=i[0]).type&&9===b.nodeType&&p&&d.relative[i[1].type]){if(b=(d.find.ID(j.matches[0].replace(_,aa),b)||[])[0],!b)return c;m&&(b=b.parentNode),a=a.slice(i.shift().value.length)}f=V.needsContext.test(a)?0:i.length;while(f--){if(j=i[f],d.relative[k=j.type])break;if((l=d.find[k])&&(e=l(j.matches[0].replace(_,aa),$.test(i[0].type)&&qa(b.parentNode)||b))){if(i.splice(f,1),a=e.length&&sa(i),!a)return G.apply(c,e),c;break}}}return(m||h(a,n))(e,b,!p,c,!b||$.test(a)&&qa(b.parentNode)||b),c},c.sortStable=u.split("").sort(B).join("")===u,c.detectDuplicates=!!l,m(),c.sortDetached=ja(function(a){return 1&a.compareDocumentPosition(n.createElement("fieldset"))}),ja(function(a){return a.innerHTML="","#"===a.firstChild.getAttribute("href")})||ka("type|href|height|width",function(a,b,c){if(!c)return a.getAttribute(b,"type"===b.toLowerCase()?1:2)}),c.attributes&&ja(function(a){return a.innerHTML="",a.firstChild.setAttribute("value",""),""===a.firstChild.getAttribute("value")})||ka("value",function(a,b,c){if(!c&&"input"===a.nodeName.toLowerCase())return a.defaultValue}),ja(function(a){return null==a.getAttribute("disabled")})||ka(J,function(a,b,c){var d;if(!c)return a[b]===!0?b.toLowerCase():(d=a.getAttributeNode(b))&&d.specified?d.value:null}),ga}(a);r.find=x,r.expr=x.selectors,r.expr[":"]=r.expr.pseudos,r.uniqueSort=r.unique=x.uniqueSort,r.text=x.getText,r.isXMLDoc=x.isXML,r.contains=x.contains,r.escapeSelector=x.escape;var y=function(a,b,c){var d=[],e=void 0!==c;while((a=a[b])&&9!==a.nodeType)if(1===a.nodeType){if(e&&r(a).is(c))break;d.push(a)}return d},z=function(a,b){for(var c=[];a;a=a.nextSibling)1===a.nodeType&&a!==b&&c.push(a);return c},A=r.expr.match.needsContext,B=/^<([a-z][^\/\0>:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i,C=/^.[^:#\[\.,]*$/;function D(a,b,c){return r.isFunction(b)?r.grep(a,function(a,d){return!!b.call(a,d,a)!==c}):b.nodeType?r.grep(a,function(a){return a===b!==c}):"string"!=typeof b?r.grep(a,function(a){return i.call(b,a)>-1!==c}):C.test(b)?r.filter(b,a,c):(b=r.filter(b,a),r.grep(a,function(a){return i.call(b,a)>-1!==c&&1===a.nodeType}))}r.filter=function(a,b,c){var d=b[0];return c&&(a=":not("+a+")"),1===b.length&&1===d.nodeType?r.find.matchesSelector(d,a)?[d]:[]:r.find.matches(a,r.grep(b,function(a){return 1===a.nodeType}))},r.fn.extend({find:function(a){var b,c,d=this.length,e=this;if("string"!=typeof a)return this.pushStack(r(a).filter(function(){for(b=0;b1?r.uniqueSort(c):c},filter:function(a){return this.pushStack(D(this,a||[],!1))},not:function(a){return this.pushStack(D(this,a||[],!0))},is:function(a){return!!D(this,"string"==typeof a&&A.test(a)?r(a):a||[],!1).length}});var E,F=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/,G=r.fn.init=function(a,b,c){var e,f;if(!a)return this;if(c=c||E,"string"==typeof a){if(e="<"===a[0]&&">"===a[a.length-1]&&a.length>=3?[null,a,null]:F.exec(a),!e||!e[1]&&b)return!b||b.jquery?(b||c).find(a):this.constructor(b).find(a);if(e[1]){if(b=b instanceof r?b[0]:b,r.merge(this,r.parseHTML(e[1],b&&b.nodeType?b.ownerDocument||b:d,!0)),B.test(e[1])&&r.isPlainObject(b))for(e in b)r.isFunction(this[e])?this[e](b[e]):this.attr(e,b[e]);return this}return f=d.getElementById(e[2]),f&&(this[0]=f,this.length=1),this}return a.nodeType?(this[0]=a,this.length=1,this):r.isFunction(a)?void 0!==c.ready?c.ready(a):a(r):r.makeArray(a,this)};G.prototype=r.fn,E=r(d);var H=/^(?:parents|prev(?:Until|All))/,I={children:!0,contents:!0,next:!0,prev:!0};r.fn.extend({has:function(a){var b=r(a,this),c=b.length;return this.filter(function(){for(var a=0;a-1:1===c.nodeType&&r.find.matchesSelector(c,a))){f.push(c);break}return this.pushStack(f.length>1?r.uniqueSort(f):f)},index:function(a){return a?"string"==typeof a?i.call(r(a),this[0]):i.call(this,a.jquery?a[0]:a):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(a,b){return this.pushStack(r.uniqueSort(r.merge(this.get(),r(a,b))))},addBack:function(a){return this.add(null==a?this.prevObject:this.prevObject.filter(a))}});function J(a,b){while((a=a[b])&&1!==a.nodeType);return a}r.each({parent:function(a){var b=a.parentNode;return b&&11!==b.nodeType?b:null},parents:function(a){return y(a,"parentNode")},parentsUntil:function(a,b,c){return y(a,"parentNode",c)},next:function(a){return J(a,"nextSibling")},prev:function(a){return J(a,"previousSibling")},nextAll:function(a){return y(a,"nextSibling")},prevAll:function(a){return y(a,"previousSibling")},nextUntil:function(a,b,c){return y(a,"nextSibling",c)},prevUntil:function(a,b,c){return y(a,"previousSibling",c)},siblings:function(a){return z((a.parentNode||{}).firstChild,a)},children:function(a){return z(a.firstChild)},contents:function(a){return a.contentDocument||r.merge([],a.childNodes)}},function(a,b){r.fn[a]=function(c,d){var e=r.map(this,b,c);return"Until"!==a.slice(-5)&&(d=c),d&&"string"==typeof d&&(e=r.filter(d,e)),this.length>1&&(I[a]||r.uniqueSort(e),H.test(a)&&e.reverse()),this.pushStack(e)}});var K=/[^\x20\t\r\n\f]+/g;function L(a){var b={};return r.each(a.match(K)||[],function(a,c){b[c]=!0}),b}r.Callbacks=function(a){a="string"==typeof a?L(a):r.extend({},a);var b,c,d,e,f=[],g=[],h=-1,i=function(){for(e=a.once,d=b=!0;g.length;h=-1){c=g.shift();while(++h-1)f.splice(c,1),c<=h&&h--}),this},has:function(a){return a?r.inArray(a,f)>-1:f.length>0},empty:function(){return f&&(f=[]),this},disable:function(){return e=g=[],f=c="",this},disabled:function(){return!f},lock:function(){return e=g=[],c||b||(f=c=""),this},locked:function(){return!!e},fireWith:function(a,c){return e||(c=c||[],c=[a,c.slice?c.slice():c],g.push(c),b||i()),this},fire:function(){return j.fireWith(this,arguments),this},fired:function(){return!!d}};return j};function M(a){return a}function N(a){throw a}function O(a,b,c){var d;try{a&&r.isFunction(d=a.promise)?d.call(a).done(b).fail(c):a&&r.isFunction(d=a.then)?d.call(a,b,c):b.call(void 0,a)}catch(a){c.call(void 0,a)}}r.extend({Deferred:function(b){var c=[["notify","progress",r.Callbacks("memory"),r.Callbacks("memory"),2],["resolve","done",r.Callbacks("once memory"),r.Callbacks("once memory"),0,"resolved"],["reject","fail",r.Callbacks("once memory"),r.Callbacks("once memory"),1,"rejected"]],d="pending",e={state:function(){return d},always:function(){return f.done(arguments).fail(arguments),this},"catch":function(a){return e.then(null,a)},pipe:function(){var a=arguments;return r.Deferred(function(b){r.each(c,function(c,d){var e=r.isFunction(a[d[4]])&&a[d[4]];f[d[1]](function(){var a=e&&e.apply(this,arguments);a&&r.isFunction(a.promise)?a.promise().progress(b.notify).done(b.resolve).fail(b.reject):b[d[0]+"With"](this,e?[a]:arguments)})}),a=null}).promise()},then:function(b,d,e){var f=0;function g(b,c,d,e){return function(){var h=this,i=arguments,j=function(){var a,j;if(!(b=f&&(d!==N&&(h=void 0,i=[a]),c.rejectWith(h,i))}};b?k():(r.Deferred.getStackHook&&(k.stackTrace=r.Deferred.getStackHook()),a.setTimeout(k))}}return r.Deferred(function(a){c[0][3].add(g(0,a,r.isFunction(e)?e:M,a.notifyWith)),c[1][3].add(g(0,a,r.isFunction(b)?b:M)),c[2][3].add(g(0,a,r.isFunction(d)?d:N))}).promise()},promise:function(a){return null!=a?r.extend(a,e):e}},f={};return r.each(c,function(a,b){var g=b[2],h=b[5];e[b[1]]=g.add,h&&g.add(function(){d=h},c[3-a][2].disable,c[0][2].lock),g.add(b[3].fire),f[b[0]]=function(){return f[b[0]+"With"](this===f?void 0:this,arguments),this},f[b[0]+"With"]=g.fireWith}),e.promise(f),b&&b.call(f,f),f},when:function(a){var b=arguments.length,c=b,d=Array(c),e=f.call(arguments),g=r.Deferred(),h=function(a){return function(c){d[a]=this,e[a]=arguments.length>1?f.call(arguments):c,--b||g.resolveWith(d,e)}};if(b<=1&&(O(a,g.done(h(c)).resolve,g.reject),"pending"===g.state()||r.isFunction(e[c]&&e[c].then)))return g.then();while(c--)O(e[c],h(c),g.reject);return g.promise()}});var P=/^(Eval|Internal|Range|Reference|Syntax|Type|URI)Error$/;r.Deferred.exceptionHook=function(b,c){a.console&&a.console.warn&&b&&P.test(b.name)&&a.console.warn("jQuery.Deferred exception: "+b.message,b.stack,c)},r.readyException=function(b){a.setTimeout(function(){throw b})};var Q=r.Deferred();r.fn.ready=function(a){return Q.then(a)["catch"](function(a){r.readyException(a)}),this},r.extend({isReady:!1,readyWait:1,holdReady:function(a){a?r.readyWait++:r.ready(!0)},ready:function(a){(a===!0?--r.readyWait:r.isReady)||(r.isReady=!0,a!==!0&&--r.readyWait>0||Q.resolveWith(d,[r]))}}),r.ready.then=Q.then;function R(){d.removeEventListener("DOMContentLoaded",R), +a.removeEventListener("load",R),r.ready()}"complete"===d.readyState||"loading"!==d.readyState&&!d.documentElement.doScroll?a.setTimeout(r.ready):(d.addEventListener("DOMContentLoaded",R),a.addEventListener("load",R));var S=function(a,b,c,d,e,f,g){var h=0,i=a.length,j=null==c;if("object"===r.type(c)){e=!0;for(h in c)S(a,b,h,c[h],!0,f,g)}else if(void 0!==d&&(e=!0,r.isFunction(d)||(g=!0),j&&(g?(b.call(a,d),b=null):(j=b,b=function(a,b,c){return j.call(r(a),c)})),b))for(;h1,null,!0)},removeData:function(a){return this.each(function(){W.remove(this,a)})}}),r.extend({queue:function(a,b,c){var d;if(a)return b=(b||"fx")+"queue",d=V.get(a,b),c&&(!d||r.isArray(c)?d=V.access(a,b,r.makeArray(c)):d.push(c)),d||[]},dequeue:function(a,b){b=b||"fx";var c=r.queue(a,b),d=c.length,e=c.shift(),f=r._queueHooks(a,b),g=function(){r.dequeue(a,b)};"inprogress"===e&&(e=c.shift(),d--),e&&("fx"===b&&c.unshift("inprogress"),delete f.stop,e.call(a,g,f)),!d&&f&&f.empty.fire()},_queueHooks:function(a,b){var c=b+"queueHooks";return V.get(a,c)||V.access(a,c,{empty:r.Callbacks("once memory").add(function(){V.remove(a,[b+"queue",c])})})}}),r.fn.extend({queue:function(a,b){var c=2;return"string"!=typeof a&&(b=a,a="fx",c--),arguments.length\x20\t\r\n\f]+)/i,ka=/^$|\/(?:java|ecma)script/i,la={option:[1,""],thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};la.optgroup=la.option,la.tbody=la.tfoot=la.colgroup=la.caption=la.thead,la.th=la.td;function ma(a,b){var c;return c="undefined"!=typeof a.getElementsByTagName?a.getElementsByTagName(b||"*"):"undefined"!=typeof a.querySelectorAll?a.querySelectorAll(b||"*"):[],void 0===b||b&&r.nodeName(a,b)?r.merge([a],c):c}function na(a,b){for(var c=0,d=a.length;c-1)e&&e.push(f);else if(j=r.contains(f.ownerDocument,f),g=ma(l.appendChild(f),"script"),j&&na(g),c){k=0;while(f=g[k++])ka.test(f.type||"")&&c.push(f)}return l}!function(){var a=d.createDocumentFragment(),b=a.appendChild(d.createElement("div")),c=d.createElement("input");c.setAttribute("type","radio"),c.setAttribute("checked","checked"),c.setAttribute("name","t"),b.appendChild(c),o.checkClone=b.cloneNode(!0).cloneNode(!0).lastChild.checked,b.innerHTML="",o.noCloneChecked=!!b.cloneNode(!0).lastChild.defaultValue}();var qa=d.documentElement,ra=/^key/,sa=/^(?:mouse|pointer|contextmenu|drag|drop)|click/,ta=/^([^.]*)(?:\.(.+)|)/;function ua(){return!0}function va(){return!1}function wa(){try{return d.activeElement}catch(a){}}function xa(a,b,c,d,e,f){var g,h;if("object"==typeof b){"string"!=typeof c&&(d=d||c,c=void 0);for(h in b)xa(a,h,c,d,b[h],f);return a}if(null==d&&null==e?(e=c,d=c=void 0):null==e&&("string"==typeof c?(e=d,d=void 0):(e=d,d=c,c=void 0)),e===!1)e=va;else if(!e)return a;return 1===f&&(g=e,e=function(a){return r().off(a),g.apply(this,arguments)},e.guid=g.guid||(g.guid=r.guid++)),a.each(function(){r.event.add(this,b,e,d,c)})}r.event={global:{},add:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,n,o,p,q=V.get(a);if(q){c.handler&&(f=c,c=f.handler,e=f.selector),e&&r.find.matchesSelector(qa,e),c.guid||(c.guid=r.guid++),(i=q.events)||(i=q.events={}),(g=q.handle)||(g=q.handle=function(b){return"undefined"!=typeof r&&r.event.triggered!==b.type?r.event.dispatch.apply(a,arguments):void 0}),b=(b||"").match(K)||[""],j=b.length;while(j--)h=ta.exec(b[j])||[],n=p=h[1],o=(h[2]||"").split(".").sort(),n&&(l=r.event.special[n]||{},n=(e?l.delegateType:l.bindType)||n,l=r.event.special[n]||{},k=r.extend({type:n,origType:p,data:d,handler:c,guid:c.guid,selector:e,needsContext:e&&r.expr.match.needsContext.test(e),namespace:o.join(".")},f),(m=i[n])||(m=i[n]=[],m.delegateCount=0,l.setup&&l.setup.call(a,d,o,g)!==!1||a.addEventListener&&a.addEventListener(n,g)),l.add&&(l.add.call(a,k),k.handler.guid||(k.handler.guid=c.guid)),e?m.splice(m.delegateCount++,0,k):m.push(k),r.event.global[n]=!0)}},remove:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,n,o,p,q=V.hasData(a)&&V.get(a);if(q&&(i=q.events)){b=(b||"").match(K)||[""],j=b.length;while(j--)if(h=ta.exec(b[j])||[],n=p=h[1],o=(h[2]||"").split(".").sort(),n){l=r.event.special[n]||{},n=(d?l.delegateType:l.bindType)||n,m=i[n]||[],h=h[2]&&new RegExp("(^|\\.)"+o.join("\\.(?:.*\\.|)")+"(\\.|$)"),g=f=m.length;while(f--)k=m[f],!e&&p!==k.origType||c&&c.guid!==k.guid||h&&!h.test(k.namespace)||d&&d!==k.selector&&("**"!==d||!k.selector)||(m.splice(f,1),k.selector&&m.delegateCount--,l.remove&&l.remove.call(a,k));g&&!m.length&&(l.teardown&&l.teardown.call(a,o,q.handle)!==!1||r.removeEvent(a,n,q.handle),delete i[n])}else for(n in i)r.event.remove(a,n+b[j],c,d,!0);r.isEmptyObject(i)&&V.remove(a,"handle events")}},dispatch:function(a){var b=r.event.fix(a),c,d,e,f,g,h,i=new Array(arguments.length),j=(V.get(this,"events")||{})[b.type]||[],k=r.event.special[b.type]||{};for(i[0]=b,c=1;c=1))for(;j!==this;j=j.parentNode||this)if(1===j.nodeType&&("click"!==a.type||j.disabled!==!0)){for(f=[],g={},c=0;c-1:r.find(e,this,null,[j]).length),g[e]&&f.push(d);f.length&&h.push({elem:j,handlers:f})}return j=this,i\x20\t\r\n\f]*)[^>]*)\/>/gi,za=/\s*$/g;function Da(a,b){return r.nodeName(a,"table")&&r.nodeName(11!==b.nodeType?b:b.firstChild,"tr")?a.getElementsByTagName("tbody")[0]||a:a}function Ea(a){return a.type=(null!==a.getAttribute("type"))+"/"+a.type,a}function Fa(a){var b=Ba.exec(a.type);return b?a.type=b[1]:a.removeAttribute("type"),a}function Ga(a,b){var c,d,e,f,g,h,i,j;if(1===b.nodeType){if(V.hasData(a)&&(f=V.access(a),g=V.set(b,f),j=f.events)){delete g.handle,g.events={};for(e in j)for(c=0,d=j[e].length;c1&&"string"==typeof q&&!o.checkClone&&Aa.test(q))return a.each(function(e){var f=a.eq(e);s&&(b[0]=q.call(this,e,f.html())),Ia(f,b,c,d)});if(m&&(e=pa(b,a[0].ownerDocument,!1,a,d),f=e.firstChild,1===e.childNodes.length&&(e=f),f||d)){for(h=r.map(ma(e,"script"),Ea),i=h.length;l")},clone:function(a,b,c){var d,e,f,g,h=a.cloneNode(!0),i=r.contains(a.ownerDocument,a);if(!(o.noCloneChecked||1!==a.nodeType&&11!==a.nodeType||r.isXMLDoc(a)))for(g=ma(h),f=ma(a),d=0,e=f.length;d0&&na(g,!i&&ma(a,"script")),h},cleanData:function(a){for(var b,c,d,e=r.event.special,f=0;void 0!==(c=a[f]);f++)if(T(c)){if(b=c[V.expando]){if(b.events)for(d in b.events)e[d]?r.event.remove(c,d):r.removeEvent(c,d,b.handle);c[V.expando]=void 0}c[W.expando]&&(c[W.expando]=void 0)}}}),r.fn.extend({detach:function(a){return Ja(this,a,!0)},remove:function(a){return Ja(this,a)},text:function(a){return S(this,function(a){return void 0===a?r.text(this):this.empty().each(function(){1!==this.nodeType&&11!==this.nodeType&&9!==this.nodeType||(this.textContent=a)})},null,a,arguments.length)},append:function(){return Ia(this,arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=Da(this,a);b.appendChild(a)}})},prepend:function(){return Ia(this,arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=Da(this,a);b.insertBefore(a,b.firstChild)}})},before:function(){return Ia(this,arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this)})},after:function(){return Ia(this,arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this.nextSibling)})},empty:function(){for(var a,b=0;null!=(a=this[b]);b++)1===a.nodeType&&(r.cleanData(ma(a,!1)),a.textContent="");return this},clone:function(a,b){return a=null!=a&&a,b=null==b?a:b,this.map(function(){return r.clone(this,a,b)})},html:function(a){return S(this,function(a){var b=this[0]||{},c=0,d=this.length;if(void 0===a&&1===b.nodeType)return b.innerHTML;if("string"==typeof a&&!za.test(a)&&!la[(ja.exec(a)||["",""])[1].toLowerCase()]){a=r.htmlPrefilter(a);try{for(;c1)}});function Ya(a,b,c,d,e){return new Ya.prototype.init(a,b,c,d,e)}r.Tween=Ya,Ya.prototype={constructor:Ya,init:function(a,b,c,d,e,f){this.elem=a,this.prop=c,this.easing=e||r.easing._default,this.options=b,this.start=this.now=this.cur(),this.end=d,this.unit=f||(r.cssNumber[c]?"":"px")},cur:function(){var a=Ya.propHooks[this.prop];return a&&a.get?a.get(this):Ya.propHooks._default.get(this)},run:function(a){var b,c=Ya.propHooks[this.prop];return this.options.duration?this.pos=b=r.easing[this.easing](a,this.options.duration*a,0,1,this.options.duration):this.pos=b=a,this.now=(this.end-this.start)*b+this.start,this.options.step&&this.options.step.call(this.elem,this.now,this),c&&c.set?c.set(this):Ya.propHooks._default.set(this),this}},Ya.prototype.init.prototype=Ya.prototype,Ya.propHooks={_default:{get:function(a){var b;return 1!==a.elem.nodeType||null!=a.elem[a.prop]&&null==a.elem.style[a.prop]?a.elem[a.prop]:(b=r.css(a.elem,a.prop,""),b&&"auto"!==b?b:0)},set:function(a){r.fx.step[a.prop]?r.fx.step[a.prop](a):1!==a.elem.nodeType||null==a.elem.style[r.cssProps[a.prop]]&&!r.cssHooks[a.prop]?a.elem[a.prop]=a.now:r.style(a.elem,a.prop,a.now+a.unit)}}},Ya.propHooks.scrollTop=Ya.propHooks.scrollLeft={set:function(a){a.elem.nodeType&&a.elem.parentNode&&(a.elem[a.prop]=a.now)}},r.easing={linear:function(a){return a},swing:function(a){return.5-Math.cos(a*Math.PI)/2},_default:"swing"},r.fx=Ya.prototype.init,r.fx.step={};var Za,$a,_a=/^(?:toggle|show|hide)$/,ab=/queueHooks$/;function bb(){$a&&(a.requestAnimationFrame(bb),r.fx.tick())}function cb(){return a.setTimeout(function(){Za=void 0}),Za=r.now()}function db(a,b){var c,d=0,e={height:a};for(b=b?1:0;d<4;d+=2-b)c=ba[d],e["margin"+c]=e["padding"+c]=a;return b&&(e.opacity=e.width=a),e}function eb(a,b,c){for(var d,e=(hb.tweeners[b]||[]).concat(hb.tweeners["*"]),f=0,g=e.length;f1)},removeAttr:function(a){return this.each(function(){r.removeAttr(this,a)})}}),r.extend({attr:function(a,b,c){var d,e,f=a.nodeType;if(3!==f&&8!==f&&2!==f)return"undefined"==typeof a.getAttribute?r.prop(a,b,c):(1===f&&r.isXMLDoc(a)||(e=r.attrHooks[b.toLowerCase()]||(r.expr.match.bool.test(b)?ib:void 0)), +void 0!==c?null===c?void r.removeAttr(a,b):e&&"set"in e&&void 0!==(d=e.set(a,c,b))?d:(a.setAttribute(b,c+""),c):e&&"get"in e&&null!==(d=e.get(a,b))?d:(d=r.find.attr(a,b),null==d?void 0:d))},attrHooks:{type:{set:function(a,b){if(!o.radioValue&&"radio"===b&&r.nodeName(a,"input")){var c=a.value;return a.setAttribute("type",b),c&&(a.value=c),b}}}},removeAttr:function(a,b){var c,d=0,e=b&&b.match(K);if(e&&1===a.nodeType)while(c=e[d++])a.removeAttribute(c)}}),ib={set:function(a,b,c){return b===!1?r.removeAttr(a,c):a.setAttribute(c,c),c}},r.each(r.expr.match.bool.source.match(/\w+/g),function(a,b){var c=jb[b]||r.find.attr;jb[b]=function(a,b,d){var e,f,g=b.toLowerCase();return d||(f=jb[g],jb[g]=e,e=null!=c(a,b,d)?g:null,jb[g]=f),e}});var kb=/^(?:input|select|textarea|button)$/i,lb=/^(?:a|area)$/i;r.fn.extend({prop:function(a,b){return S(this,r.prop,a,b,arguments.length>1)},removeProp:function(a){return this.each(function(){delete this[r.propFix[a]||a]})}}),r.extend({prop:function(a,b,c){var d,e,f=a.nodeType;if(3!==f&&8!==f&&2!==f)return 1===f&&r.isXMLDoc(a)||(b=r.propFix[b]||b,e=r.propHooks[b]),void 0!==c?e&&"set"in e&&void 0!==(d=e.set(a,c,b))?d:a[b]=c:e&&"get"in e&&null!==(d=e.get(a,b))?d:a[b]},propHooks:{tabIndex:{get:function(a){var b=r.find.attr(a,"tabindex");return b?parseInt(b,10):kb.test(a.nodeName)||lb.test(a.nodeName)&&a.href?0:-1}}},propFix:{"for":"htmlFor","class":"className"}}),o.optSelected||(r.propHooks.selected={get:function(a){var b=a.parentNode;return b&&b.parentNode&&b.parentNode.selectedIndex,null},set:function(a){var b=a.parentNode;b&&(b.selectedIndex,b.parentNode&&b.parentNode.selectedIndex)}}),r.each(["tabIndex","readOnly","maxLength","cellSpacing","cellPadding","rowSpan","colSpan","useMap","frameBorder","contentEditable"],function(){r.propFix[this.toLowerCase()]=this});function mb(a){var b=a.match(K)||[];return b.join(" ")}function nb(a){return a.getAttribute&&a.getAttribute("class")||""}r.fn.extend({addClass:function(a){var b,c,d,e,f,g,h,i=0;if(r.isFunction(a))return this.each(function(b){r(this).addClass(a.call(this,b,nb(this)))});if("string"==typeof a&&a){b=a.match(K)||[];while(c=this[i++])if(e=nb(c),d=1===c.nodeType&&" "+mb(e)+" "){g=0;while(f=b[g++])d.indexOf(" "+f+" ")<0&&(d+=f+" ");h=mb(d),e!==h&&c.setAttribute("class",h)}}return this},removeClass:function(a){var b,c,d,e,f,g,h,i=0;if(r.isFunction(a))return this.each(function(b){r(this).removeClass(a.call(this,b,nb(this)))});if(!arguments.length)return this.attr("class","");if("string"==typeof a&&a){b=a.match(K)||[];while(c=this[i++])if(e=nb(c),d=1===c.nodeType&&" "+mb(e)+" "){g=0;while(f=b[g++])while(d.indexOf(" "+f+" ")>-1)d=d.replace(" "+f+" "," ");h=mb(d),e!==h&&c.setAttribute("class",h)}}return this},toggleClass:function(a,b){var c=typeof a;return"boolean"==typeof b&&"string"===c?b?this.addClass(a):this.removeClass(a):r.isFunction(a)?this.each(function(c){r(this).toggleClass(a.call(this,c,nb(this),b),b)}):this.each(function(){var b,d,e,f;if("string"===c){d=0,e=r(this),f=a.match(K)||[];while(b=f[d++])e.hasClass(b)?e.removeClass(b):e.addClass(b)}else void 0!==a&&"boolean"!==c||(b=nb(this),b&&V.set(this,"__className__",b),this.setAttribute&&this.setAttribute("class",b||a===!1?"":V.get(this,"__className__")||""))})},hasClass:function(a){var b,c,d=0;b=" "+a+" ";while(c=this[d++])if(1===c.nodeType&&(" "+mb(nb(c))+" ").indexOf(b)>-1)return!0;return!1}});var ob=/\r/g;r.fn.extend({val:function(a){var b,c,d,e=this[0];{if(arguments.length)return d=r.isFunction(a),this.each(function(c){var e;1===this.nodeType&&(e=d?a.call(this,c,r(this).val()):a,null==e?e="":"number"==typeof e?e+="":r.isArray(e)&&(e=r.map(e,function(a){return null==a?"":a+""})),b=r.valHooks[this.type]||r.valHooks[this.nodeName.toLowerCase()],b&&"set"in b&&void 0!==b.set(this,e,"value")||(this.value=e))});if(e)return b=r.valHooks[e.type]||r.valHooks[e.nodeName.toLowerCase()],b&&"get"in b&&void 0!==(c=b.get(e,"value"))?c:(c=e.value,"string"==typeof c?c.replace(ob,""):null==c?"":c)}}}),r.extend({valHooks:{option:{get:function(a){var b=r.find.attr(a,"value");return null!=b?b:mb(r.text(a))}},select:{get:function(a){var b,c,d,e=a.options,f=a.selectedIndex,g="select-one"===a.type,h=g?null:[],i=g?f+1:e.length;for(d=f<0?i:g?f:0;d-1)&&(c=!0);return c||(a.selectedIndex=-1),f}}}}),r.each(["radio","checkbox"],function(){r.valHooks[this]={set:function(a,b){if(r.isArray(b))return a.checked=r.inArray(r(a).val(),b)>-1}},o.checkOn||(r.valHooks[this].get=function(a){return null===a.getAttribute("value")?"on":a.value})});var pb=/^(?:focusinfocus|focusoutblur)$/;r.extend(r.event,{trigger:function(b,c,e,f){var g,h,i,j,k,m,n,o=[e||d],p=l.call(b,"type")?b.type:b,q=l.call(b,"namespace")?b.namespace.split("."):[];if(h=i=e=e||d,3!==e.nodeType&&8!==e.nodeType&&!pb.test(p+r.event.triggered)&&(p.indexOf(".")>-1&&(q=p.split("."),p=q.shift(),q.sort()),k=p.indexOf(":")<0&&"on"+p,b=b[r.expando]?b:new r.Event(p,"object"==typeof b&&b),b.isTrigger=f?2:3,b.namespace=q.join("."),b.rnamespace=b.namespace?new RegExp("(^|\\.)"+q.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,b.result=void 0,b.target||(b.target=e),c=null==c?[b]:r.makeArray(c,[b]),n=r.event.special[p]||{},f||!n.trigger||n.trigger.apply(e,c)!==!1)){if(!f&&!n.noBubble&&!r.isWindow(e)){for(j=n.delegateType||p,pb.test(j+p)||(h=h.parentNode);h;h=h.parentNode)o.push(h),i=h;i===(e.ownerDocument||d)&&o.push(i.defaultView||i.parentWindow||a)}g=0;while((h=o[g++])&&!b.isPropagationStopped())b.type=g>1?j:n.bindType||p,m=(V.get(h,"events")||{})[b.type]&&V.get(h,"handle"),m&&m.apply(h,c),m=k&&h[k],m&&m.apply&&T(h)&&(b.result=m.apply(h,c),b.result===!1&&b.preventDefault());return b.type=p,f||b.isDefaultPrevented()||n._default&&n._default.apply(o.pop(),c)!==!1||!T(e)||k&&r.isFunction(e[p])&&!r.isWindow(e)&&(i=e[k],i&&(e[k]=null),r.event.triggered=p,e[p](),r.event.triggered=void 0,i&&(e[k]=i)),b.result}},simulate:function(a,b,c){var d=r.extend(new r.Event,c,{type:a,isSimulated:!0});r.event.trigger(d,null,b)}}),r.fn.extend({trigger:function(a,b){return this.each(function(){r.event.trigger(a,b,this)})},triggerHandler:function(a,b){var c=this[0];if(c)return r.event.trigger(a,b,c,!0)}}),r.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(a,b){r.fn[b]=function(a,c){return arguments.length>0?this.on(b,null,a,c):this.trigger(b)}}),r.fn.extend({hover:function(a,b){return this.mouseenter(a).mouseleave(b||a)}}),o.focusin="onfocusin"in a,o.focusin||r.each({focus:"focusin",blur:"focusout"},function(a,b){var c=function(a){r.event.simulate(b,a.target,r.event.fix(a))};r.event.special[b]={setup:function(){var d=this.ownerDocument||this,e=V.access(d,b);e||d.addEventListener(a,c,!0),V.access(d,b,(e||0)+1)},teardown:function(){var d=this.ownerDocument||this,e=V.access(d,b)-1;e?V.access(d,b,e):(d.removeEventListener(a,c,!0),V.remove(d,b))}}});var qb=a.location,rb=r.now(),sb=/\?/;r.parseXML=function(b){var c;if(!b||"string"!=typeof b)return null;try{c=(new a.DOMParser).parseFromString(b,"text/xml")}catch(d){c=void 0}return c&&!c.getElementsByTagName("parsererror").length||r.error("Invalid XML: "+b),c};var tb=/\[\]$/,ub=/\r?\n/g,vb=/^(?:submit|button|image|reset|file)$/i,wb=/^(?:input|select|textarea|keygen)/i;function xb(a,b,c,d){var e;if(r.isArray(b))r.each(b,function(b,e){c||tb.test(a)?d(a,e):xb(a+"["+("object"==typeof e&&null!=e?b:"")+"]",e,c,d)});else if(c||"object"!==r.type(b))d(a,b);else for(e in b)xb(a+"["+e+"]",b[e],c,d)}r.param=function(a,b){var c,d=[],e=function(a,b){var c=r.isFunction(b)?b():b;d[d.length]=encodeURIComponent(a)+"="+encodeURIComponent(null==c?"":c)};if(r.isArray(a)||a.jquery&&!r.isPlainObject(a))r.each(a,function(){e(this.name,this.value)});else for(c in a)xb(c,a[c],b,e);return d.join("&")},r.fn.extend({serialize:function(){return r.param(this.serializeArray())},serializeArray:function(){return this.map(function(){var a=r.prop(this,"elements");return a?r.makeArray(a):this}).filter(function(){var a=this.type;return this.name&&!r(this).is(":disabled")&&wb.test(this.nodeName)&&!vb.test(a)&&(this.checked||!ia.test(a))}).map(function(a,b){var c=r(this).val();return null==c?null:r.isArray(c)?r.map(c,function(a){return{name:b.name,value:a.replace(ub,"\r\n")}}):{name:b.name,value:c.replace(ub,"\r\n")}}).get()}});var yb=/%20/g,zb=/#.*$/,Ab=/([?&])_=[^&]*/,Bb=/^(.*?):[ \t]*([^\r\n]*)$/gm,Cb=/^(?:about|app|app-storage|.+-extension|file|res|widget):$/,Db=/^(?:GET|HEAD)$/,Eb=/^\/\//,Fb={},Gb={},Hb="*/".concat("*"),Ib=d.createElement("a");Ib.href=qb.href;function Jb(a){return function(b,c){"string"!=typeof b&&(c=b,b="*");var d,e=0,f=b.toLowerCase().match(K)||[];if(r.isFunction(c))while(d=f[e++])"+"===d[0]?(d=d.slice(1)||"*",(a[d]=a[d]||[]).unshift(c)):(a[d]=a[d]||[]).push(c)}}function Kb(a,b,c,d){var e={},f=a===Gb;function g(h){var i;return e[h]=!0,r.each(a[h]||[],function(a,h){var j=h(b,c,d);return"string"!=typeof j||f||e[j]?f?!(i=j):void 0:(b.dataTypes.unshift(j),g(j),!1)}),i}return g(b.dataTypes[0])||!e["*"]&&g("*")}function Lb(a,b){var c,d,e=r.ajaxSettings.flatOptions||{};for(c in b)void 0!==b[c]&&((e[c]?a:d||(d={}))[c]=b[c]);return d&&r.extend(!0,a,d),a}function Mb(a,b,c){var d,e,f,g,h=a.contents,i=a.dataTypes;while("*"===i[0])i.shift(),void 0===d&&(d=a.mimeType||b.getResponseHeader("Content-Type"));if(d)for(e in h)if(h[e]&&h[e].test(d)){i.unshift(e);break}if(i[0]in c)f=i[0];else{for(e in c){if(!i[0]||a.converters[e+" "+i[0]]){f=e;break}g||(g=e)}f=f||g}if(f)return f!==i[0]&&i.unshift(f),c[f]}function Nb(a,b,c,d){var e,f,g,h,i,j={},k=a.dataTypes.slice();if(k[1])for(g in a.converters)j[g.toLowerCase()]=a.converters[g];f=k.shift();while(f)if(a.responseFields[f]&&(c[a.responseFields[f]]=b),!i&&d&&a.dataFilter&&(b=a.dataFilter(b,a.dataType)),i=f,f=k.shift())if("*"===f)f=i;else if("*"!==i&&i!==f){if(g=j[i+" "+f]||j["* "+f],!g)for(e in j)if(h=e.split(" "),h[1]===f&&(g=j[i+" "+h[0]]||j["* "+h[0]])){g===!0?g=j[e]:j[e]!==!0&&(f=h[0],k.unshift(h[1]));break}if(g!==!0)if(g&&a["throws"])b=g(b);else try{b=g(b)}catch(l){return{state:"parsererror",error:g?l:"No conversion from "+i+" to "+f}}}return{state:"success",data:b}}r.extend({active:0,lastModified:{},etag:{},ajaxSettings:{url:qb.href,type:"GET",isLocal:Cb.test(qb.protocol),global:!0,processData:!0,async:!0,contentType:"application/x-www-form-urlencoded; charset=UTF-8",accepts:{"*":Hb,text:"text/plain",html:"text/html",xml:"application/xml, text/xml",json:"application/json, text/javascript"},contents:{xml:/\bxml\b/,html:/\bhtml/,json:/\bjson\b/},responseFields:{xml:"responseXML",text:"responseText",json:"responseJSON"},converters:{"* text":String,"text html":!0,"text json":JSON.parse,"text xml":r.parseXML},flatOptions:{url:!0,context:!0}},ajaxSetup:function(a,b){return b?Lb(Lb(a,r.ajaxSettings),b):Lb(r.ajaxSettings,a)},ajaxPrefilter:Jb(Fb),ajaxTransport:Jb(Gb),ajax:function(b,c){"object"==typeof b&&(c=b,b=void 0),c=c||{};var e,f,g,h,i,j,k,l,m,n,o=r.ajaxSetup({},c),p=o.context||o,q=o.context&&(p.nodeType||p.jquery)?r(p):r.event,s=r.Deferred(),t=r.Callbacks("once memory"),u=o.statusCode||{},v={},w={},x="canceled",y={readyState:0,getResponseHeader:function(a){var b;if(k){if(!h){h={};while(b=Bb.exec(g))h[b[1].toLowerCase()]=b[2]}b=h[a.toLowerCase()]}return null==b?null:b},getAllResponseHeaders:function(){return k?g:null},setRequestHeader:function(a,b){return null==k&&(a=w[a.toLowerCase()]=w[a.toLowerCase()]||a,v[a]=b),this},overrideMimeType:function(a){return null==k&&(o.mimeType=a),this},statusCode:function(a){var b;if(a)if(k)y.always(a[y.status]);else for(b in a)u[b]=[u[b],a[b]];return this},abort:function(a){var b=a||x;return e&&e.abort(b),A(0,b),this}};if(s.promise(y),o.url=((b||o.url||qb.href)+"").replace(Eb,qb.protocol+"//"),o.type=c.method||c.type||o.method||o.type,o.dataTypes=(o.dataType||"*").toLowerCase().match(K)||[""],null==o.crossDomain){j=d.createElement("a");try{j.href=o.url,j.href=j.href,o.crossDomain=Ib.protocol+"//"+Ib.host!=j.protocol+"//"+j.host}catch(z){o.crossDomain=!0}}if(o.data&&o.processData&&"string"!=typeof o.data&&(o.data=r.param(o.data,o.traditional)),Kb(Fb,o,c,y),k)return y;l=r.event&&o.global,l&&0===r.active++&&r.event.trigger("ajaxStart"),o.type=o.type.toUpperCase(),o.hasContent=!Db.test(o.type),f=o.url.replace(zb,""),o.hasContent?o.data&&o.processData&&0===(o.contentType||"").indexOf("application/x-www-form-urlencoded")&&(o.data=o.data.replace(yb,"+")):(n=o.url.slice(f.length),o.data&&(f+=(sb.test(f)?"&":"?")+o.data,delete o.data),o.cache===!1&&(f=f.replace(Ab,"$1"),n=(sb.test(f)?"&":"?")+"_="+rb++ +n),o.url=f+n),o.ifModified&&(r.lastModified[f]&&y.setRequestHeader("If-Modified-Since",r.lastModified[f]),r.etag[f]&&y.setRequestHeader("If-None-Match",r.etag[f])),(o.data&&o.hasContent&&o.contentType!==!1||c.contentType)&&y.setRequestHeader("Content-Type",o.contentType),y.setRequestHeader("Accept",o.dataTypes[0]&&o.accepts[o.dataTypes[0]]?o.accepts[o.dataTypes[0]]+("*"!==o.dataTypes[0]?", "+Hb+"; q=0.01":""):o.accepts["*"]);for(m in o.headers)y.setRequestHeader(m,o.headers[m]);if(o.beforeSend&&(o.beforeSend.call(p,y,o)===!1||k))return y.abort();if(x="abort",t.add(o.complete),y.done(o.success),y.fail(o.error),e=Kb(Gb,o,c,y)){if(y.readyState=1,l&&q.trigger("ajaxSend",[y,o]),k)return y;o.async&&o.timeout>0&&(i=a.setTimeout(function(){y.abort("timeout")},o.timeout));try{k=!1,e.send(v,A)}catch(z){if(k)throw z;A(-1,z)}}else A(-1,"No Transport");function A(b,c,d,h){var j,m,n,v,w,x=c;k||(k=!0,i&&a.clearTimeout(i),e=void 0,g=h||"",y.readyState=b>0?4:0,j=b>=200&&b<300||304===b,d&&(v=Mb(o,y,d)),v=Nb(o,v,y,j),j?(o.ifModified&&(w=y.getResponseHeader("Last-Modified"),w&&(r.lastModified[f]=w),w=y.getResponseHeader("etag"),w&&(r.etag[f]=w)),204===b||"HEAD"===o.type?x="nocontent":304===b?x="notmodified":(x=v.state,m=v.data,n=v.error,j=!n)):(n=x,!b&&x||(x="error",b<0&&(b=0))),y.status=b,y.statusText=(c||x)+"",j?s.resolveWith(p,[m,x,y]):s.rejectWith(p,[y,x,n]),y.statusCode(u),u=void 0,l&&q.trigger(j?"ajaxSuccess":"ajaxError",[y,o,j?m:n]),t.fireWith(p,[y,x]),l&&(q.trigger("ajaxComplete",[y,o]),--r.active||r.event.trigger("ajaxStop")))}return y},getJSON:function(a,b,c){return r.get(a,b,c,"json")},getScript:function(a,b){return r.get(a,void 0,b,"script")}}),r.each(["get","post"],function(a,b){r[b]=function(a,c,d,e){return r.isFunction(c)&&(e=e||d,d=c,c=void 0),r.ajax(r.extend({url:a,type:b,dataType:e,data:c,success:d},r.isPlainObject(a)&&a))}}),r._evalUrl=function(a){return r.ajax({url:a,type:"GET",dataType:"script",cache:!0,async:!1,global:!1,"throws":!0})},r.fn.extend({wrapAll:function(a){var b;return this[0]&&(r.isFunction(a)&&(a=a.call(this[0])),b=r(a,this[0].ownerDocument).eq(0).clone(!0),this[0].parentNode&&b.insertBefore(this[0]),b.map(function(){var a=this;while(a.firstElementChild)a=a.firstElementChild;return a}).append(this)),this},wrapInner:function(a){return r.isFunction(a)?this.each(function(b){r(this).wrapInner(a.call(this,b))}):this.each(function(){var b=r(this),c=b.contents();c.length?c.wrapAll(a):b.append(a)})},wrap:function(a){var b=r.isFunction(a);return this.each(function(c){r(this).wrapAll(b?a.call(this,c):a)})},unwrap:function(a){return this.parent(a).not("body").each(function(){r(this).replaceWith(this.childNodes)}),this}}),r.expr.pseudos.hidden=function(a){return!r.expr.pseudos.visible(a)},r.expr.pseudos.visible=function(a){return!!(a.offsetWidth||a.offsetHeight||a.getClientRects().length)},r.ajaxSettings.xhr=function(){try{return new a.XMLHttpRequest}catch(b){}};var Ob={0:200,1223:204},Pb=r.ajaxSettings.xhr();o.cors=!!Pb&&"withCredentials"in Pb,o.ajax=Pb=!!Pb,r.ajaxTransport(function(b){var c,d;if(o.cors||Pb&&!b.crossDomain)return{send:function(e,f){var g,h=b.xhr();if(h.open(b.type,b.url,b.async,b.username,b.password),b.xhrFields)for(g in b.xhrFields)h[g]=b.xhrFields[g];b.mimeType&&h.overrideMimeType&&h.overrideMimeType(b.mimeType),b.crossDomain||e["X-Requested-With"]||(e["X-Requested-With"]="XMLHttpRequest");for(g in e)h.setRequestHeader(g,e[g]);c=function(a){return function(){c&&(c=d=h.onload=h.onerror=h.onabort=h.onreadystatechange=null,"abort"===a?h.abort():"error"===a?"number"!=typeof h.status?f(0,"error"):f(h.status,h.statusText):f(Ob[h.status]||h.status,h.statusText,"text"!==(h.responseType||"text")||"string"!=typeof h.responseText?{binary:h.response}:{text:h.responseText},h.getAllResponseHeaders()))}},h.onload=c(),d=h.onerror=c("error"),void 0!==h.onabort?h.onabort=d:h.onreadystatechange=function(){4===h.readyState&&a.setTimeout(function(){c&&d()})},c=c("abort");try{h.send(b.hasContent&&b.data||null)}catch(i){if(c)throw i}},abort:function(){c&&c()}}}),r.ajaxPrefilter(function(a){a.crossDomain&&(a.contents.script=!1)}),r.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/\b(?:java|ecma)script\b/},converters:{"text script":function(a){return r.globalEval(a),a}}}),r.ajaxPrefilter("script",function(a){void 0===a.cache&&(a.cache=!1),a.crossDomain&&(a.type="GET")}),r.ajaxTransport("script",function(a){if(a.crossDomain){var b,c;return{send:function(e,f){b=r(" + + + + + + + + + + diff --git a/ABC_Score/config/application.html b/ABC_Score/config/application.html new file mode 100644 index 00000000..cf8b3cdf --- /dev/null +++ b/ABC_Score/config/application.html @@ -0,0 +1,138 @@ + + + + + + Ruby Critic - Home + + + + + + + + + + + + +
+ + + +
+
+
+ +
+
+ + + Updated + + +
+
+

config / application.rb

+
+
+ +
+ +
+
+
+
+
+ A +
+
+
+
+
19 lines of codes
+
0 methods
+
+
+
N/A complexity/method
+
3 churn
+
+
+
0.0 complexity
+
0 duplications
+
+
+
+
+
+
+ +
+
+
+ + require_relative 'boot' + +require 'rails/all' + +# Require the gems listed in Gemfile, including any gems +# you've limited to :test, :development, or :production. +Bundler.require(*Rails.groups) + +module SecretariaPpgi + class Application < Rails::Application
  1. SecretariaPpgi::Application has no descriptive comment
+ # Initialize configuration defaults for originally generated Rails version. + config.load_defaults 5.2 + + # Settings in config/environments/* take precedence over those specified here. + # Application configuration can go into files in config/initializers + # -- all .rb files in that directory are automatically loaded after loading + # the framework and any gems in your application. + end +end +
+
+
+ +
+
+
+ + + + + + + + + + + + + diff --git a/ABC_Score/config/boot.html b/ABC_Score/config/boot.html new file mode 100644 index 00000000..0850468f --- /dev/null +++ b/ABC_Score/config/boot.html @@ -0,0 +1,123 @@ + + + + + + Ruby Critic - Home + + + + + + + + + + + + +
+ + + +
+
+
+ +
+
+ + + Updated + + +
+
+

config / boot.rb

+
+
+ +
+ +
+
+
+
+
+ A +
+
+
+
+
4 lines of codes
+
0 methods
+
+
+
N/A complexity/method
+
1 churn
+
+
+
0.0 complexity
+
0 duplications
+
+
+
+
+
+
+ +
+
+
+ + ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__) + +require 'bundler/setup' # Set up gems listed in the Gemfile. +require 'bootsnap/setup' # Speed up boot time by caching expensive operations. + +
+
+ +
+
+
+ + + + + + + + + + + + + diff --git a/ABC_Score/config/environment.html b/ABC_Score/config/environment.html new file mode 100644 index 00000000..6287083f --- /dev/null +++ b/ABC_Score/config/environment.html @@ -0,0 +1,124 @@ + + + + + + Ruby Critic - Home + + + + + + + + + + + + +
+ + + +
+
+
+ +
+
+ + + Updated + + +
+
+

config / environment.rb

+
+
+ +
+ +
+
+
+
+
+ A +
+
+
+
+
5 lines of codes
+
0 methods
+
+
+
N/A complexity/method
+
1 churn
+
+
+
0.0 complexity
+
0 duplications
+
+
+
+
+
+
+ +
+
+
+ + # Load the Rails application. +require_relative 'application' + +# Initialize the Rails application. +Rails.application.initialize! + +
+
+ +
+
+
+ + + + + + + + + + + + + diff --git a/ABC_Score/config/environments/development.html b/ABC_Score/config/environments/development.html new file mode 100644 index 00000000..caab7e63 --- /dev/null +++ b/ABC_Score/config/environments/development.html @@ -0,0 +1,182 @@ + + + + + + Ruby Critic - Home + + + + + + + + + + + + +
+ + + +
+
+
+ +
+
+ + + Updated + + +
+
+

config/environments / development.rb

+
+
+ +
+ +
+
+
+
+
+ A +
+
+
+
+
63 lines of codes
+
0 methods
+
+
+
N/A complexity/method
+
3 churn
+
+
+
0.0 complexity
+
0 duplications
+
+
+
+
+
+
+ +
+
+
+ + Rails.application.configure do + # Settings specified here will take precedence over those in config/application.rb. + + # In the development environment your application's code is reloaded on + # every request. This slows down response time but is perfect for development + # since you don't have to restart the web server when you make code changes. + config.cache_classes = false + + # Do not eager load code on boot. + config.eager_load = false + + # Show full error reports. + config.consider_all_requests_local = true + + # Enable/disable caching. By default caching is disabled. + # Run rails dev:cache to toggle caching. + if Rails.root.join('tmp', 'caching-dev.txt').exist? + config.action_controller.perform_caching = true + + config.cache_store = :memory_store + config.public_file_server.headers = { + 'Cache-Control' => "public, max-age=#{2.days.to_i}" + } + else + config.action_controller.perform_caching = false + + config.cache_store = :null_store + end + + # Store uploaded files on the local file system (see config/storage.yml for options) + config.active_storage.service = :local + + # action mailer url + config.action_mailer.default_url_options = { host: 'localhost', port: 3000 } + # Don't care if the mailer can't send. + config.action_mailer.raise_delivery_errors = false + + config.action_mailer.perform_caching = false + + # Print deprecation notices to the Rails logger. + config.active_support.deprecation = :log + + # Raise an error on page load if there are pending migrations. + config.active_record.migration_error = :page_load + + # Highlight code that triggered database queries in logs. + config.active_record.verbose_query_logs = true + + # Debug mode disables concatenation and preprocessing of assets. + # This option may cause significant delays in view rendering with a large + # number of complex assets. + config.assets.debug = true + + # Suppress logger output for asset requests. + config.assets.quiet = true + + # Raises error for missing translations + # config.action_view.raise_on_missing_translations = true + + # Use an evented file watcher to asynchronously detect changes in source code, + # routes, locales, etc. This feature depends on the listen gem. + config.file_watcher = ActiveSupport::EventedFileUpdateChecker +end + +
+
+ +
+
+
+ + + + + + + + + + + + + diff --git a/ABC_Score/config/environments/production.html b/ABC_Score/config/environments/production.html new file mode 100644 index 00000000..250a6a3a --- /dev/null +++ b/ABC_Score/config/environments/production.html @@ -0,0 +1,217 @@ + + + + + + Ruby Critic - Home + + + + + + + + + + + + +
+ + + +
+
+
+ +
+
+ + + Updated + + +
+
+

config/environments / production.rb

+
+
+ +
+ +
+
+
+
+
+ A +
+
+
+
+
98 lines of codes
+
0 methods
+
+
+
N/A complexity/method
+
3 churn
+
+
+
0.0 complexity
+
0 duplications
+
+
+
+
+
+
+ +
+
+
+ + # frozen_string_literal: true + +Rails.application.configure do + # Settings specified here will take precedence over those in config/application.rb. + + # Code is not reloaded between requests. + config.cache_classes = true + + # Eager load code on boot. This eager loads most of Rails and + # your application in memory, allowing both threaded web servers + # and those relying on copy on write to perform better. + # Rake tasks automatically ignore this option for performance. + config.eager_load = true + + # Full error reports are disabled and caching is turned on. + config.consider_all_requests_local = false + config.action_controller.perform_caching = true + + # Ensures that a master key has been made available in either ENV["RAILS_MASTER_KEY"] + # or in config/master.key. This key is used to decrypt credentials (and other encrypted files). + # config.require_master_key = true + + # Disable serving static files from the `/public` folder by default since + # Apache or NGINX already handles this. + config.public_file_server.enabled = ENV['RAILS_SERVE_STATIC_FILES'].present? + + # Compress JavaScripts and CSS. + config.assets.js_compressor = :uglifier + # config.assets.css_compressor = :sass + + # Do not fallback to assets pipeline if a precompiled asset is missed. + config.assets.compile = false + + # `config.assets.precompile` and `config.assets.version` have moved to config/initializers/assets.rb + + # Enable serving of images, stylesheets, and JavaScripts from an asset server. + # config.action_controller.asset_host = 'http://assets.example.com' + + # Specifies the header that your server uses for sending files. + # config.action_dispatch.x_sendfile_header = 'X-Sendfile' # for Apache + # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for NGINX + + # Store uploaded files on the local file system (see config/storage.yml for options) + config.active_storage.service = :local + + # Mount Action Cable outside main process or domain + # config.action_cable.mount_path = nil + # config.action_cable.url = 'wss://example.com/cable' + # config.action_cable.allowed_request_origins = [ 'http://example.com', /http:\/\/example.*/ ] + + # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. + # config.force_ssl = true + + # Use the lowest log level to ensure availability of diagnostic information + # when problems arise. + config.log_level = :debug + + # Prepend all log lines with the following tags. + config.log_tags = [:request_id] + + # Use a different cache store in production. + # config.cache_store = :mem_cache_store + + # Use a real queuing backend for Active Job (and separate queues per environment) + # config.active_job.queue_adapter = :resque + # config.active_job.queue_name_prefix = "secretaria_ppgi_#{Rails.env}" + + # @TODO change this value to the production host + config.action_mailer.default_url_options = { host: 'localhost', port: 3000 } + config.action_mailer.perform_caching = false + + # Ignore bad email addresses and do not raise email delivery errors. + # Set this to true and configure the email server for immediate delivery to raise delivery errors. + # config.action_mailer.raise_delivery_errors = false + + # Enable locale fallbacks for I18n (makes lookups for any locale fall back to + # the I18n.default_locale when a translation cannot be found). + config.i18n.fallbacks = true + + # Send deprecation notices to registered listeners. + config.active_support.deprecation = :notify + + # Use default logging formatter so that PID and timestamp are not suppressed. + config.log_formatter = ::Logger::Formatter.new + + # Use a different logger for distributed setups. + # require 'syslog/logger' + # config.logger = ActiveSupport::TaggedLogging.new(Syslog::Logger.new 'app-name') + + if ENV['RAILS_LOG_TO_STDOUT'].present? + logger = ActiveSupport::Logger.new(STDOUT) + logger.formatter = config.log_formatter + config.logger = ActiveSupport::TaggedLogging.new(logger) + end + + # Do not dump schema after migrations. + config.active_record.dump_schema_after_migration = false +end + +
+
+ +
+
+
+ + + + + + + + + + + + + diff --git a/ABC_Score/config/environments/test.html b/ABC_Score/config/environments/test.html new file mode 100644 index 00000000..5c97a690 --- /dev/null +++ b/ABC_Score/config/environments/test.html @@ -0,0 +1,165 @@ + + + + + + Ruby Critic - Home + + + + + + + + + + + + +
+ + + +
+
+
+ +
+
+ + + Updated + + +
+
+

config/environments / test.rb

+
+
+ +
+ +
+
+
+
+
+ A +
+
+
+
+
46 lines of codes
+
0 methods
+
+
+
N/A complexity/method
+
2 churn
+
+
+
0.0 complexity
+
0 duplications
+
+
+
+
+
+
+ +
+
+
+ + Rails.application.configure do + # Settings specified here will take precedence over those in config/application.rb. + + # The test environment is used exclusively to run your application's + # test suite. You never need to work with it otherwise. Remember that + # your test database is "scratch space" for the test suite and is wiped + # and recreated between test runs. Don't rely on the data there! + config.cache_classes = true + + # Do not eager load code on boot. This avoids loading your whole application + # just for the purpose of running a single test. If you are using a tool that + # preloads Rails for running tests, you may have to set it to true. + config.eager_load = false + + # Configure public file server for tests with Cache-Control for performance. + config.public_file_server.enabled = true + config.public_file_server.headers = { + 'Cache-Control' => "public, max-age=#{1.hour.to_i}" + } + + # Show full error reports and disable caching. + config.consider_all_requests_local = true + config.action_controller.perform_caching = false + + # Raise exceptions instead of rendering exception templates. + config.action_dispatch.show_exceptions = false + + # Disable request forgery protection in test environment. + config.action_controller.allow_forgery_protection = false + + # Store uploaded files on the local file system in a temporary directory + config.active_storage.service = :test + + config.action_mailer.perform_caching = false + + # Tell Action Mailer not to deliver emails to the real world. + # The :test delivery method accumulates sent emails in the + # ActionMailer::Base.deliveries array. + config.action_mailer.delivery_method = :test + + # Print deprecation notices to the stderr. + config.active_support.deprecation = :stderr + + # Raises error for missing translations + # config.action_view.raise_on_missing_translations = true +end + +
+
+ +
+
+
+ + + + + + + + + + + + + diff --git a/ABC_Score/config/initializers/application_controller_renderer.html b/ABC_Score/config/initializers/application_controller_renderer.html new file mode 100644 index 00000000..191343c1 --- /dev/null +++ b/ABC_Score/config/initializers/application_controller_renderer.html @@ -0,0 +1,127 @@ + + + + + + Ruby Critic - Home + + + + + + + + + + + + +
+ + + +
+
+
+ +
+
+ + + Updated + + +
+
+

config/initializers / application_controller_renderer.rb

+
+
+ +
+ +
+
+
+
+
+ A +
+
+
+
+
8 lines of codes
+
0 methods
+
+
+
N/A complexity/method
+
1 churn
+
+
+
0.0 complexity
+
0 duplications
+
+
+
+
+
+
+ +
+
+
+ + # Be sure to restart your server when you modify this file. + +# ActiveSupport::Reloader.to_prepare do +# ApplicationController.renderer.defaults.merge!( +# http_host: 'example.org', +# https: false +# ) +# end + +
+
+ +
+
+
+ + + + + + + + + + + + + diff --git a/ABC_Score/config/initializers/assets.html b/ABC_Score/config/initializers/assets.html new file mode 100644 index 00000000..2bc38efd --- /dev/null +++ b/ABC_Score/config/initializers/assets.html @@ -0,0 +1,133 @@ + + + + + + Ruby Critic - Home + + + + + + + + + + + + +
+ + + +
+
+
+ +
+
+ + + Updated + + +
+
+

config/initializers / assets.rb

+
+
+ +
+ +
+
+
+
+
+ A +
+
+
+
+
14 lines of codes
+
0 methods
+
+
+
N/A complexity/method
+
1 churn
+
+
+
0.0 complexity
+
0 duplications
+
+
+
+
+
+
+ +
+
+
+ + # Be sure to restart your server when you modify this file. + +# Version of your assets, change this if you want to expire all your assets. +Rails.application.config.assets.version = '1.0' + +# Add additional assets to the asset load path. +# Rails.application.config.assets.paths << Emoji.images_path +# Add Yarn node_modules folder to the asset load path. +Rails.application.config.assets.paths << Rails.root.join('node_modules') + +# Precompile additional assets. +# application.js, application.css, and all non-JS/CSS in the app/assets +# folder are already added. +# Rails.application.config.assets.precompile += %w( admin.js admin.css ) + +
+
+ +
+
+
+ + + + + + + + + + + + + diff --git a/ABC_Score/config/initializers/backtrace_silencers.html b/ABC_Score/config/initializers/backtrace_silencers.html new file mode 100644 index 00000000..13020bc1 --- /dev/null +++ b/ABC_Score/config/initializers/backtrace_silencers.html @@ -0,0 +1,126 @@ + + + + + + Ruby Critic - Home + + + + + + + + + + + + +
+ + + +
+
+
+ +
+
+ + + Updated + + +
+
+

config/initializers / backtrace_silencers.rb

+
+
+ +
+ +
+
+
+
+
+ A +
+
+
+
+
7 lines of codes
+
0 methods
+
+
+
N/A complexity/method
+
1 churn
+
+
+
0.0 complexity
+
0 duplications
+
+
+
+
+
+
+ +
+
+
+ + # Be sure to restart your server when you modify this file. + +# You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces. +# Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ } + +# You can also remove all the silencers if you're trying to debug a problem that might stem from framework code. +# Rails.backtrace_cleaner.remove_silencers! + +
+
+ +
+
+
+ + + + + + + + + + + + + diff --git a/ABC_Score/config/initializers/content_security_policy.html b/ABC_Score/config/initializers/content_security_policy.html new file mode 100644 index 00000000..637c5731 --- /dev/null +++ b/ABC_Score/config/initializers/content_security_policy.html @@ -0,0 +1,144 @@ + + + + + + Ruby Critic - Home + + + + + + + + + + + + +
+ + + +
+
+
+ +
+
+ + + Updated + + +
+
+

config/initializers / content_security_policy.rb

+
+
+ +
+ +
+
+
+
+
+ A +
+
+
+
+
25 lines of codes
+
0 methods
+
+
+
N/A complexity/method
+
1 churn
+
+
+
0.0 complexity
+
0 duplications
+
+
+
+
+
+
+ +
+
+
+ + # Be sure to restart your server when you modify this file. + +# Define an application-wide content security policy +# For further information see the following documentation +# https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy + +# Rails.application.config.content_security_policy do |policy| +# policy.default_src :self, :https +# policy.font_src :self, :https, :data +# policy.img_src :self, :https, :data +# policy.object_src :none +# policy.script_src :self, :https +# policy.style_src :self, :https + +# # Specify URI for violation reports +# # policy.report_uri "/csp-violation-report-endpoint" +# end + +# If you are using UJS then enable automatic nonce generation +# Rails.application.config.content_security_policy_nonce_generator = -> request { SecureRandom.base64(16) } + +# Report CSP violations to a specified URI +# For further information see the following documentation: +# https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy-Report-Only +# Rails.application.config.content_security_policy_report_only = true + +
+
+ +
+
+
+ + + + + + + + + + + + + diff --git a/ABC_Score/config/initializers/cookies_serializer.html b/ABC_Score/config/initializers/cookies_serializer.html new file mode 100644 index 00000000..9df57e66 --- /dev/null +++ b/ABC_Score/config/initializers/cookies_serializer.html @@ -0,0 +1,124 @@ + + + + + + Ruby Critic - Home + + + + + + + + + + + + +
+ + + +
+
+
+ +
+
+ + + Updated + + +
+
+

config/initializers / cookies_serializer.rb

+
+
+ +
+ +
+
+
+
+
+ A +
+
+
+
+
5 lines of codes
+
0 methods
+
+
+
N/A complexity/method
+
1 churn
+
+
+
0.0 complexity
+
0 duplications
+
+
+
+
+
+
+ +
+
+
+ + # Be sure to restart your server when you modify this file. + +# Specify a serializer for the signed and encrypted cookie jars. +# Valid options are :json, :marshal, and :hybrid. +Rails.application.config.action_dispatch.cookies_serializer = :json + +
+
+ +
+
+
+ + + + + + + + + + + + + diff --git a/ABC_Score/config/initializers/devise.html b/ABC_Score/config/initializers/devise.html new file mode 100644 index 00000000..333b2a89 --- /dev/null +++ b/ABC_Score/config/initializers/devise.html @@ -0,0 +1,418 @@ + + + + + + Ruby Critic - Home + + + + + + + + + + + + +
+ + + +
+
+
+ +
+
+ + + Updated + + +
+
+

config/initializers / devise.rb

+
+
+ +
+ +
+
+
+
+
+ A +
+
+
+
+
299 lines of codes
+
0 methods
+
+
+
N/A complexity/method
+
2 churn
+
+
+
0.0 complexity
+
0 duplications
+
+
+
+
+
+
+ +
+
+
+ + # frozen_string_literal: true + +# Use this hook to configure devise mailer, warden hooks and so forth. +# Many of these configuration options can be set straight in your model. +Devise.setup do |config| + # The secret key used by Devise. Devise uses this key to generate + # random tokens. Changing this key will render invalid all existing + # confirmation, reset password and unlock tokens in the database. + # Devise will use the `secret_key_base` as its `secret_key` + # by default. You can change it below and use your own secret key. + # config.secret_key = '53c9f741be418fcb535205b1faaad3062f2fb772dcf8b618c3fe37a03164092b11cce7e2fd0b2f88d17ebece35eac91546f6f987a3c739ff3681aa5721b55f8a' + + # ==> Controller configuration + # Configure the parent class to the devise controllers. + # config.parent_controller = 'DeviseController' + + # ==> Mailer Configuration + # Configure the e-mail address which will be shown in Devise::Mailer, + # note that it will be overwritten if you use your own mailer class + # with default "from" parameter. + config.mailer_sender = 'please-change-me-at-config-initializers-devise@example.com' + + # Configure the class responsible to send e-mails. + # config.mailer = 'Devise::Mailer' + + # Configure the parent class responsible to send e-mails. + # config.parent_mailer = 'ActionMailer::Base' + + # ==> ORM configuration + # Load and configure the ORM. Supports :active_record (default) and + # :mongoid (bson_ext recommended) by default. Other ORMs may be + # available as additional gems. + require 'devise/orm/active_record' + + # ==> Configuration for any authentication mechanism + # Configure which keys are used when authenticating a user. The default is + # just :email. You can configure it to use [:username, :subdomain], so for + # authenticating a user, both parameters are required. Remember that those + # parameters are used only when authenticating and not when retrieving from + # session. If you need permissions, you should implement that in a before filter. + # You can also supply a hash where the value is a boolean determining whether + # or not authentication should be aborted when the value is not present. + # config.authentication_keys = [:email] + + # Configure parameters from the request object used for authentication. Each entry + # given should be a request method and it will automatically be passed to the + # find_for_authentication method and considered in your model lookup. For instance, + # if you set :request_keys to [:subdomain], :subdomain will be used on authentication. + # The same considerations mentioned for authentication_keys also apply to request_keys. + # config.request_keys = [] + + # Configure which authentication keys should be case-insensitive. + # These keys will be downcased upon creating or modifying a user and when used + # to authenticate or find a user. Default is :email. + config.case_insensitive_keys = [:email] + + # Configure which authentication keys should have whitespace stripped. + # These keys will have whitespace before and after removed upon creating or + # modifying a user and when used to authenticate or find a user. Default is :email. + config.strip_whitespace_keys = [:email] + + # Tell if authentication through request.params is enabled. True by default. + # It can be set to an array that will enable params authentication only for the + # given strategies, for example, `config.params_authenticatable = [:database]` will + # enable it only for database (email + password) authentication. + # config.params_authenticatable = true + + # Tell if authentication through HTTP Auth is enabled. False by default. + # It can be set to an array that will enable http authentication only for the + # given strategies, for example, `config.http_authenticatable = [:database]` will + # enable it only for database authentication. The supported strategies are: + # :database = Support basic authentication with authentication key + password + # config.http_authenticatable = false + + # If 401 status code should be returned for AJAX requests. True by default. + # config.http_authenticatable_on_xhr = true + + # The realm used in Http Basic Authentication. 'Application' by default. + # config.http_authentication_realm = 'Application' + + # It will change confirmation, password recovery and other workflows + # to behave the same regardless if the e-mail provided was right or wrong. + # Does not affect registerable. + # config.paranoid = true + + # By default Devise will store the user in session. You can skip storage for + # particular strategies by setting this option. + # Notice that if you are skipping storage for all authentication paths, you + # may want to disable generating routes to Devise's sessions controller by + # passing skip: :sessions to `devise_for` in your config/routes.rb + config.skip_session_storage = [:http_auth] + + # By default, Devise cleans up the CSRF token on authentication to + # avoid CSRF token fixation attacks. This means that, when using AJAX + # requests for sign in and sign up, you need to get a new CSRF token + # from the server. You can disable this option at your own risk. + # config.clean_up_csrf_token_on_authentication = true + + # When false, Devise will not attempt to reload routes on eager load. + # This can reduce the time taken to boot the app but if your application + # requires the Devise mappings to be loaded during boot time the application + # won't boot properly. + # config.reload_routes = true + + # ==> Configuration for :database_authenticatable + # For bcrypt, this is the cost for hashing the password and defaults to 11. If + # using other algorithms, it sets how many times you want the password to be hashed. + # + # Limiting the stretches to just one in testing will increase the performance of + # your test suite dramatically. However, it is STRONGLY RECOMMENDED to not use + # a value less than 10 in other environments. Note that, for bcrypt (the default + # algorithm), the cost increases exponentially with the number of stretches (e.g. + # a value of 20 is already extremely slow: approx. 60 seconds for 1 calculation). + config.stretches = Rails.env.test? ? 1 : 11 + + # Set up a pepper to generate the hashed password. + # config.pepper = '30f4c027405b92d0b7d5f25895b625a97791988e1867e0b79fb3d5c05b0ffff2ace19181de61c481bdb477e1c2fff8be2203afd78336ce261b97e9296f2259e9' + + # Send a notification to the original email when the user's email is changed. + # config.send_email_changed_notification = false + + # Send a notification email when the user's password is changed. + # config.send_password_change_notification = false + + # ==> Configuration for :confirmable + # A period that the user is allowed to access the website even without + # confirming their account. For instance, if set to 2.days, the user will be + # able to access the website for two days without confirming their account, + # access will be blocked just in the third day. + # You can also set it to nil, which will allow the user to access the website + # without confirming their account. + # Default is 0.days, meaning the user cannot access the website without + # confirming their account. + # config.allow_unconfirmed_access_for = 2.days + + # A period that the user is allowed to confirm their account before their + # token becomes invalid. For example, if set to 3.days, the user can confirm + # their account within 3 days after the mail was sent, but on the fourth day + # their account can't be confirmed with the token any more. + # Default is nil, meaning there is no restriction on how long a user can take + # before confirming their account. + # config.confirm_within = 3.days + + # If true, requires any email changes to be confirmed (exactly the same way as + # initial account confirmation) to be applied. Requires additional unconfirmed_email + # db field (see migrations). Until confirmed, new email is stored in + # unconfirmed_email column, and copied to email column on successful confirmation. + config.reconfirmable = true + + # Defines which key will be used when confirming an account + # config.confirmation_keys = [:email] + + # ==> Configuration for :rememberable + # The time the user will be remembered without asking for credentials again. + # config.remember_for = 2.weeks + + # Invalidates all the remember me tokens when the user signs out. + config.expire_all_remember_me_on_sign_out = true + + # If true, extends the user's remember period when remembered via cookie. + # config.extend_remember_period = false + + # Options to be passed to the created cookie. For instance, you can set + # secure: true in order to force SSL only cookies. + # config.rememberable_options = {} + + # ==> Configuration for :validatable + # Range for password length. + config.password_length = 6..128 + + # Email regex used to validate email formats. It simply asserts that + # one (and only one) @ exists in the given string. This is mainly + # to give user feedback and not to assert the e-mail validity. + config.email_regexp = /\A[^@\s]+@[^@\s]+\z/ + + # ==> Configuration for :timeoutable + # The time you want to timeout the user session without activity. After this + # time the user will be asked for credentials again. Default is 30 minutes. + # config.timeout_in = 30.minutes + + # ==> Configuration for :lockable + # Defines which strategy will be used to lock an account. + # :failed_attempts = Locks an account after a number of failed attempts to sign in. + # :none = No lock strategy. You should handle locking by yourself. + # config.lock_strategy = :failed_attempts + + # Defines which key will be used when locking and unlocking an account + # config.unlock_keys = [:email] + + # Defines which strategy will be used to unlock an account. + # :email = Sends an unlock link to the user email + # :time = Re-enables login after a certain amount of time (see :unlock_in below) + # :both = Enables both strategies + # :none = No unlock strategy. You should handle unlocking by yourself. + # config.unlock_strategy = :both + + # Number of authentication tries before locking an account if lock_strategy + # is failed attempts. + # config.maximum_attempts = 20 + + # Time interval to unlock the account if :time is enabled as unlock_strategy. + # config.unlock_in = 1.hour + + # Warn on the last attempt before the account is locked. + # config.last_attempt_warning = true + + # ==> Configuration for :recoverable + # + # Defines which key will be used when recovering the password for an account + # config.reset_password_keys = [:email] + + # Time interval you can reset your password with a reset password key. + # Don't put a too small interval or your users won't have the time to + # change their passwords. + config.reset_password_within = 6.hours + + # When set to false, does not sign a user in automatically after their password is + # reset. Defaults to true, so a user is signed in automatically after a reset. + # config.sign_in_after_reset_password = true + + # ==> Configuration for :encryptable + # Allow you to use another hashing or encryption algorithm besides bcrypt (default). + # You can use :sha1, :sha512 or algorithms from others authentication tools as + # :clearance_sha1, :authlogic_sha512 (then you should set stretches above to 20 + # for default behavior) and :restful_authentication_sha1 (then you should set + # stretches to 10, and copy REST_AUTH_SITE_KEY to pepper). + # + # Require the `devise-encryptable` gem when using anything other than bcrypt + # config.encryptor = :sha512 + + # ==> Scopes configuration + # Turn scoped views on. Before rendering "sessions/new", it will first check for + # "users/sessions/new". It's turned off by default because it's slower if you + # are using only default views. + # config.scoped_views = false + + # Configure the default scope given to Warden. By default it's the first + # devise role declared in your routes (usually :user). + # config.default_scope = :user + + # Set this configuration to false if you want /users/sign_out to sign out + # only the current scope. By default, Devise signs out all scopes. + # config.sign_out_all_scopes = true + + # ==> Navigation configuration + # Lists the formats that should be treated as navigational. Formats like + # :html, should redirect to the sign in page when the user does not have + # access, but formats like :xml or :json, should return 401. + # + # If you have any extra navigational formats, like :iphone or :mobile, you + # should add them to the navigational formats lists. + # + # The "*/*" below is required to match Internet Explorer requests. + # config.navigational_formats = ['*/*', :html] + + # The default HTTP method used to sign out a resource. Default is :delete. + config.sign_out_via = :delete + + # ==> OmniAuth + # Add a new OmniAuth provider. Check the wiki for more information on setting + # up on your models and hooks. + # config.omniauth :github, 'APP_ID', 'APP_SECRET', scope: 'user,public_repo' + + # ==> Warden configuration + # If you want to use other strategies, that are not supported by Devise, or + # change the failure app, you can configure them inside the config.warden block. + # + # config.warden do |manager| + # manager.intercept_401 = false + # manager.default_strategies(scope: :user).unshift :some_external_strategy + # end + + # ==> Mountable engine configurations + # When using Devise inside an engine, let's call it `MyEngine`, and this engine + # is mountable, there are some extra configurations to be taken into account. + # The following options are available, assuming the engine is mounted as: + # + # mount MyEngine, at: '/my_engine' + # + # The router that invoked `devise_for`, in the example above, would be: + # config.router_name = :my_engine + # + # When using OmniAuth, Devise cannot automatically set OmniAuth path, + # so you need to do it manually. For the users scope, it would be: + # config.omniauth_path_prefix = '/my_engine/users/auth' + + # ==> Turbolinks configuration + # If your app is using Turbolinks, Turbolinks::Controller needs to be included to make redirection work correctly: + # + # ActiveSupport.on_load(:devise_failure_app) do + # include Turbolinks::Controller + # end + + # ==> Configuration for :registerable + + # When set to false, does not sign a user in automatically after their password is + # changed. Defaults to true, so a user is signed in automatically after changing a password. + # config.sign_in_after_change_password = true +end + +
+
+ +
+
+
+ + + + + + + + + + + + + diff --git a/ABC_Score/config/initializers/filter_parameter_logging.html b/ABC_Score/config/initializers/filter_parameter_logging.html new file mode 100644 index 00000000..3a92cc6e --- /dev/null +++ b/ABC_Score/config/initializers/filter_parameter_logging.html @@ -0,0 +1,123 @@ + + + + + + Ruby Critic - Home + + + + + + + + + + + + +
+ + + +
+
+
+ +
+
+ + + Updated + + +
+
+

config/initializers / filter_parameter_logging.rb

+
+
+ +
+ +
+
+
+
+
+ A +
+
+
+
+
4 lines of codes
+
0 methods
+
+
+
N/A complexity/method
+
1 churn
+
+
+
0.0 complexity
+
0 duplications
+
+
+
+
+
+
+ +
+
+
+ + # Be sure to restart your server when you modify this file. + +# Configure sensitive parameters which will be filtered from the log file. +Rails.application.config.filter_parameters += [:password] + +
+
+ +
+
+
+ + + + + + + + + + + + + diff --git a/ABC_Score/config/initializers/inflections.html b/ABC_Score/config/initializers/inflections.html new file mode 100644 index 00000000..4c35bfc5 --- /dev/null +++ b/ABC_Score/config/initializers/inflections.html @@ -0,0 +1,135 @@ + + + + + + Ruby Critic - Home + + + + + + + + + + + + +
+ + + +
+
+
+ +
+
+ + + Updated + + +
+
+

config/initializers / inflections.rb

+
+
+ +
+ +
+
+
+
+
+ A +
+
+
+
+
16 lines of codes
+
0 methods
+
+
+
N/A complexity/method
+
1 churn
+
+
+
0.0 complexity
+
0 duplications
+
+
+
+
+
+
+ +
+
+
+ + # Be sure to restart your server when you modify this file. + +# Add new inflection rules using the following format. Inflections +# are locale specific, and you may define rules for as many different +# locales as you wish. All of these examples are active by default: +# ActiveSupport::Inflector.inflections(:en) do |inflect| +# inflect.plural /^(ox)$/i, '\1en' +# inflect.singular /^(ox)en/i, '\1' +# inflect.irregular 'person', 'people' +# inflect.uncountable %w( fish sheep ) +# end + +# These inflection rules are supported but not enabled by default: +# ActiveSupport::Inflector.inflections(:en) do |inflect| +# inflect.acronym 'RESTful' +# end + +
+
+ +
+
+
+ + + + + + + + + + + + + diff --git a/ABC_Score/config/initializers/mime_types.html b/ABC_Score/config/initializers/mime_types.html new file mode 100644 index 00000000..117c12a6 --- /dev/null +++ b/ABC_Score/config/initializers/mime_types.html @@ -0,0 +1,123 @@ + + + + + + Ruby Critic - Home + + + + + + + + + + + + +
+ + + +
+
+
+ +
+
+ + + Updated + + +
+
+

config/initializers / mime_types.rb

+
+
+ +
+ +
+
+
+
+
+ A +
+
+
+
+
4 lines of codes
+
0 methods
+
+
+
N/A complexity/method
+
1 churn
+
+
+
0.0 complexity
+
0 duplications
+
+
+
+
+
+
+ +
+
+
+ + # Be sure to restart your server when you modify this file. + +# Add new mime types for use in respond_to blocks: +# Mime::Type.register "text/richtext", :rtf + +
+
+ +
+
+
+ + + + + + + + + + + + + diff --git a/ABC_Score/config/initializers/wrap_parameters.html b/ABC_Score/config/initializers/wrap_parameters.html new file mode 100644 index 00000000..740ba38a --- /dev/null +++ b/ABC_Score/config/initializers/wrap_parameters.html @@ -0,0 +1,133 @@ + + + + + + Ruby Critic - Home + + + + + + + + + + + + +
+ + + +
+
+
+ +
+
+ + + Updated + + +
+
+

config/initializers / wrap_parameters.rb

+
+
+ +
+ +
+
+
+
+
+ A +
+
+
+
+
14 lines of codes
+
0 methods
+
+
+
N/A complexity/method
+
1 churn
+
+
+
0.0 complexity
+
0 duplications
+
+
+
+
+
+
+ +
+
+
+ + # Be sure to restart your server when you modify this file. + +# This file contains settings for ActionController::ParamsWrapper which +# is enabled by default. + +# Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array. +ActiveSupport.on_load(:action_controller) do + wrap_parameters format: [:json] +end + +# To enable root element in JSON for ActiveRecord objects. +# ActiveSupport.on_load(:active_record) do +# self.include_root_in_json = true +# end + +
+
+ +
+
+
+ + + + + + + + + + + + + diff --git a/ABC_Score/config/puma.html b/ABC_Score/config/puma.html new file mode 100644 index 00000000..7cf4ff19 --- /dev/null +++ b/ABC_Score/config/puma.html @@ -0,0 +1,153 @@ + + + + + + Ruby Critic - Home + + + + + + + + + + + + +
+ + + +
+
+
+ +
+
+ + + Updated + + +
+
+

config / puma.rb

+
+
+ +
+ +
+
+
+
+
+ A +
+
+
+
+
34 lines of codes
+
0 methods
+
+
+
N/A complexity/method
+
1 churn
+
+
+
0.0 complexity
+
0 duplications
+
+
+
+
+
+
+ +
+
+
+ + # Puma can serve each request in a thread from an internal thread pool. +# The `threads` method setting takes two numbers: a minimum and maximum. +# Any libraries that use thread pools should be configured to match +# the maximum value specified for Puma. Default is set to 5 threads for minimum +# and maximum; this matches the default thread size of Active Record. +# +threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 } +threads threads_count, threads_count + +# Specifies the `port` that Puma will listen on to receive requests; default is 3000. +# +port ENV.fetch("PORT") { 3000 } + +# Specifies the `environment` that Puma will run in. +# +environment ENV.fetch("RAILS_ENV") { "development" } + +# Specifies the number of `workers` to boot in clustered mode. +# Workers are forked webserver processes. If using threads and workers together +# the concurrency of the application would be max `threads` * `workers`. +# Workers do not work on JRuby or Windows (both of which do not support +# processes). +# +# workers ENV.fetch("WEB_CONCURRENCY") { 2 } + +# Use the `preload_app!` method when specifying a `workers` number. +# This directive tells Puma to first boot the application and load code +# before forking the application. This takes advantage of Copy On Write +# process behavior so workers use less memory. +# +# preload_app! + +# Allow puma to be restarted by `rails restart` command. +plugin :tmp_restart + +
+
+ +
+
+
+ + + + + + + + + + + + + diff --git a/ABC_Score/config/routes.html b/ABC_Score/config/routes.html new file mode 100644 index 00000000..989bbfb3 --- /dev/null +++ b/ABC_Score/config/routes.html @@ -0,0 +1,136 @@ + + + + + + Ruby Critic - Home + + + + + + + + + + + + +
+ + + +
+
+
+ +
+
+ + + Updated + + +
+
+

config / routes.rb

+
+
+ +
+ +
+
+
+
+
+ A +
+
+
+
+
17 lines of codes
+
0 methods
+
+
+
N/A complexity/method
+
39 churn
+
+
+
3.89 complexity
+
0 duplications
+
+
+
+
+
+
+ +
+
+
+ + # frozen_string_literal: true + +Rails.application.routes.draw do + resources :accreditations + resources :sei_processes + + resources :requirements do + member do + delete :delete_document_attachment + end + end + + get 'home/index' + devise_for :users + # For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html + root to: 'home#index' +end + +
+
+ +
+
+
+ + + + + + + + + + + + + diff --git a/ABC_Score/config/spring.html b/ABC_Score/config/spring.html new file mode 100644 index 00000000..da676339 --- /dev/null +++ b/ABC_Score/config/spring.html @@ -0,0 +1,125 @@ + + + + + + Ruby Critic - Home + + + + + + + + + + + + +
+ + + +
+
+
+ +
+
+ + + Updated + + +
+
+

config / spring.rb

+
+
+ +
+ +
+
+
+
+
+ A +
+
+
+
+
6 lines of codes
+
0 methods
+
+
+
N/A complexity/method
+
1 churn
+
+
+
0.0 complexity
+
0 duplications
+
+
+
+
+
+
+ +
+
+
+ + %w[ + .ruby-version + .rbenv-vars + tmp/restart.txt + tmp/caching-dev.txt +].each { |path| Spring.watch(path) } + +
+
+ +
+
+
+ + + + + + + + + + + + + diff --git a/ABC_Score/db/migrate/20191114162918_devise_create_users.html b/ABC_Score/db/migrate/20191114162918_devise_create_users.html new file mode 100644 index 00000000..d88d9fe7 --- /dev/null +++ b/ABC_Score/db/migrate/20191114162918_devise_create_users.html @@ -0,0 +1,163 @@ + + + + + + Ruby Critic - Home + + + + + + + + + + + + +
+ + + +
+
+
+ +
+
+ + + Updated + + +
+
+

db/migrate / 20191114162918_devise_create_users.rb

+
+
+ +
+ +
+
+
+
+
+ A +
+
+
+
+
44 lines of codes
+
1 methods
+
+
+
10.3 complexity/method
+
4 churn
+
+
+
10.25 complexity
+
0 duplications
+
+
+
+
+
+
+ +
+
+
+ + # frozen_string_literal: true + +class DeviseCreateUsers < ActiveRecord::Migration[5.2]
  1. DeviseCreateUsers has no descriptive comment
+ def change
  1. DeviseCreateUsers#change has approx 9 statements
+ create_table :users do |t|
  1. DeviseCreateUsers#change has the variable name 't'
+ ## Database authenticatable + t.string :email, null: false, default: ""
  1. DeviseCreateUsers#change refers to 't' more than self (maybe move it to another class?) Locations: 0 1 2 3 4 5
+ t.string :encrypted_password, null: false, default: ""
  1. DeviseCreateUsers#change refers to 't' more than self (maybe move it to another class?) Locations: 0 1 2 3 4 5
+ + ## Recoverable + t.string :reset_password_token
  1. DeviseCreateUsers#change refers to 't' more than self (maybe move it to another class?) Locations: 0 1 2 3 4 5
+ t.datetime :reset_password_sent_at
  1. DeviseCreateUsers#change refers to 't' more than self (maybe move it to another class?) Locations: 0 1 2 3 4 5
+ + ## Rememberable + t.datetime :remember_created_at
  1. DeviseCreateUsers#change refers to 't' more than self (maybe move it to another class?) Locations: 0 1 2 3 4 5
+ + ## Trackable + # t.integer :sign_in_count, default: 0, null: false + # t.datetime :current_sign_in_at + # t.datetime :last_sign_in_at + # t.inet :current_sign_in_ip + # t.inet :last_sign_in_ip + + ## Confirmable + # t.string :confirmation_token + # t.datetime :confirmed_at + # t.datetime :confirmation_sent_at + # t.string :unconfirmed_email # Only if using reconfirmable + + ## Lockable + # t.integer :failed_attempts, default: 0, null: false # Only if lock strategy is :failed_attempts + # t.string :unlock_token # Only if unlock strategy is :email or :both + # t.datetime :locked_at + + + t.timestamps null: false
  1. DeviseCreateUsers#change refers to 't' more than self (maybe move it to another class?) Locations: 0 1 2 3 4 5
+ end + + add_index :users, :email, unique: true + add_index :users, :reset_password_token, unique: true + # add_index :users, :confirmation_token, unique: true + # add_index :users, :unlock_token, unique: true + end +end +
+
+
+ +
+
+
+ + + + + + + + + + + + + diff --git a/ABC_Score/db/migrate/20191114163205_add_info_to_users.html b/ABC_Score/db/migrate/20191114163205_add_info_to_users.html new file mode 100644 index 00000000..f3b37949 --- /dev/null +++ b/ABC_Score/db/migrate/20191114163205_add_info_to_users.html @@ -0,0 +1,126 @@ + + + + + + Ruby Critic - Home + + + + + + + + + + + + +
+ + + +
+
+
+ +
+
+ + + Updated + + +
+
+

db/migrate / 20191114163205_add_info_to_users.rb

+
+
+ +
+ +
+
+
+
+
+ A +
+
+
+
+
7 lines of codes
+
1 methods
+
+
+
3.0 complexity/method
+
2 churn
+
+
+
3.0 complexity
+
0 duplications
+
+
+
+
+
+
+ +
+
+
+ + class AddInfoToUsers < ActiveRecord::Migration[5.2]
  1. AddInfoToUsers has no descriptive comment
+ def change + add_column :users, :full_name, :string + add_column :users, :registration, :string + add_column :users, :role, :integer + end +end +
+
+
+ +
+
+
+ + + + + + + + + + + + + diff --git a/ABC_Score/db/migrate/20201113221041_create_requirements.html b/ABC_Score/db/migrate/20201113221041_create_requirements.html new file mode 100644 index 00000000..15405ef5 --- /dev/null +++ b/ABC_Score/db/migrate/20201113221041_create_requirements.html @@ -0,0 +1,129 @@ + + + + + + Ruby Critic - Home + + + + + + + + + + + + +
+ + + +
+
+
+ +
+
+ + + Updated + + +
+
+

db/migrate / 20201113221041_create_requirements.rb

+
+
+ +
+ +
+
+
+
+
+ A +
+
+
+
+
10 lines of codes
+
1 methods
+
+
+
4.7 complexity/method
+
1 churn
+
+
+
4.71 complexity
+
0 duplications
+
+
+
+
+
+
+ +
+
+
+ + class CreateRequirements < ActiveRecord::Migration[5.2]
  1. CreateRequirements has no descriptive comment
+ def change + create_table :requirements do |t|
  1. CreateRequirements#change has the variable name 't'
+ t.string :title
  1. CreateRequirements#change refers to 't' more than self (maybe move it to another class?) Locations: 0 1 2
+ t.text :content
  1. CreateRequirements#change refers to 't' more than self (maybe move it to another class?) Locations: 0 1 2
+ + t.timestamps
  1. CreateRequirements#change refers to 't' more than self (maybe move it to another class?) Locations: 0 1 2
+ end + end +end +
+
+
+ +
+
+
+ + + + + + + + + + + + + diff --git a/ABC_Score/db/migrate/20201113222004_create_sei_processes.html b/ABC_Score/db/migrate/20201113222004_create_sei_processes.html new file mode 100644 index 00000000..2344fb1e --- /dev/null +++ b/ABC_Score/db/migrate/20201113222004_create_sei_processes.html @@ -0,0 +1,130 @@ + + + + + + Ruby Critic - Home + + + + + + + + + + + + +
+ + + +
+
+
+ +
+
+ + + Updated + + +
+
+

db/migrate / 20201113222004_create_sei_processes.rb

+
+
+ +
+ +
+
+
+
+
+ A +
+
+
+
+
11 lines of codes
+
1 methods
+
+
+
5.9 complexity/method
+
1 churn
+
+
+
5.89 complexity
+
0 duplications
+
+
+
+
+
+
+ +
+
+
+ + class CreateSeiProcesses < ActiveRecord::Migration[5.2]
  1. CreateSeiProcesses has no descriptive comment
+ def change + create_table :sei_processes do |t|
  1. CreateSeiProcesses#change has the variable name 't'
+ t.belongs_to :user, foreign_key: true
  1. CreateSeiProcesses#change refers to 't' more than self (maybe move it to another class?) Locations: 0 1 2 3
+ t.integer :status
  1. CreateSeiProcesses#change refers to 't' more than self (maybe move it to another class?) Locations: 0 1 2 3
+ t.string :code
  1. CreateSeiProcesses#change refers to 't' more than self (maybe move it to another class?) Locations: 0 1 2 3
+ + t.timestamps
  1. CreateSeiProcesses#change refers to 't' more than self (maybe move it to another class?) Locations: 0 1 2 3
+ end + end +end +
+
+
+ +
+
+
+ + + + + + + + + + + + + diff --git a/ABC_Score/db/migrate/20201113222556_create_active_storage_tables.active_storage.html b/ABC_Score/db/migrate/20201113222556_create_active_storage_tables.active_storage.html new file mode 100644 index 00000000..ae6c6cf6 --- /dev/null +++ b/ABC_Score/db/migrate/20201113222556_create_active_storage_tables.active_storage.html @@ -0,0 +1,146 @@ + + + + + + Ruby Critic - Home + + + + + + + + + + + + +
+ + + +
+
+
+ +
+
+ + + Updated + + +
+
+

db/migrate / 20201113222556_create_active_storage_tables.active_storage.rb

+
+
+ +
+ +
+
+
+
+
+ A +
+
+
+
+
27 lines of codes
+
1 methods
+
+
+
18.9 complexity/method
+
2 churn
+
+
+
18.91 complexity
+
0 duplications
+
+
+
+
+
+
+ +
+
+
+ + # This migration comes from active_storage (originally 20170806125915) +class CreateActiveStorageTables < ActiveRecord::Migration[5.2] + def change
  1. CreateActiveStorageTables#change has approx 16 statements
+ create_table :active_storage_blobs do |t|
  1. CreateActiveStorageTables#change has the variable name 't' Locations: 0 1
+ t.string :key, null: false
  1. CreateActiveStorageTables#change refers to 't' more than self (maybe move it to another class?) Locations: 0 1 2 3 4 5 6 7 8 9 10 11 12 13
+ t.string :filename, null: false
  1. CreateActiveStorageTables#change refers to 't' more than self (maybe move it to another class?) Locations: 0 1 2 3 4 5 6 7 8 9 10 11 12 13
+ t.string :content_type
  1. CreateActiveStorageTables#change refers to 't' more than self (maybe move it to another class?) Locations: 0 1 2 3 4 5 6 7 8 9 10 11 12 13
+ t.text :metadata
  1. CreateActiveStorageTables#change refers to 't' more than self (maybe move it to another class?) Locations: 0 1 2 3 4 5 6 7 8 9 10 11 12 13
+ t.bigint :byte_size, null: false
  1. CreateActiveStorageTables#change refers to 't' more than self (maybe move it to another class?) Locations: 0 1 2 3 4 5 6 7 8 9 10 11 12 13
+ t.string :checksum, null: false
  1. CreateActiveStorageTables#change refers to 't' more than self (maybe move it to another class?) Locations: 0 1 2 3 4 5 6 7 8 9 10 11 12 13
+ t.datetime :created_at, null: false
  1. CreateActiveStorageTables#change calls 't.datetime :created_at, null: false' 2 times Locations: 0 1
  2. CreateActiveStorageTables#change refers to 't' more than self (maybe move it to another class?) Locations: 0 1 2 3 4 5 6 7 8 9 10 11 12 13
+ + t.index [ :key ], unique: true
  1. CreateActiveStorageTables#change refers to 't' more than self (maybe move it to another class?) Locations: 0 1 2 3 4 5 6 7 8 9 10 11 12 13
+ end + + create_table :active_storage_attachments do |t|
  1. CreateActiveStorageTables#change has the variable name 't' Locations: 0 1
+ t.string :name, null: false
  1. CreateActiveStorageTables#change refers to 't' more than self (maybe move it to another class?) Locations: 0 1 2 3 4 5 6 7 8 9 10 11 12 13
+ t.references :record, null: false, polymorphic: true, index: false
  1. CreateActiveStorageTables#change refers to 't' more than self (maybe move it to another class?) Locations: 0 1 2 3 4 5 6 7 8 9 10 11 12 13
+ t.references :blob, null: false
  1. CreateActiveStorageTables#change refers to 't' more than self (maybe move it to another class?) Locations: 0 1 2 3 4 5 6 7 8 9 10 11 12 13
+ + t.datetime :created_at, null: false
  1. CreateActiveStorageTables#change calls 't.datetime :created_at, null: false' 2 times Locations: 0 1
  2. CreateActiveStorageTables#change refers to 't' more than self (maybe move it to another class?) Locations: 0 1 2 3 4 5 6 7 8 9 10 11 12 13
+ + t.index [ :record_type, :record_id, :name, :blob_id ], name: "index_active_storage_attachments_uniqueness", unique: true
  1. CreateActiveStorageTables#change refers to 't' more than self (maybe move it to another class?) Locations: 0 1 2 3 4 5 6 7 8 9 10 11 12 13
+ t.foreign_key :active_storage_blobs, column: :blob_id
  1. CreateActiveStorageTables#change refers to 't' more than self (maybe move it to another class?) Locations: 0 1 2 3 4 5 6 7 8 9 10 11 12 13
+ end + end +end +
+
+
+ +
+
+
+ + + + + + + + + + + + + diff --git a/ABC_Score/db/migrate/20201114202425_create_accreditations.html b/ABC_Score/db/migrate/20201114202425_create_accreditations.html new file mode 100644 index 00000000..014c9c15 --- /dev/null +++ b/ABC_Score/db/migrate/20201114202425_create_accreditations.html @@ -0,0 +1,131 @@ + + + + + + Ruby Critic - Home + + + + + + + + + + + + +
+ + + +
+
+
+ +
+
+ + + Updated + + +
+
+

db/migrate / 20201114202425_create_accreditations.rb

+
+
+ +
+ +
+
+
+
+
+ A +
+
+
+
+
12 lines of codes
+
1 methods
+
+
+
7.1 complexity/method
+
2 churn
+
+
+
7.07 complexity
+
0 duplications
+
+
+
+
+
+
+ +
+
+
+ + class CreateAccreditations < ActiveRecord::Migration[5.2]
  1. CreateAccreditations has no descriptive comment
+ def change
  1. CreateAccreditations#change has approx 6 statements
+ create_table :accreditations do |t|
  1. CreateAccreditations#change has the variable name 't'
+ t.belongs_to :user, foreign_key: true
  1. CreateAccreditations#change refers to 't' more than self (maybe move it to another class?) Locations: 0 1 2 3 4
+ t.date :start_date
  1. CreateAccreditations#change refers to 't' more than self (maybe move it to another class?) Locations: 0 1 2 3 4
+ t.date :end_date
  1. CreateAccreditations#change refers to 't' more than self (maybe move it to another class?) Locations: 0 1 2 3 4
+ t.references :sei_process, foreign_key: true
  1. CreateAccreditations#change refers to 't' more than self (maybe move it to another class?) Locations: 0 1 2 3 4
+ + t.timestamps
  1. CreateAccreditations#change refers to 't' more than self (maybe move it to another class?) Locations: 0 1 2 3 4
+ end + end +end +
+
+
+ +
+
+
+ + + + + + + + + + + + + diff --git a/ABC_Score/db/schema.html b/ABC_Score/db/schema.html new file mode 100644 index 00000000..090eae03 --- /dev/null +++ b/ABC_Score/db/schema.html @@ -0,0 +1,207 @@ + + + + + + Ruby Critic - Home + + + + + + + + + + + + +
+ + + +
+
+
+ +
+
+ + + Updated + + +
+
+

db / schema.rb

+
+
+ +
+ +
+
+
+
+
+ B +
+
+
+
+
88 lines of codes
+
0 methods
+
+
+
N/A complexity/method
+
39 churn
+
+
+
65.7 complexity
+
0 duplications
+
+
+
+
+
+
+ +
+
+
+ + # This file is auto-generated from the current state of the database. Instead +# of editing this file, please use the migrations feature of Active Record to +# incrementally modify your database, and then regenerate this schema definition. +# +# Note that this schema.rb definition is the authoritative source for your +# database schema. If you need to create the application database on another +# system, you should be using db:schema:load, not running all the migrations +# from scratch. The latter is a flawed and unsustainable approach (the more migrations +# you'll amass, the slower it'll run and the greater likelihood for issues). +# +# It's strongly recommended that you check this file into your version control system. + +ActiveRecord::Schema.define(version: 2020_11_14_202425) do + + # These are extensions that must be enabled in order to support this database + enable_extension "plpgsql" + + # Cria tabela de Credenciamento + create_table "accreditations", force: :cascade do |t| + t.bigint "user_id" + t.date "start_date" + t.date "end_date" + t.bigint "sei_process_id" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["sei_process_id"], name: "index_accreditations_on_sei_process_id" + t.index ["user_id"], name: "index_accreditations_on_user_id" + end + + create_table "active_storage_attachments", force: :cascade do |t| + t.string "name", null: false + t.string "record_type", null: false + t.bigint "record_id", null: false + t.bigint "blob_id", null: false + t.datetime "created_at", null: false + t.index ["blob_id"], name: "index_active_storage_attachments_on_blob_id" + t.index ["record_type", "record_id", "name", "blob_id"], name: "index_active_storage_attachments_uniqueness", unique: true + end + + create_table "active_storage_blobs", force: :cascade do |t| + t.string "key", null: false + t.string "filename", null: false + t.string "content_type" + t.text "metadata" + t.bigint "byte_size", null: false + t.string "checksum", null: false + t.datetime "created_at", null: false + t.index ["key"], name: "index_active_storage_blobs_on_key", unique: true + end + + # Cria tabela de Requisitos + create_table "requirements", force: :cascade do |t| + t.string "title" + t.text "content" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end + + # Cria tabela de Processos Sei + create_table "sei_processes", force: :cascade do |t| + t.bigint "user_id" + t.integer "status" + t.string "code" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["user_id"], name: "index_sei_processes_on_user_id" + end + + create_table "users", force: :cascade do |t| + t.string "email", default: "", null: false + t.string "encrypted_password", default: "", null: false + t.string "reset_password_token" + t.datetime "reset_password_sent_at" + t.datetime "remember_created_at" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.string "full_name" + t.string "registration" + t.integer "role" + t.index ["email"], name: "index_users_on_email", unique: true + t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true + end + + add_foreign_key "accreditations", "sei_processes" + add_foreign_key "accreditations", "users" + add_foreign_key "active_storage_attachments", "active_storage_blobs", column: "blob_id" + add_foreign_key "sei_processes", "users" +end + +
+
+ +
+
+
+ + + + + + + + + + + + + diff --git a/ABC_Score/db/seeds.html b/ABC_Score/db/seeds.html new file mode 100644 index 00000000..dc8f2fa2 --- /dev/null +++ b/ABC_Score/db/seeds.html @@ -0,0 +1,145 @@ + + + + + + Ruby Critic - Home + + + + + + + + + + + + +
+ + + +
+
+
+ +
+
+ + + Updated + + +
+
+

db / seeds.rb

+
+
+ +
+ +
+
+
+
+
+ A +
+
+
+
+
26 lines of codes
+
1 methods
+
+
+
3.9 complexity/method
+
21 churn
+
+
+
3.86 complexity
+
0 duplications
+
+
+
+
+
+
+ +
+
+
+ + # This file should contain all the record creation needed to seed the database with its default values. +# The data can then be loaded with the rails db:seed command (or created alongside the database with db:setup). +# +# Examples: +# +# movies = Movie.create([{ name: 'Star Wars' }, { name: 'Lord of the Rings' }]) +# Character.create(name: 'Luke', movie: movies.first) + +def destroy_records class_name
  1. destroy_records doesn't depend on instance state (maybe move it to another class?)
+ class_name.each do |obj| + obj.allow_deletion! + obj.destroy + end +end + +# All Stuff +destroy_records(Requirement.all) +destroy_records(Accreditation.all) +destroy_records(SeiProcess.all) + +# Users +User.destroy_all +User.create(full_name: "Administrador", email: "admin@admin.com", password: "admin123", role: "administrator", registration: "000000000") +User.create(full_name: "Secretário", email: "secretary@secretary.com", password: "admin123", role: "secretary", registration: "000000000") +User.create(full_name: "Professor", email: "professor@professor.com", password: "admin123", role: "professor", registration: "000000000") +User.create(full_name: "Aluno", email: "student@student.com", password: "admin123", role: "student", registration: "000000000") +
+
+
+ +
+
+
+ + + + + + + + + + + + + diff --git a/ABC_Score/features/step_definitions/credenciamento_professores_steps.html b/ABC_Score/features/step_definitions/credenciamento_professores_steps.html new file mode 100644 index 00000000..2659e536 --- /dev/null +++ b/ABC_Score/features/step_definitions/credenciamento_professores_steps.html @@ -0,0 +1,334 @@ + + + + + + Ruby Critic - Home + + + + + + + + + + + + +
+ + + +
+
+
+ +
+
+ + + Updated + + +
+
+

features/step_definitions / credenciamento_professores_steps.rb

+
+
+ +
+ +
+
+
+
+
+ D +
+
+
+
+
215 lines of codes
+
5 methods
+
+
+
38.5 complexity/method
+
19 churn
+
+
+
192.6 complexity
+
116 duplications
+
+
+
+
+
+
+ +
+
+
+ + require 'uri' +require 'cgi' +require File.expand_path(File.join(File.dirname(__FILE__), "..", "support", "paths")) +require File.expand_path(File.join(File.dirname(__FILE__), "..", "support", "selectors")) + +module WithinHelpers
  1. WithinHelpers has no descriptive comment
+ def with_scope(locator) + locator ? within(*selector_for(locator)) { yield } : yield + end +end +World(WithinHelpers) + +module UserSessionHelper
  1. UserSessionHelper has no descriptive comment
+ def current_user + @current_user + end + + def login user
  1. UserSessionHelper#login has approx 6 statements
+ @current_user = user + visit new_user_session_path + fill_in("Email", with: user.email) + fill_in("Password", with: user.password) + click_button("Log in") + Current.user = @current_user + end + + def sower_begin (user=nil) + if user != nil + @saved_user = user + visit '/' + click_link("Sair") + end + + sower = User.create( + full_name: "Sower", + email: "sower@admin.com", + password: "admin123", + role: "administrator", + registration: "000000000" + ) + login(sower) + end + + def sower_finish + Current.user = nil + visit '/' + click_link("Sair") + @current_user = nil + + login(@saved_user) if @saved_user != nil + end +end +World(UserSessionHelper) if respond_to?(:World) + +Dado "que existam as seguintes solicitações:" do |table| + sower_begin(@current_user) + file = fixture_file_upload(Rails.root.join('public', 'TestImage.png'), 'image/png') + + table.hashes.each do |row| + name = row['user_full_name'] + user = User.create!(
  1. Identical code found in 2 nodes Locations: 0 1
+ full_name: name, + email: name+'@professor.com', + password: name+'123', + role: 'professor', + registration: '200000000' + ) + SeiProcess.create!( + user_id: user.id, + status: row['status'], + code: 0, + documents: [file] + ) + end + sower_finish +end + +Dado "que existam os seguintes credenciamentos sem prazo definido:" do |table|
  1. Dado#que existam os seguintes credenciamentos sem prazo definido: has a flog score of 26
+ sower_begin(@current_user) + file = fixture_file_upload(Rails.root.join('public', 'TestImage.png'), 'image/png') + + table.hashes.each do |row| + name = row['user_full_name'] + user = User.create!(
  1. Identical code found in 2 nodes Locations: 0 1
+ full_name: name, + email: name+'@professor.com', + password: name+'123', + role: 'professor', + registration: '200000000' + ) + sei_process = SeiProcess.create!( + user_id: user.id, + status: 'Aprovado', + code: 0, + documents: [file] + ) + Accreditation.create!( + user_id: user.id, + sei_process_id: sei_process.id, + start_date: row['start_date'] + ) + end + sower_finish +end + +Dado /^que o registro "([^"]*)" já exista na tabela de requisitos$/ do |text| + sower_begin(@current_user) + Requirement.create!(title: text) + sower_finish +end + +Dado /^que eu esteja cadastrado e logado como (.*)$/ do |input| + user_props = [:full_name, :email, :password, :role, :registration] + + values = input.gsub!(/"/,'').split(/,\s?/) + record = Hash[user_props.zip(values)] + user = User.create!(record) + login(user) +end + +Dado /^que eu esteja na página (.+)$/ do |page_name| + visit path_to(page_name) +end + +Quando /^eu escolho avaliar "([^"]*)"$/ do |name|
  1. Similar code found in 2 nodes Locations: 0 1
+ user_id = User.find_by(full_name: name).id + process_id = SeiProcess.find_by(user_id: user_id).id + visit "/sei_processes/#{process_id}/edit" +end + +Quando /^eu escolho credenciar "([^"]*)"$/ do |name|
  1. Similar code found in 2 nodes Locations: 0 1
+ user_id = User.find_by(full_name: name).id + accreditation_id = Accreditation.find_by(user_id: user_id).id + visit "/accreditations/#{accreditation_id}/edit" +end + +Quando /^eu anexo o arquivo "([^"]*)" em '([^']*)'$/ do |path, field| + attach_file(field, File.expand_path(path)) +end + +Quando /^eu clico em '([^']*)'$/ do |link| + click_link(link) +end + +Então /^eu devo estar na página (.+)$/ do |page_name| + current_path = URI.parse(current_url).path + if current_path.respond_to? :should + current_path.should == path_to(page_name) + else + assert_equal path_to(page_name), current_path + end +end + +Quando /^eu escolho '([^']*)'$/ do |option| + choose(option) +end + +Quando /^eu marco apenas os seguintes estados: (.*)$/ do |statuses| + all("input[type=checkbox]").each do |checkbox| + checkbox.uncheck + end + statuses.split(/,[ ]*/).each do |status| + check("statuses[#{status}]") + end +end + +Quando /^eu preencho em '([^']*)' com/m do |field, text| + fill_in(field, :with => text) +end + +Quando /^eu preencho com "([^"]*)" em '([^']*)'$/ do |text, field| + fill_in(field, :with => text) +end + +Quando /^eu seleciono uma data final (posterior|anterior) a data inicial$/ do |status| + if status == 'posterior' + date1 = Date.tomorrow.strftime("%Y-%0m-%0e") + elsif status == 'anterior' + date1 = Date.yesterday.strftime("%Y-%0m-%0e") + end + date2 = Date.current.strftime("%Y-%0m-%0e") + + fill_in 'Data final', with: date1 + fill_in 'Data inicial', with: date2 +end + +Quando /^eu aperto '([^']*)'$/ do |button| + click_button(button) +end + +Então /^eu devo ver "([^"]*)"$/ do |text|
  1. Similar code found in 2 nodes Locations: 0 1
+ if page.respond_to? :should + page.should have_content(text) + else + assert page.has_content?(text) + end +end + +Então /^eu não devo ver "([^"]*)"$/ do |text|
  1. Similar code found in 2 nodes Locations: 0 1
+ if page.respond_to? :should + page.should have_no_content(text) + else + assert page.has_no_content?(text) + end +end + +Então /^eu devo receber uma mensagem de (sucesso|erro)$/ do |status| + if status == 'sucesso' + find(".notice", text: /sucesso!$/) + elsif status == 'erro' + find("#error_explanation") + else + raise StandardError.new('Mensagem não encontrada') + end +end +
+
+
+ +
+
+
+ + + + + + + + + + + + + diff --git a/ABC_Score/features/support/env.html b/ABC_Score/features/support/env.html new file mode 100644 index 00000000..2dd4f68f --- /dev/null +++ b/ABC_Score/features/support/env.html @@ -0,0 +1,178 @@ + + + + + + Ruby Critic - Home + + + + + + + + + + + + +
+ + + +
+
+
+ +
+
+ + + Updated + + +
+
+

features/support / env.rb

+
+
+ +
+ +
+
+
+
+
+ A +
+
+
+
+
59 lines of codes
+
0 methods
+
+
+
N/A complexity/method
+
6 churn
+
+
+
0.0 complexity
+
0 duplications
+
+
+
+
+
+
+ +
+
+
+ + # IMPORTANT: This file is generated by cucumber-rails - edit at your own peril. +# It is recommended to regenerate this file in the future when you upgrade to a +# newer version of cucumber-rails. Consider adding your own code to a new file +# instead of editing this one. Cucumber will automatically load all features/**/*.rb +# files. + +require 'cucumber/rails' + +# frozen_string_literal: true + +# Capybara defaults to CSS3 selectors rather than XPath. +# If you'd prefer to use XPath, just uncomment this line and adjust any +# selectors in your step definitions to use the XPath syntax. +# Capybara.default_selector = :xpath + +# By default, any exception happening in your Rails application will bubble up +# to Cucumber so that your scenario will fail. This is a different from how +# your application behaves in the production environment, where an error page will +# be rendered instead. +# +# Sometimes we want to override this default behaviour and allow Rails to rescue +# exceptions and display an error page (just like when the app is running in production). +# Typical scenarios where you want to do this is when you test your error pages. +# There are two ways to allow Rails to rescue exceptions: +# +# 1) Tag your scenario (or feature) with @allow-rescue +# +# 2) Set the value below to true. Beware that doing this globally is not +# recommended as it will mask a lot of errors for you! +# +ActionController::Base.allow_rescue = false + +# Remove/comment out the lines below if your app doesn't have a database. +# For some databases (like MongoDB and CouchDB) you may need to use :truncation instead. +begin + DatabaseCleaner.strategy = :transaction +rescue NameError + raise "You need to add database_cleaner to your Gemfile (in the :test group) if you wish to use it." +end + +# You may also want to configure DatabaseCleaner to use different strategies for certain features and scenarios. +# See the DatabaseCleaner documentation for details. Example: +# +# Before('@no-txn,@selenium,@culerity,@celerity,@javascript') do +# # { except: [:widgets] } may not do what you expect here +# # as Cucumber::Rails::Database.javascript_strategy overrides +# # this setting. +# DatabaseCleaner.strategy = :truncation +# end +# +# Before('not @no-txn', 'not @selenium', 'not @culerity', 'not @celerity', 'not @javascript') do +# DatabaseCleaner.strategy = :transaction +# end +# + +# Possible values are :truncation and :transaction +# The :transaction strategy is faster, but might give you threading problems. +# See https://github.com/cucumber/cucumber-rails/blob/master/features/choose_javascript_database_strategy.feature +Cucumber::Rails::Database.javascript_strategy = :truncation + +
+
+ +
+
+
+ + + + + + + + + + + + + diff --git a/ABC_Score/features/support/hooks.html b/ABC_Score/features/support/hooks.html new file mode 100644 index 00000000..5b51e325 --- /dev/null +++ b/ABC_Score/features/support/hooks.html @@ -0,0 +1,160 @@ + + + + + + Ruby Critic - Home + + + + + + + + + + + + +
+ + + +
+
+
+ +
+
+ + + Updated + + +
+
+

features/support / hooks.rb

+
+
+ +
+ +
+
+
+
+
+ A +
+
+
+
+
41 lines of codes
+
2 methods
+
+
+
21.7 complexity/method
+
3 churn
+
+
+
43.48 complexity
+
0 duplications
+
+
+
+
+
+
+ +
+
+
+ + After do |scenario| + add_screenshot(scenario) + + if scenario.failed? + add_browser_logs + end + end + + def add_screenshot(scenario) + nome_cenario = scenario.name.gsub(/[^A-Za-z0-9]/, '') + nome_cenario = nome_cenario.gsub(' ','_').downcase! + screenshot = "log/screenshots/#{nome_cenario}.png" + # page.save_screenshot(screenshot) + attach(screenshot, 'image/png') + end + + def add_browser_logs
  1. main#add_browser_logs has a flog score of 37
  2. add_browser_logs has approx 8 statements
+ time_now = Time.now + # Getting current URL + current_url = Capybara.current_url.to_s + # Gather browser logs + logs = page.driver.browser.manage.logs.get(:browser).map {|line| [line.level, line.message]}
  1. add_browser_logs refers to 'line' more than self (maybe move it to another class?) Locations: 0 1
+ # Remove warnings and info messages + logs.reject! { |line| ['WARNING', 'INFO'].include?(line.first) }
  1. add_browser_logs refers to 'line' more than self (maybe move it to another class?) Locations: 0 1
  2. add_browser_logs refers to 'logs' more than self (maybe move it to another class?) Locations: 0 1 2
+ logs.any? == true
  1. add_browser_logs refers to 'logs' more than self (maybe move it to another class?) Locations: 0 1 2
+ embed(time_now.strftime('%Y-%m-%d-%H-%M-%S' + "\n") + ( "Current URL: " + current_url + "\n") + logs.join("\n"), 'text/plain', 'BROWSER ERROR')
  1. add_browser_logs refers to 'logs' more than self (maybe move it to another class?) Locations: 0 1 2
+end + +at_exit do + time = Time.now.getutc + ReportBuilder.configure do |config| + config.json_path = 'report.json' + config.report_path = 'cucumber_web_report' + config.report_types = [:html] + config.report_tabs = %w[Overview Features Scenarios Errors] + config.report_title = 'Cucumber Report Builder web automation test results' + config.compress_images = false + config.additional_info = { 'Project name' => 'Test', 'Platform' => 'Integration', 'Report generated' => time } + end + ReportBuilder.build_report +end +
+
+
+ +
+
+
+ + + + + + + + + + + + + diff --git a/ABC_Score/features/support/paths.html b/ABC_Score/features/support/paths.html new file mode 100644 index 00000000..bd1411ef --- /dev/null +++ b/ABC_Score/features/support/paths.html @@ -0,0 +1,161 @@ + + + + + + Ruby Critic - Home + + + + + + + + + + + + +
+ + + +
+
+
+ +
+
+ + + Updated + + +
+
+

features/support / paths.rb

+
+
+ +
+ +
+
+
+
+
+ A +
+
+
+
+
42 lines of codes
+
1 methods
+
+
+
15.6 complexity/method
+
5 churn
+
+
+
15.58 complexity
+
0 duplications
+
+
+
+
+
+
+ +
+
+
+ + module NavigationHelpers
  1. NavigationHelpers has no descriptive comment
+ # Maps a name to a path. Used by the + # + # When /^I go to (.+)$/ do |page_name| + # + # step definition in web_steps.rb + # + def path_to(page_name)
  1. NavigationHelpers#path_to has approx 8 statements
+ case page_name + + when /^the home\s?page$/ + '/' + + when /^de requisitos para o credenciamento$/ + '/requirements' + + when /^de solicitações de credenciamento$/ + '/sei_processes' + + when /^de credenciamentos$/ + '/accreditations' + + # Add more mappings here. + # Here is an example that pulls values out of the Regexp: + # + # when /^(.*)'s profile page$/i + # user_profile_path(User.find_by_login($1)) + + else + begin + page_name =~ /^the (.*) page$/ + path_components = $1.split(/\s+/) + self.send(path_components.push('path').join('_').to_sym) + rescue NoMethodError, ArgumentError + raise "Can't find mapping from \"#{page_name}\" to a path.\n" + + "Now, go and add a mapping in #{__FILE__}" + end + end + end +end + +World(NavigationHelpers) +
+
+
+ +
+
+
+ + + + + + + + + + + + + diff --git a/ABC_Score/features/support/selectors.html b/ABC_Score/features/support/selectors.html new file mode 100644 index 00000000..56f5378b --- /dev/null +++ b/ABC_Score/features/support/selectors.html @@ -0,0 +1,163 @@ + + + + + + Ruby Critic - Home + + + + + + + + + + + + +
+ + + +
+
+
+ +
+
+ + + Updated + + +
+
+

features/support / selectors.rb

+
+
+ +
+ +
+
+
+
+
+ A +
+
+
+
+
44 lines of codes
+
1 methods
+
+
+
4.4 complexity/method
+
2 churn
+
+
+
4.36 complexity
+
0 duplications
+
+
+
+
+
+
+ +
+
+
+ + # TL;DR: YOU SHOULD DELETE THIS FILE +# +# This file is used by web_steps.rb, which you should also delete +# +# You have been warned +module HtmlSelectorsHelpers + # Maps a name to a selector. Used primarily by the + # + # When /^(.+) within (.+)$/ do |step, scope| + # + # step definitions in web_steps.rb + # + def selector_for(locator) + case locator + + when "the page" + "html > body" + + # Add more mappings here. + # Here is an example that pulls values out of the Regexp: + # + # when /^the (notice|error|info) flash$/ + # ".flash.#{$1}" + + # You can also return an array to use a different selector + # type, like: + # + # when /the header/ + # [:xpath, "//header"] + + # This allows you to provide a quoted selector as the scope + # for "within" steps as was previously the default for the + # web steps: + when /^"(.+)"$/ + $1 + + else + raise "Can't find mapping from \"#{locator}\" to a selector.\n" + + "Now, go and add a mapping in #{__FILE__}" + end + end +end + +World(HtmlSelectorsHelpers) + +
+
+ +
+
+
+ + + + + + + + + + + + + diff --git a/ABC_Score/overview.html b/ABC_Score/overview.html new file mode 100644 index 00000000..3d2d8300 --- /dev/null +++ b/ABC_Score/overview.html @@ -0,0 +1,185 @@ + + + + + + Ruby Critic - Home + + + + + + + + + + + + +
+ + + +
+
+ +
+
+
+ +
+

Overview

+
+ +
+ +
+
+
+
+
+
+ + + +
+
+ +
+ + + +
+
+ + + +
+
+

Summary

+ +
+ +
+
+
+

A

+
+
+
    +
  • 55

    files
  • +
  • 243

    churns
  • +
  • 61

    smells
  • +
+
+
+
+ +
+
+
+

B

+
+
+
    +
  • 2

    files
  • +
  • 54

    churns
  • +
  • 5

    smells
  • +
+
+
+
+ +
+
+
+

C

+
+
+
    +
  • 4

    files
  • +
  • 47

    churns
  • +
  • 25

    smells
  • +
+
+
+
+ +
+
+
+

D

+
+
+
    +
  • 4

    files
  • +
  • 24

    churns
  • +
  • 10

    smells
  • +
+
+
+
+ +
+
+
+

F

+
+
+
    +
  • 3

    files
  • +
  • 17

    churns
  • +
  • 25

    smells
  • +
+
+
+
+ +
+
+
+ +
+
+
+
+ + +
+
+
+ + + + + + + + + + + + + diff --git a/ABC_Score/simple_cov_index.html b/ABC_Score/simple_cov_index.html new file mode 100644 index 00000000..7222e7b9 --- /dev/null +++ b/ABC_Score/simple_cov_index.html @@ -0,0 +1,954 @@ + + + + + + Ruby Critic - Home + + + + + + + + + + + + +
+ + + +
+
+ +
+
+
+ +
+

Coverage

+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
RatingNameCoverage
+
F
+
+ + 0%
+
F
+
+ + 0%
+
F
+
+ + 0%
+
F
+
+ + 0%
+
F
+
+ + 0%
+
F
+
+ + 0%
+
F
+
+ + 0%
+
F
+
+ + 0%
+
F
+
+ + 0%
+
F
+
+ + 0%
+
F
+
+ + 0%
+
F
+
+ + 0%
+
F
+
+ + 0%
+
F
+
+ + 0%
+
F
+
+ + 0%
+
F
+
+ + 0%
+
F
+
+ + 0%
+
F
+
+ + 0%
+
F
+
+ + 0%
+
F
+
+ + 0%
+
F
+
+ + 0%
+
F
+
+ + 0%
+
F
+
+ + 0%
+
F
+
+ + 0%
+
F
+
+ + 0%
+
F
+
+ + 0%
+
F
+
+ + 0%
+
F
+
+ + 0%
+
F
+
+ + 0%
+
F
+
+ + 0%
+
F
+
+ + 0%
+
F
+
+ + 0%
+
F
+
+ + 0%
+
F
+
+ + 0%
+
F
+
+ + 0%
+
F
+
+ + 0%
+
F
+
+ + 0%
+
F
+
+ + 0%
+
F
+
+ + 0%
+
F
+
+ + 0%
+
F
+
+ + 0%
+
F
+
+ + 0%
+
F
+
+ + 0%
+
F
+
+ + 0%
+
F
+
+ + 0%
+
F
+
+ + 0%
+
F
+
+ + 0%
+
F
+
+ + 0%
+
F
+
+ + 0%
+
F
+
+ + 0%
+
F
+
+ + 0%
+
+
+
+
+ + +
+
+
+ + + + + + + + + + + + + diff --git a/ABC_Score/smells_index.html b/ABC_Score/smells_index.html new file mode 100644 index 00000000..c6b331ac --- /dev/null +++ b/ABC_Score/smells_index.html @@ -0,0 +1,2765 @@ + + + + + + Ruby Critic - Home + + + + + + + + + + + + +
+ + + +
+
+ +
+
+
+ +
+

Smells

+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
SmellLocationsStatus
IrresponsibleModule + + + +
DuplicateMethodCall + + + +
InstanceVariableAssumption + + + +
IrresponsibleModule + + + +
MissingSafeMethod + + + +
NilCheck + + + +
UtilityFunction + + + +
DuplicateMethodCall + + + +
InstanceVariableAssumption + + + +
IrresponsibleModule + + + +
MissingSafeMethod + + + +
NilCheck + + + +
UtilityFunction + + + +
DuplicateMethodCall + + + +
InstanceVariableAssumption + + + +
IrresponsibleModule + + + +
MissingSafeMethod + + + +
UtilityFunction + + + +
IrresponsibleModule + + + +
IrresponsibleModule + + + +
IrresponsibleModule + + + +
IrresponsibleModule + + + +
IrresponsibleModule + + + +
DuplicateCode + + + +
DuplicateMethodCall + + + +
DuplicateMethodCall + + + +
InstanceVariableAssumption + + + +
IrresponsibleModule + + + +
DuplicateMethodCall + + + +
DuplicateMethodCall + + + +
DuplicateMethodCall + + + +
InstanceVariableAssumption + + + +
InstanceVariableAssumption + + + +
InstanceVariableAssumption + + + +
IrresponsibleModule + + + +
TooManyStatements + + + +
DuplicateMethodCall + + + +
DuplicateMethodCall + + + +
DuplicateMethodCall + + + +
DuplicateMethodCall + + + +
DuplicateMethodCall + + + +
InstanceVariableAssumption + + + +
InstanceVariableAssumption + + + +
InstanceVariableAssumption + + + +
IrresponsibleModule + + + +
NilCheck + + + +
TooManyInstanceVariables + + + +
TooManyStatements + + + +
TooManyStatements + + + +
IrresponsibleModule + + + +
IrresponsibleModule + + + +
IrresponsibleModule + + + +
IrresponsibleModule + + + +
IrresponsibleModule + + + +
IrresponsibleModule + + + +
IrresponsibleModule + + + +
IrresponsibleModule + + + +
IrresponsibleModule + + + +
IrresponsibleModule + + + +
IrresponsibleModule + + + +
IrresponsibleModule + + + +
TooManyStatements + + + +
HighComplexity + + + +
FeatureEnvy + + + +
FeatureEnvy + + + +
TooManyStatements + + + +
DuplicateCode + + + +
DuplicateCode + + + +
DuplicateCode + + + +
HighComplexity + + + +
IrresponsibleModule + + + +
IrresponsibleModule + + + +
TooManyStatements + + + +
DuplicateCode + + + +
DuplicateCode + + + +
DuplicateCode + + + +
DuplicateCode + + + +
DuplicateCode + + + +
DuplicateCode + + + +
DuplicateCode + + + +
DuplicateCode + + + +
HighComplexity + + + +
HighComplexity + + + +
DuplicateCode + + + +
DuplicateCode + + + +
DuplicateCode + + + +
HighComplexity + + + +
HighComplexity + + + +
HighComplexity + + + +
HighComplexity + + + +
UtilityFunction + + + +
IrresponsibleModule + + + +
FeatureEnvy + + + +
IrresponsibleModule + + + +
TooManyStatements + + + +
UncommunicativeVariableName + + + +
FeatureEnvy + + + +
IrresponsibleModule + + + +
UncommunicativeVariableName + + + +
DuplicateMethodCall + + + +
FeatureEnvy + + + +
TooManyStatements + + + +
UncommunicativeVariableName + + + +
FeatureEnvy + + + +
IrresponsibleModule + + + +
TooManyStatements + + + +
UncommunicativeVariableName + + + +
FeatureEnvy + + + +
IrresponsibleModule + + + +
UncommunicativeVariableName + + + +
+
+
+
+
+ + +
+
+
+ + + + + + + + + + + + + diff --git a/ABC_Score/spec/controllers/accreditations_controller_spec.html b/ABC_Score/spec/controllers/accreditations_controller_spec.html new file mode 100644 index 00000000..adcafae7 --- /dev/null +++ b/ABC_Score/spec/controllers/accreditations_controller_spec.html @@ -0,0 +1,226 @@ + + + + + + Ruby Critic - Home + + + + + + + + + + + + +
+ + + +
+
+
+ +
+
+ + + Updated + + +
+
+

spec/controllers / accreditations_controller_spec.rb

+
+
+ +
+ +
+
+
+
+
+ F +
+
+
+
+
107 lines of codes
+
0 methods
+
+
+
N/A complexity/method
+
4 churn
+
+
+
201.52 complexity
+
169 duplications
+
+
+
+
+
+
+ +
+
+
+ + require 'rails_helper' + +RSpec.describe AccreditationsController, type: :controller do + fixtures :users + + let(:file) { + fixture_file_upload(Rails.root.join('public', 'TestImage.png'), 'image/png') + } + + let(:valid_prof_params) { + {user_id: users(:admin).id, status: 'Espera', code: 0, documents: [file]} + } + + before(:each) do
  1. Similar code found in 4 nodes Locations: 0 1 2 3
+ sign_in users(:prof) + Current.user = users(:prof) + @process = SeiProcess.create!(valid_prof_params) + + sign_out users(:prof) + sign_in users(:admin) + Current.user = users(:admin) + end + + let(:valid_attributes) { + {user_id: users(:prof).id, start_date: '2020-11-15', end_date: '2021-11-15', sei_process_id: @process.id} + } + + let(:invalid_attributes) { + {user_id: users(:prof).id, start_date: '2020-11-15', end_date: '2019-11-15', sei_process_id: @process.id} + } + + let(:valid_session) { {} } + + describe "GET #index" do
  1. Similar code found in 2 nodes Locations: 0 1
+ it "returns a success response" do + Accreditation.create! valid_attributes + get :index, params: {}, session: valid_session + expect(response).to be_successful + end + end + + describe "GET #show" do
  1. Similar code found in 4 nodes Locations: 0 1 2 3
+ it "returns a success response" do + accreditation = Accreditation.create! valid_attributes + get :show, params: {id: accreditation.to_param}, session: valid_session + expect(response).to be_successful + end + end + + describe "GET #edit" do
  1. Similar code found in 4 nodes Locations: 0 1 2 3
+ it "returns a success response" do + accreditation = Accreditation.create! valid_attributes + get :edit, params: {id: accreditation.to_param}, session: valid_session + expect(response).to be_successful + end + end + + describe "PUT #update" do + context "with valid params" do + let(:new_attributes) { + {end_date: '2022-11-15'} + } + + it "updates the requested accreditation" do
  1. describe(PUT #update)::context(with valid params)::it#updates the requested accreditation has a flog score of 27
+ accreditation = Accreditation.create! valid_attributes + put :update, params: {id: accreditation.to_param, accreditation: new_attributes}, session: valid_session + accreditation.reload + expect(accreditation.end_date).to eq(Date.parse(new_attributes[:end_date])) + end + end + + context "with invalid params" do + it "returns a success response (i.e. to display the 'edit' template)" do
  1. Similar code found in 2 nodes Locations: 0 1
+ accreditation = Accreditation.create! valid_attributes + put :update, params: {id: accreditation.to_param, accreditation: invalid_attributes}, session: valid_session + expect(response).to be_successful + end + end + end + + describe "DELETE #destroy" do + it "destroys the requested accreditation" do
  1. Similar code found in 3 nodes Locations: 0 1 2
+ accreditation = Accreditation.create! valid_attributes + expect { + delete :destroy, params: {id: accreditation.to_param}, session: valid_session + }.to change(Accreditation, :count).by(-1) + end + + it "redirects to the accreditations list" do
  1. Similar code found in 2 nodes Locations: 0 1
+ accreditation = Accreditation.create! valid_attributes + delete :destroy, params: {id: accreditation.to_param}, session: valid_session + expect(response).to redirect_to(accreditations_url) + end + + it "fails to destroy the requested accreditation" do
  1. describe(DELETE #destroy)::it#fails to destroy the requested accreditation has a flog score of 25
+ accreditation = Accreditation.create! valid_attributes + sign_out users(:admin) + + sign_in users(:prof) + Current.user = users(:prof) + + expect { + delete :destroy, params: {id: accreditation.to_param}, session: valid_session + }.to change(Accreditation, :count).by(0) + end + end +end +
+
+
+ +
+
+
+ + + + + + + + + + + + + diff --git a/ABC_Score/spec/controllers/home_controller_spec.html b/ABC_Score/spec/controllers/home_controller_spec.html new file mode 100644 index 00000000..f78d1b13 --- /dev/null +++ b/ABC_Score/spec/controllers/home_controller_spec.html @@ -0,0 +1,131 @@ + + + + + + Ruby Critic - Home + + + + + + + + + + + + +
+ + + +
+
+
+ +
+
+ + + Updated + + +
+
+

spec/controllers / home_controller_spec.rb

+
+
+ +
+ +
+
+
+
+
+ A +
+
+
+
+
12 lines of codes
+
0 methods
+
+
+
N/A complexity/method
+
2 churn
+
+
+
9.7 complexity
+
0 duplications
+
+
+
+
+
+
+ +
+
+
+ + require 'rails_helper' + +RSpec.describe HomeController, type: :controller do + + describe "GET #index" do + it "returns http success" do + get :index + expect(response).to have_http_status(:success) + end + end + +end + +
+
+ +
+
+
+ + + + + + + + + + + + + diff --git a/ABC_Score/spec/controllers/requirements_controller_spec.html b/ABC_Score/spec/controllers/requirements_controller_spec.html new file mode 100644 index 00000000..e37f710a --- /dev/null +++ b/ABC_Score/spec/controllers/requirements_controller_spec.html @@ -0,0 +1,335 @@ + + + + + + Ruby Critic - Home + + + + + + + + + + + + +
+ + + +
+
+
+ +
+
+ + + Updated + + +
+
+

spec/controllers / requirements_controller_spec.rb

+
+
+ +
+ +
+
+
+
+
+ F +
+
+
+
+
216 lines of codes
+
0 methods
+
+
+
N/A complexity/method
+
10 churn
+
+
+
427.71 complexity
+
274 duplications
+
+
+
+
+
+
+ +
+
+
+ + require 'rails_helper' + +RSpec.describe RequirementsController, type: :controller do + fixtures :users + + let(:valid_attributes) {
  1. Similar code found in 2 nodes Locations: 0 1
+ file = fixture_file_upload(Rails.root.join('public', 'TestImage.png'), 'image/png') + {title: "test", content: "", documents: [file]} + } + + let(:invalid_attributes) { + {"title" => ""} + } + + let(:valid_session) { {} } + + context "by an admin" do + before(:each) do + sign_in users(:admin) + Current.user = users(:admin) + end + + describe "GET #index" do
  1. Similar code found in 2 nodes Locations: 0 1
+ it "returns a success response" do + Requirement.create! valid_attributes + get :index, params: {}, session: valid_session + expect(response).to be_successful + end + end + + describe "GET #show" do
  1. Similar code found in 4 nodes Locations: 0 1 2 3
+ it "returns a success response" do + requirement = Requirement.create! valid_attributes + get :show, params: {id: requirement.to_param}, session: valid_session + expect(response).to be_successful + end + end + + describe "GET #new" do + it "returns a success response" do + get :new, params: {}, session: valid_session + expect(response).to be_successful + end + end + + describe "GET #edit" do
  1. Similar code found in 4 nodes Locations: 0 1 2 3
+ it "returns a success response" do + requirement = Requirement.create! valid_attributes + get :edit, params: {id: requirement.to_param}, session: valid_session + expect(response).to be_successful + end + end + end + + describe "POST #create" do + context "with valid params" do + before(:each) do + sign_in users(:admin) + Current.user = users(:admin) + end + + it "creates a new Requirement" do
  1. Similar code found in 4 nodes Locations: 0 1 2 3
+ expect { + post :create, params: {requirement: valid_attributes}, session: valid_session + }.to change(Requirement, :count).by(1) + end + + it "redirects to the created requirement" do + post :create, params: {requirement: valid_attributes}, session: valid_session + expect(response).to redirect_to(Requirement.last) + end + end + + context "with invalid params" do + before(:each) do + sign_in users(:admin) + Current.user = users(:admin) + end + + it "returns a success response (i.e. to display the 'new' template)" do + post :create, params: {requirement: invalid_attributes}, session: valid_session + expect(response).to be_successful + end + end + + context "by a non-admin user" do + before(:each) do + sign_in users(:prof) + Current.user = users(:prof) + end + + it "does not create a new Requirement" do
  1. Similar code found in 4 nodes Locations: 0 1 2 3
+ expect { + post :create, params: {requirement: valid_attributes}, session: valid_session + }.to change(Requirement, :count).by(0) + end + end + end + + describe "PUT #update" do + let(:new_attributes) {
  1. Similar code found in 2 nodes Locations: 0 1
+ file = fixture_file_upload(Rails.root.join('public', 'TestImage.png'), 'image/png') + {title: "Novo", content: "Conteúdo", documents: [file]} + } + + context "with valid params" do + before(:each) do + sign_in users(:admin) + Current.user = users(:admin) + end + + it "updates the requested requirement" do
  1. describe(PUT #update)::context(with valid params)::it#updates the requested requirement has a flog score of 36
+ requirement = Requirement.create! valid_attributes + put :update, params: {id: requirement.to_param, requirement: new_attributes}, session: valid_session + requirement.reload + expect(requirement.title).to eq(new_attributes[:title]) + expect(requirement.content).to eq(new_attributes[:content]) + end + + it "redirects to the requirement" do + requirement = Requirement.create! valid_attributes + put :update, params: {id: requirement.to_param, requirement: valid_attributes}, session: valid_session + expect(response).to redirect_to(requirement) + end + end + + context "with invalid params" do + before(:each) do + sign_in users(:admin) + Current.user = users(:admin) + end + + it "returns a success response (i.e. to display the 'edit' template)" do
  1. Similar code found in 2 nodes Locations: 0 1
+ requirement = Requirement.create! valid_attributes + put :update, params: {id: requirement.to_param, requirement: invalid_attributes}, session: valid_session + expect(response).to be_successful + end + end + + context "by a non-admin user" do + before(:each) do
  1. Similar code found in 4 nodes Locations: 0 1 2 3
+ sign_in users(:admin) + Current.user = users(:admin) + @requirement = Requirement.create! valid_attributes + + sign_out users(:admin) + sign_in users(:prof) + Current.user = users(:prof) + end + + it "updates the requested requirement" do
  1. describe(PUT #update)::context(by a non-admin user)::it#updates the requested requirement has a flog score of 33
+ put :update, params: {id: @requirement.to_param, requirement: new_attributes}, session: valid_session + @requirement.reload + expect(@requirement.title).to_not eq(new_attributes[:title]) + expect(@requirement.content).to_not eq(new_attributes[:content]) + end + end + end + + describe "Documents Management"do + before(:each) do + sign_in users(:admin) + Current.user = users(:admin) + end + + it 'purges a specific file' do + requirement = Requirement.create! valid_attributes + requirement.documents.each do | document | + expect { + delete :delete_document_attachment, params: { id: document.id, requirement_id: requirement.id } + }.to change(ActiveStorage::Attachment, :count).by(-1) + end + end + end + + describe "DELETE #destroy" do + context "by an admin" do + before(:each) do + sign_in users(:admin) + Current.user = users(:admin) + end + + it "destroys the requested requirement" do
  1. Similar code found in 3 nodes Locations: 0 1 2
+ requirement = Requirement.create! valid_attributes + expect { + delete :destroy, params: {id: requirement.to_param}, session: valid_session + }.to change(Requirement, :count).by(-1) + end + + it "redirects to the requirements list" do
  1. Similar code found in 2 nodes Locations: 0 1
+ requirement = Requirement.create! valid_attributes + delete :destroy, params: {id: requirement.to_param}, session: valid_session + expect(response).to redirect_to(requirements_url) + end + end + + context "by a non-admin user" do + before(:each) do
  1. Similar code found in 4 nodes Locations: 0 1 2 3
+ sign_in users(:admin) + Current.user = users(:admin) + @requirement = Requirement.create! valid_attributes + + sign_out users(:admin) + sign_in users(:prof) + Current.user = users(:prof) + end + + it "does not destroy the requested requirement" do + expect { + delete :destroy, params: {id: @requirement.to_param}, session: valid_session + }.to change(Requirement, :count).by(0) + end + end + end + +end +
+
+
+ +
+
+
+ + + + + + + + + + + + + diff --git a/ABC_Score/spec/controllers/sei_processes_controller_spec.html b/ABC_Score/spec/controllers/sei_processes_controller_spec.html new file mode 100644 index 00000000..5388f33c --- /dev/null +++ b/ABC_Score/spec/controllers/sei_processes_controller_spec.html @@ -0,0 +1,336 @@ + + + + + + Ruby Critic - Home + + + + + + + + + + + + +
+ + + +
+
+
+ +
+
+ + + Updated + + +
+
+

spec/controllers / sei_processes_controller_spec.rb

+
+
+ +
+ +
+
+
+
+
+ F +
+
+
+
+
217 lines of codes
+
0 methods
+
+
+
N/A complexity/method
+
3 churn
+
+
+
410.26 complexity
+
274 duplications
+
+
+
+
+
+
+ +
+
+
+ + require 'rails_helper' + +RSpec.describe SeiProcessesController, type: :controller do + fixtures :users + + let(:file) { + fixture_file_upload(Rails.root.join('public', 'TestImage.png'), 'image/png') + } + + let(:valid_admin_params) { + {user_id: users(:admin).id, status: 'Espera', code: 0, documents: [file]} + } + + let(:valid_prof_params) { + {user_id: users(:prof).id, status: 'Espera', code: 0, documents: [file]} + } + + let(:invalid_status_params) { + {user_id: users(:prof).id, status: 'Aprovado', code: 0, documents: [file]} + } + + let(:invalid_docs_params_by_admin) { + {user_id: users(:admin).id, status: 'Espera', code: 0} + } + + let(:invalid_docs_params_by_prof) { + {user_id: users(:prof).id, status: 'Espera', code: 0} + } + + let(:some_process) { + SeiProcess.create!(valid_prof_params) + } + + let(:valid_session) { {} } + + describe "GET #index" do + context 'taken by an admin' do
  1. Similar code found in 3 nodes Locations: 0 1 2
+ before(:each) do + sign_in users(:admin) + Current.user = users(:admin) + end + + it "returns a success response" do + get :index, params: {}, session: valid_session + expect(response).to be_successful + end + end + + context 'taken by an non-admin user' do
  1. Similar code found in 3 nodes Locations: 0 1 2
+ before(:each) do + sign_in users(:prof) + Current.user = users(:prof) + end + + it "returns a success response" do + get :index, params: {}, session: valid_session + expect(response).to be_successful + end + end + end + + describe "GET #show" do
  1. Similar code found in 2 nodes Locations: 0 1
+ before(:each) do + sign_in users(:admin) + Current.user = users(:admin) + end + + it "returns a success response" do + get :show, params: {id: some_process.id}, session: valid_session + expect(response).to be_successful + end + end + + describe "GET #new" do
  1. Similar code found in 3 nodes Locations: 0 1 2
+ before(:each) do + sign_in users(:prof) + Current.user = users(:prof) + end + + it "returns a success response" do + get :new, params: {}, session: valid_session + expect(response).to be_successful + end + end + + describe "GET #edit" do
  1. Similar code found in 2 nodes Locations: 0 1
+ before(:each) do + sign_in users(:admin) + Current.user = users(:admin) + end + + it "returns a success response" do + get :edit, params: {id: some_process.id}, session: valid_session + expect(response).to be_successful + end + end + + describe "POST #create" do + before(:each) do + sign_in users(:prof) + Current.user = users(:prof) + end + + context "with valid params" do + it "creates a new SeiProcess" do
  1. Similar code found in 4 nodes Locations: 0 1 2 3
+ expect { + post :create, params: {sei_process: valid_prof_params}, session: valid_session + }.to change(SeiProcess, :count).by(1) + end + + it "redirects to the sei processes list" do + post :create, params: {sei_process: valid_prof_params}, session: valid_session + expect(response).to redirect_to(sei_processes_url) + end + + it "corrects invalid parameters during creation" do + post :create, params: {sei_process: invalid_status_params}, session: valid_session + expect(SeiProcess.last.status).to eq(valid_prof_params[:status]) + end + end + + context "with invalid params" do + it "do not creates a new SeiProcess due to lack of documents" do
  1. Similar code found in 4 nodes Locations: 0 1 2 3
+ expect { + post :create, params: {sei_process: invalid_docs_params_by_prof}, session: valid_session + }.to change(SeiProcess, :count).by(0) + end + end + end + + describe "PUT #update" do + let(:approval_param) { + {status: 'Aprovado'} + } + + let(:rejection_param) { + {status: 'Rejeitado'} + } + + context "when user has permission" do + before(:each) do + sign_in users(:admin) + Current.user = users(:admin) + end + + it "updates the requested sei_process" do
  1. Similar code found in 2 nodes Locations: 0 1
  2. describe(PUT #update)::context(when user has permission)::it#updates the requested sei_process has a flog score of 28
+ put :update, params: {id: some_process.id, sei_process: rejection_param}, session: valid_session + some_process.reload + expect(some_process.status).to eq(rejection_param[:status]) + end + + it "redirects to the sei processes list" do + put :update, params: {id: some_process.id, sei_process: rejection_param}, session: valid_session + expect(response).to redirect_to(sei_processes_url) + end + + it "creates an accreditation when a process is approved" do + expect { + put :update, params: {id: some_process.id, sei_process: approval_param}, session: valid_session + }.to change(Accreditation, :count).by(1) + end + end + + context "when user does not have permission" do + before(:each) do + sign_in users(:prof) + Current.user = users(:prof) + end + + it "does not update the requested sei_process" do
  1. Similar code found in 2 nodes Locations: 0 1
  2. describe(PUT #update)::context(when user does not have permission)::it#does not update the requested sei_process has a flog score of 28
+ put :update, params: {id: some_process.id, sei_process: approval_param}, session: valid_session + some_process.reload + expect(some_process.status).to_not eq(approval_param[:status]) + end + end + end + + describe "DELETE #destroy" do + context "when user has permission" do + before(:each) do + sign_in users(:admin) + Current.user = users(:admin) + end + + it "destroys the requested sei_process" do + process = SeiProcess.create!(valid_admin_params) + process.update_attributes(user_id: users(:prof)) + + expect { + delete :destroy, params: {id: process.to_param}, session: valid_session + }.to change(SeiProcess, :count).by(-1) + end + + it "redirects to the sei_processes list" do + process = SeiProcess.create!(valid_admin_params) + process.update_attributes(user_id: users(:prof)) + + delete :destroy, params: {id: process.to_param}, session: valid_session + expect(response).to redirect_to(sei_processes_url) + end + end + + context "when user does not have permission" do + before(:each) do + sign_in users(:prof) + Current.user = users(:prof) + end + + it "does not destroys the requested sei_process" do
  1. Similar code found in 3 nodes Locations: 0 1 2
+ process = SeiProcess.create!(valid_prof_params) + expect { + delete :destroy, params: {id: process.to_param}, session: valid_session + }.to change(SeiProcess, :count).by(0) + end + end + end +end +
+
+
+ +
+
+
+ + + + + + + + + + + + + diff --git a/ABC_Score/spec/helpers/home_helper_spec.html b/ABC_Score/spec/helpers/home_helper_spec.html new file mode 100644 index 00000000..ac17d367 --- /dev/null +++ b/ABC_Score/spec/helpers/home_helper_spec.html @@ -0,0 +1,134 @@ + + + + + + Ruby Critic - Home + + + + + + + + + + + + +
+ + + +
+
+
+ +
+
+ + + Updated + + +
+
+

spec/helpers / home_helper_spec.rb

+
+
+ +
+ +
+
+
+
+
+ A +
+
+
+
+
15 lines of codes
+
0 methods
+
+
+
N/A complexity/method
+
4 churn
+
+
+
0.0 complexity
+
0 duplications
+
+
+
+
+
+
+ +
+
+
+ + # require 'rails_helper' + +# # Specs in this file have access to a helper object that includes +# # the HomeHelper. For example: +# # +# # describe HomeHelper do +# # describe "string concat" do +# # it "concats two strings with spaces" do +# # expect(helper.concat_strings("this","that")).to eq("this that") +# # end +# # end +# # end +# RSpec.describe HomeHelper, type: :helper do +# pending "add some examples to (or delete) #{__FILE__}" +# end + +
+
+ +
+
+
+ + + + + + + + + + + + + diff --git a/ABC_Score/spec/models/accreditation_spec.html b/ABC_Score/spec/models/accreditation_spec.html new file mode 100644 index 00000000..e2853d7d --- /dev/null +++ b/ABC_Score/spec/models/accreditation_spec.html @@ -0,0 +1,185 @@ + + + + + + Ruby Critic - Home + + + + + + + + + + + + +
+ + + +
+
+
+ +
+
+ + + Updated + + +
+
+

spec/models / accreditation_spec.rb

+
+
+ +
+ +
+
+
+
+
+ C +
+
+
+
+
66 lines of codes
+
0 methods
+
+
+
N/A complexity/method
+
6 churn
+
+
+
98.6 complexity
+
25 duplications
+
+
+
+
+
+
+ +
+
+
+ + require 'rails_helper' + +RSpec.describe Accreditation, type: :model do + fixtures :users + + let(:file) { + fixture_file_upload(Rails.root.join('public', 'TestImage.png'), 'image/png') + } + + let(:valid_prof_params) { + {user_id: users(:admin).id, status: 'Espera', code: 0, documents: [file]} + } + + let(:valid_attributes) { + {user_id: users(:admin).id, sei_process_id: @process.id} + } + + before(:each) do
  1. Similar code found in 4 nodes Locations: 0 1 2 3
+ sign_in users(:prof) + Current.user = users(:prof) + @process = SeiProcess.create! valid_prof_params + + sign_out users(:prof) + sign_in users(:admin) + Current.user = users(:admin) + end + + context "valid record" do + it "has valid attributes" do + expect( + Accreditation.new(valid_attributes) + ).to be_valid + end + end + + context "invalid record" do + it "has no user" do + expect( + Accreditation.new(sei_process_id: @process.id) + ).to_not be_valid + end + + it "has no process" do + expect( + Accreditation.new(user_id: users(:admin).id) + ).to_not be_valid + end + + it "has an unavailable process" do + Accreditation.create!(valid_attributes) + expect( + Accreditation.new(valid_attributes) + ).to_not be_valid + end + + it "has been created by a non-admin user" do + sign_out users(:admin) + sign_in users(:prof) + Current.user = users(:prof) + + expect( + Accreditation.new(valid_attributes) + ).to_not be_valid + end + end +end +
+
+
+ +
+
+
+ + + + + + + + + + + + + diff --git a/ABC_Score/spec/models/requirement_spec.html b/ABC_Score/spec/models/requirement_spec.html new file mode 100644 index 00000000..30011755 --- /dev/null +++ b/ABC_Score/spec/models/requirement_spec.html @@ -0,0 +1,146 @@ + + + + + + Ruby Critic - Home + + + + + + + + + + + + +
+ + + +
+
+
+ +
+
+ + + Updated + + +
+
+

spec/models / requirement_spec.rb

+
+
+ +
+ +
+
+
+
+
+ A +
+
+
+
+
27 lines of codes
+
0 methods
+
+
+
N/A complexity/method
+
7 churn
+
+
+
38.05 complexity
+
0 duplications
+
+
+
+
+
+
+ +
+
+
+ + require 'rails_helper' + +RSpec.describe Requirement, type: :model do + fixtures :users + + before(:each) do + sign_in users(:admin) + Current.user = users(:admin) + end + + describe "Title validation" do + it "is valid with valid attributes" do + expect(Requirement.new({"title" => "testing"})).to be_valid + end + + it "is not valid without a title" do + file = fixture_file_upload(Rails.root.join('public', 'TestImage.png'), 'image/png') + expect(Requirement.new({"content" => "", "documents" => [file]})).to_not be_valid + end + + it "is not equal to previous requirement title" do + Requirement.create! title: "testing" + expect(Requirement.new({"title" => "testing"})).to_not be_valid + end + end + +end + +
+
+ +
+
+
+ + + + + + + + + + + + + diff --git a/ABC_Score/spec/models/sei_process_spec.html b/ABC_Score/spec/models/sei_process_spec.html new file mode 100644 index 00000000..1a26ad79 --- /dev/null +++ b/ABC_Score/spec/models/sei_process_spec.html @@ -0,0 +1,206 @@ + + + + + + Ruby Critic - Home + + + + + + + + + + + + +
+ + + +
+
+
+ +
+
+ + + Updated + + +
+
+

spec/models / sei_process_spec.rb

+
+
+ +
+ +
+
+
+
+
+ C +
+
+
+
+
87 lines of codes
+
0 methods
+
+
+
N/A complexity/method
+
6 churn
+
+
+
98.34 complexity
+
70 duplications
+
+
+
+
+
+
+ +
+
+
+ + require 'rails_helper' + +RSpec.describe SeiProcess, type: :model do + fixtures :users + + let(:file) { + fixture_file_upload(Rails.root.join('public', 'TestImage.png'), 'image/png') + } + + let(:valid_admin_params) { + {user_id: users(:admin).id, status: 'Espera', code: 0, documents: [file]} + } + + let(:valid_prof_params) { + {user_id: users(:prof).id, status: 'Espera', code: 0, documents: [file]} + } + + let(:invalid_status_params) { + {user_id: users(:prof).id, status: 'Aprovado', code: 0, documents: [file]} + } + + let(:invalid_docs_params_by_admin) { + {user_id: users(:admin).id, status: 'Espera', code: 0} + } + + let(:invalid_docs_params_by_prof) { + {user_id: users(:prof).id, status: 'Espera', code: 0} + } + + describe 'checks an admins creation' do
  1. Similar code found in 2 nodes Locations: 0 1
+ before(:each) do + Current.user = users(:admin) + end + + context 'when a valid record' do + it 'has valid attributes' do + expect( + SeiProcess.new(valid_admin_params) + ).to be_valid + end + end + + context 'when an invalid record' do + it 'has no attached documents' do + expect( + SeiProcess.new(invalid_docs_params_by_admin) + ).to_not be_valid + end + end + end + + describe 'checks a non-admins creation' do
  1. Similar code found in 2 nodes Locations: 0 1
+ before(:each) do + Current.user = users(:prof) + end + + context 'when a valid record' do + it 'has valid attributes' do + expect( + SeiProcess.new(valid_prof_params) + ).to be_valid + end + end + + context 'when an invalid record' do + it 'has no attached documents' do + expect( + SeiProcess.new(invalid_docs_params_by_prof) + ).to_not be_valid + end + end + end + + describe 'checks a not signed in users creation' do + before(:each) do + Current.user = nil + end + + context 'when an invalid record' do + it 'comes from not signed in user' do + expect( + SeiProcess.new(valid_admin_params) + ).to_not be_valid + end + end + end +end +
+
+
+ +
+
+
+ + + + + + + + + + + + + diff --git a/ABC_Score/spec/models/user_spec.html b/ABC_Score/spec/models/user_spec.html new file mode 100644 index 00000000..27a26a27 --- /dev/null +++ b/ABC_Score/spec/models/user_spec.html @@ -0,0 +1,124 @@ + + + + + + Ruby Critic - Home + + + + + + + + + + + + +
+ + + +
+
+
+ +
+
+ + + Updated + + +
+
+

spec/models / user_spec.rb

+
+
+ +
+ +
+
+
+
+
+ A +
+
+
+
+
5 lines of codes
+
0 methods
+
+
+
N/A complexity/method
+
4 churn
+
+
+
0.0 complexity
+
0 duplications
+
+
+
+
+
+
+ +
+
+
+ + # require 'rails_helper' + +# RSpec.describe User, type: :model do +# pending "add some examples to (or delete) #{__FILE__}" +# end + +
+
+ +
+
+
+ + + + + + + + + + + + + diff --git a/ABC_Score/spec/rails_helper.html b/ABC_Score/spec/rails_helper.html new file mode 100644 index 00000000..9767b910 --- /dev/null +++ b/ABC_Score/spec/rails_helper.html @@ -0,0 +1,198 @@ + + + + + + Ruby Critic - Home + + + + + + + + + + + + +
+ + + +
+
+
+ +
+
+ + + Updated + + +
+
+

spec / rails_helper.rb

+
+
+ +
+ +
+
+
+
+
+ A +
+
+
+
+
79 lines of codes
+
0 methods
+
+
+
N/A complexity/method
+
17 churn
+
+
+
0.0 complexity
+
0 duplications
+
+
+
+
+
+
+ +
+
+
+ + require 'simplecov' +SimpleCov.start +# This file is copied to spec/ when you run 'rails generate rspec:install' +require 'spec_helper' +ENV['RAILS_ENV'] ||= 'test' +require File.expand_path('../config/environment', __dir__) +# Prevent database truncation if the environment is production +abort("The Rails environment is running in production mode!") if Rails.env.production? +require 'rspec/rails' +# note: require 'devise' after require 'rspec/rails' +require 'devise' +# Add additional requires below this line. Rails is not loaded until this point! + +# Requires supporting ruby files with custom matchers and macros, etc, in +# spec/support/ and its subdirectories. Files matching `spec/**/*_spec.rb` are +# run as spec files by default. This means that files in spec/support that end +# in _spec.rb will both be required and run as specs, causing the specs to be +# run twice. It is recommended that you do not name files matching this glob to +# end with _spec.rb. You can configure this pattern with the --pattern +# option on the command line or in ~/.rspec, .rspec or `.rspec-local`. +# +# The following line is provided for convenience purposes. It has the downside +# of increasing the boot-up time by auto-requiring all files in the support +# directory. Alternatively, in the individual `*_spec.rb` files, manually +# require only the support files necessary. +# +# Dir[Rails.root.join('spec', 'support', '**', '*.rb')].sort.each { |f| require f } + +# Checks for pending migrations and applies them before tests are run. +# If you are not using ActiveRecord, you can remove these lines. +begin + ActiveRecord::Migration.maintain_test_schema! +rescue ActiveRecord::PendingMigrationError => e + puts e.to_s.strip + exit 1 +end +RSpec.configure do |config| + # For Devise > 4.1.1 + config.include Devise::Test::ControllerHelpers, type: :controller + config.include Devise::Test::IntegrationHelpers, type: :model + config.include Devise::Test::IntegrationHelpers, type: :request + # Use the following instead if you are on Devise <= 4.1.1 + # config.include Devise::TestHelpers, :type => :controller + + # Remove this line if you're not using ActiveRecord or ActiveRecord fixtures + config.fixture_path = "#{::Rails.root}/spec/fixtures" + + # If you're not using ActiveRecord, or you'd prefer not to run each of your + # examples within a transaction, remove the following line or assign false + # instead of true. + config.use_transactional_fixtures = true + + # You can uncomment this line to turn off ActiveRecord support entirely. + # config.use_active_record = false + + # RSpec Rails can automatically mix in different behaviours to your tests + # based on their file location, for example enabling you to call `get` and + # `post` in specs under `spec/controllers`. + # + # You can disable this behaviour by removing the line below, and instead + # explicitly tag your specs with their type, e.g.: + # + # RSpec.describe UsersController, type: :controller do + # # ... + # end + # + # The different available types are documented in the features, such as in + # https://relishapp.com/rspec/rspec-rails/docs + config.infer_spec_type_from_file_location! + + # Filter lines from Rails gems in backtraces. + config.filter_rails_from_backtrace! + # arbitrary gems may also be filtered via: + # config.filter_gems_from_backtrace("gem name") +end + +module Current
  1. Current has no descriptive comment
+ thread_mattr_accessor :user +end +
+
+
+ +
+
+
+ + + + + + + + + + + + + diff --git a/ABC_Score/spec/routing/accreditations_routing_spec.html b/ABC_Score/spec/routing/accreditations_routing_spec.html new file mode 100644 index 00000000..fd24ecb2 --- /dev/null +++ b/ABC_Score/spec/routing/accreditations_routing_spec.html @@ -0,0 +1,157 @@ + + + + + + Ruby Critic - Home + + + + + + + + + + + + +
+ + + +
+
+
+ +
+
+ + + Updated + + +
+
+

spec/routing / accreditations_routing_spec.rb

+
+
+ +
+ +
+
+
+
+
+ D +
+
+
+
+
38 lines of codes
+
0 methods
+
+
+
N/A complexity/method
+
2 churn
+
+
+
46.0 complexity
+
106 duplications
+
+
+
+
+
+
+ +
+
+
+ + require "rails_helper" + +RSpec.describe AccreditationsController, type: :routing do
  1. Similar code found in 3 nodes Locations: 0 1 2
+ describe "routing" do + it "routes to #index" do + expect(get: "/accreditations").to route_to("accreditations#index") + end + + it "routes to #new" do + expect(get: "/accreditations/new").to route_to("accreditations#new") + end + + it "routes to #show" do + expect(get: "/accreditations/1").to route_to("accreditations#show", id: "1") + end + + it "routes to #edit" do + expect(get: "/accreditations/1/edit").to route_to("accreditations#edit", id: "1") + end + + + it "routes to #create" do + expect(post: "/accreditations").to route_to("accreditations#create") + end + + it "routes to #update via PUT" do + expect(put: "/accreditations/1").to route_to("accreditations#update", id: "1") + end + + it "routes to #update via PATCH" do + expect(patch: "/accreditations/1").to route_to("accreditations#update", id: "1") + end + + it "routes to #destroy" do + expect(delete: "/accreditations/1").to route_to("accreditations#destroy", id: "1") + end + end +end +
+
+
+ +
+
+
+ + + + + + + + + + + + + diff --git a/ABC_Score/spec/routing/requirements_routing_spec.html b/ABC_Score/spec/routing/requirements_routing_spec.html new file mode 100644 index 00000000..571aab55 --- /dev/null +++ b/ABC_Score/spec/routing/requirements_routing_spec.html @@ -0,0 +1,157 @@ + + + + + + Ruby Critic - Home + + + + + + + + + + + + +
+ + + +
+
+
+ +
+
+ + + Updated + + +
+
+

spec/routing / requirements_routing_spec.rb

+
+
+ +
+ +
+
+
+
+
+ D +
+
+
+
+
38 lines of codes
+
0 methods
+
+
+
N/A complexity/method
+
2 churn
+
+
+
46.0 complexity
+
106 duplications
+
+
+
+
+
+
+ +
+
+
+ + require "rails_helper" + +RSpec.describe RequirementsController, type: :routing do
  1. Similar code found in 3 nodes Locations: 0 1 2
+ describe "routing" do + it "routes to #index" do + expect(:get => "/requirements").to route_to("requirements#index") + end + + it "routes to #new" do + expect(:get => "/requirements/new").to route_to("requirements#new") + end + + it "routes to #show" do + expect(:get => "/requirements/1").to route_to("requirements#show", :id => "1") + end + + it "routes to #edit" do + expect(:get => "/requirements/1/edit").to route_to("requirements#edit", :id => "1") + end + + + it "routes to #create" do + expect(:post => "/requirements").to route_to("requirements#create") + end + + it "routes to #update via PUT" do + expect(:put => "/requirements/1").to route_to("requirements#update", :id => "1") + end + + it "routes to #update via PATCH" do + expect(:patch => "/requirements/1").to route_to("requirements#update", :id => "1") + end + + it "routes to #destroy" do + expect(:delete => "/requirements/1").to route_to("requirements#destroy", :id => "1") + end + end +end +
+
+
+ +
+
+
+ + + + + + + + + + + + + diff --git a/ABC_Score/spec/routing/sei_processes_routing_spec.html b/ABC_Score/spec/routing/sei_processes_routing_spec.html new file mode 100644 index 00000000..b1fc8084 --- /dev/null +++ b/ABC_Score/spec/routing/sei_processes_routing_spec.html @@ -0,0 +1,157 @@ + + + + + + Ruby Critic - Home + + + + + + + + + + + + +
+ + + +
+
+
+ +
+
+ + + Updated + + +
+
+

spec/routing / sei_processes_routing_spec.rb

+
+
+ +
+ +
+
+
+
+
+ D +
+
+
+
+
38 lines of codes
+
0 methods
+
+
+
N/A complexity/method
+
1 churn
+
+
+
46.0 complexity
+
106 duplications
+
+
+
+
+
+
+ +
+
+
+ + require "rails_helper" + +RSpec.describe SeiProcessesController, type: :routing do
  1. Similar code found in 3 nodes Locations: 0 1 2
+ describe "routing" do + it "routes to #index" do + expect(:get => "/sei_processes").to route_to("sei_processes#index") + end + + it "routes to #new" do + expect(:get => "/sei_processes/new").to route_to("sei_processes#new") + end + + it "routes to #show" do + expect(:get => "/sei_processes/1").to route_to("sei_processes#show", :id => "1") + end + + it "routes to #edit" do + expect(:get => "/sei_processes/1/edit").to route_to("sei_processes#edit", :id => "1") + end + + + it "routes to #create" do + expect(:post => "/sei_processes").to route_to("sei_processes#create") + end + + it "routes to #update via PUT" do + expect(:put => "/sei_processes/1").to route_to("sei_processes#update", :id => "1") + end + + it "routes to #update via PATCH" do + expect(:patch => "/sei_processes/1").to route_to("sei_processes#update", :id => "1") + end + + it "routes to #destroy" do + expect(:delete => "/sei_processes/1").to route_to("sei_processes#destroy", :id => "1") + end + end +end +
+
+
+ +
+
+
+ + + + + + + + + + + + + diff --git a/ABC_Score/spec/spec_helper.html b/ABC_Score/spec/spec_helper.html new file mode 100644 index 00000000..0f929235 --- /dev/null +++ b/ABC_Score/spec/spec_helper.html @@ -0,0 +1,219 @@ + + + + + + Ruby Critic - Home + + + + + + + + + + + + +
+ + + +
+
+
+ +
+
+ + + Updated + + +
+
+

spec / spec_helper.rb

+
+
+ +
+ +
+
+
+
+
+ A +
+
+
+
+
100 lines of codes
+
0 methods
+
+
+
N/A complexity/method
+
8 churn
+
+
+
0.0 complexity
+
0 duplications
+
+
+
+
+
+
+ +
+
+
+ + # This file was generated by the `rails generate rspec:install` command. Conventionally, all +# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`. +# The generated `.rspec` file contains `--require spec_helper` which will cause +# this file to always be loaded, without a need to explicitly require it in any +# files. +# +# Given that it is always loaded, you are encouraged to keep this file as +# light-weight as possible. Requiring heavyweight dependencies from this file +# will add to the boot time of your test suite on EVERY test run, even for an +# individual file that may not need all of that loaded. Instead, consider making +# a separate helper file that requires the additional dependencies and performs +# the additional setup, and require it from the spec files that actually need +# it. +# +# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration +RSpec.configure do |config| + # rspec-expectations config goes here. You can use an alternate + # assertion/expectation library such as wrong or the stdlib/minitest + # assertions if you prefer. + config.expect_with :rspec do |expectations| + # This option will default to `true` in RSpec 4. It makes the `description` + # and `failure_message` of custom matchers include text for helper methods + # defined using `chain`, e.g.: + # be_bigger_than(2).and_smaller_than(4).description + # # => "be bigger than 2 and smaller than 4" + # ...rather than: + # # => "be bigger than 2" + expectations.include_chain_clauses_in_custom_matcher_descriptions = true + end + + # rspec-mocks config goes here. You can use an alternate test double + # library (such as bogus or mocha) by changing the `mock_with` option here. + config.mock_with :rspec do |mocks| + # Prevents you from mocking or stubbing a method that does not exist on + # a real object. This is generally recommended, and will default to + # `true` in RSpec 4. + mocks.verify_partial_doubles = true + end + + # This option will default to `:apply_to_host_groups` in RSpec 4 (and will + # have no way to turn it off -- the option exists only for backwards + # compatibility in RSpec 3). It causes shared context metadata to be + # inherited by the metadata hash of host groups and examples, rather than + # triggering implicit auto-inclusion in groups with matching metadata. + config.shared_context_metadata_behavior = :apply_to_host_groups + +# The settings below are suggested to provide a good initial experience +# with RSpec, but feel free to customize to your heart's content. +=begin + # This allows you to limit a spec run to individual examples or groups + # you care about by tagging them with `:focus` metadata. When nothing + # is tagged with `:focus`, all examples get run. RSpec also provides + # aliases for `it`, `describe`, and `context` that include `:focus` + # metadata: `fit`, `fdescribe` and `fcontext`, respectively. + config.filter_run_when_matching :focus + + # Allows RSpec to persist some state between runs in order to support + # the `--only-failures` and `--next-failure` CLI options. We recommend + # you configure your source control system to ignore this file. + config.example_status_persistence_file_path = "spec/examples.txt" + + # Limits the available syntax to the non-monkey patched syntax that is + # recommended. For more details, see: + # - http://rspec.info/blog/2012/06/rspecs-new-expectation-syntax/ + # - http://www.teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/ + # - http://rspec.info/blog/2014/05/notable-changes-in-rspec-3/#zero-monkey-patching-mode + config.disable_monkey_patching! + + # This setting enables warnings. It's recommended, but in some cases may + # be too noisy due to issues in dependencies. + config.warnings = true + + # Many RSpec users commonly either run the entire suite or an individual + # file, and it's useful to allow more verbose output when running an + # individual spec file. + if config.files_to_run.one? + # Use the documentation formatter for detailed output, + # unless a formatter has already been configured + # (e.g. via a command-line flag). + config.default_formatter = "doc" + end + + # Print the 10 slowest examples and example groups at the + # end of the spec run, to help surface which specs are running + # particularly slow. + config.profile_examples = 10 + + # Run specs in random order to surface order dependencies. If you find an + # order dependency and want to debug it, you can fix the order by providing + # the seed, which is printed after each run. + # --seed 1234 + config.order = :random + + # Seed global randomization in this process using the `--seed` CLI option. + # Setting this allows you to use `--seed` to deterministically reproduce + # test failures related to randomization by passing the same `--seed` value + # as the one that triggered the failure. + Kernel.srand config.seed +=end +end + +
+
+ +
+
+
+ + + + + + + + + + + + + diff --git a/ABC_Score/spec/views/home/index.html.erb_spec.html b/ABC_Score/spec/views/home/index.html.erb_spec.html new file mode 100644 index 00000000..aee42409 --- /dev/null +++ b/ABC_Score/spec/views/home/index.html.erb_spec.html @@ -0,0 +1,124 @@ + + + + + + Ruby Critic - Home + + + + + + + + + + + + +
+ + + +
+
+
+ +
+
+ + + Updated + + +
+
+

spec/views/home / index.html.erb_spec.rb

+
+
+ +
+ +
+
+
+
+
+ A +
+
+
+
+
5 lines of codes
+
0 methods
+
+
+
N/A complexity/method
+
4 churn
+
+
+
0.0 complexity
+
0 duplications
+
+
+
+
+
+
+ +
+
+
+ + # require 'rails_helper' + +# RSpec.describe "home/index.html.erb", type: :view do +# pending "add some examples to (or delete) #{__FILE__}" +# end + +
+
+ +
+
+
+ + + + + + + + + + + + + diff --git a/ABC_Score/spike.html b/ABC_Score/spike.html new file mode 100644 index 00000000..79c464f1 --- /dev/null +++ b/ABC_Score/spike.html @@ -0,0 +1,140 @@ + + + + + + Ruby Critic - Home + + + + + + + + + + + + +
+ + + +
+
+
+ +
+
+ + + Updated + + +
+
+

. / spike.rb

+
+
+ +
+ +
+
+
+
+
+ A +
+
+
+
+
21 lines of codes
+
1 methods
+
+
+
1.6 complexity/method
+
8 churn
+
+
+
1.56 complexity
+
0 duplications
+
+
+
+
+
+
+ +
+
+
+ + require 'date' + +datedate = (Date.today+5).strftime("%Y-%0m-%0e") +p datedate + +val0 = 0 +val1 = 1 + +p val0 && val1 +p val0 || val1 + +def metodo (var=nil) + p var +end +metodo(13) +metodo + +h1 = {"user_id"=>"", "status"=>"3", "code"=>"0"} +h2 = {"user_id"=>"1", "status"=>"2"} +h1.merge!(h2) +p h1 + +
+
+ +
+
+
+ + + + + + + + + + + + + diff --git a/ABC_Score/test/application_system_test_case.html b/ABC_Score/test/application_system_test_case.html new file mode 100644 index 00000000..9f3ffd0f --- /dev/null +++ b/ABC_Score/test/application_system_test_case.html @@ -0,0 +1,124 @@ + + + + + + Ruby Critic - Home + + + + + + + + + + + + +
+ + + +
+
+
+ +
+
+ + + Updated + + +
+
+

test / application_system_test_case.rb

+
+
+ +
+ +
+
+
+
+
+ A +
+
+
+
+
5 lines of codes
+
0 methods
+
+
+
N/A complexity/method
+
1 churn
+
+
+
0.0 complexity
+
0 duplications
+
+
+
+
+
+
+ +
+
+
+ + require "test_helper" + +class ApplicationSystemTestCase < ActionDispatch::SystemTestCase
  1. ApplicationSystemTestCase has no descriptive comment
+ driven_by :selenium, using: :chrome, screen_size: [1400, 1400] +end +
+
+
+ +
+
+
+ + + + + + + + + + + + + diff --git a/ABC_Score/test/test_helper.html b/ABC_Score/test/test_helper.html new file mode 100644 index 00000000..017c6828 --- /dev/null +++ b/ABC_Score/test/test_helper.html @@ -0,0 +1,129 @@ + + + + + + Ruby Critic - Home + + + + + + + + + + + + +
+ + + +
+
+
+ +
+
+ + + Updated + + +
+
+

test / test_helper.rb

+
+
+ +
+ +
+
+
+
+
+ A +
+
+
+
+
10 lines of codes
+
0 methods
+
+
+
N/A complexity/method
+
5 churn
+
+
+
0.0 complexity
+
0 duplications
+
+
+
+
+
+
+ +
+
+
+ + ENV['RAILS_ENV'] ||= 'test' +require_relative '../config/environment' +require 'rails/test_help' + +class ActiveSupport::TestCase
  1. ActiveSupport::TestCase has no descriptive comment
+ # Setup all fixtures in test/fixtures/*.yml for all tests in alphabetical order. + fixtures :all + + # Add more helper methods to be used by all tests here... +end +
+
+
+ +
+
+
+ + + + + + + + + + + + + diff --git a/Gemfile b/Gemfile index 54be0f4e..7f5aab79 100644 --- a/Gemfile +++ b/Gemfile @@ -24,6 +24,7 @@ gem 'coffee-rails', '~> 4.2' gem 'turbolinks', '~> 5' # Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder gem 'jbuilder', '~> 2.5' +gem 'simplecov', require: false, group: :test # Use Redis adapter to run Action Cable in production # gem 'redis', '~> 4.0' # Use ActiveModel has_secure_password @@ -42,7 +43,6 @@ group :development, :test do # Call 'byebug' anywhere in the code to stop execution and get a debugger console gem 'byebug', platforms: [:mri, :mingw, :x64_mingw] gem 'rspec-rails' - end group :development do @@ -56,15 +56,26 @@ end group :test do # Adds support for Capybara system testing and selenium driver - gem 'capybara', '>= 2.15' gem 'selenium-webdriver' # Easy installation and use of chromedriver to run system tests with Chrome gem 'webdrivers' gem 'cucumber-rails', require: false + gem 'cucumber-rails-training-wheels' # some pre-fabbed step definitions # database_cleaner is not required, but highly recommended gem 'database_cleaner' + gem 'capybara', '>= 2.15' + gem 'launchy' # a useful debugging aid for user stories gem 'shoulda-matchers' end # Windows does not include zoneinfo files, so bundle the tzinfo-data gem gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby] + +# Rails 5.2 and Rails 6 +gem 'active_storage_validations' + +# Optional, to use :dimension validator or :aspect_ratio validator +gem 'mini_magick', '>= 4.9.5' + +# Ordena a lista pelo ABC Score em conformidade a Sprint 3 +gem 'flog' \ No newline at end of file diff --git a/Gemfile.lock b/Gemfile.lock index 0a9e4f86..c6df9a4b 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,43 +1,45 @@ GEM remote: https://rubygems.org/ specs: - actioncable (5.2.3) - actionpack (= 5.2.3) + actioncable (5.2.4.4) + actionpack (= 5.2.4.4) nio4r (~> 2.0) websocket-driver (>= 0.6.1) - actionmailer (5.2.3) - actionpack (= 5.2.3) - actionview (= 5.2.3) - activejob (= 5.2.3) + actionmailer (5.2.4.4) + actionpack (= 5.2.4.4) + actionview (= 5.2.4.4) + activejob (= 5.2.4.4) mail (~> 2.5, >= 2.5.4) rails-dom-testing (~> 2.0) - actionpack (5.2.3) - actionview (= 5.2.3) - activesupport (= 5.2.3) - rack (~> 2.0) + actionpack (5.2.4.4) + actionview (= 5.2.4.4) + activesupport (= 5.2.4.4) + rack (~> 2.0, >= 2.0.8) rack-test (>= 0.6.3) rails-dom-testing (~> 2.0) rails-html-sanitizer (~> 1.0, >= 1.0.2) - actionview (5.2.3) - activesupport (= 5.2.3) + actionview (5.2.4.4) + activesupport (= 5.2.4.4) builder (~> 3.1) erubi (~> 1.4) rails-dom-testing (~> 2.0) rails-html-sanitizer (~> 1.0, >= 1.0.3) - activejob (5.2.3) - activesupport (= 5.2.3) + active_storage_validations (0.9.0) + rails (>= 5.2.0) + activejob (5.2.4.4) + activesupport (= 5.2.4.4) globalid (>= 0.3.6) - activemodel (5.2.3) - activesupport (= 5.2.3) - activerecord (5.2.3) - activemodel (= 5.2.3) - activesupport (= 5.2.3) + activemodel (5.2.4.4) + activesupport (= 5.2.4.4) + activerecord (5.2.4.4) + activemodel (= 5.2.4.4) + activesupport (= 5.2.4.4) arel (>= 9.0) - activestorage (5.2.3) - actionpack (= 5.2.3) - activerecord (= 5.2.3) + activestorage (5.2.4.4) + actionpack (= 5.2.4.4) + activerecord (= 5.2.4.4) marcel (~> 0.3.1) - activesupport (5.2.3) + activesupport (5.2.4.4) concurrent-ruby (~> 1.0, >= 1.0.2) i18n (>= 0.7, < 2) minitest (~> 5.1) @@ -45,14 +47,13 @@ GEM addressable (2.7.0) public_suffix (>= 2.0.2, < 5.0) arel (9.0.0) - backports (3.15.0) - bcrypt (3.1.13) + bcrypt (3.1.16) bindex (0.8.1) - bootsnap (1.4.5) + bootsnap (1.5.1) msgpack (~> 1.0) - builder (3.2.3) - byebug (11.0.1) - capybara (3.29.0) + builder (3.2.4) + byebug (11.1.3) + capybara (3.33.0) addressable mini_mime (>= 0.1.3) nokogiri (~> 1.8) @@ -68,131 +69,168 @@ GEM coffee-script-source execjs coffee-script-source (1.12.2) - concurrent-ruby (1.1.5) - crass (1.0.5) - cucumber (3.1.2) - builder (>= 2.1.2) - cucumber-core (~> 3.2.0) - cucumber-expressions (~> 6.0.1) - cucumber-wire (~> 0.0.1) - diff-lcs (~> 1.3) - gherkin (~> 5.1.0) - multi_json (>= 1.7.5, < 2.0) - multi_test (>= 0.1.2) - cucumber-core (3.2.1) - backports (>= 3.8.0) - cucumber-tag_expressions (~> 1.1.0) - gherkin (~> 5.0) - cucumber-expressions (6.0.1) - cucumber-rails (2.0.0) - capybara (>= 2.12, < 4) - cucumber (>= 3.0.2, < 4) - mime-types (>= 2.0, < 4) + concurrent-ruby (1.1.7) + crass (1.0.6) + cucumber (5.2.0) + builder (~> 3.2, >= 3.2.4) + cucumber-core (~> 8.0, >= 8.0.1) + cucumber-create-meta (~> 2.0, >= 2.0.2) + cucumber-cucumber-expressions (~> 10.3, >= 10.3.0) + cucumber-gherkin (~> 15.0, >= 15.0.2) + cucumber-html-formatter (~> 9.0, >= 9.0.0) + cucumber-messages (~> 13.1, >= 13.1.0) + cucumber-wire (~> 4.0, >= 4.0.1) + diff-lcs (~> 1.4, >= 1.4.4) + multi_test (~> 0.1, >= 0.1.2) + sys-uname (~> 1.2, >= 1.2.1) + cucumber-core (8.0.1) + cucumber-gherkin (~> 15.0, >= 15.0.2) + cucumber-messages (~> 13.0, >= 13.0.1) + cucumber-tag-expressions (~> 2.0, >= 2.0.4) + cucumber-create-meta (2.0.4) + cucumber-messages (~> 13.1, >= 13.1.0) + sys-uname (~> 1.2, >= 1.2.1) + cucumber-cucumber-expressions (10.3.0) + cucumber-gherkin (15.0.2) + cucumber-messages (~> 13.0, >= 13.0.1) + cucumber-html-formatter (9.0.0) + cucumber-messages (~> 13.0, >= 13.0.1) + cucumber-messages (13.2.0) + protobuf-cucumber (~> 3.10, >= 3.10.8) + cucumber-rails (2.2.0) + capybara (>= 2.18, < 4) + cucumber (>= 3.0.2, < 6) + mime-types (~> 3.2) nokogiri (~> 1.8) - railties (>= 4.2, < 7) - cucumber-tag_expressions (1.1.1) - cucumber-wire (0.0.1) - database_cleaner (1.7.0) - devise (4.7.1) + rails (>= 5.0, < 7) + cucumber-rails-training-wheels (1.0.0) + cucumber-rails (>= 1.1.1) + cucumber-tag-expressions (2.0.4) + cucumber-wire (4.0.1) + cucumber-core (~> 8.0, >= 8.0.1) + cucumber-cucumber-expressions (~> 10.3, >= 10.3.0) + cucumber-messages (~> 13.0, >= 13.0.1) + database_cleaner (1.8.5) + devise (4.7.3) bcrypt (~> 3.0) orm_adapter (~> 0.1) railties (>= 4.1.0) responders warden (~> 1.2.3) - diff-lcs (1.3) - erubi (1.9.0) + diff-lcs (1.4.4) + docile (1.3.2) + erubi (1.10.0) execjs (2.7.0) - ffi (1.11.1) - gherkin (5.1.0) + ffi (1.13.1) + ffi (1.13.1-x64-mingw32) + flog (4.6.4) + path_expander (~> 1.0) + ruby_parser (~> 3.1, > 3.1.0) + sexp_processor (~> 4.8) globalid (0.4.2) activesupport (>= 4.2.0) - i18n (1.7.0) + i18n (1.8.5) concurrent-ruby (~> 1.0) - jbuilder (2.9.1) - activesupport (>= 4.2.0) + jbuilder (2.10.1) + activesupport (>= 5.0.0) + launchy (2.5.0) + addressable (~> 2.7) listen (3.1.5) rb-fsevent (~> 0.9, >= 0.9.4) rb-inotify (~> 0.9, >= 0.9.7) ruby_dep (~> 1.2) - loofah (2.3.1) + loofah (2.7.0) crass (~> 1.0.2) nokogiri (>= 1.5.9) mail (2.7.1) mini_mime (>= 0.1.1) marcel (0.3.3) mimemagic (~> 0.3.2) - method_source (0.9.2) - mime-types (3.3) + method_source (1.0.0) + middleware (0.1.0) + mime-types (3.3.1) mime-types-data (~> 3.2015) - mime-types-data (3.2019.1009) - mimemagic (0.3.3) + mime-types-data (3.2020.1104) + mimemagic (0.3.5) + mini_magick (4.11.0) mini_mime (1.0.2) mini_portile2 (2.4.0) - minitest (5.12.2) - msgpack (1.3.1) - multi_json (1.14.1) + minitest (5.14.2) + msgpack (1.3.3) + msgpack (1.3.3-x64-mingw32) multi_test (0.1.2) - nio4r (2.5.2) - nokogiri (1.10.8) + nio4r (2.5.4) + nokogiri (1.10.10) + mini_portile2 (~> 2.4.0) + nokogiri (1.10.10-x64-mingw32) mini_portile2 (~> 2.4.0) orm_adapter (0.5.0) - pg (1.1.4) - public_suffix (4.0.1) + path_expander (1.1.0) + pg (1.2.3) + pg (1.2.3-x64-mingw32) + protobuf-cucumber (3.10.8) + activesupport (>= 3.2) + middleware + thor + thread_safe + public_suffix (4.0.6) puma (3.12.6) rack (2.2.3) rack-test (1.1.0) rack (>= 1.0, < 3) - rails (5.2.3) - actioncable (= 5.2.3) - actionmailer (= 5.2.3) - actionpack (= 5.2.3) - actionview (= 5.2.3) - activejob (= 5.2.3) - activemodel (= 5.2.3) - activerecord (= 5.2.3) - activestorage (= 5.2.3) - activesupport (= 5.2.3) + rails (5.2.4.4) + actioncable (= 5.2.4.4) + actionmailer (= 5.2.4.4) + actionpack (= 5.2.4.4) + actionview (= 5.2.4.4) + activejob (= 5.2.4.4) + activemodel (= 5.2.4.4) + activerecord (= 5.2.4.4) + activestorage (= 5.2.4.4) + activesupport (= 5.2.4.4) bundler (>= 1.3.0) - railties (= 5.2.3) + railties (= 5.2.4.4) sprockets-rails (>= 2.0.0) rails-dom-testing (2.0.3) activesupport (>= 4.2.0) nokogiri (>= 1.6) rails-html-sanitizer (1.3.0) loofah (~> 2.3) - railties (5.2.3) - actionpack (= 5.2.3) - activesupport (= 5.2.3) + railties (5.2.4.4) + actionpack (= 5.2.4.4) + activesupport (= 5.2.4.4) method_source rake (>= 0.8.7) thor (>= 0.19.0, < 2.0) - rake (13.0.0) - rb-fsevent (0.10.3) - rb-inotify (0.10.0) + rake (13.0.1) + rb-fsevent (0.10.4) + rb-inotify (0.10.1) ffi (~> 1.0) - regexp_parser (1.6.0) - responders (3.0.0) + regexp_parser (1.8.2) + responders (3.0.1) actionpack (>= 5.0) railties (>= 5.0) - rspec-core (3.9.0) - rspec-support (~> 3.9.0) - rspec-expectations (3.9.0) + rspec-core (3.10.0) + rspec-support (~> 3.10.0) + rspec-expectations (3.10.0) diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.9.0) - rspec-mocks (3.9.0) + rspec-support (~> 3.10.0) + rspec-mocks (3.10.0) diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.9.0) - rspec-rails (3.9.0) - actionpack (>= 3.0) - activesupport (>= 3.0) - railties (>= 3.0) - rspec-core (~> 3.9.0) - rspec-expectations (~> 3.9.0) - rspec-mocks (~> 3.9.0) - rspec-support (~> 3.9.0) - rspec-support (3.9.0) + rspec-support (~> 3.10.0) + rspec-rails (4.0.1) + actionpack (>= 4.2) + activesupport (>= 4.2) + railties (>= 4.2) + rspec-core (~> 3.9) + rspec-expectations (~> 3.9) + rspec-mocks (~> 3.9) + rspec-support (~> 3.9) + rspec-support (3.10.0) ruby_dep (1.5.0) - rubyzip (2.0.0) + ruby_parser (3.15.0) + sexp_processor (~> 4.9) + rubyzip (2.3.0) sass (3.7.4) sass-listen (~> 4.0.0) sass-listen (4.0.0) @@ -204,44 +242,53 @@ GEM sprockets (>= 2.8, < 4.0) sprockets-rails (>= 2.0, < 4.0) tilt (>= 1.1, < 3) - selenium-webdriver (3.142.6) + selenium-webdriver (3.142.7) childprocess (>= 0.5, < 4.0) rubyzip (>= 1.2.2) - shoulda-matchers (4.1.2) + sexp_processor (4.15.1) + shoulda-matchers (4.4.1) activesupport (>= 4.2.0) - spring (2.1.0) + simplecov (0.19.1) + docile (~> 1.1) + simplecov-html (~> 0.11) + simplecov-html (0.12.3) + spring (2.1.1) spring-watcher-listen (2.0.1) listen (>= 2.7, < 4.0) spring (>= 1.2, < 3.0) sprockets (3.7.2) concurrent-ruby (~> 1.0) rack (> 1, < 3) - sprockets-rails (3.2.1) + sprockets-rails (3.2.2) actionpack (>= 4.0) activesupport (>= 4.0) sprockets (>= 3.0.0) - thor (0.20.3) + sys-uname (1.2.2) + ffi (~> 1.1) + thor (1.0.1) thread_safe (0.3.6) tilt (2.0.10) turbolinks (5.2.1) turbolinks-source (~> 5.2) turbolinks-source (5.2.0) - tzinfo (1.2.5) + tzinfo (1.2.8) thread_safe (~> 0.1) + tzinfo-data (1.2020.4) + tzinfo (>= 1.0.0) uglifier (4.2.0) execjs (>= 0.3.0, < 3) - warden (1.2.8) - rack (>= 2.0.6) + warden (1.2.9) + rack (>= 2.0.9) web-console (3.7.0) actionview (>= 5.0) activemodel (>= 5.0) bindex (>= 0.4.0) railties (>= 5.0) - webdrivers (4.1.3) + webdrivers (4.4.1) nokogiri (~> 1.6) rubyzip (>= 1.3.0) selenium-webdriver (>= 3.0, < 4.0) - websocket-driver (0.7.1) + websocket-driver (0.7.3) websocket-extensions (>= 0.1.0) websocket-extensions (0.1.5) xpath (3.2.0) @@ -249,17 +296,23 @@ GEM PLATFORMS ruby + x64-mingw32 DEPENDENCIES + active_storage_validations bootsnap (>= 1.1.0) byebug capybara (>= 2.15) coffee-rails (~> 4.2) cucumber-rails + cucumber-rails-training-wheels database_cleaner devise + flog jbuilder (~> 2.5) + launchy listen (>= 3.0.5, < 3.2) + mini_magick (>= 4.9.5) pg (>= 0.18, < 2.0) puma (~> 3.12) rails (~> 5.2.3) @@ -267,6 +320,7 @@ DEPENDENCIES sass-rails (~> 5.0) selenium-webdriver shoulda-matchers + simplecov spring spring-watcher-listen (~> 2.0.0) turbolinks (~> 5) @@ -279,4 +333,4 @@ RUBY VERSION ruby 2.6.3p62 BUNDLED WITH - 1.17.2 + 1.17.3 diff --git a/Saikuro/app/controllers/accreditations_controller.rb_cyclo.html b/Saikuro/app/controllers/accreditations_controller.rb_cyclo.html new file mode 100644 index 00000000..5dfeb612 --- /dev/null +++ b/Saikuro/app/controllers/accreditations_controller.rb_cyclo.html @@ -0,0 +1,89 @@ +Cyclometric Complexity + + +
+

Class : AccreditationsController

+
Total Complexity: 16
+
Total Lines: 63
+ + + + + + + + + +
MethodComplexity# Lines
index28
show11
edit11
update510
destroy510
set_accreditation12
accreditation_params12
+
+ + diff --git a/Saikuro/app/controllers/application_controller.rb_cyclo.html b/Saikuro/app/controllers/application_controller.rb_cyclo.html new file mode 100644 index 00000000..a7381936 --- /dev/null +++ b/Saikuro/app/controllers/application_controller.rb_cyclo.html @@ -0,0 +1,100 @@ +Cyclometric Complexity + + +
+

Global :

+
Total Complexity: 0
+
Total Lines: 1
+ + +
MethodComplexity# Lines
+
+
+

Module : Current

+
Total Complexity: 0
+
Total Lines: 2
+ + +
MethodComplexity# Lines
+
+
+

Class : ApplicationController

+
Total Complexity: 3
+
Total Lines: 18
+ + + + +
MethodComplexity# Lines
set_current_user16
configure_permitted_parameters12
+
+ + diff --git a/Saikuro/app/controllers/home_controller.rb_cyclo.html b/Saikuro/app/controllers/home_controller.rb_cyclo.html new file mode 100644 index 00000000..25895e3b --- /dev/null +++ b/Saikuro/app/controllers/home_controller.rb_cyclo.html @@ -0,0 +1,83 @@ +Cyclometric Complexity + + +
+

Class : HomeController

+
Total Complexity: 1
+
Total Lines: 3
+ + + +
MethodComplexity# Lines
index11
+
+ + diff --git a/Saikuro/app/controllers/requirements_controller.rb_cyclo.html b/Saikuro/app/controllers/requirements_controller.rb_cyclo.html new file mode 100644 index 00000000..14e23280 --- /dev/null +++ b/Saikuro/app/controllers/requirements_controller.rb_cyclo.html @@ -0,0 +1,92 @@ +Cyclometric Complexity + + +
+

Class : RequirementsController

+
Total Complexity: 22
+
Total Lines: 88
+ + + + + + + + + + + + +
MethodComplexity# Lines
index13
show11
new12
edit11
create512
delete_document_attachment15
update510
destroy510
set_requirement12
requirement_params12
+
+ + diff --git a/Saikuro/app/controllers/sei_processes_controller.rb_cyclo.html b/Saikuro/app/controllers/sei_processes_controller.rb_cyclo.html new file mode 100644 index 00000000..91b6896e --- /dev/null +++ b/Saikuro/app/controllers/sei_processes_controller.rb_cyclo.html @@ -0,0 +1,91 @@ +Cyclometric Complexity + + +
+

Class : SeiProcessesController

+
Total Complexity: 23
+
Total Lines: 100
+ + + + + + + + + + + +
MethodComplexity# Lines
index213
show11
new14
edit11
create514
update616
destroy510
set_sei_process12
sei_process_params12
+
+ + diff --git a/Saikuro/app/models/accreditation.rb_cyclo.html b/Saikuro/app/models/accreditation.rb_cyclo.html new file mode 100644 index 00000000..04d8c330 --- /dev/null +++ b/Saikuro/app/models/accreditation.rb_cyclo.html @@ -0,0 +1,87 @@ +Cyclometric Complexity + + +
+

Class : Accreditation

+
Total Complexity: 8
+
Total Lines: 38
+ + + + + + + +
MethodComplexity# Lines
current_user_is_admin12
check_role26
check_date26
allow_deletion!12
check_permission22
+
+ + diff --git a/Saikuro/app/models/requirement.rb_cyclo.html b/Saikuro/app/models/requirement.rb_cyclo.html new file mode 100644 index 00000000..67490aa3 --- /dev/null +++ b/Saikuro/app/models/requirement.rb_cyclo.html @@ -0,0 +1,86 @@ +Cyclometric Complexity + + +
+

Class : Requirement

+
Total Complexity: 6
+
Total Lines: 29
+ + + + + + +
MethodComplexity# Lines
current_user_is_admin12
check_role26
allow_deletion!12
check_permission24
+
+ + diff --git a/Saikuro/app/models/sei_process.rb_cyclo.html b/Saikuro/app/models/sei_process.rb_cyclo.html new file mode 100644 index 00000000..bf1f89bf --- /dev/null +++ b/Saikuro/app/models/sei_process.rb_cyclo.html @@ -0,0 +1,88 @@ +Cyclometric Complexity + + +
+

Class : SeiProcess

+
Total Complexity: 9
+
Total Lines: 49
+ + + + + + + + +
MethodComplexity# Lines
self.all_statuses12
current_user_is_admin12
check_signed_in26
check_role26
allow_deletion!12
check_permission22
+
+ + diff --git a/Saikuro/app/models/user.rb_cyclo.html b/Saikuro/app/models/user.rb_cyclo.html new file mode 100644 index 00000000..185dbe25 --- /dev/null +++ b/Saikuro/app/models/user.rb_cyclo.html @@ -0,0 +1,90 @@ +Cyclometric Complexity + + +
+

Global :

+
Total Complexity: 0
+
Total Lines: 1
+ + +
MethodComplexity# Lines
+
+
+

Class : User

+
Total Complexity: 0
+
Total Lines: 10
+ + +
MethodComplexity# Lines
+
+ + diff --git a/Saikuro/cmd.txt b/Saikuro/cmd.txt new file mode 100644 index 00000000..eb6fc721 --- /dev/null +++ b/Saikuro/cmd.txt @@ -0,0 +1 @@ +saikuro -c -p app/models/user.rb -p app/models/accreditation.rb -p app/models/requirement.rb -p app/models/sei_process.rb -p app/controllers/application_controller.rb -p app/controllers/home_controller.rb -p app/controllers/accreditations_controller.rb -p app/controllers/requirements_controller.rb -p app/controllers/sei_processes_controller.rb -y 0 -w 11 -e 16 -o detailed/ \ No newline at end of file diff --git a/Saikuro/index_cyclo.html b/Saikuro/index_cyclo.html new file mode 100644 index 00000000..8a5c0306 --- /dev/null +++ b/Saikuro/index_cyclo.html @@ -0,0 +1,107 @@ +Index for cyclomatic complexity + + + +

Index for cyclomatic complexity

+ +
+

Analyzed Files

+ + diff --git a/app/assets/javascripts/accreditations.coffee b/app/assets/javascripts/accreditations.coffee new file mode 100644 index 00000000..24f83d18 --- /dev/null +++ b/app/assets/javascripts/accreditations.coffee @@ -0,0 +1,3 @@ +# Place all the behaviors and hooks related to the matching controller here. +# All this logic will automatically be available in application.js. +# You can use CoffeeScript in this file: http://coffeescript.org/ diff --git a/app/assets/javascripts/requirements.coffee b/app/assets/javascripts/requirements.coffee new file mode 100644 index 00000000..24f83d18 --- /dev/null +++ b/app/assets/javascripts/requirements.coffee @@ -0,0 +1,3 @@ +# Place all the behaviors and hooks related to the matching controller here. +# All this logic will automatically be available in application.js. +# You can use CoffeeScript in this file: http://coffeescript.org/ diff --git a/app/assets/javascripts/sei_processes.coffee b/app/assets/javascripts/sei_processes.coffee new file mode 100644 index 00000000..24f83d18 --- /dev/null +++ b/app/assets/javascripts/sei_processes.coffee @@ -0,0 +1,3 @@ +# Place all the behaviors and hooks related to the matching controller here. +# All this logic will automatically be available in application.js. +# You can use CoffeeScript in this file: http://coffeescript.org/ diff --git a/app/assets/stylesheets/accreditations.scss b/app/assets/stylesheets/accreditations.scss new file mode 100644 index 00000000..5c7ed1fa --- /dev/null +++ b/app/assets/stylesheets/accreditations.scss @@ -0,0 +1,3 @@ +// Place all the styles related to the Accreditations controller here. +// They will automatically be included in application.css. +// You can use Sass (SCSS) here: http://sass-lang.com/ diff --git a/app/assets/stylesheets/requirements.scss b/app/assets/stylesheets/requirements.scss new file mode 100644 index 00000000..911f2332 --- /dev/null +++ b/app/assets/stylesheets/requirements.scss @@ -0,0 +1,3 @@ +// Place all the styles related to the Requirements controller here. +// They will automatically be included in application.css. +// You can use Sass (SCSS) here: http://sass-lang.com/ diff --git a/app/assets/stylesheets/scaffolds.scss b/app/assets/stylesheets/scaffolds.scss new file mode 100644 index 00000000..60451880 --- /dev/null +++ b/app/assets/stylesheets/scaffolds.scss @@ -0,0 +1,84 @@ +body { + background-color: #fff; + color: #333; + margin: 33px; + font-family: verdana, arial, helvetica, sans-serif; + font-size: 13px; + line-height: 18px; +} + +p, ol, ul, td { + font-family: verdana, arial, helvetica, sans-serif; + font-size: 13px; + line-height: 18px; +} + +pre { + background-color: #eee; + padding: 10px; + font-size: 11px; +} + +a { + color: #000; + + &:visited { + color: #666; + } + + &:hover { + color: #fff; + background-color: #000; + } +} + +th { + padding-bottom: 5px; +} + +td { + padding: 0 5px 7px; +} + +div { + &.field, &.actions { + margin-bottom: 10px; + } +} + +#notice { + color: green; +} + +.field_with_errors { + padding: 2px; + background-color: red; + display: table; +} + +#error_explanation { + width: 450px; + border: 2px solid red; + padding: 7px 7px 0; + margin-bottom: 20px; + background-color: #f0f0f0; + + h2 { + text-align: left; + font-weight: bold; + padding: 5px 5px 5px 15px; + font-size: 12px; + margin: -7px -7px 0; + background-color: #c00; + color: #fff; + } + + ul li { + font-size: 12px; + list-style: square; + } +} + +label { + display: block; +} diff --git a/app/assets/stylesheets/sei_processes.scss b/app/assets/stylesheets/sei_processes.scss new file mode 100644 index 00000000..d0b190c0 --- /dev/null +++ b/app/assets/stylesheets/sei_processes.scss @@ -0,0 +1,3 @@ +// Place all the styles related to the SeiProcesses controller here. +// They will automatically be included in application.css. +// You can use Sass (SCSS) here: http://sass-lang.com/ diff --git a/app/controllers/accreditations_controller.rb b/app/controllers/accreditations_controller.rb new file mode 100644 index 00000000..4bac61d1 --- /dev/null +++ b/app/controllers/accreditations_controller.rb @@ -0,0 +1,73 @@ +class AccreditationsController < ApplicationController + before_action :set_accreditation, only: [:show, :edit, :update, :destroy] + + ## + # Ação index da classe Accreditation. + # Renderiza a view index, que exibe os credenciamentos criados (dependente do usuário que está logado). + # GET /accreditations + def index + # Lista todas os credenciamentos para um administrador + if current_user.role == "administrator" + @accreditations = Accreditation.all + # Lista os credenciamentos próprios para um professor + else + @accreditations = Accreditation.where(user_id: current_user.id) + end + end + + ## + # Ação show da classe Accreditation. Mostra detalhes de um registro criado. + # GET /accreditations/1 + def show + end + + ## + # Ação edit da classe Accreditation. Renderiza página para atualizar um registro. + # GET /accreditations/1/edit + def edit + end + + ## + # Método responsável por atualizar um registro com os dados inseridos na view edit. + # Recebe os dados da view edit enviados para o servidor e faz o tratamento dos dados para decidir se a modificação é válida ou não. + # Redireciona para a view index caso os dados sejam válidos. + # PATCH/PUT /accreditations/1 + def update + respond_to do |format| + # Quando condições da model forem cumpridas, atualiza o registro no banco, redireciona para pagina index da tabela atual e mostra uma mensagem de sucesso + if @accreditation.update(accreditation_params) + format.html { redirect_to accreditations_url, notice: 'Credenciamento atualizado com sucesso!' } + # Mostra uma mensagem de erro se condições da model não forem cumpridas + else + format.html { render :edit } + end + end + end + + ## + # Método responsável por excluir um registro salvo na tabela. + # Redireciona para a view index. + # DELETE /accreditations/1 + def destroy + respond_to do |format| + # Mensagem de sucesso ao excluir o credenciamento quando condições da model forem cumpridas + if @accreditation.destroy + format.html { redirect_to accreditations_url, notice: 'Credenciamento excluído com sucesso!' } + # Mensagem de erro se condições da model não forem cumpridas + else + format.html { redirect_to accreditations_url, notice: 'Erro: não foi possível excluir o credenciamento!' } + end + end + end + + private + # Define parametros de Credenciamento + def set_accreditation + @accreditation = Accreditation.find(params[:id]) + end + + # Never trust parameters from the scary internet, only allow the white list through. + def accreditation_params + params.require(:accreditation).permit(:user_id, :start_date, :end_date, :sei_proccess_id) + end +end diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 457cc5f9..93d0b4b0 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -1,8 +1,21 @@ # frozen_string_literal: true +module Current + thread_mattr_accessor :user +end + class ApplicationController < ActionController::Base before_action :configure_permitted_parameters, if: :devise_controller? + around_action :set_current_user + def set_current_user + Current.user = current_user + yield + ensure + # to address the thread variable leak issues in Puma/Thin webserver + Current.user = nil + end + protected def configure_permitted_parameters diff --git a/app/controllers/requirements_controller.rb b/app/controllers/requirements_controller.rb new file mode 100644 index 00000000..dc55b82e --- /dev/null +++ b/app/controllers/requirements_controller.rb @@ -0,0 +1,106 @@ +class RequirementsController < ApplicationController + before_action :set_requirement, only: [:show, :edit, :update, :destroy] + + ## + # GET /requirements + # Esse método faz uma completa listagem a partir de Requirements + # para os requisitos criados + def index + # Lista todos os tipos de requisitos criados + @requirements = Requirement.all + end + + ## + # GET /requirements/1 + # Demonstração em detalhes os dados de um registro anteriormente criado + # para visualização + def show + end + + ## + # GET /requirements/new + # Permite, a partir do preenchimento dos dados necessários para um requisito, + # a criação de um novo requisito + def new + @requirement = Requirement.new + end + + ## + # GET /requirements/1/edit + # Permite uma visualização dos dados de um registro, e além disso apresenta + # a possibilidade de atualização de alguns dados específicos do mesmo + def edit + end + + ## + # POST /requirements + # Faz uma validação dos dados disponibilizados pelo usuário, e resulta na + # aceitação ou negação dos mesmos + def create + @requirement = Requirement.new(requirement_params) + + respond_to do |format| + # Quando condições da model forem cumpridas, cria um novo registro no banco, redireciona para pagina index da entidade atual e mostra uma mensagem de sucesso + if @requirement.save + format.html { redirect_to @requirement, notice: 'Requisitos criados com sucesso!' } + # Mostra uma mensagem de erro se condições da model não forem cumpridas + else + format.html { render :new } + end + end + end + + ## + # Realiza uma busca pelo documento que se deseja apagar, + # e após isso deleta o mesmo + def delete_document_attachment + @document = ActiveStorage::Attachment.find_by(id: params[:id]) + @requirement_id = params[:requirement_id] + @document&.purge + redirect_to edit_requirement_path(@requirement_id) + end + + ## + # PATCH/PUT /requirements/1 + # Faz o tratamento dos dados modificados pelo usuário para decidir se a modificação é válida ou não + # Realiza uma validação dos dados que foram editados pelo usuário, + # e resulta na aceitação ou não do mesmo + def update + respond_to do |format| + # Quando condições da model forem cumpridas, atualiza o registro no banco, redireciona para pagina de detalhes do registro recém modificado e mostra uma mensagem de sucesso + if @requirement.update(requirement_params) + format.html { redirect_to @requirement, notice: 'Requisitos atualizados com sucesso!' } + # Mostra uma mensagem de erro se condições da model não forem cumpridas + else + format.html { render :edit } + end + end + end + + ## + # DELETE /requirements/1 + # Tenta realizar a exclusão de um requirement, e apresenta + # uma mensagem de sucesso ou falha + def destroy + respond_to do |format| + # Mensagem de sucesso ao excluir requisitos quando condições da model forem cumpridas + if @requirement.destroy + format.html { redirect_to requirements_url, notice: 'Requisitos excluídos com sucesso!' } + # Mensagem de erro se condições da model não forem cumpridas + else + format.html { redirect_to requirements_url, notice: 'Erro: não foi possível excluir os requisitos!' } + end + end + end + + private + # Define parametros de Requisito + def set_requirement + @requirement = Requirement.find(params[:id]) + end + + # Never trust parameters from the scary internet, only allow the white list through. + def requirement_params + params.require(:requirement).permit(:title, :content, documents: []) + end +end diff --git a/app/controllers/sei_processes_controller.rb b/app/controllers/sei_processes_controller.rb new file mode 100644 index 00000000..d1c6232b --- /dev/null +++ b/app/controllers/sei_processes_controller.rb @@ -0,0 +1,115 @@ +class SeiProcessesController < ApplicationController + before_action :set_sei_process, only: [:show, :edit, :update, :destroy] + + ## + # Ação index da classe SeiProcess. + # Renderiza a view index, que exibe os processos e solicitações criados (dependente do usuário que está logado). + # GET /sei_processes + def index + # Filtra solicitações baseadas nos estados marcados como visíveis + @all_statuses = SeiProcess.all_statuses + session[:statuses] = params[:statuses] || session[:statuses] || @all_statuses.zip([]).to_h + @status_filter = session[:statuses].keys + + # Lista todas as solicitações de credenciamento para um administrador + if current_user.role == "administrator" + @sei_processes = SeiProcess.where(status: @status_filter) + # Lista as solicitações próprias para um professor + else + @my_processes = SeiProcess.where(user_id: current_user.id, status: @status_filter) + end + end + + ## + # Ação show da classe SeiProcess. Mostra detalhes de um registro criado. + # GET /sei_processes/1 + def show + end + + ## + # Ação new da classe SeiProcess. Renderiza página para criação de um registro. + # Recebe também o registro Requisitos de Credenciamento para exibição na view new. + # GET /sei_processes/new + def new + # Renderiza Requisitos de Credenciamento, caso existam, na página de criação de processo ou de abrir solicitação de credenciamento + @requirements = Requirement.find_by(title: 'Requisitos de Credenciamento') + @sei_process = SeiProcess.new + end + + ## + # Ação edit da classe SeiProcess. Renderiza página para atualizar um registro. + # GET /sei_processes/1/edit + def edit + end + + ## + # Método responsável por criar um registro com os dados inseridos na view new. + # Recebe os dados da view new e faz o tratamento para decidir se o registro é válido ou não. + # Redireciona para a view index caso os dados sejam válidos. + # POST /sei_processes + def create + # Faz correções de entradas inválidas baseando-se nos dados do usuário logado + mandatory_params = {'user_id' => current_user.id, 'status' => 'Espera', 'code' => '0'} + @sei_process = SeiProcess.new(sei_process_params.merge(mandatory_params)) + + respond_to do |format| + # Quando condições da model forem cumpridas, cria um novo registro no banco, redireciona para pagina index da tabela atual e mostra uma mensagem de sucesso + if @sei_process.save + format.html { redirect_to sei_processes_url, notice: 'Processo aberto com sucesso!' } + # Mostra uma mensagem de erro se condições da model não forem cumpridas + else + format.html { render :new } + end + end + end + + ## + # Método responsável por atualizar um registro com os dados inseridos na view edit. + # Recebe os dados da view edit e faz o tratamento dos dados modificados pelo usuário. + # Caso os dados sejam válidos, o registro é atualizado no banco e um novo registro de Accreditation correspondente é criado e o usuário é redirecionado para a view index. + # PATCH/PUT /sei_processes/1 + def update + respond_to do |format| + # Quando condições da model forem cumpridas, atualiza o registro no banco, redireciona para pagina index da tabela atual e mostra uma mensagem de sucesso + if @sei_process.update(sei_process_params) + format.html { redirect_to sei_processes_url, notice: 'Processo atualizado com sucesso!' } + + # Cria o credenciamento correspondente aa solicitação aprovada + if @sei_process.status == 'Aprovado' && (Accreditation.find_by(sei_process: @sei_process.id) == nil) + Accreditation.create!(user_id: @sei_process.user_id, sei_process_id: @sei_process.id) + end + + # Mostra uma mensagem de erro se condições da model não forem cumpridas + else + format.html { render :edit } + end + end + end + + ## + # Método responsável por excluir um registro salvo na tabela. + # Redireciona para a view index. + # DELETE /sei_processes/1 + def destroy + respond_to do |format| + # Mensagem de sucesso ao excluir processo ou solicitação quando condições da model forem cumpridas + if @sei_process.destroy + format.html { redirect_to sei_processes_url, notice: 'Processo excluído com sucesso!' } + # Mensagem de erro se condições da model não forem cumpridas + else + format.html { redirect_to sei_processes_url, notice: 'Erro: não foi possível excluir o processo!' } + end + end + end + + private + # Define parametros de Processo + def set_sei_process + @sei_process = SeiProcess.find(params[:id]) + end + + # Never trust parameters from the scary internet, only allow the white list through. + def sei_process_params + params.require(:sei_process).permit(:user_id, :status, :code, documents: []) + end +end diff --git a/app/helpers/accreditations_helper.rb b/app/helpers/accreditations_helper.rb new file mode 100644 index 00000000..ad946900 --- /dev/null +++ b/app/helpers/accreditations_helper.rb @@ -0,0 +1,2 @@ +module AccreditationsHelper +end diff --git a/app/helpers/requirements_helper.rb b/app/helpers/requirements_helper.rb new file mode 100644 index 00000000..377e5e33 --- /dev/null +++ b/app/helpers/requirements_helper.rb @@ -0,0 +1,2 @@ +module RequirementsHelper +end diff --git a/app/helpers/sei_processes_helper.rb b/app/helpers/sei_processes_helper.rb new file mode 100644 index 00000000..c74ddc6e --- /dev/null +++ b/app/helpers/sei_processes_helper.rb @@ -0,0 +1,2 @@ +module SeiProcessesHelper +end diff --git a/app/models/accreditation.rb b/app/models/accreditation.rb new file mode 100644 index 00000000..3cc3ef82 --- /dev/null +++ b/app/models/accreditation.rb @@ -0,0 +1,54 @@ +## + # Model da classe Accreditation + # É necessário a presença de um usuário + # É necessário a presença de um sei_process + # É necessário a presença de um sei_process único + # Em métodos com ações de create ou update, é necessário a verificação do método check_role + # Em métodos com ações de update, é necessário a verificação do método check_date + +class Accreditation < ApplicationRecord + belongs_to :user + belongs_to :sei_process + validates :sei_process, uniqueness: true + + validate :check_role, on: [:create, :update] + ## + # Define se usuário atual está logado e se é administrador do sistema + def current_user_is_admin + Current.user != nil && Current.user.role == 'administrator' + end + ## + # Permite criação ou atualização do credenciamento por um administrador + # Retorna "true" caso o usuário seja um administrador ou retorna "false" caso contrário + def check_role + unless current_user_is_admin + self.errors.add(:base, 'Usuário sem permissão') + return false + end + true + end + + validate :check_date, on: :update + ## + # Permite atualização com base em decorrência real de um período (data final superior a data inicial) + # Retorna "true" caso em caso de data final superior a data inicial ou retorna "false" caso contrário + def check_date + if (start_date == nil) || (end_date == nil) || (end_date < start_date) + self.errors.add(:end_date, 'inválida') + return false + end + true + end + + before_destroy :check_permission + ## + # Habilita deleção (usado por 'db/seeds.rb') + def allow_deletion! + @allow_deletion = true + end + ## + # Permite deleção de credenciamento por um administrador ou caso metodo 'allow_deletion!' tenha sido chamado pela instancia + def check_permission + throw(:abort) unless @allow_deletion || current_user_is_admin + end +end diff --git a/app/models/requirement.rb b/app/models/requirement.rb new file mode 100644 index 00000000..28a72abc --- /dev/null +++ b/app/models/requirement.rb @@ -0,0 +1,41 @@ +## +# Model de Requirement. +# Pede para que o titulo seja unico, e necessariamente preenchido +# indica que um único requisito pode ter também diversos documentos anexados +class Requirement < ApplicationRecord + validates :title, presence: true, uniqueness: true + has_many_attached :documents + + ## + # Define se usuário atual está logado e se é administrador do sistema + validate :check_role, on: [:create, :update] + def current_user_is_admin + Current.user != nil && Current.user.role == 'administrator' + end + + ## + # Permite criação ou atualização de requisitos por um administrador + def check_role + unless current_user_is_admin + self.errors.add(:base, 'Usuário sem permissão') + return false + end + true + end + + ## + # Indica se o usuário tem permissão para apagar + before_destroy :check_permission + # Habilita deleção (usado por 'db/seeds.rb') + def allow_deletion! + @allow_deletion = true + end + + ## + # Permite deleção de requisitos por um administrador ou caso metodo 'allow_deletion!' tenha sido chamado pela instancia + def check_permission + unless @allow_deletion || current_user_is_admin + throw(:abort) + end + end +end diff --git a/app/models/sei_process.rb b/app/models/sei_process.rb new file mode 100644 index 00000000..4fd6db53 --- /dev/null +++ b/app/models/sei_process.rb @@ -0,0 +1,65 @@ +## + # Model da classe SeiProcess + # É necessário a presença de um usuário + # É necessário a presença de um documento anexado, podendo haver mais. + # Em métodos com ações de create, é necessário a verificação do método check_signed_in + # Em métodos com ações de update, é necessário a verificação do método check_role + +class SeiProcess < ApplicationRecord + belongs_to :user + has_many_attached :documents + validates :documents, attached: true + + enum status: { + Espera: 0, + Aprovado: 1, + Rejeitado: 2 + } + ## + # Lista os status possíveis para os registros + def self.all_statuses + %w[Espera Aprovado Rejeitado] + end + + ## + # Define se usuário atual está logado e se é administrador do sistema + def current_user_is_admin + Current.user != nil && Current.user.role == 'administrator' + end + + validate :check_signed_in, on: :create + ## + # Permite criação de processo ou solicitação caso usuário esteja logado + # Retorna "true" caso o usuário esteja logado ou retorna "false" caso contrário + def check_signed_in + if Current.user == nil + self.errors.add(:base, 'Usuário sem permissão') + return false + end + true + end + + validate :check_role, on: :update + ## + # Permite atualização de processo ou solicitação por um administrador + # Retorna "true" caso o usuário seja um administrador ou retorna "false" caso contrário + def check_role + unless current_user_is_admin + self.errors.add(:base, 'Usuário sem permissão') + return false + end + true + end + + before_destroy :check_permission + ## + # Habilita deleção (usado por 'db/seeds.rb') + def allow_deletion! + @allow_deletion = true + end + ## + # Permite deleção de processo ou solicitação por um administrador ou caso metodo 'allow_deletion!' tenha sido chamado pela instancia + def check_permission + throw(:abort) unless @allow_deletion || current_user_is_admin + end +end diff --git a/app/views/accreditations/_form_edit.html.erb b/app/views/accreditations/_form_edit.html.erb new file mode 100644 index 00000000..9ad93b5f --- /dev/null +++ b/app/views/accreditations/_form_edit.html.erb @@ -0,0 +1,27 @@ +<%= form_with(model: accreditation, local: true) do |form| %> + <% if accreditation.errors.any? %> +
+

<%= pluralize(accreditation.errors.count, "erro") %> impediu este credenciamento de ser salvo:

+ +
    + <% accreditation.errors.full_messages.each do |message| %> +
  • <%= message %>
  • + <% end %> +
+
+ <% end %> + +
+ <%= form.label :start_date, "Data inicial" %> + <%= form.date_field :start_date, value: Date.current %> +
+ +
+ <%= form.label :end_date, "Data final" %> + <%= form.date_field :end_date %> +
+ +
+ <%= form.submit "Salvar" %> +
+<% end %> \ No newline at end of file diff --git a/app/views/accreditations/edit.html.erb b/app/views/accreditations/edit.html.erb new file mode 100644 index 00000000..81a27cdf --- /dev/null +++ b/app/views/accreditations/edit.html.erb @@ -0,0 +1,31 @@ +

Definir Prazo de Credenciamento

+ +

+ Código do Processo SEI: + <%= @accreditation.sei_process_id %> +

+

+ Nome: + <%= @accreditation.user.full_name %> +

+

+ Matrícula: + <%= @accreditation.user.registration %> +

+ +<% if @accreditation.sei_process.documents.attached? %> +

+ Documentos anexados: + <% @accreditation.sei_process.documents.each do | document | %> +

+ <%= link_to document.filename, rails_blob_path(document, disposition: 'attachment') %> +
+ <% end %> +

+<% end %> +
+ +<%= render 'form_edit', accreditation: @accreditation %> + +<%= link_to 'Mostrar', @accreditation %> | +<%= link_to 'Voltar', accreditations_path %> diff --git a/app/views/accreditations/index.html.erb b/app/views/accreditations/index.html.erb new file mode 100644 index 00000000..73159adb --- /dev/null +++ b/app/views/accreditations/index.html.erb @@ -0,0 +1,41 @@ +

+ +

Credenciamentos

+ + + + + + + + + + + + + + + <% @accreditations.each do |accreditation| %> + + + + + + + <% if current_user.role != 'administrator' || accreditation.end_date != nil %> + + <% else %> + + <% end %> + + <% end %> + +
NomeMatrículaCódigo SEIData de InícioData de Fim
<%= accreditation.user.full_name %><%= accreditation.user.registration %><%= accreditation.sei_process_id %><%= accreditation.start_date %><%= accreditation.end_date %><%= link_to 'Mostrar', accreditation %><%= link_to 'Definir Prazo', edit_accreditation_path(accreditation) %>
+ +<% if current_user.role == 'professor' %> +
+ <%= link_to 'Abrir Solicitação de Credenciamento', new_sei_process_path %> +<% end %> + +
+<%= link_to 'Página Inicial', home_index_path %> diff --git a/app/views/accreditations/show.html.erb b/app/views/accreditations/show.html.erb new file mode 100644 index 00000000..f2069d57 --- /dev/null +++ b/app/views/accreditations/show.html.erb @@ -0,0 +1,41 @@ +

+ +

Credenciamento

+ +

+ Código do Processo SEI: + <%= @accreditation.sei_process_id %> +

+

+ Nome: + <%= @accreditation.user.full_name %> +

+

+ Matrícula: + <%= @accreditation.user.registration %> +

+

+ Data de Início: + <%= @accreditation.start_date %> +

+

+ Data de Fim: + <%= @accreditation.end_date %> +

+ +<% if @accreditation.sei_process.documents.attached? %> +

+ Documentos anexados: + <% @accreditation.sei_process.documents.each do | document | %> +

+ <%= link_to document.filename, rails_blob_path(document, disposition: 'attachment') %> +
+ <% end %> +

+<% end %> +
+ +<% if current_user.role == 'administrator' %> + <%= link_to 'Editar', edit_accreditation_path(@accreditation) %> | +<% end %> +<%= link_to 'Voltar', accreditations_path %> diff --git a/app/views/home/index.html.erb b/app/views/home/index.html.erb index 33ba387d..3e9e44b4 100644 --- a/app/views/home/index.html.erb +++ b/app/views/home/index.html.erb @@ -1,12 +1,27 @@ -

Home#index

-

Find me in app/views/home/index.html.erb

+

Início

<% if user_signed_in? %>

Usuário atual

Nome: <%= current_user.full_name %>

Email: <%= current_user.email %>

Cargo: <%= current_user.role %>

- <%= link_to "Sair", destroy_user_session_path, method: :delete %> + + <% if current_user.role == "administrator" %> + <%= link_to "Lista de Requisitos", requirements_path %> + <%= link_to "Lista de Processos", sei_processes_path %> + <%= link_to "Lista de Credenciamentos", accreditations_path %> + + <% else %> + <%= link_to "Meus Processos Abertos", sei_processes_path %> + + <% if current_user.role == "professor" %> + <%= link_to "Meus Credenciamentos", accreditations_path %> + <% end %> + <% end %> + +
+ <%= link_to "Sair", destroy_user_session_path, method: :delete %> + <% else %>

Entre para acessar o sistema!

Você também pode se registrar ou pegar uma conta no arquivo db/seeds.rb

diff --git a/app/views/requirements/_form_edit.html.erb b/app/views/requirements/_form_edit.html.erb new file mode 100644 index 00000000..27fbc0b8 --- /dev/null +++ b/app/views/requirements/_form_edit.html.erb @@ -0,0 +1,46 @@ +<%= form_with(model: requirement, local: true) do |form| %> + <% if requirement.errors.any? %> +
+

<%= pluralize(requirement.errors.count, "erro") %> impediu o salvamento dos requisitos:

+ +
    + <% requirement.errors.full_messages.each do |message| %> +
  • <%= message %>
  • + <% end %> +
+
+ <% end %> + +
+ <%= form.label :title, "Título" %> + <%= form.text_field :title %> +
+ +
+ <%= form.label :content, "Conteúdo" %> + <%= form.text_area :content %> +
+ + <% if @requirement.documents.attached? %> +
Documentos anexados:
+ <% @requirement.documents.each do | document | %> +
+ <%= link_to document.filename, rails_blob_path(document, disposition: 'attachment') %> + <%= link_to 'Remover', delete_document_attachment_requirement_url(document.id, requirement_id: @requirement.id), + method: :delete, + data: { } + %> +
+ <% end %> + <% end %> +
+ +
+ <%= form.label :documents, "Documentos" %> + <%= form.file_field :documents, multiple: true %> +
+ +
+ <%= form.submit "Salvar" %> +
+<% end %> diff --git a/app/views/requirements/_form_new.html.erb b/app/views/requirements/_form_new.html.erb new file mode 100644 index 00000000..2e84275d --- /dev/null +++ b/app/views/requirements/_form_new.html.erb @@ -0,0 +1,32 @@ +<%= form_with(model: requirement, local: true) do |form| %> + <% if requirement.errors.any? %> +
+

<%= pluralize(requirement.errors.count, "erro") %> impediu o salvamento dos requisitos:

+ +
    + <% requirement.errors.full_messages.each do |message| %> +
  • <%= message %>
  • + <% end %> +
+
+ <% end %> + +
+ <%= form.label :title, "Título" %> + <%= form.text_field :title %> +
+ +
+ <%= form.label :content, "Conteúdo" %> + <%= form.text_area :content %> +
+ +
+ <%= form.label :documents, "Documentos" %> + <%= form.file_field :documents, multiple: true %> +
+ +
+ <%= form.submit "Enviar" %> +
+<% end %> diff --git a/app/views/requirements/edit.html.erb b/app/views/requirements/edit.html.erb new file mode 100644 index 00000000..1e0ff4ca --- /dev/null +++ b/app/views/requirements/edit.html.erb @@ -0,0 +1,6 @@ +

Editar Requisitos

+ +<%= render 'form_edit', requirement: @requirement %> + +<%= link_to 'Mostrar', @requirement %> | +<%= link_to 'Voltar', requirements_path %> diff --git a/app/views/requirements/index.html.erb b/app/views/requirements/index.html.erb new file mode 100644 index 00000000..d11e2de3 --- /dev/null +++ b/app/views/requirements/index.html.erb @@ -0,0 +1,28 @@ +

+ +

Informações de Requisitos

+ + + + + + + + + + + <% @requirements.each do |requirement| %> + + + + + + + <% end %> + +
Tipo de Requisito
<%= requirement.title %><%= link_to 'Mostrar', requirement %><%= link_to 'Editar', edit_requirement_path(requirement) %><%= link_to 'Excluir', requirement, method: :delete, data: { confirm: 'Tem certeza que deseja excluir essa Informação?' } %>
+ +
+<%= link_to 'Adicionar Informação de Requisitos', new_requirement_path %> +
+<%= link_to 'Página Inicial', home_index_path %> diff --git a/app/views/requirements/new.html.erb b/app/views/requirements/new.html.erb new file mode 100644 index 00000000..ad004d74 --- /dev/null +++ b/app/views/requirements/new.html.erb @@ -0,0 +1,5 @@ +

Nova Informação de Requisitos

+ +<%= render 'form_new', requirement: @requirement %> + +<%= link_to 'Voltar', requirements_path %> diff --git a/app/views/requirements/show.html.erb b/app/views/requirements/show.html.erb new file mode 100644 index 00000000..cec4f83b --- /dev/null +++ b/app/views/requirements/show.html.erb @@ -0,0 +1,25 @@ +

+ +

+ Tipo dos Requisitos: + <%= @requirement.title %> +

+ +

+ Conteúdo: + <%= @requirement.content %> +

+ +<% if @requirement.documents.attached? %> +

+ Documentos anexados + <% @requirement.documents.each do |document| %> +

+ <%= link_to document.filename, rails_blob_path(document, disposition: 'attachment') %> +
+ <% end %> +

+<% end %> + +<%= link_to 'Editar', edit_requirement_path(@requirement) %> | +<%= link_to 'Voltar', requirements_path %> diff --git a/app/views/sei_processes/_form_edit.html.erb b/app/views/sei_processes/_form_edit.html.erb new file mode 100644 index 00000000..33a1d5cd --- /dev/null +++ b/app/views/sei_processes/_form_edit.html.erb @@ -0,0 +1,23 @@ +<%= form_with(model: sei_process, local: true) do |form| %> + <% if sei_process.errors.any? %> +
+

<%= pluralize(sei_process.errors.count, "erro") %> impediu este processo de ser salvo:

+ +
    + <% sei_process.errors.full_messages.each do |message| %> +
  • <%= message %>
  • + <% end %> +
+
+ <% end %> + +
+ <%= form.label :status, "Avaliar" %> + <%= form.radio_button :status, 'Aprovado', id: 'Aprovado', :checked => true %> Aprovado + <%= form.radio_button :status, 'Rejeitado', id: 'Rejeitado' %> Rejeitado +
+ +
+ <%= form.submit "Enviar" %> +
+<% end %> diff --git a/app/views/sei_processes/_form_new.html.erb b/app/views/sei_processes/_form_new.html.erb new file mode 100644 index 00000000..87106176 --- /dev/null +++ b/app/views/sei_processes/_form_new.html.erb @@ -0,0 +1,34 @@ +<%= form_with(model: sei_process, local: true) do |form| %> + <% if sei_process.errors.any? %> +
+

<%= pluralize(sei_process.errors.count, "erro") %> impediu este processo de ser salvo:

+ +
    + <% sei_process.errors.full_messages.each do |message| %> +
  • <%= message %>
  • + <% end %> +
+
+ <% end %> + +
+ <%= form.hidden_field :user_id %> +
+ +
+ <%= form.hidden_field :status %> +
+ +
+ <%= form.hidden_field :code %> +
+ +
+ <%= form.label :documents, "Documentos" %> + <%= form.file_field :documents, multiple: true %> +
+ +
+ <%= form.submit "Enviar" %> +
+<% end %> diff --git a/app/views/sei_processes/_index_admin.html.erb b/app/views/sei_processes/_index_admin.html.erb new file mode 100644 index 00000000..d3daf616 --- /dev/null +++ b/app/views/sei_processes/_index_admin.html.erb @@ -0,0 +1,27 @@ + + + + + + + + + + + + + <% @sei_processes.each do |sei_process| %> + + + + + + <% if sei_process.status != 'Espera' %> + + <% else %> + + <% end %> + + <% end %> + +
NomeMatrículaCódigo do ProcessoStatus
<%= sei_process.user.full_name %><%= sei_process.user.registration %><%= sei_process.id %><%= sei_process.status %><%= link_to 'Mostrar', sei_process_path(sei_process) %><%= link_to 'Avaliar', edit_sei_process_path(sei_process) %>
diff --git a/app/views/sei_processes/_index_user.html.erb b/app/views/sei_processes/_index_user.html.erb new file mode 100644 index 00000000..18f3fa20 --- /dev/null +++ b/app/views/sei_processes/_index_user.html.erb @@ -0,0 +1,19 @@ + + + + + + + + + + + <% @my_processes.each do |sei_process| %> + + + + + + <% end %> + +
Código do ProcessoStatus
<%= sei_process.id %><%= sei_process.status %><%= link_to 'Mostrar', sei_process_path(sei_process) %>
diff --git a/app/views/sei_processes/_show_accreditation_requirements.html.erb b/app/views/sei_processes/_show_accreditation_requirements.html.erb new file mode 100644 index 00000000..65e84397 --- /dev/null +++ b/app/views/sei_processes/_show_accreditation_requirements.html.erb @@ -0,0 +1,15 @@ +<% if @requirements != nil %> +

<%= @requirements.title %>

+ <%= @requirements.content %> + + <% if @requirements.documents.attached? %> +

+ Documentos Necessários: + <% @requirements.documents.each do | document | %> +

+ <%= link_to document.filename, rails_blob_path(document, disposition: 'attachment') %> +
+ <% end %> +

+ <% end %> +<% end %> diff --git a/app/views/sei_processes/edit.html.erb b/app/views/sei_processes/edit.html.erb new file mode 100644 index 00000000..892f934d --- /dev/null +++ b/app/views/sei_processes/edit.html.erb @@ -0,0 +1,25 @@ +

<%= "Processo #{@sei_process.id}" %>

+ +

+ Nome: + <%= @sei_process.user.full_name %> +

+

+ Matrícula: + <%= @sei_process.user.registration %> +

+ +<% if @sei_process.documents.attached? %> +

+ Documentos anexados: + <% @sei_process.documents.each do | document | %> +

+ <%= link_to document.filename, rails_blob_path(document, disposition: 'attachment') %> +
+ <% end %> +

+<% end %> + +<%= render 'form_edit', sei_process: @sei_process %> + +<%= link_to 'Voltar', sei_processes_path %> diff --git a/app/views/sei_processes/index.html.erb b/app/views/sei_processes/index.html.erb new file mode 100644 index 00000000..5b41b39d --- /dev/null +++ b/app/views/sei_processes/index.html.erb @@ -0,0 +1,23 @@ +

+ +

SEI Processos

+ +<%= form_tag sei_processes_path, :method => :get, id: 'statuses_form' do %> + <% @all_statuses.each do |status| %> + <%= check_box_tag "statuses[#{status}]", 'yes', @status_filter.include?(status), id: "statuses_#{status}" %> + <%= status %> + <% end %> + <%= submit_tag "Atualizar", id: 'statuses_submit' %> +<% end %> +
+ +<% if current_user.role == "administrator" %> + <%= render 'index_admin' %> +<% else %> + <%= render 'index_user' %> +<% end %> + +
+<%= link_to 'Abrir Novo Processo', new_sei_process_path %> +
+<%= link_to 'Página Inicial', home_index_path %> diff --git a/app/views/sei_processes/new.html.erb b/app/views/sei_processes/new.html.erb new file mode 100644 index 00000000..262c5f6f --- /dev/null +++ b/app/views/sei_processes/new.html.erb @@ -0,0 +1,15 @@ +

Abrir Processo

+ +<% if (current_user.role == 'administrator') || (current_user.role == 'professor') %> + <%= render 'show_accreditation_requirements' %> +<% end %> + +<%= render 'form_new', sei_process: @sei_process %> + +<% if current_user.role == 'professor' %> +
Voltar para
+ <%= link_to 'Meus Processos', sei_processes_path %> + <%= link_to 'Meus Credenciamentos', accreditations_path %> +<% else %> + <%= link_to 'Voltar', sei_processes_path %> +<% end %> diff --git a/app/views/sei_processes/show.html.erb b/app/views/sei_processes/show.html.erb new file mode 100644 index 00000000..6dc27079 --- /dev/null +++ b/app/views/sei_processes/show.html.erb @@ -0,0 +1,28 @@ +

<%= "Processo #{@sei_process.id}" %>

+ +

+ Nome: + <%= @sei_process.user.full_name %> +

+

+ Matrícula: + <%= @sei_process.user.registration %> +

+ +<% if @sei_process.documents.attached? %> +

+ Documentos anexados: + <% @sei_process.documents.each do | document | %> +

+ <%= link_to document.filename, rails_blob_path(document, disposition: 'attachment') %> +
+ <% end %> +

+<% end %> + +

+ Status: + <%= @sei_process.status %> +

+ +<%= link_to 'Voltar', sei_processes_path %> diff --git a/config/database.yml b/config/database.yml index bbc24623..9930ddf5 100644 --- a/config/database.yml +++ b/config/database.yml @@ -82,4 +82,4 @@ production: <<: *default database: secretaria_ppgi_production username: secretaria_ppgi - password: <%= ENV['SECRETARIA_PPGI_DATABASE_PASSWORD'] %> + password: <%= ENV['SECRETARIA_PPGI_DATABASE_PASSWORD'] %> \ No newline at end of file diff --git a/config/routes.rb b/config/routes.rb index f33f7f68..e0f4a3df 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,6 +1,15 @@ # frozen_string_literal: true Rails.application.routes.draw do + resources :accreditations + resources :sei_processes + + resources :requirements do + member do + delete :delete_document_attachment + end + end + get 'home/index' devise_for :users # For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html diff --git a/coverage/.last_run.json b/coverage/.last_run.json new file mode 100644 index 00000000..29a6023c --- /dev/null +++ b/coverage/.last_run.json @@ -0,0 +1,5 @@ +{ + "result": { + "covered_percent": 98.9 + } +} diff --git a/coverage/.resultset.json b/coverage/.resultset.json new file mode 100644 index 00000000..84fabcc8 --- /dev/null +++ b/coverage/.resultset.json @@ -0,0 +1,1854 @@ +{ + "RSpec": { + "coverage": { + "/mnt/c/Users/Fontenelle/Desktop/UnB/9 semestre/Engenharia de Software/secretaria_ppgi/config/environment.rb": { + "lines": [ + null, + 1, + null, + null, + 1 + ] + }, + "/mnt/c/Users/Fontenelle/Desktop/UnB/9 semestre/Engenharia de Software/secretaria_ppgi/config/application.rb": { + "lines": [ + 1, + null, + 1, + null, + null, + null, + 1, + null, + 1, + 1, + null, + 1, + null, + null, + null, + null, + null, + null, + null + ] + }, + "/mnt/c/Users/Fontenelle/Desktop/UnB/9 semestre/Engenharia de Software/secretaria_ppgi/config/boot.rb": { + "lines": [ + 1, + null, + 1, + 1 + ] + }, + "/mnt/c/Users/Fontenelle/Desktop/UnB/9 semestre/Engenharia de Software/secretaria_ppgi/config/environments/test.rb": { + "lines": [ + 1, + null, + null, + null, + null, + null, + null, + 1, + null, + null, + null, + null, + 1, + null, + null, + 1, + 1, + null, + null, + null, + null, + 1, + 1, + null, + null, + 1, + null, + null, + 1, + null, + null, + 1, + null, + 1, + null, + null, + null, + null, + 1, + null, + null, + 1, + null, + null, + null, + null + ] + }, + "/mnt/c/Users/Fontenelle/Desktop/UnB/9 semestre/Engenharia de Software/secretaria_ppgi/config/initializers/application_controller_renderer.rb": { + "lines": [ + null, + null, + null, + null, + null, + null, + null, + null + ] + }, + "/mnt/c/Users/Fontenelle/Desktop/UnB/9 semestre/Engenharia de Software/secretaria_ppgi/config/initializers/assets.rb": { + "lines": [ + null, + null, + null, + 1, + null, + null, + null, + null, + 1, + null, + null, + null, + null, + null + ] + }, + "/mnt/c/Users/Fontenelle/Desktop/UnB/9 semestre/Engenharia de Software/secretaria_ppgi/config/initializers/backtrace_silencers.rb": { + "lines": [ + null, + null, + null, + null, + null, + null, + null + ] + }, + "/mnt/c/Users/Fontenelle/Desktop/UnB/9 semestre/Engenharia de Software/secretaria_ppgi/config/initializers/content_security_policy.rb": { + "lines": [ + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null + ] + }, + "/mnt/c/Users/Fontenelle/Desktop/UnB/9 semestre/Engenharia de Software/secretaria_ppgi/config/initializers/cookies_serializer.rb": { + "lines": [ + null, + null, + null, + null, + 1 + ] + }, + "/mnt/c/Users/Fontenelle/Desktop/UnB/9 semestre/Engenharia de Software/secretaria_ppgi/config/initializers/devise.rb": { + "lines": [ + null, + null, + null, + null, + 1, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + 1, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + 1, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + 1, + null, + null, + null, + null, + 1, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + 1, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + 1, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + 1, + null, + null, + null, + null, + null, + null, + null, + null, + null, + 1, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + 1, + null, + null, + null, + null, + 1, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + 1, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + 1, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null + ] + }, + "/mnt/c/Users/Fontenelle/Desktop/UnB/9 semestre/Engenharia de Software/secretaria_ppgi/config/initializers/filter_parameter_logging.rb": { + "lines": [ + null, + null, + null, + 1 + ] + }, + "/mnt/c/Users/Fontenelle/Desktop/UnB/9 semestre/Engenharia de Software/secretaria_ppgi/config/initializers/inflections.rb": { + "lines": [ + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null + ] + }, + "/mnt/c/Users/Fontenelle/Desktop/UnB/9 semestre/Engenharia de Software/secretaria_ppgi/config/initializers/mime_types.rb": { + "lines": [ + null, + null, + null, + null + ] + }, + "/mnt/c/Users/Fontenelle/Desktop/UnB/9 semestre/Engenharia de Software/secretaria_ppgi/config/initializers/wrap_parameters.rb": { + "lines": [ + null, + null, + null, + null, + null, + null, + 1, + 2, + null, + null, + null, + null, + null, + null + ] + }, + "/mnt/c/Users/Fontenelle/Desktop/UnB/9 semestre/Engenharia de Software/secretaria_ppgi/config/routes.rb": { + "lines": [ + null, + null, + 1, + 1, + 1, + null, + 1, + 1, + 1, + null, + null, + null, + 1, + 1, + null, + 1, + null + ] + }, + "/mnt/c/Users/Fontenelle/Desktop/UnB/9 semestre/Engenharia de Software/secretaria_ppgi/app/models/user.rb": { + "lines": [ + null, + null, + 1, + 1, + 1, + null, + null, + null, + 1, + null, + null, + 1, + null + ] + }, + "/mnt/c/Users/Fontenelle/Desktop/UnB/9 semestre/Engenharia de Software/secretaria_ppgi/app/models/application_record.rb": { + "lines": [ + 1, + 1, + null + ] + }, + "/mnt/c/Users/Fontenelle/Desktop/UnB/9 semestre/Engenharia de Software/secretaria_ppgi/app/helpers/accreditations_helper.rb": { + "lines": [ + 1, + null + ] + }, + "/mnt/c/Users/Fontenelle/Desktop/UnB/9 semestre/Engenharia de Software/secretaria_ppgi/app/helpers/application_helper.rb": { + "lines": [ + 1, + null + ] + }, + "/mnt/c/Users/Fontenelle/Desktop/UnB/9 semestre/Engenharia de Software/secretaria_ppgi/app/helpers/home_helper.rb": { + "lines": [ + 1, + null + ] + }, + "/mnt/c/Users/Fontenelle/Desktop/UnB/9 semestre/Engenharia de Software/secretaria_ppgi/app/helpers/requirements_helper.rb": { + "lines": [ + 1, + null + ] + }, + "/mnt/c/Users/Fontenelle/Desktop/UnB/9 semestre/Engenharia de Software/secretaria_ppgi/app/helpers/sei_processes_helper.rb": { + "lines": [ + 1, + null + ] + }, + "/mnt/c/Users/Fontenelle/Desktop/UnB/9 semestre/Engenharia de Software/secretaria_ppgi/app/controllers/accreditations_controller.rb": { + "lines": [ + 1, + 1, + null, + null, + null, + 1, + null, + 1, + 1, + null, + null, + 0, + null, + null, + null, + null, + null, + 1, + null, + null, + null, + null, + 1, + null, + null, + null, + null, + 1, + 2, + null, + 2, + 2, + null, + null, + 2, + null, + null, + null, + null, + null, + null, + 1, + 3, + null, + 3, + 4, + null, + null, + 2, + null, + null, + null, + null, + 1, + null, + 1, + 7, + null, + null, + null, + 1, + 2, + null, + null, + null + ] + }, + "/mnt/c/Users/Fontenelle/Desktop/UnB/9 semestre/Engenharia de Software/secretaria_ppgi/app/controllers/application_controller.rb": { + "lines": [ + null, + null, + 1, + 1, + null, + null, + 1, + 1, + null, + 1, + 1, + 41, + 41, + null, + null, + 41, + null, + null, + 1, + null, + 1, + 0, + null, + null + ] + }, + "/mnt/c/Users/Fontenelle/Desktop/UnB/9 semestre/Engenharia de Software/secretaria_ppgi/spec/controllers/home_controller_spec.rb": { + "lines": [ + 1, + null, + 1, + null, + 1, + 1, + 1, + 1, + null, + null, + null, + null + ] + }, + "/mnt/c/Users/Fontenelle/Desktop/UnB/9 semestre/Engenharia de Software/secretaria_ppgi/app/controllers/home_controller.rb": { + "lines": [ + 1, + 1, + null, + null + ] + }, + "/mnt/c/Users/Fontenelle/Desktop/UnB/9 semestre/Engenharia de Software/secretaria_ppgi/spec/controllers/requirements_controller_spec.rb": { + "lines": [ + 1, + null, + 1, + 1, + null, + 1, + 14, + 14, + null, + null, + 1, + 2, + null, + null, + 16, + null, + 1, + 1, + 4, + 4, + null, + null, + 1, + 1, + 1, + 1, + 1, + null, + null, + null, + 1, + 1, + 1, + 1, + 1, + null, + null, + null, + 1, + 1, + 1, + 1, + null, + null, + null, + 1, + 1, + 1, + 1, + 1, + null, + null, + null, + null, + 1, + 1, + 1, + 2, + 2, + null, + null, + 1, + 1, + 1, + null, + null, + null, + 1, + 1, + 1, + null, + null, + null, + 1, + 1, + 1, + 1, + null, + null, + 1, + 1, + 1, + null, + null, + null, + 1, + 1, + 1, + 1, + null, + null, + 1, + 1, + 1, + null, + null, + null, + null, + null, + 1, + 1, + 2, + 2, + null, + null, + 1, + 1, + 2, + 2, + null, + null, + 1, + 1, + 1, + 1, + 1, + 1, + null, + null, + 1, + 1, + 1, + 1, + null, + null, + null, + 1, + 1, + 1, + 1, + null, + null, + 1, + 1, + 1, + 1, + null, + null, + null, + 1, + 1, + 1, + 1, + 1, + null, + 1, + 1, + 1, + null, + null, + 1, + 1, + 1, + 1, + 1, + null, + null, + null, + null, + 1, + 1, + 1, + 1, + null, + null, + 1, + 1, + 1, + 1, + 1, + null, + null, + null, + null, + null, + 1, + 1, + 1, + 2, + 2, + null, + null, + 1, + 1, + 1, + 1, + null, + null, + null, + 1, + 1, + 1, + 1, + null, + null, + null, + 1, + 1, + 1, + 1, + 1, + null, + 1, + 1, + 1, + null, + null, + 1, + 1, + 1, + null, + null, + null, + null, + null, + null + ] + }, + "/mnt/c/Users/Fontenelle/Desktop/UnB/9 semestre/Engenharia de Software/secretaria_ppgi/app/controllers/requirements_controller.rb": { + "lines": [ + 1, + 1, + null, + null, + null, + 1, + null, + 1, + null, + null, + null, + null, + 1, + null, + null, + null, + null, + 1, + 1, + null, + null, + null, + null, + 1, + null, + null, + null, + null, + 1, + 4, + null, + 4, + null, + 4, + 4, + null, + null, + 4, + null, + null, + null, + null, + null, + 1, + 1, + 1, + 1, + 1, + null, + null, + null, + null, + 1, + 4, + null, + 4, + 4, + null, + null, + 4, + null, + null, + null, + null, + null, + null, + 1, + 3, + null, + 3, + 4, + null, + null, + 2, + null, + null, + null, + null, + 1, + null, + 1, + 9, + null, + null, + null, + 1, + 8, + null, + null + ] + }, + "/mnt/c/Users/Fontenelle/Desktop/UnB/9 semestre/Engenharia de Software/secretaria_ppgi/spec/controllers/sei_processes_controller_spec.rb": { + "lines": [ + 1, + null, + 1, + 1, + null, + 1, + 12, + null, + null, + 1, + 2, + null, + null, + 1, + 10, + null, + null, + 1, + 1, + null, + null, + 1, + 0, + null, + null, + 1, + 1, + null, + null, + 1, + 6, + null, + null, + 17, + null, + 1, + 1, + 1, + 1, + 1, + null, + null, + 1, + 1, + 1, + null, + null, + null, + 1, + 1, + 1, + 1, + null, + null, + 1, + 1, + 1, + null, + null, + null, + null, + 1, + 1, + 1, + 1, + null, + null, + 1, + 1, + 1, + null, + null, + null, + 1, + 1, + 1, + 1, + null, + null, + 1, + 1, + 1, + null, + null, + null, + 1, + 1, + 1, + 1, + null, + null, + 1, + 1, + 1, + null, + null, + null, + 1, + 1, + 4, + 4, + null, + null, + 1, + 1, + 1, + 1, + null, + null, + null, + 1, + 1, + 1, + null, + null, + 1, + 1, + 1, + null, + null, + null, + 1, + 1, + 1, + 1, + null, + null, + null, + null, + null, + 1, + 1, + 2, + null, + null, + 1, + 2, + null, + null, + 1, + 1, + 3, + 3, + null, + null, + 1, + 1, + 1, + 1, + null, + null, + 1, + 1, + 1, + null, + null, + 1, + 1, + 1, + null, + null, + null, + null, + 1, + 1, + 1, + 1, + null, + null, + 1, + 1, + 1, + 1, + null, + null, + null, + null, + 1, + 1, + 1, + 2, + 2, + null, + null, + 1, + 1, + 1, + null, + 1, + 1, + null, + null, + null, + 1, + 1, + 1, + null, + 1, + 1, + null, + null, + null, + 1, + 1, + 1, + 1, + null, + null, + 1, + 1, + 1, + 1, + null, + null, + null, + null, + null + ] + }, + "/mnt/c/Users/Fontenelle/Desktop/UnB/9 semestre/Engenharia de Software/secretaria_ppgi/app/controllers/sei_processes_controller.rb": { + "lines": [ + 1, + 1, + null, + null, + null, + 1, + null, + 2, + 2, + 2, + null, + null, + 2, + 1, + null, + null, + 1, + null, + null, + null, + null, + null, + 1, + null, + null, + null, + null, + 1, + null, + 1, + 1, + null, + null, + null, + null, + 1, + null, + null, + null, + null, + 1, + null, + 4, + 4, + null, + 4, + null, + 4, + 6, + null, + null, + 2, + null, + null, + null, + null, + null, + null, + 1, + 4, + null, + 4, + 6, + null, + null, + 3, + 1, + null, + null, + null, + null, + 2, + null, + null, + null, + null, + null, + null, + 1, + 3, + null, + 3, + 4, + null, + null, + 2, + null, + null, + null, + null, + 1, + null, + 1, + 9, + null, + null, + null, + 1, + 8, + null, + null + ] + }, + "/mnt/c/Users/Fontenelle/Desktop/UnB/9 semestre/Engenharia de Software/secretaria_ppgi/spec/helpers/home_helper_spec.rb": { + "lines": [ + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null + ] + }, + "/mnt/c/Users/Fontenelle/Desktop/UnB/9 semestre/Engenharia de Software/secretaria_ppgi/spec/models/accreditation_spec.rb": { + "lines": [ + 1, + null, + 1, + 1, + null, + 1, + 5, + null, + null, + 1, + 5, + null, + null, + 1, + 3, + null, + null, + 1, + 5, + 5, + 5, + null, + 5, + 5, + 5, + null, + null, + 1, + 1, + 1, + null, + null, + null, + null, + null, + 1, + 1, + 1, + null, + null, + null, + null, + 1, + 1, + null, + null, + null, + null, + 1, + 1, + 1, + null, + null, + null, + null, + 1, + 1, + 1, + 1, + null, + 1, + null, + null, + null, + null, + null + ] + }, + "/mnt/c/Users/Fontenelle/Desktop/UnB/9 semestre/Engenharia de Software/secretaria_ppgi/app/models/accreditation.rb": { + "lines": [ + 1, + 1, + 1, + 1, + null, + 1, + null, + 1, + 20, + null, + null, + 1, + 17, + 1, + 1, + null, + 16, + null, + null, + 1, + null, + 1, + 2, + 1, + 1, + null, + 1, + null, + null, + 1, + null, + 1, + 0, + null, + null, + 1, + 3, + null, + null + ] + }, + "/mnt/c/Users/Fontenelle/Desktop/UnB/9 semestre/Engenharia de Software/secretaria_ppgi/spec/models/requirement_spec.rb": { + "lines": [ + 1, + null, + 1, + 1, + null, + 1, + 3, + 3, + null, + null, + 1, + 1, + 1, + null, + null, + 1, + 1, + 1, + null, + null, + 1, + 1, + 1, + null, + null, + null, + null + ] + }, + "/mnt/c/Users/Fontenelle/Desktop/UnB/9 semestre/Engenharia de Software/secretaria_ppgi/app/models/requirement.rb": { + "lines": [ + 1, + 1, + 1, + null, + 1, + null, + 1, + 26, + null, + null, + 1, + 23, + 2, + 2, + null, + 21, + null, + null, + 1, + null, + 1, + 0, + null, + null, + 1, + 3, + 1, + null, + null, + null + ] + }, + "/mnt/c/Users/Fontenelle/Desktop/UnB/9 semestre/Engenharia de Software/secretaria_ppgi/spec/models/sei_process_spec.rb": { + "lines": [ + 1, + null, + 1, + 1, + null, + 1, + 3, + null, + null, + 1, + 2, + null, + null, + 1, + 1, + null, + null, + 1, + 0, + null, + null, + 1, + 1, + null, + null, + 1, + 1, + null, + null, + 1, + 1, + 2, + null, + null, + 1, + 1, + 1, + null, + null, + null, + null, + null, + 1, + 1, + 1, + null, + null, + null, + null, + null, + null, + 1, + 1, + 2, + null, + null, + 1, + 1, + 1, + null, + null, + null, + null, + null, + 1, + 1, + 1, + null, + null, + null, + null, + null, + null, + 1, + 1, + 1, + null, + null, + 1, + 1, + 1, + null, + null, + null, + null, + null, + null + ] + }, + "/mnt/c/Users/Fontenelle/Desktop/UnB/9 semestre/Engenharia de Software/secretaria_ppgi/app/models/sei_process.rb": { + "lines": [ + 1, + 1, + 1, + 1, + null, + 1, + null, + null, + null, + null, + null, + 1, + 2, + null, + null, + null, + 1, + 9, + null, + null, + 1, + null, + 1, + 31, + 1, + 1, + null, + 30, + null, + null, + 1, + null, + 1, + 6, + 1, + 1, + null, + 5, + null, + null, + 1, + null, + 1, + 0, + null, + null, + 1, + 3, + null, + null + ] + }, + "/mnt/c/Users/Fontenelle/Desktop/UnB/9 semestre/Engenharia de Software/secretaria_ppgi/spec/models/user_spec.rb": { + "lines": [ + null, + null, + null, + null, + null + ] + }, + "/mnt/c/Users/Fontenelle/Desktop/UnB/9 semestre/Engenharia de Software/secretaria_ppgi/spec/routing/accreditations_routing_spec.rb": { + "lines": [ + 1, + null, + 1, + 1, + 1, + 1, + null, + null, + 1, + 1, + null, + null, + 1, + 1, + null, + null, + 1, + 1, + null, + null, + null, + 1, + 1, + null, + null, + 1, + 1, + null, + null, + 1, + 1, + null, + null, + 1, + 1, + null, + null, + null + ] + }, + "/mnt/c/Users/Fontenelle/Desktop/UnB/9 semestre/Engenharia de Software/secretaria_ppgi/spec/routing/requirements_routing_spec.rb": { + "lines": [ + 1, + null, + 1, + 1, + 1, + 1, + null, + null, + 1, + 1, + null, + null, + 1, + 1, + null, + null, + 1, + 1, + null, + null, + null, + 1, + 1, + null, + null, + 1, + 1, + null, + null, + 1, + 1, + null, + null, + 1, + 1, + null, + null, + null + ] + }, + "/mnt/c/Users/Fontenelle/Desktop/UnB/9 semestre/Engenharia de Software/secretaria_ppgi/spec/routing/sei_processes_routing_spec.rb": { + "lines": [ + 1, + null, + 1, + 1, + 1, + 1, + null, + null, + 1, + 1, + null, + null, + 1, + 1, + null, + null, + 1, + 1, + null, + null, + null, + 1, + 1, + null, + null, + 1, + 1, + null, + null, + 1, + 1, + null, + null, + 1, + 1, + null, + null, + null + ] + }, + "/mnt/c/Users/Fontenelle/Desktop/UnB/9 semestre/Engenharia de Software/secretaria_ppgi/spec/views/home/index.html.erb_spec.rb": { + "lines": [ + null, + null, + null, + null, + null + ] + } + }, + "timestamp": 1606947321 + } +} diff --git a/tmp/.keep b/coverage/.resultset.json.lock similarity index 100% rename from tmp/.keep rename to coverage/.resultset.json.lock diff --git a/coverage/assets/0.12.3/DataTables-1.10.20/images/sort_asc.png b/coverage/assets/0.12.3/DataTables-1.10.20/images/sort_asc.png new file mode 100644 index 00000000..e1ba61a8 Binary files /dev/null and b/coverage/assets/0.12.3/DataTables-1.10.20/images/sort_asc.png differ diff --git a/coverage/assets/0.12.3/DataTables-1.10.20/images/sort_asc_disabled.png b/coverage/assets/0.12.3/DataTables-1.10.20/images/sort_asc_disabled.png new file mode 100644 index 00000000..fb11dfe2 Binary files /dev/null and b/coverage/assets/0.12.3/DataTables-1.10.20/images/sort_asc_disabled.png differ diff --git a/coverage/assets/0.12.3/DataTables-1.10.20/images/sort_both.png b/coverage/assets/0.12.3/DataTables-1.10.20/images/sort_both.png new file mode 100644 index 00000000..af5bc7c5 Binary files /dev/null and b/coverage/assets/0.12.3/DataTables-1.10.20/images/sort_both.png differ diff --git a/coverage/assets/0.12.3/DataTables-1.10.20/images/sort_desc.png b/coverage/assets/0.12.3/DataTables-1.10.20/images/sort_desc.png new file mode 100644 index 00000000..0e156deb Binary files /dev/null and b/coverage/assets/0.12.3/DataTables-1.10.20/images/sort_desc.png differ diff --git a/coverage/assets/0.12.3/DataTables-1.10.20/images/sort_desc_disabled.png b/coverage/assets/0.12.3/DataTables-1.10.20/images/sort_desc_disabled.png new file mode 100644 index 00000000..c9fdd8a1 Binary files /dev/null and b/coverage/assets/0.12.3/DataTables-1.10.20/images/sort_desc_disabled.png differ diff --git a/coverage/assets/0.12.3/application.css b/coverage/assets/0.12.3/application.css new file mode 100644 index 00000000..916699ed --- /dev/null +++ b/coverage/assets/0.12.3/application.css @@ -0,0 +1 @@ +html,body,div,span,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abbr,acronym,address,code,del,dfn,em,img,q,dl,dt,dd,ol,ul,li,fieldset,form,label,legend,table,caption,tbody,tfoot,thead,tr,th,td,article,aside,dialog,figure,footer,header,hgroup,nav,section{margin:0;padding:0;border:0;font-weight:inherit;font-style:inherit;font-size:100%;font-family:inherit;vertical-align:baseline}article,aside,dialog,figure,footer,header,hgroup,nav,section{display:block}body{line-height:1.5}table{border-collapse:separate;border-spacing:0}caption,th,td{text-align:left;font-weight:normal}table,td,th{vertical-align:middle}blockquote:before,blockquote:after,q:before,q:after{content:""}blockquote,q{quotes:"" ""}a img{border:0}html{font-size:100.01%}body{font-size:82%;color:#222;background:#fff;font-family:"Helvetica Neue",Arial,Helvetica,sans-serif}h1,h2,h3,h4,h5,h6{font-weight:normal;color:#111}h1{font-size:3em;line-height:1;margin-bottom:.5em}h2{font-size:2em;margin-bottom:.75em}h3{font-size:1.5em;line-height:1;margin-bottom:1em}h4{font-size:1.2em;line-height:1.25;margin-bottom:1.25em}h5{font-size:1em;font-weight:bold;margin-bottom:1.5em}h6{font-size:1em;font-weight:bold}h1 img,h2 img,h3 img,h4 img,h5 img,h6 img{margin:0}p{margin:0 0 1.5em}p img.left{float:left;margin:1.5em 1.5em 1.5em 0;padding:0}p img.right{float:right;margin:1.5em 0 1.5em 1.5em}a:focus,a:hover{color:#000}a{color:#009;text-decoration:underline}blockquote{margin:1.5em;color:#666;font-style:italic}strong{font-weight:bold}em,dfn{font-style:italic}dfn{font-weight:bold}sup,sub{line-height:0}abbr,acronym{border-bottom:1px dotted #666}address{margin:0 0 1.5em;font-style:italic}del{color:#666}pre{margin:1.5em 0;white-space:pre}pre,code,tt{font:1em 'andale mono','lucida console',monospace;line-height:1.5}li ul,li ol{margin:0}ul,ol{margin:0 1.5em 1.5em 0;padding-left:3.333em}ul{list-style-type:disc}ol{list-style-type:decimal}dl{margin:0 0 1.5em 0}dl dt{font-weight:bold}dd{margin-left:1.5em}table{margin-bottom:1.4em;width:100%}th{font-weight:bold}thead th{background:#c3d9ff}th,td,caption{padding:4px 10px 4px 5px}tr.even td{background:#efefef}tfoot{font-style:italic}caption{background:#eee}.small{font-size:.8em;margin-bottom:1.875em;line-height:1.875em}.large{font-size:1.2em;line-height:2.5em;margin-bottom:1.25em}.hide{display:none}.quiet{color:#666}.loud{color:#000}.highlight{background:#ff0}.added{background:#060;color:#fff}.removed{background:#900;color:#fff}.first{margin-left:0;padding-left:0}.last{margin-right:0;padding-right:0}.top{margin-top:0;padding-top:0}.bottom{margin-bottom:0;padding-bottom:0}label{font-weight:bold}fieldset{padding:1.4em;margin:0 0 1.5em 0;border:1px solid #ccc}legend{font-weight:bold;font-size:1.2em}input[type=text],input[type=password],input.text,input.title,textarea,select{background-color:#fff;border:1px solid #bbb}input[type=text]:focus,input[type=password]:focus,input.text:focus,input.title:focus,textarea:focus,select:focus{border-color:#666}input[type=text],input[type=password],input.text,input.title,textarea,select{margin:.5em 0}input.text,input.title{width:300px;padding:5px}input.title{font-size:1.5em}textarea{width:390px;height:250px;padding:5px}input[type=checkbox],input[type=radio],input.checkbox,input.radio{position:relative;top:.25em}form.inline{line-height:3}form.inline p{margin-bottom:0}.error,.notice,.success{padding:.8em;margin-bottom:1em;border:2px solid #ddd}.error{background:#fbe3e4;color:#8a1f11;border-color:#fbc2c4}.notice{background:#fff6bf;color:#514721;border-color:#ffd324}.success{background:#e6efc2;color:#264409;border-color:#c6d880}.error a{color:#8a1f11}.notice a{color:#514721}.success a{color:#264409}.box{padding:1.5em;margin-bottom:1.5em;background:#e5ecf9}hr{background:#ddd;color:#ddd;clear:both;float:none;width:100%;height:.1em;margin:0 0 1.45em;border:0}hr.space{background:#fff;color:#fff;visibility:hidden}.clearfix:after,.container:after{content:"\0020";display:block;height:0;clear:both;visibility:hidden;overflow:hidden}.clearfix,.container{display:block}.clear{clear:both}table.dataTable{width:100%;margin:0 auto;clear:both;border-collapse:separate;border-spacing:0}table.dataTable thead th,table.dataTable tfoot th{font-weight:bold}table.dataTable thead th,table.dataTable thead td{padding:10px 18px;border-bottom:1px solid #111}table.dataTable thead th:active,table.dataTable thead td:active{outline:0}table.dataTable tfoot th,table.dataTable tfoot td{padding:10px 18px 6px 18px;border-top:1px solid #111}table.dataTable thead .sorting,table.dataTable thead .sorting_asc,table.dataTable thead .sorting_desc,table.dataTable thead .sorting_asc_disabled,table.dataTable thead .sorting_desc_disabled{cursor:pointer;*cursor:hand;background-repeat:no-repeat;background-position:center right}table.dataTable thead .sorting{background-image:url("DataTables-1.10.20/images/sort_both.png")}table.dataTable thead .sorting_asc{background-image:url("DataTables-1.10.20/images/sort_asc.png")}table.dataTable thead .sorting_desc{background-image:url("DataTables-1.10.20/images/sort_desc.png")}table.dataTable thead .sorting_asc_disabled{background-image:url("DataTables-1.10.20/images/sort_asc_disabled.png")}table.dataTable thead .sorting_desc_disabled{background-image:url("DataTables-1.10.20/images/sort_desc_disabled.png")}table.dataTable tbody tr{background-color:#fff}table.dataTable tbody tr.selected{background-color:#b0bed9}table.dataTable tbody th,table.dataTable tbody td{padding:8px 10px}table.dataTable.row-border tbody th,table.dataTable.row-border tbody td,table.dataTable.display tbody th,table.dataTable.display tbody td{border-top:1px solid #ddd}table.dataTable.row-border tbody tr:first-child th,table.dataTable.row-border tbody tr:first-child td,table.dataTable.display tbody tr:first-child th,table.dataTable.display tbody tr:first-child td{border-top:0}table.dataTable.cell-border tbody th,table.dataTable.cell-border tbody td{border-top:1px solid #ddd;border-right:1px solid #ddd}table.dataTable.cell-border tbody tr th:first-child,table.dataTable.cell-border tbody tr td:first-child{border-left:1px solid #ddd}table.dataTable.cell-border tbody tr:first-child th,table.dataTable.cell-border tbody tr:first-child td{border-top:0}table.dataTable.stripe tbody tr.odd,table.dataTable.display tbody tr.odd{background-color:#f9f9f9}table.dataTable.stripe tbody tr.odd.selected,table.dataTable.display tbody tr.odd.selected{background-color:#acbad4}table.dataTable.hover tbody tr:hover,table.dataTable.display tbody tr:hover{background-color:#f6f6f6}table.dataTable.hover tbody tr:hover.selected,table.dataTable.display tbody tr:hover.selected{background-color:#aab7d1}table.dataTable.order-column tbody tr>.sorting_1,table.dataTable.order-column tbody tr>.sorting_2,table.dataTable.order-column tbody tr>.sorting_3,table.dataTable.display tbody tr>.sorting_1,table.dataTable.display tbody tr>.sorting_2,table.dataTable.display tbody tr>.sorting_3{background-color:#fafafa}table.dataTable.order-column tbody tr.selected>.sorting_1,table.dataTable.order-column tbody tr.selected>.sorting_2,table.dataTable.order-column tbody tr.selected>.sorting_3,table.dataTable.display tbody tr.selected>.sorting_1,table.dataTable.display tbody tr.selected>.sorting_2,table.dataTable.display tbody tr.selected>.sorting_3{background-color:#acbad5}table.dataTable.display tbody tr.odd>.sorting_1,table.dataTable.order-column.stripe tbody tr.odd>.sorting_1{background-color:#f1f1f1}table.dataTable.display tbody tr.odd>.sorting_2,table.dataTable.order-column.stripe tbody tr.odd>.sorting_2{background-color:#f3f3f3}table.dataTable.display tbody tr.odd>.sorting_3,table.dataTable.order-column.stripe tbody tr.odd>.sorting_3{background-color:whitesmoke}table.dataTable.display tbody tr.odd.selected>.sorting_1,table.dataTable.order-column.stripe tbody tr.odd.selected>.sorting_1{background-color:#a6b4cd}table.dataTable.display tbody tr.odd.selected>.sorting_2,table.dataTable.order-column.stripe tbody tr.odd.selected>.sorting_2{background-color:#a8b5cf}table.dataTable.display tbody tr.odd.selected>.sorting_3,table.dataTable.order-column.stripe tbody tr.odd.selected>.sorting_3{background-color:#a9b7d1}table.dataTable.display tbody tr.even>.sorting_1,table.dataTable.order-column.stripe tbody tr.even>.sorting_1{background-color:#fafafa}table.dataTable.display tbody tr.even>.sorting_2,table.dataTable.order-column.stripe tbody tr.even>.sorting_2{background-color:#fcfcfc}table.dataTable.display tbody tr.even>.sorting_3,table.dataTable.order-column.stripe tbody tr.even>.sorting_3{background-color:#fefefe}table.dataTable.display tbody tr.even.selected>.sorting_1,table.dataTable.order-column.stripe tbody tr.even.selected>.sorting_1{background-color:#acbad5}table.dataTable.display tbody tr.even.selected>.sorting_2,table.dataTable.order-column.stripe tbody tr.even.selected>.sorting_2{background-color:#aebcd6}table.dataTable.display tbody tr.even.selected>.sorting_3,table.dataTable.order-column.stripe tbody tr.even.selected>.sorting_3{background-color:#afbdd8}table.dataTable.display tbody tr:hover>.sorting_1,table.dataTable.order-column.hover tbody tr:hover>.sorting_1{background-color:#eaeaea}table.dataTable.display tbody tr:hover>.sorting_2,table.dataTable.order-column.hover tbody tr:hover>.sorting_2{background-color:#ececec}table.dataTable.display tbody tr:hover>.sorting_3,table.dataTable.order-column.hover tbody tr:hover>.sorting_3{background-color:#efefef}table.dataTable.display tbody tr:hover.selected>.sorting_1,table.dataTable.order-column.hover tbody tr:hover.selected>.sorting_1{background-color:#a2aec7}table.dataTable.display tbody tr:hover.selected>.sorting_2,table.dataTable.order-column.hover tbody tr:hover.selected>.sorting_2{background-color:#a3b0c9}table.dataTable.display tbody tr:hover.selected>.sorting_3,table.dataTable.order-column.hover tbody tr:hover.selected>.sorting_3{background-color:#a5b2cb}table.dataTable.no-footer{border-bottom:1px solid #111}table.dataTable.nowrap th,table.dataTable.nowrap td{white-space:nowrap}table.dataTable.compact thead th,table.dataTable.compact thead td{padding:4px 17px 4px 4px}table.dataTable.compact tfoot th,table.dataTable.compact tfoot td{padding:4px}table.dataTable.compact tbody th,table.dataTable.compact tbody td{padding:4px}table.dataTable th.dt-left,table.dataTable td.dt-left{text-align:left}table.dataTable th.dt-center,table.dataTable td.dt-center,table.dataTable td.dataTables_empty{text-align:center}table.dataTable th.dt-right,table.dataTable td.dt-right{text-align:right}table.dataTable th.dt-justify,table.dataTable td.dt-justify{text-align:justify}table.dataTable th.dt-nowrap,table.dataTable td.dt-nowrap{white-space:nowrap}table.dataTable thead th.dt-head-left,table.dataTable thead td.dt-head-left,table.dataTable tfoot th.dt-head-left,table.dataTable tfoot td.dt-head-left{text-align:left}table.dataTable thead th.dt-head-center,table.dataTable thead td.dt-head-center,table.dataTable tfoot th.dt-head-center,table.dataTable tfoot td.dt-head-center{text-align:center}table.dataTable thead th.dt-head-right,table.dataTable thead td.dt-head-right,table.dataTable tfoot th.dt-head-right,table.dataTable tfoot td.dt-head-right{text-align:right}table.dataTable thead th.dt-head-justify,table.dataTable thead td.dt-head-justify,table.dataTable tfoot th.dt-head-justify,table.dataTable tfoot td.dt-head-justify{text-align:justify}table.dataTable thead th.dt-head-nowrap,table.dataTable thead td.dt-head-nowrap,table.dataTable tfoot th.dt-head-nowrap,table.dataTable tfoot td.dt-head-nowrap{white-space:nowrap}table.dataTable tbody th.dt-body-left,table.dataTable tbody td.dt-body-left{text-align:left}table.dataTable tbody th.dt-body-center,table.dataTable tbody td.dt-body-center{text-align:center}table.dataTable tbody th.dt-body-right,table.dataTable tbody td.dt-body-right{text-align:right}table.dataTable tbody th.dt-body-justify,table.dataTable tbody td.dt-body-justify{text-align:justify}table.dataTable tbody th.dt-body-nowrap,table.dataTable tbody td.dt-body-nowrap{white-space:nowrap}table.dataTable,table.dataTable th,table.dataTable td{box-sizing:content-box}.dataTables_wrapper{position:relative;clear:both;*zoom:1;zoom:1}.dataTables_wrapper .dataTables_length{float:left}.dataTables_wrapper .dataTables_filter{float:right;text-align:right}.dataTables_wrapper .dataTables_filter input{margin-left:.5em}.dataTables_wrapper .dataTables_info{clear:both;float:left;padding-top:.755em}.dataTables_wrapper .dataTables_paginate{float:right;text-align:right;padding-top:.25em}.dataTables_wrapper .dataTables_paginate .paginate_button{box-sizing:border-box;display:inline-block;min-width:1.5em;padding:.5em 1em;margin-left:2px;text-align:center;text-decoration:none !important;cursor:pointer;*cursor:hand;color:#333 !important;border:1px solid transparent;border-radius:2px}.dataTables_wrapper .dataTables_paginate .paginate_button.current,.dataTables_wrapper .dataTables_paginate .paginate_button.current:hover{color:#333 !important;border:1px solid #979797;background-color:white;background:-webkit-gradient(linear,left top,left bottom,color-stop(0,white),color-stop(100%,#dcdcdc));background:-webkit-linear-gradient(top,white 0,#dcdcdc 100%);background:-moz-linear-gradient(top,white 0,#dcdcdc 100%);background:-ms-linear-gradient(top,white 0,#dcdcdc 100%);background:-o-linear-gradient(top,white 0,#dcdcdc 100%);background:linear-gradient(to bottom,white 0,#dcdcdc 100%)}.dataTables_wrapper .dataTables_paginate .paginate_button.disabled,.dataTables_wrapper .dataTables_paginate .paginate_button.disabled:hover,.dataTables_wrapper .dataTables_paginate .paginate_button.disabled:active{cursor:default;color:#666 !important;border:1px solid transparent;background:transparent;box-shadow:none}.dataTables_wrapper .dataTables_paginate .paginate_button:hover{color:white !important;border:1px solid #111;background-color:#585858;background:-webkit-gradient(linear,left top,left bottom,color-stop(0,#585858),color-stop(100%,#111));background:-webkit-linear-gradient(top,#585858 0,#111 100%);background:-moz-linear-gradient(top,#585858 0,#111 100%);background:-ms-linear-gradient(top,#585858 0,#111 100%);background:-o-linear-gradient(top,#585858 0,#111 100%);background:linear-gradient(to bottom,#585858 0,#111 100%)}.dataTables_wrapper .dataTables_paginate .paginate_button:active{outline:0;background-color:#2b2b2b;background:-webkit-gradient(linear,left top,left bottom,color-stop(0,#2b2b2b),color-stop(100%,#0c0c0c));background:-webkit-linear-gradient(top,#2b2b2b 0,#0c0c0c 100%);background:-moz-linear-gradient(top,#2b2b2b 0,#0c0c0c 100%);background:-ms-linear-gradient(top,#2b2b2b 0,#0c0c0c 100%);background:-o-linear-gradient(top,#2b2b2b 0,#0c0c0c 100%);background:linear-gradient(to bottom,#2b2b2b 0,#0c0c0c 100%);box-shadow:inset 0 0 3px #111}.dataTables_wrapper .dataTables_paginate .ellipsis{padding:0 1em}.dataTables_wrapper .dataTables_processing{position:absolute;top:50%;left:50%;width:100%;height:40px;margin-left:-50%;margin-top:-25px;padding-top:20px;text-align:center;font-size:1.2em;background-color:white;background:-webkit-gradient(linear,left top,right top,color-stop(0,rgba(255,255,255,0)),color-stop(25%,rgba(255,255,255,0.9)),color-stop(75%,rgba(255,255,255,0.9)),color-stop(100%,rgba(255,255,255,0)));background:-webkit-linear-gradient(left,rgba(255,255,255,0) 0,rgba(255,255,255,0.9) 25%,rgba(255,255,255,0.9) 75%,rgba(255,255,255,0) 100%);background:-moz-linear-gradient(left,rgba(255,255,255,0) 0,rgba(255,255,255,0.9) 25%,rgba(255,255,255,0.9) 75%,rgba(255,255,255,0) 100%);background:-ms-linear-gradient(left,rgba(255,255,255,0) 0,rgba(255,255,255,0.9) 25%,rgba(255,255,255,0.9) 75%,rgba(255,255,255,0) 100%);background:-o-linear-gradient(left,rgba(255,255,255,0) 0,rgba(255,255,255,0.9) 25%,rgba(255,255,255,0.9) 75%,rgba(255,255,255,0) 100%);background:linear-gradient(to right,rgba(255,255,255,0) 0,rgba(255,255,255,0.9) 25%,rgba(255,255,255,0.9) 75%,rgba(255,255,255,0) 100%)}.dataTables_wrapper .dataTables_length,.dataTables_wrapper .dataTables_filter,.dataTables_wrapper .dataTables_info,.dataTables_wrapper .dataTables_processing,.dataTables_wrapper .dataTables_paginate{color:#333}.dataTables_wrapper .dataTables_scroll{clear:both}.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody{*margin-top:-1px;-webkit-overflow-scrolling:touch}.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody>table>thead>tr>th,.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody>table>thead>tr>td,.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody>table>tbody>tr>th,.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody>table>tbody>tr>td{vertical-align:middle}.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody>table>thead>tr>th>div.dataTables_sizing,.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody>table>thead>tr>td>div.dataTables_sizing,.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody>table>tbody>tr>th>div.dataTables_sizing,.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody>table>tbody>tr>td>div.dataTables_sizing{height:0;overflow:hidden;margin:0 !important;padding:0 !important}.dataTables_wrapper.no-footer .dataTables_scrollBody{border-bottom:1px solid #111}.dataTables_wrapper.no-footer div.dataTables_scrollHead table.dataTable,.dataTables_wrapper.no-footer div.dataTables_scrollBody>table{border-bottom:0}.dataTables_wrapper:after{visibility:hidden;display:block;content:"";clear:both;height:0}@media screen and (max-width:767px){.dataTables_wrapper .dataTables_info,.dataTables_wrapper .dataTables_paginate{float:none;text-align:center}.dataTables_wrapper .dataTables_paginate{margin-top:.5em}}@media screen and (max-width:640px){.dataTables_wrapper .dataTables_length,.dataTables_wrapper .dataTables_filter{float:none;text-align:center}.dataTables_wrapper .dataTables_filter{margin-top:.5em}}pre .comment,pre .template_comment,pre .diff .header,pre .javadoc{color:#998;font-style:italic}pre .keyword,pre .css .rule .keyword,pre .winutils,pre .javascript .title,pre .lisp .title{color:#000;font-weight:bold}pre .number,pre .hexcolor{color:#458}pre .string,pre .tag .value,pre .phpdoc,pre .tex .formula{color:#d14}pre .subst{color:#712}pre .constant,pre .title,pre .id{color:#900;font-weight:bold}pre .javascript .title,pre .lisp .title,pre .subst{font-weight:normal}pre .class .title,pre .haskell .label,pre .tex .command{color:#458;font-weight:bold}pre .tag,pre .tag .title,pre .rules .property,pre .django .tag .keyword{color:navy;font-weight:normal}pre .attribute,pre .variable,pre .instancevar,pre .lisp .body{color:teal}pre .regexp{color:#009926}pre .class{color:#458;font-weight:bold}pre .symbol,pre .ruby .symbol .string,pre .ruby .symbol .keyword,pre .ruby .symbol .keymethods,pre .lisp .keyword,pre .tex .special,pre .input_number{color:#990073}pre .builtin,pre .built_in,pre .lisp .title{color:#0086b3}pre .preprocessor,pre .pi,pre .doctype,pre .shebang,pre .cdata{color:#999;font-weight:bold}pre .deletion{background:#fdd}pre .addition{background:#dfd}pre .diff .change{background:#0086b3}pre .chunk{color:#aaa}pre .tex .formula{opacity:.5}.ui-helper-hidden{display:none}.ui-helper-hidden-accessible{position:absolute;left:-99999999px}.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:after{content:".";display:block;height:0;clear:both;visibility:hidden}.ui-helper-clearfix{display:inline-block}/*\*/* html .ui-helper-clearfix{height:1%}.ui-helper-clearfix{display:block}/**/.ui-helper-zfix{width:100%;height:100%;top:0;left:0;position:absolute;opacity:0;filter:Alpha(Opacity=0)}.ui-state-disabled{cursor:default !important}.ui-icon{display:block;text-indent:-99999px;overflow:hidden;background-repeat:no-repeat}.ui-widget-overlay{position:absolute;top:0;left:0;width:100%;height:100%}.ui-widget{font-family: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:Verdana,Arial,sans-serif;font-size:1em}.ui-widget-content{border:1px solid #aaa;background:#fff url(images/ui-bg_flat_75_ffffff_40x100.png) 50% 50% repeat-x;color:#222}.ui-widget-content a{color:#222}.ui-widget-header{border:1px solid #aaa;background:#ccc url(images/ui-bg_highlight-soft_75_cccccc_1x100.png) 50% 50% repeat-x;color:#222;font-weight:bold}.ui-widget-header a{color:#222}.ui-state-default,.ui-widget-content .ui-state-default,.ui-widget-header .ui-state-default{border:1px solid #d3d3d3;background:#e6e6e6 url(images/ui-bg_glass_75_e6e6e6_1x400.png) 50% 50% repeat-x;font-weight:normal;color:#555}.ui-state-default a,.ui-state-default a:link,.ui-state-default a:visited{color:#555;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 #999;background:#dadada url(images/ui-bg_glass_75_dadada_1x400.png) 50% 50% repeat-x;font-weight:normal;color:#212121}.ui-state-hover a,.ui-state-hover a:hover{color:#212121;text-decoration:none}.ui-state-active,.ui-widget-content .ui-state-active,.ui-widget-header .ui-state-active{border:1px solid #aaa;background:#fff url(images/ui-bg_glass_65_ffffff_1x400.png) 50% 50% repeat-x;font-weight:normal;color:#212121}.ui-state-active a,.ui-state-active a:link,.ui-state-active a:visited{color:#212121;text-decoration:none}.ui-widget :active{outline:0}.ui-state-highlight,.ui-widget-content .ui-state-highlight,.ui-widget-header .ui-state-highlight{border:1px solid #fcefa1;background:#fbf9ee url(images/ui-bg_glass_55_fbf9ee_1x400.png) 50% 50% 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:#fef1ec url(images/ui-bg_glass_95_fef1ec_1x400.png) 50% 50% repeat-x;color:#cd0a0a}.ui-state-error a,.ui-widget-content .ui-state-error a,.ui-widget-header .ui-state-error a{color:#cd0a0a}.ui-state-error-text,.ui-widget-content .ui-state-error-text,.ui-widget-header .ui-state-error-text{color:#cd0a0a}.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}.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_222222_256x240.png)}.ui-state-default .ui-icon{background-image:url(images/ui-icons_888888_256x240.png)}.ui-state-hover .ui-icon,.ui-state-focus .ui-icon{background-image:url(images/ui-icons_454545_256x240.png)}.ui-state-active .ui-icon{background-image:url(images/ui-icons_454545_256x240.png)}.ui-state-highlight .ui-icon{background-image:url(images/ui-icons_2e83ff_256x240.png)}.ui-state-error .ui-icon,.ui-state-error-text .ui-icon{background-image:url(images/ui-icons_cd0a0a_256x240.png)}.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{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}.ui-corner-tl{-moz-border-radius-topleft:4px;-webkit-border-top-left-radius:4px;border-top-left-radius:4px}.ui-corner-tr{-moz-border-radius-topright:4px;-webkit-border-top-right-radius:4px;border-top-right-radius:4px}.ui-corner-bl{-moz-border-radius-bottomleft:4px;-webkit-border-bottom-left-radius:4px;border-bottom-left-radius:4px}.ui-corner-br{-moz-border-radius-bottomright:4px;-webkit-border-bottom-right-radius:4px;border-bottom-right-radius:4px}.ui-corner-top{-moz-border-radius-topleft:4px;-webkit-border-top-left-radius:4px;border-top-left-radius:4px;-moz-border-radius-topright:4px;-webkit-border-top-right-radius:4px;border-top-right-radius:4px}.ui-corner-bottom{-moz-border-radius-bottomleft:4px;-webkit-border-bottom-left-radius:4px;border-bottom-left-radius:4px;-moz-border-radius-bottomright:4px;-webkit-border-bottom-right-radius:4px;border-bottom-right-radius:4px}.ui-corner-right{-moz-border-radius-topright:4px;-webkit-border-top-right-radius:4px;border-top-right-radius:4px;-moz-border-radius-bottomright:4px;-webkit-border-bottom-right-radius:4px;border-bottom-right-radius:4px}.ui-corner-left{-moz-border-radius-topleft:4px;-webkit-border-top-left-radius:4px;border-top-left-radius:4px;-moz-border-radius-bottomleft:4px;-webkit-border-bottom-left-radius:4px;border-bottom-left-radius:4px}.ui-corner-all{-moz-border-radius:4px;-webkit-border-radius:4px;border-radius:4px}.ui-widget-overlay{background:#aaa url(images/ui-bg_flat_0_aaaaaa_40x100.png) 50% 50% repeat-x;opacity:.30;filter:Alpha(Opacity=30)}.ui-widget-shadow{margin:-8px 0 0 -8px;padding:8px;background:#aaa url(images/ui-bg_flat_0_aaaaaa_40x100.png) 50% 50% repeat-x;opacity:.30;filter:Alpha(Opacity=30);-moz-border-radius:8px;-webkit-border-radius:8px;border-radius:8px}#colorbox,#cboxOverlay,#cboxWrapper{position:absolute;top:0;left:0;z-index:9999;overflow:hidden}#cboxOverlay{position:fixed;width:100%;height:100%}#cboxMiddleLeft,#cboxBottomLeft{clear:left}#cboxContent{position:relative}#cboxLoadedContent{overflow:auto}#cboxTitle{margin:0}#cboxLoadingOverlay,#cboxLoadingGraphic{position:absolute;top:0;left:0;width:100%;height:100%}#cboxPrevious,#cboxNext,#cboxClose,#cboxSlideshow{cursor:pointer}.cboxPhoto{float:left;margin:auto;border:0;display:block;max-width:none}.cboxIframe{width:100%;height:100%;display:block;border:0}#colorbox,#cboxContent,#cboxLoadedContent{box-sizing:content-box}#cboxOverlay{background:#000}#cboxTopLeft{width:14px;height:14px;background:url(colorbox/controls.png) no-repeat 0 0}#cboxTopCenter{height:14px;background:url(colorbox/border.png) repeat-x top left}#cboxTopRight{width:14px;height:14px;background:url(colorbox/controls.png) no-repeat -36px 0}#cboxBottomLeft{width:14px;height:43px;background:url(colorbox/controls.png) no-repeat 0 -32px}#cboxBottomCenter{height:43px;background:url(colorbox/border.png) repeat-x bottom left}#cboxBottomRight{width:14px;height:43px;background:url(colorbox/controls.png) no-repeat -36px -32px}#cboxMiddleLeft{width:14px;background:url(colorbox/controls.png) repeat-y -175px 0}#cboxMiddleRight{width:14px;background:url(colorbox/controls.png) repeat-y -211px 0}#cboxContent{background:#fff;overflow:visible}.cboxIframe{background:#fff}#cboxError{padding:50px;border:1px solid #ccc}#cboxLoadedContent{margin-bottom:5px}#cboxLoadingOverlay{background:url(colorbox/loading_background.png) no-repeat center center}#cboxLoadingGraphic{background:url(colorbox/loading.gif) no-repeat center center}#cboxTitle{position:absolute;bottom:-25px;left:0;text-align:center;width:100%;font-weight:bold;color:#7c7c7c}#cboxCurrent{position:absolute;bottom:-25px;left:58px;font-weight:bold;color:#7c7c7c}#cboxPrevious,#cboxNext,#cboxClose,#cboxSlideshow{position:absolute;bottom:-29px;background:url(colorbox/controls.png) no-repeat 0 0;width:23px;height:23px;text-indent:-9999px}#cboxPrevious{left:0;background-position:-51px -25px}#cboxPrevious:hover{background-position:-51px 0}#cboxNext{left:27px;background-position:-75px -25px}#cboxNext:hover{background-position:-75px 0}#cboxClose{right:0;background-position:-100px -25px}#cboxClose:hover{background-position:-100px 0}.cboxSlideshow_on #cboxSlideshow{background-position:-125px 0;right:27px}.cboxSlideshow_on #cboxSlideshow:hover{background-position:-150px 0}.cboxSlideshow_off #cboxSlideshow{background-position:-150px -25px;right:27px}.cboxSlideshow_off #cboxSlideshow:hover{background-position:-125px 0}#loading{position:fixed;left:40%;top:50%}a{color:#333;text-decoration:none}a:hover{color:#000;text-decoration:underline}body{font-family:"Lucida Grande",Helvetica,"Helvetica Neue",Arial,sans-serif;padding:12px;background-color:#333}h1,h2,h3,h4{color:#1c2324;margin:0;padding:0;margin-bottom:12px}table{width:100%}#content{clear:left;background-color:white;border:2px solid #ddd;border-top:8px solid #ddd;padding:18px;-webkit-border-bottom-left-radius:5px;-webkit-border-bottom-right-radius:5px;-webkit-border-top-right-radius:5px;-moz-border-radius-bottomleft:5px;-moz-border-radius-bottomright:5px;-moz-border-radius-topright:5px;border-bottom-left-radius:5px;border-bottom-right-radius:5px;border-top-right-radius:5px}.dataTables_filter,.dataTables_info{padding:2px 6px}abbr.timeago{text-decoration:none;border:0;font-weight:bold}.timestamp{float:right;color:#ddd}.group_tabs{list-style:none;float:left;margin:0;padding:0}.group_tabs li{display:inline;float:left}.group_tabs li a{font-family:Helvetica,Arial,sans-serif;display:block;float:left;text-decoration:none;padding:4px 8px;background-color:#aaa;background:-webkit-gradient(linear,0 0,0 bottom,from(#ddd),to(#aaa));background:-moz-linear-gradient(#ddd,#aaa);background:linear-gradient(#ddd,#aaa);text-shadow:#e5e5e5 1px 1px 0;border-bottom:0;color:#333;font-weight:bold;margin-right:8px;border-top:1px solid #efefef;-webkit-border-top-left-radius:2px;-webkit-border-top-right-radius:2px;-moz-border-radius-topleft:2px;-moz-border-radius-topright:2px;border-top-left-radius:2px;border-top-right-radius:2px}.group_tabs li a:hover{background-color:#ccc;background:-webkit-gradient(linear,0 0,0 bottom,from(#eee),to(#aaa));background:-moz-linear-gradient(#eee,#aaa);background:linear-gradient(#eee,#aaa)}.group_tabs li a:active{padding-top:5px;padding-bottom:3px}.group_tabs li.active a{color:black;text-shadow:#fff 1px 1px 0;background-color:#ddd;background:-webkit-gradient(linear,0 0,0 bottom,from(white),to(#ddd));background:-moz-linear-gradient(white,#ddd);background:linear-gradient(white,#ddd)}.file_list{margin-bottom:18px}.file_list--responsive{overflow-x:auto;overflow-y:hidden}a.src_link{background:url("./magnify.png") no-repeat left 50%;padding-left:18px}tr,td{margin:0;padding:0}th{white-space:nowrap}th.ui-state-default{cursor:pointer}th span.ui-icon{float:left}td{padding:4px 8px}td.strong{font-weight:bold}.cell--number{text-align:right}.source_table h3,.source_table h4{padding:0;margin:0;margin-bottom:4px}.source_table .header{padding:10px}.source_table pre{margin:0;padding:0;white-space:normal;color:#000;font-family:"Monaco","Inconsolata","Consolas",monospace}.source_table code{color:#000;font-family:"Monaco","Inconsolata","Consolas",monospace}.source_table pre{background-color:#333}.source_table pre ol{margin:0;padding:0;margin-left:45px;font-size:12px;color:white}.source_table pre li{margin:0;padding:2px 6px;border-left:5px solid white}.source_table pre li code{white-space:pre;white-space:pre-wrap}.source_table pre .hits{float:right;margin-left:10px;padding:2px 4px;background-color:#444;background:-webkit-gradient(linear,0 0,0 bottom,from(#222),to(#666));background:-moz-linear-gradient(#222,#666);background:linear-gradient(#222,#666);color:white;font-family:Helvetica,"Helvetica Neue",Arial,sans-serif;font-size:10px;font-weight:bold;text-align:center;border-radius:6px}#footer{color:#ddd;font-size:12px;font-weight:bold;margin-top:12px;text-align:right}#footer a{color:#eee;text-decoration:underline}#footer a:hover{color:#fff;text-decoration:none}.green{color:#090}.red{color:#900}.yellow{color:#da0}.blue{color:blue}thead th{background:white}.source_table .covered{border-color:#090}.source_table .missed{border-color:#900}.source_table .never{border-color:black}.source_table .skipped{border-color:#fc0}.source_table .missed-branch{border-color:#bf0000}.source_table .covered:nth-child(odd){background-color:#cdf2cd}.source_table .covered:nth-child(even){background-color:#dbf2db}.source_table .missed:nth-child(odd){background-color:#f7c0c0}.source_table .missed:nth-child(even){background-color:#f7cfcf}.source_table .never:nth-child(odd){background-color:#efefef}.source_table .never:nth-child(even){background-color:#f4f4f4}.source_table .skipped:nth-child(odd){background-color:#fbf0c0}.source_table .skipped:nth-child(even){background-color:#fbffcf}.source_table .missed-branch:nth-child(odd){background-color:#cc8e8e}.source_table .missed-branch:nth-child(even){background-color:#cc6e6e} \ No newline at end of file diff --git a/coverage/assets/0.12.3/application.js b/coverage/assets/0.12.3/application.js new file mode 100644 index 00000000..e1c2ab23 --- /dev/null +++ b/coverage/assets/0.12.3/application.js @@ -0,0 +1,7 @@ +!function(e,t){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=e.document?t(e,!0):function(e){if(!e.document)throw new Error("jQuery requires a window with a document");return t(e)}:t(e)}("undefined"!=typeof window?window:this,function(T,e){"use strict";function g(e,t,n){var r,a,i=(n=n||le).createElement("script");if(i.text=e,t)for(r in Se)(a=t[r]||t.getAttribute&&t.getAttribute(r))&&i.setAttribute(r,a);n.head.appendChild(i).parentNode.removeChild(i)}function m(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?pe[ge.call(e)]||"object":typeof e}function s(e){var t=!!e&&"length"in e&&e.length,n=m(e);return!we(e)&&!xe(e)&&("array"===n||0===t||"number"==typeof t&&0D.cacheLength&&delete n[r.shift()],n[e+" "]=t}var r=[];return n}function l(e){return e[q]=!0,e}function a(e){var t=E.createElement("fieldset");try{return!!e(t)}catch(n){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function t(e,t){for(var n=e.split("|"),r=n.length;r--;)D.attrHandle[n[r]]=t}function u(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&e.sourceIndex-t.sourceIndex;if(r)return r;if(n)for(;n=n.nextSibling;)if(n===t)return-1;return e?1:-1}function r(t){return function(e){return"input"===e.nodeName.toLowerCase()&&e.type===t}}function i(n){return function(e){var t=e.nodeName.toLowerCase();return("input"===t||"button"===t)&&e.type===n}}function o(t){return function(e){return"form"in e?e.parentNode&&!1===e.disabled?"label"in e?"label"in e.parentNode?e.parentNode.disabled===t:e.disabled===t:e.isDisabled===t||e.isDisabled!==!t&&_e(e)===t:e.disabled===t:"label"in e&&e.disabled===t}}function s(o){return l(function(i){return i=+i,l(function(e,t){for(var n,r=o([],e.length,i),a=r.length;a--;)e[n=r[a]]&&(e[n]=!(t[n]=e[n]))})})}function p(e){return e&&"undefined"!=typeof e.getElementsByTagName&&e}function c(){}function g(e){for(var t=0,n=e.length,r="";t+~]|"+re+")"+re+"*"),fe=new RegExp(re+"|>"),de=new RegExp(oe),he=new RegExp("^"+ae+"$"),pe={ID:new RegExp("^#("+ae+")"),CLASS:new RegExp("^\\.("+ae+")"),TAG:new RegExp("^("+ae+"|[*])"),ATTR:new RegExp("^"+ie),PSEUDO:new RegExp("^"+oe),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+re+"*(even|odd|(([+-]|)(\\d*)n|)"+re+"*(?:([+-]|)"+re+"*(\\d+)|))"+re+"*\\)|)","i"),bool:new RegExp("^(?:"+ne+")$","i"),needsContext:new RegExp("^"+re+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+re+"*((?:-\\d)?\\d*)"+re+"*\\)|)(?=[^-]|$)","i")},ge=/HTML$/i,me=/^(?:input|select|textarea|button)$/i,ve=/^h\d$/i,ye=/^[^{]+\{\s*\[native \w/,be=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,we=/[+~]/,xe=new RegExp("\\\\([\\da-f]{1,6}"+re+"?|("+re+")|.)","ig"),Se=function(e,t,n){var r="0x"+t-65536;return r!=r||n?t:r<0?String.fromCharCode(r+65536):String.fromCharCode(r>>10|55296,1023&r|56320)},De=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,Te=function(e,t){return t?"\0"===e?"\ufffd":e.slice(0,-1)+"\\"+e.charCodeAt(e.length-1).toString(16)+" ":"\\"+e},Ce=function(){L()},_e=f(function(e){return!0===e.disabled&&"fieldset"===e.nodeName.toLowerCase()},{dir:"parentNode",next:"legend"});try{Q.apply(Y=ee.call(W.childNodes),W.childNodes),Y[W.childNodes.length].nodeType}catch(Ae){Q={apply:Y.length?function(e,t){K.apply(e,ee.call(t))}:function(e,t){for(var n=e.length,r=0;e[n++]=t[r++];);e.length=n-1}}}for(v in S=w.support={},C=w.isXML=function(e){var t=e.namespaceURI,n=(e.ownerDocument||e).documentElement;return!ge.test(t||n&&n.nodeName||"HTML")},L=w.setDocument=function(e){var t,n,r=e?e.ownerDocument||e:W;return r!==E&&9===r.nodeType&&r.documentElement&&(R=(E=r).documentElement,F=!C(E),W!==E&&(n=E.defaultView)&&n.top!==n&&(n.addEventListener?n.addEventListener("unload",Ce,!1):n.attachEvent&&n.attachEvent("onunload",Ce)),S.attributes=a(function(e){return e.className="i",!e.getAttribute("className")}),S.getElementsByTagName=a(function(e){return e.appendChild(E.createComment("")),!e.getElementsByTagName("*").length}),S.getElementsByClassName=ye.test(E.getElementsByClassName),S.getById=a(function(e){return R.appendChild(e).id=q,!E.getElementsByName||!E.getElementsByName(q).length}),S.getById?(D.filter.ID=function(e){var t=e.replace(xe,Se);return function(e){return e.getAttribute("id")===t}},D.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&F){var n=t.getElementById(e);return n?[n]:[]}}):(D.filter.ID=function(e){var n=e.replace(xe,Se);return function(e){var t="undefined"!=typeof e.getAttributeNode&&e.getAttributeNode("id");return t&&t.value===n}},D.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&F){var n,r,a,i=t.getElementById(e);if(i){if((n=i.getAttributeNode("id"))&&n.value===e)return[i];for(a=t.getElementsByName(e),r=0;i=a[r++];)if((n=i.getAttributeNode("id"))&&n.value===e)return[i]}return[]}}),D.find.TAG=S.getElementsByTagName?function(e,t){return"undefined"!=typeof t.getElementsByTagName?t.getElementsByTagName(e):S.qsa?t.querySelectorAll(e):void 0}:function(e,t){var n,r=[],a=0,i=t.getElementsByTagName(e);if("*"!==e)return i;for(;n=i[a++];)1===n.nodeType&&r.push(n);return r},D.find.CLASS=S.getElementsByClassName&&function(e,t){if("undefined"!=typeof t.getElementsByClassName&&F)return t.getElementsByClassName(e)},H=[],P=[],(S.qsa=ye.test(E.querySelectorAll))&&(a(function(e){R.appendChild(e).innerHTML="",e.querySelectorAll("[msallowcapture^='']").length&&P.push("[*^$]="+re+"*(?:''|\"\")"),e.querySelectorAll("[selected]").length||P.push("\\["+re+"*(?:value|"+ne+")"),e.querySelectorAll("[id~="+q+"-]").length||P.push("~="),e.querySelectorAll(":checked").length||P.push(":checked"),e.querySelectorAll("a#"+q+"+*").length||P.push(".#.+[+~]")}),a(function(e){e.innerHTML="";var t=E.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),e.querySelectorAll("[name=d]").length&&P.push("name"+re+"*[*^$|!~]?="),2!==e.querySelectorAll(":enabled").length&&P.push(":enabled",":disabled"),R.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&P.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),P.push(",.*:")})),(S.matchesSelector=ye.test(M=R.matches||R.webkitMatchesSelector||R.mozMatchesSelector||R.oMatchesSelector||R.msMatchesSelector))&&a(function(e){S.disconnectedMatch=M.call(e,"*"),M.call(e,"[s!='']:x"),H.push("!=",oe)}),P=P.length&&new RegExp(P.join("|")),H=H.length&&new RegExp(H.join("|")),t=ye.test(R.compareDocumentPosition),O=t||ye.test(R.contains)?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)for(;t=t.parentNode;)if(t===e)return!0;return!1},G=t?function(e,t){if(e===t)return j=!0,0;var n=!e.compareDocumentPosition-!t.compareDocumentPosition;return n||(1&(n=(e.ownerDocument||e)===(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!S.sortDetached&&t.compareDocumentPosition(e)===n?e===E||e.ownerDocument===W&&O(W,e)?-1:t===E||t.ownerDocument===W&&O(W,t)?1:I?te(I,e)-te(I,t):0:4&n?-1:1)}:function(e,t){if(e===t)return j=!0,0;var n,r=0,a=e.parentNode,i=t.parentNode,o=[e],s=[t];if(!a||!i)return e===E?-1:t===E?1:a?-1:i?1:I?te(I,e)-te(I,t):0;if(a===i)return u(e,t);for(n=e;n=n.parentNode;)o.unshift(n);for(n=t;n=n.parentNode;)s.unshift(n);for(;o[r]===s[r];)r++;return r?u(o[r],s[r]):o[r]===W?-1:s[r]===W?1:0}),E},w.matches=function(e,t){return w(e,null,null,t)},w.matchesSelector=function(e,t){if((e.ownerDocument||e)!==E&&L(e),S.matchesSelector&&F&&!V[t+" "]&&(!H||!H.test(t))&&(!P||!P.test(t)))try{var n=M.call(e,t);if(n||S.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(Ae){V(t,!0)}return 0":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(xe,Se),e[3]=(e[3]||e[4]||e[5]||"").replace(xe,Se),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||w.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&w.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return pe.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&de.test(n)&&(t=_(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(xe,Se).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=U[e+" "];return t||(t=new RegExp("(^|"+re+")"+e+"("+re+"|$)"))&&U(e,function(e){return t.test("string"==typeof e.className&&e.className||"undefined"!=typeof e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(n,r,a){return function(e){var t=w.attr(e,n);return null==t?"!="===r:!r||(t+="","="===r?t===a:"!="===r?t!==a:"^="===r?a&&0===t.indexOf(a):"*="===r?a&&-1:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;Te.filter=function(e,t,n){var r=t[0];return n&&(e=":not("+e+")"),1===t.length&&1===r.nodeType?Te.find.matchesSelector(r,e)?[r]:[]:Te.find.matches(e,Te.grep(t,function(e){return 1===e.nodeType}))},Te.fn.extend({find:function(e){var t,n,r=this.length,a=this;if("string"!=typeof e)return this.pushStack(Te(e).filter(function(){for(t=0;t)[^>]*|#([\w-]+))$/;(Te.fn.init=function(e,t,n){var r,a;if(!e)return this;if(n=n||je,"string"!=typeof e)return e.nodeType?(this[0]=e,this.length=1,this):we(e)?n.ready!==undefined?n.ready(e):e(Te):Te.makeArray(e,this);if(!(r="<"===e[0]&&">"===e[e.length-1]&&3<=e.length?[null,e,null]:Le.exec(e))||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(r[1]){if(t=t instanceof Te?t[0]:t,Te.merge(this,Te.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:le,!0)),Ie.test(r[1])&&Te.isPlainObject(t))for(r in t)we(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}return(a=le.getElementById(r[2]))&&(this[0]=a,this.length=1),this}).prototype=Te.fn,je=Te(le);var Ee=/^(?:parents|prev(?:Until|All))/,Re={children:!0,contents:!0,next:!0,prev:!0};Te.fn.extend({has:function(e){var t=Te(e,this),n=t.length;return this.filter(function(){for(var e=0;e\x20\t\r\n\f]*)/i,rt=/^$|^module$|\/(?:java|ecma)script/i,at={option:[1,""],thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};at.optgroup=at.option,at.tbody=at.tfoot=at.colgroup=at.caption=at.thead,at.th=at.td;var it,ot,st=/<|&#?\w+;/;it=le.createDocumentFragment().appendChild(le.createElement("div")),(ot=le.createElement("input")).setAttribute("type","radio"),ot.setAttribute("checked","checked"),ot.setAttribute("name","t"),it.appendChild(ot),be.checkClone=it.cloneNode(!0).cloneNode(!0).lastChild.checked,it.innerHTML="",be.noCloneChecked=!!it.cloneNode(!0).lastChild.defaultValue;var lt=/^key/,ut=/^(?:mouse|pointer|contextmenu|drag|drop)|click/,ct=/^([^.]*)(?:\.(.+)|)/;Te.event={global:{},add:function(t,e,n,r,a){var i,o,s,l,u,c,f,d,h,p,g,m=Be.get(t);if(m)for(n.handler&&(n=(i=n).handler,a=i.selector),a&&Te.find.matchesSelector(Je,a),n.guid||(n.guid=Te.guid++),(l=m.events)||(l=m.events={}),(o=m.handle)||(o=m.handle=function(e){return void 0!==Te&&Te.event.triggered!==e.type?Te.event.dispatch.apply(t,arguments):undefined}),u=(e=(e||"").match(Fe)||[""]).length;u--;)h=g=(s=ct.exec(e[u])||[])[1],p=(s[2]||"").split(".").sort(),h&&(f=Te.event.special[h]||{},h=(a?f.delegateType:f.bindType)||h,f=Te.event.special[h]||{},c=Te.extend({type:h,origType:g,data:r,handler:n,guid:n.guid,selector:a,needsContext:a&&Te.expr.match.needsContext.test(a),namespace:p.join(".")},i),(d=l[h])||((d=l[h]=[]).delegateCount=0,f.setup&&!1!==f.setup.call(t,r,p,o)||t.addEventListener&&t.addEventListener(h,o)),f.add&&(f.add.call(t,c),c.handler.guid||(c.handler.guid=n.guid)),a?d.splice(d.delegateCount++,0,c):d.push(c),Te.event.global[h]=!0)},remove:function(e,t,n,r,a){var i,o,s,l,u,c,f,d,h,p,g,m=Be.hasData(e)&&Be.get(e);if(m&&(l=m.events)){for(u=(t=(t||"").match(Fe)||[""]).length;u--;)if(h=g=(s=ct.exec(t[u])||[])[1],p=(s[2]||"").split(".").sort(),h){for(f=Te.event.special[h]||{},d=l[h=(r?f.delegateType:f.bindType)||h]||[],s=s[2]&&new RegExp("(^|\\.)"+p.join("\\.(?:.*\\.|)")+"(\\.|$)"),o=i=d.length;i--;)c=d[i],!a&&g!==c.origType||n&&n.guid!==c.guid||s&&!s.test(c.namespace)||r&&r!==c.selector&&("**"!==r||!c.selector)||(d.splice(i,1),c.selector&&d.delegateCount--,f.remove&&f.remove.call(e,c));o&&!d.length&&(f.teardown&&!1!==f.teardown.call(e,p,m.handle)||Te.removeEvent(e,h,m.handle),delete l[h])}else for(h in l)Te.event.remove(e,h+t[u],n,r,!0);Te.isEmptyObject(l)&&Be.remove(e,"handle events")}},dispatch:function(e){var t,n,r,a,i,o,s=Te.event.fix(e),l=new Array(arguments.length),u=(Be.get(this,"events")||{})[s.type]||[],c=Te.event.special[s.type]||{};for(l[0]=s,t=1;t\x20\t\r\n\f]*)[^>]*)\/>/gi,dt=/\s*$/g;Te.extend({htmlPrefilter:function(e){return e.replace(ft,"<$1>")},clone:function(e,t,n){var r,a,i,o,s=e.cloneNode(!0),l=Ye(e);if(!(be.noCloneChecked||1!==e.nodeType&&11!==e.nodeType||Te.isXMLDoc(e)))for(o=w(s),r=0,a=(i=w(e)).length;r").attr(n.scriptAttrs||{}).prop({charset:n.scriptCharset,src:n.url}).on("load error",a=function(e){r.remove(),a=null,e&&t("error"===e.type?404:200,e.type)}),le.head.appendChild(r[0])},abort:function(){a&&a()}}});var an,on=[],sn=/(=)\?(?=&|$)|\?\?/;Te.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=on.pop()||Te.expando+"_"+Ot++;return this[e]=!0,e}}),Te.ajaxPrefilter("json jsonp",function(e,t,n){var r,a,i,o=!1!==e.jsonp&&(sn.test(e.url)?"url":"string"==typeof e.data&&0===(e.contentType||"").indexOf("application/x-www-form-urlencoded")&&sn.test(e.data)&&"data");if(o||"jsonp"===e.dataTypes[0])return r=e.jsonpCallback=we(e.jsonpCallback)?e.jsonpCallback():e.jsonpCallback,o?e[o]=e[o].replace(sn,"$1"+r):!1!==e.jsonp&&(e.url+=(qt.test(e.url)?"&":"?")+e.jsonp+"="+r),e.converters["script json"]=function(){return i||Te.error(r+" was not called"),i[0]},e.dataTypes[0]="json",a=T[r],T[r]=function(){i=arguments},n.always(function(){a===undefined?Te(T).removeProp(r):T[r]=a,e[r]&&(e.jsonpCallback=t.jsonpCallback,on.push(r)),i&&we(a)&&a(i[0]),i=a=undefined}),"script"}),be.createHTMLDocument=((an=le.implementation.createHTMLDocument("").body).innerHTML="
",2===an.childNodes.length),Te.parseHTML=function(e,t,n){return"string"!=typeof e?[]:("boolean"==typeof t&&(n=t,t=!1),t||(be.createHTMLDocument?((r=(t=le.implementation.createHTMLDocument("")).createElement("base")).href=le.location.href,t.head.appendChild(r)):t=le),i=!n&&[],(a=Ie.exec(e))?[t.createElement(a[1])]:(a=S([e],t,i),i&&i.length&&Te(i).remove(),Te.merge([],a.childNodes)));var r,a,i},Te.fn.load=function(e,t,n){var r,a,i,o=this,s=e.indexOf(" ");return-1").append(Te.parseHTML(e)).find(r):e)}).always(n&&function(e,t){o.each(function(){n.apply(this,i||[e.responseText,t,e])})}),this},Te.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){Te.fn[t]=function(e){return this.on(t,e)}}),Te.expr.pseudos.animated=function(t){return Te.grep(Te.timers,function(e){return t===e.elem}).length},Te.offset={setOffset:function(e,t,n){var r,a,i,o,s,l,u=Te.css(e,"position"),c=Te(e),f={};"static"===u&&(e.style.position="relative"),s=c.offset(),i=Te.css(e,"top"),l=Te.css(e,"left"),("absolute"===u||"fixed"===u)&&-1<(i+l).indexOf("auto")?(o=(r=c.position()).top,a=r.left):(o=parseFloat(i)||0,a=parseFloat(l)||0),we(t)&&(t=t.call(e,n,Te.extend({},s))),null!=t.top&&(f.top=t.top-s.top+o),null!=t.left&&(f.left=t.left-s.left+a),"using"in t?t.using.call(e,f):c.css(f)}},Te.fn.extend({offset:function(t){if(arguments.length)return t===undefined?this:this.each(function(e){Te.offset.setOffset(this,t,e)});var e,n,r=this[0];return r?r.getClientRects().length?(e=r.getBoundingClientRect(),n=r.ownerDocument.defaultView,{top:e.top+n.pageYOffset,left:e.left+n.pageXOffset}):{top:0,left:0}:void 0},position:function(){if(this[0]){var e,t,n,r=this[0],a={top:0,left:0};if("fixed"===Te.css(r,"position"))t=r.getBoundingClientRect();else{for(t=this.offset(),n=r.ownerDocument,e=r.offsetParent||n.documentElement;e&&(e===n.body||e===n.documentElement)&&"static"===Te.css(e,"position");)e=e.parentNode;e&&e!==r&&1===e.nodeType&&((a=Te(e).offset()).top+=Te.css(e,"borderTopWidth",!0),a.left+=Te.css(e,"borderLeftWidth",!0))}return{top:t.top-a.top-Te.css(r,"marginTop",!0),left:t.left-a.left-Te.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){for(var e=this.offsetParent;e&&"static"===Te.css(e,"position");)e=e.offsetParent;return e||Je})}}),Te.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(t,a){var i="pageYOffset"===a;Te.fn[t]=function(e){return Me(this,function(e,t,n){var r;if(xe(e)?r=e:9===e.nodeType&&(r=e.defaultView),n===undefined)return r?r[a]:e[t];r?r.scrollTo(i?r.pageXOffset:n,i?n:r.pageYOffset):e[t]=n},t,e,arguments.length)}}),Te.each(["top","left"],function(e,n){Te.cssHooks[n]=M(be.pixelPosition,function(e,t){if(t)return t=H(e,n),gt.test(t)?Te(e).position()[n]+"px":t})}),Te.each({Height:"height",Width:"width"},function(o,s){Te.each({padding:"inner"+o,content:s,"":"outer"+o},function(r,i){Te.fn[i]=function(e,t){var n=arguments.length&&(r||"boolean"!=typeof e),a=r||(!0===e||!0===t?"margin":"border");return Me(this,function(e,t,n){var r;return xe(e)?0===i.indexOf("outer")?e["inner"+o]:e.document.documentElement["client"+o]:9===e.nodeType?(r=e.documentElement,Math.max(e.body["scroll"+o],r["scroll"+o],e.body["offset"+o],r["offset"+o],r["client"+o])):n===undefined?Te.css(e,t,a):Te.style(e,t,n,a)},s,n?e:undefined,n)}})}),Te.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(e,n){Te.fn[n]=function(e,t){return 0"}for(var i=0,o="",s=[];e.length||t.length;){var l=r().splice(0,1)[0];if(o+=x(n.substr(i,l.offset-i)),i=l.offset,"start"==l.event)o+=a(l.node),s.push(l.node);else if("stop"==l.event){var u=s.length;do{var c=s[--u];o+=""}while(c!=l.node);for(s.splice(u,1);u'+x(a[0])+""):n+=x(a[0]),r=t.lR.lastIndex,a=t.lR.exec(e)}return n+=x(e.substr(r,e.length-r))}function f(e,t){if(t.sL&&T[t.sL]){var n=D(t.sL,e);return g+=n.keyword_count,n.value}return r(e,t)}function d(e,t){var n=e.cN?'':"";e.rB?(m+=n,e.buffer=""):e.eB?(m+=x(t)+n,e.buffer=""):(m+=n,e.buffer=t),h.push(e),p+=e.r}function i(e,t,n){var r=h[h.length-1];if(n)return m+=f(r.buffer+e,r),!1;var a=l(t,r);if(a)return m+=f(r.buffer+e,r),d(a,t),a.rB;var i=u(h.length-1,t);if(i){var o=r.cN?"":"";for(r.rE?m+=f(r.buffer+e,r)+o:r.eE?m+=f(r.buffer+e,r)+o+x(t):m+=f(r.buffer+e+t,r)+o;1":"",m+=o,i--,h.length--;var s=h[h.length-1];return h.length--,h[h.length-1].buffer="",s.starts&&d(s.starts,""),r.rE}if(c(t,r))throw"Illegal"}var s=T[e],h=[s.dM],p=0,g=0,m="";try{var v=0;s.dM.buffer="";do{var y=n(t,v),b=i(y[0],y[1],y[2]);v+=y[0].length,b||(v+=y[1].length)}while(!y[2]);if(1o.keyword_count+o.r&&(o=l),l.keyword_count+l.r>i.keyword_count+i.r&&(o=i,i=l)}}var u=e.className;u.match(i.language)||(u=u?u+" "+i.language:i.language);var c=g(e);if(c.length)(f=document.createElement("pre")).innerHTML=i.value,i.value=m(c,g(f),r);if(n&&(i.value=i.value.replace(/^((<[^>]+>|\t)+)/gm,function(e,t){return t.replace(/\t/g,n)})),t&&(i.value=i.value.replace(/\n/g,"
")),/MSIE [678]/.test(navigator.userAgent)&&"CODE"==e.tagName&&"PRE"==e.parentNode.tagName){var f=e.parentNode,d=document.createElement("div");d.innerHTML="
"+i.value+"
",e=d.firstChild.firstChild,d.firstChild.cN=f.cN,f.parentNode.replaceChild(d.firstChild,f)}else e.innerHTML=i.value;e.className=u,e.dataset={},e.dataset.result={language:i.language,kw:i.keyword_count,re:i.r},o&&o.language&&(e.dataset.second_best={language:o.language,kw:o.keyword_count,re:o.r})}}function i(){if(!i.called){i.called=!0,v();for(var e=document.getElementsByTagName("pre"),t=0;t|>=|>>|>>=|>>>|>>>=|\\?|\\[|\\{|\\(|\\^|\\^=|\\||\\|=|\\|\\||~",this.BE={b:"\\\\.",r:0},this.ASM={cN:"string",b:"'",e:"'",i:"\\n",c:[this.BE],r:0},this.QSM={cN:"string",b:'"',e:'"',i:"\\n",c:[this.BE],r:0},this.CLCM={cN:"comment",b:"//",e:"$"},this.CBLCLM={cN:"comment",b:"/\\*",e:"\\*/"},this.HCM={cN:"comment",b:"#",e:"$"},this.NM={cN:"number",b:this.NR,r:0},this.CNM={cN:"number",b:this.CNR,r:0},this.inherit=function(e,t){var n={};for(var r in e)n[r]=e[r];if(t)for(var r in t)n[r]=t[r];return n}};hljs.LANGUAGES.ruby=function(){var e="[a-zA-Z_][a-zA-Z0-9_]*(\\!|\\?)?",t="[a-zA-Z_]\\w*[!?=]?|[-+~]\\@|<<|>>|=~|===?|<=>|[<>]=?|\\*\\*|[-/+%^&*~`|]|\\[\\]=?",n={keyword:{and:1,"false":1,then:1,defined:1,module:1,"in":1,"return":1,redo:1,"if":1,BEGIN:1,retry:1,end:1,"for":1,"true":1,self:1,when:1,next:1,until:1,"do":1,begin:1,unless:1,END:1,rescue:1,nil:1,"else":1,"break":1,undef:1,not:1,"super":1,"class":1,"case":1,require:1,"yield":1,alias:1,"while":1,ensure:1,elsif:1,or:1,def:1},keymethods:{__id__:1,__send__:1,abort:1,abs:1,"all?":1,allocate:1,ancestors:1,"any?":1,arity:1,assoc:1,at:1,at_exit:1,autoload:1,"autoload?":1,"between?":1,binding:1,binmode:1,"block_given?":1,call:1,callcc:1,caller:1,capitalize:1,"capitalize!":1,casecmp:1,"catch":1,ceil:1,center:1,chomp:1,"chomp!":1,chop:1,"chop!":1,chr:1,"class":1,class_eval:1,"class_variable_defined?":1,class_variables:1,clear:1,clone:1,close:1,close_read:1,close_write:1,"closed?":1,coerce:1,collect:1,"collect!":1,compact:1,"compact!":1,concat:1,"const_defined?":1,const_get:1,const_missing:1,const_set:1,constants:1,count:1,crypt:1,"default":1,default_proc:1,"delete":1,"delete!":1,delete_at:1,delete_if:1,detect:1,display:1,div:1,divmod:1,downcase:1,"downcase!":1,downto:1,dump:1,dup:1,each:1,each_byte:1,each_index:1,each_key:1,each_line:1,each_pair:1,each_value:1,each_with_index:1,"empty?":1,entries:1,eof:1,"eof?":1,"eql?":1,"equal?":1,eval:1,exec:1,exit:1,"exit!":1,extend:1,fail:1,fcntl:1,fetch:1,fileno:1,fill:1,find:1,find_all:1,first:1,flatten:1,"flatten!":1,floor:1,flush:1,for_fd:1,foreach:1,fork:1,format:1,freeze:1,"frozen?":1,fsync:1,getc:1,gets:1,global_variables:1,grep:1,gsub:1,"gsub!":1,"has_key?":1,"has_value?":1,hash:1,hex:1,id:1,include:1,"include?":1,included_modules:1,index:1,indexes:1,indices:1,induced_from:1,inject:1,insert:1,inspect:1,instance_eval:1,instance_method:1,instance_methods:1,"instance_of?":1,"instance_variable_defined?":1,instance_variable_get:1,instance_variable_set:1,instance_variables:1,"integer?":1,intern:1,invert:1,ioctl:1,"is_a?":1,isatty:1,"iterator?":1,join:1,"key?":1,keys:1,"kind_of?":1,lambda:1,last:1,length:1,lineno:1,ljust:1,load:1,local_variables:1,loop:1,lstrip:1,"lstrip!":1,map:1,"map!":1,match:1,max:1,"member?":1,merge:1,"merge!":1,method:1,"method_defined?":1,method_missing:1,methods:1,min:1,module_eval:1,modulo:1,name:1,nesting:1,"new":1,next:1,"next!":1,"nil?":1,nitems:1,"nonzero?":1,object_id:1,oct:1,open:1,pack:1,partition:1,pid:1,pipe:1,pop:1,popen:1,pos:1,prec:1,prec_f:1,prec_i:1,print:1,printf:1,private_class_method:1,private_instance_methods:1,"private_method_defined?":1,private_methods:1,proc:1,protected_instance_methods:1, +"protected_method_defined?":1,protected_methods:1,public_class_method:1,public_instance_methods:1,"public_method_defined?":1,public_methods:1,push:1,putc:1,puts:1,quo:1,raise:1,rand:1,rassoc:1,read:1,read_nonblock:1,readchar:1,readline:1,readlines:1,readpartial:1,rehash:1,reject:1,"reject!":1,remainder:1,reopen:1,replace:1,require:1,"respond_to?":1,reverse:1,"reverse!":1,reverse_each:1,rewind:1,rindex:1,rjust:1,round:1,rstrip:1,"rstrip!":1,scan:1,seek:1,select:1,send:1,set_trace_func:1,shift:1,singleton_method_added:1,singleton_methods:1,size:1,sleep:1,slice:1,"slice!":1,sort:1,"sort!":1,sort_by:1,split:1,sprintf:1,squeeze:1,"squeeze!":1,srand:1,stat:1,step:1,store:1,strip:1,"strip!":1,sub:1,"sub!":1,succ:1,"succ!":1,sum:1,superclass:1,swapcase:1,"swapcase!":1,sync:1,syscall:1,sysopen:1,sysread:1,sysseek:1,system:1,syswrite:1,taint:1,"tainted?":1,tell:1,test:1,"throw":1,times:1,to_a:1,to_ary:1,to_f:1,to_hash:1,to_i:1,to_int:1,to_io:1,to_proc:1,to_s:1,to_str:1,to_sym:1,tr:1,"tr!":1,tr_s:1,"tr_s!":1,trace_var:1,transpose:1,trap:1,truncate:1,"tty?":1,type:1,ungetc:1,uniq:1,"uniq!":1,unpack:1,unshift:1,untaint:1,untrace_var:1,upcase:1,"upcase!":1,update:1,upto:1,"value?":1,values:1,values_at:1,warn:1,write:1,write_nonblock:1,"zero?":1,zip:1}},r={cN:"yardoctag",b:"@[A-Za-z]+"},a={cN:"comment",b:"#",e:"$",c:[r]},i={cN:"comment",b:"^\\=begin",e:"^\\=end",c:[r],r:10},o={cN:"comment",b:"^__END__",e:"\\n$"},s={cN:"subst",b:"#\\{",e:"}",l:e,k:n},l=[hljs.BE,s],u={cN:"string",b:"'",e:"'",c:l,r:0},c={cN:"string",b:'"',e:'"',c:l,r:0},f={cN:"string",b:"%[qw]?\\(",e:"\\)",c:l,r:10},d={cN:"string",b:"%[qw]?\\[",e:"\\]",c:l,r:10},h={cN:"string",b:"%[qw]?{",e:"}",c:l,r:10},p={cN:"string",b:"%[qw]?<",e:">",c:l,r:10},g={cN:"string",b:"%[qw]?/",e:"/",c:l,r:10},m={cN:"string",b:"%[qw]?%",e:"%",c:l,r:10},v={cN:"string",b:"%[qw]?-",e:"-",c:l,r:10},y={cN:"string",b:"%[qw]?\\|",e:"\\|",c:l,r:10},b={cN:"function",b:"\\bdef\\s+",e:" |$|;",l:e,k:n,c:[{cN:"title",b:t,l:e,k:n},{cN:"params",b:"\\(",e:"\\)",l:e,k:n},a,i,o]},w={cN:"identifier",b:e,l:e,k:n,r:0},x=[a,i,o,u,c,f,d,h,p,g,m,v,y,{cN:"class",b:"\\b(class|module)\\b",e:"$|;",k:{"class":1,module:1},c:[{cN:"title",b:"[A-Za-z_]\\w*(::\\w+)*(\\?|\\!)?",r:0},{cN:"inheritance",b:"<\\s*",c:[{cN:"parent",b:"("+hljs.IR+"::)?"+hljs.IR}]},a,i,o]},b,{cN:"constant",b:"(::)?([A-Z]\\w*(::)?)+",r:0},{cN:"symbol",b:":",c:[u,c,f,d,h,p,g,m,v,y,w],r:0},{cN:"number",b:"(\\b0[0-7_]+)|(\\b0x[0-9a-fA-F_]+)|(\\b[1-9][0-9_]*(\\.[0-9_]+)?)|[0_]\\b",r:0},{cN:"number",b:"\\?\\w"},{cN:"variable",b:"(\\$\\W)|((\\$|\\@\\@?)(\\w+))"},w,{b:"("+hljs.RSR+")\\s*",c:[a,i,o,{cN:"regexp",b:"/",e:"/[a-z]*",i:"\\n",c:[hljs.BE]}],r:0}];return s.c=x,{dM:{l:e,k:n,c:b.c[1].c=x}}}(),function(c,s,o){function l(e,t,n){var r=s.createElement(e);return t&&(r.id=te+t),n&&(r.style.cssText=n),c(r)}function f(){return o.innerHeight?o.innerHeight:c(o).height()}function u(e,n){n!==Object(n)&&(n={}),this.cache={},this.el=e,this.value=function(e){var t;return this.cache[e]===undefined&&((t=c(this.el).attr("data-cbox-"+e))!==undefined?this.cache[e]=t:n[e]!==undefined?this.cache[e]=n[e]:Q[e]!==undefined&&(this.cache[e]=Q[e])),this.cache[e]},this.get=function(e){var t=this.value(e);return c.isFunction(t)?t.call(this.el,this):t}}function i(e){var t=k.length,n=(X+e)%t;return n<0?t+n:n}function d(e,t){return Math.round((/%/.test(e)?("x"===t?I.width():f())/100:1)*parseInt(e,10))}function h(e,t){return e.get("photo")||e.get("photoRegex").test(t)}function p(e,t){return e.get("retinaUrl")&&1"),w()}}function a(){S||(t=!1,I=c(o),S=l(ce).attr({id:ee,"class":!1===c.support.opacity?te+"IE":"",role:"dialog",tabindex:"-1"}).hide(),x=l(ce,"Overlay").hide(),E=c([l(ce,"LoadingOverlay")[0],l(ce,"LoadingGraphic")[0]]),D=l(ce,"Wrapper"),T=l(ce,"Content").append(R=l(ce,"Title"),F=l(ce,"Current"),M=c('