diff options
Diffstat (limited to 'wqflask')
67 files changed, 16992 insertions, 3919 deletions
diff --git a/wqflask/base/data_set.py b/wqflask/base/data_set.py index 1cd57b4b..414cc71a 100755 --- a/wqflask/base/data_set.py +++ b/wqflask/base/data_set.py @@ -29,6 +29,7 @@ import json import gzip import cPickle as pickle import itertools +from operator import itemgetter from redis import Redis Redis = Redis() @@ -292,6 +293,7 @@ class DatasetGroup(object): self.incparentsf1 = False self.allsamples = None + self._datasets = None def get_specified_markers(self, markers = []): self.markers = HumanMarkers(self.name, markers) @@ -305,6 +307,56 @@ class DatasetGroup(object): self.markers = marker_class(self.name) + def datasets(self): + key = "group_dataset_menu:v1:" + self.name + print("key is:", key) + with Bench("Loading cache"): + result = Redis.get(key) + if result: + self._datasets = pickle.loads(result) + return self._datasets + + dataset_menu = [] + print("[tape4] webqtlConfig.PUBLICTHRESH:", webqtlConfig.PUBLICTHRESH) + print("[tape4] type webqtlConfig.PUBLICTHRESH:", type(webqtlConfig.PUBLICTHRESH)) + results = g.db.execute(''' + (SELECT '#PublishFreeze',PublishFreeze.FullName,PublishFreeze.Name + FROM PublishFreeze,InbredSet + WHERE PublishFreeze.InbredSetId = InbredSet.Id + and InbredSet.Name = %s + and PublishFreeze.public > %s) + UNION + (SELECT '#GenoFreeze',GenoFreeze.FullName,GenoFreeze.Name + FROM GenoFreeze, InbredSet + WHERE GenoFreeze.InbredSetId = InbredSet.Id + and InbredSet.Name = %s + and GenoFreeze.public > %s) + UNION + (SELECT Tissue.Name, ProbeSetFreeze.FullName,ProbeSetFreeze.Name + FROM ProbeSetFreeze, ProbeFreeze, InbredSet, Tissue + WHERE ProbeSetFreeze.ProbeFreezeId = ProbeFreeze.Id + and ProbeFreeze.TissueId = Tissue.Id + and ProbeFreeze.InbredSetId = InbredSet.Id + and InbredSet.Name like %s + and ProbeSetFreeze.public > %s + ORDER BY Tissue.Name, ProbeSetFreeze.CreateTime desc, ProbeSetFreeze.AvgId) + ''', (self.name, webqtlConfig.PUBLICTHRESH, + self.name, webqtlConfig.PUBLICTHRESH, + "%" + self.name + "%", webqtlConfig.PUBLICTHRESH)) + + for tissue_name, dataset in itertools.groupby(results.fetchall(), itemgetter(0)): + if tissue_name in ['#PublishFreeze', '#GenoFreeze']: + for item in dataset: + dataset_menu.append(dict(tissue=None, datasets=[item[1:]])) + else: + dataset_sub_menu = [item[1:] for item in dataset] + dataset_menu.append(dict(tissue=tissue_name, + datasets=dataset_sub_menu)) + + Redis.set(key, pickle.dumps(dataset_menu, pickle.HIGHEST_PROTOCOL)) + Redis.expire(key, 60*5) + self._datasets = dataset_menu + return self._datasets def get_f1_parent_strains(self): try: @@ -319,7 +371,7 @@ class DatasetGroup(object): self.parlist = [maternal, paternal] def get_samplelist(self): - key = "samplelist:v4:" + self.name + key = "samplelist:v2:" + self.name print("key is:", key) with Bench("Loading cache"): result = Redis.get(key) @@ -332,14 +384,29 @@ class DatasetGroup(object): print(" self.samplelist: ", self.samplelist) else: print("Cache not hit") - try: - self.samplelist = get_group_samplelists.get_samplelist(self.name + ".geno") - except IOError: + + from utility.tools import plink_command + PLINK_PATH,PLINK_COMMAND = plink_command() + + geno_file_path = webqtlConfig.GENODIR+self.name+".geno" + plink_file_path = PLINK_PATH+"/"+self.name+".fam" + + if os.path.isfile(plink_file_path): + self.samplelist = get_group_samplelists.get_samplelist("plink", plink_file_path) + elif os.path.isfile(geno_file_path): + self.samplelist = get_group_samplelists.get_samplelist("geno", geno_file_path) + else: self.samplelist = None print("after get_samplelist") Redis.set(key, json.dumps(self.samplelist)) Redis.expire(key, 60*5) + def all_samples_ordered(self): + result = [] + lists = (self.parlist, self.f1list, self.samplelist) + [result.extend(l) for l in lists if l] + return result + def read_genotype_file(self): '''Read genotype from .geno file instead of database''' #if self.group == 'BXD300': @@ -633,7 +700,7 @@ class PhenotypeDataSet(DataSet): 'sequence', 'units', 'comments'] # Fields displayed in the search results table header - self.header_fields = ['', + self.header_fields = ['Index', 'ID', 'Description', 'Authors', @@ -737,7 +804,7 @@ class PhenotypeDataSet(DataSet): this_trait.LRS_score_repr = LRS_score_repr = '%3.1f' % this_trait.lrs this_trait.LRS_score_value = LRS_score_value = this_trait.lrs - this_trait.LRS_location_repr = LRS_location_repr = 'Chr %s: %.4f Mb' % (LRS_Chr, float(LRS_Mb)) + this_trait.LRS_location_repr = LRS_location_repr = 'Chr%s: %.6f' % (LRS_Chr, float(LRS_Mb)) def retrieve_sample_data(self, trait): query = """ @@ -753,11 +820,11 @@ class PhenotypeDataSet(DataSet): WHERE PublishXRef.InbredSetId = PublishFreeze.InbredSetId AND PublishData.Id = PublishXRef.DataId AND PublishXRef.Id = %s AND - PublishFreeze.Id = %d AND PublishData.StrainId = Strain.Id + PublishFreeze.Id = %s AND PublishData.StrainId = Strain.Id Order BY Strain.Name - """ % (trait, self.id) - results = g.db.execute(query).fetchall() + """ + results = g.db.execute(query, (trait, self.id)).fetchall() return results @@ -777,7 +844,7 @@ class GenotypeDataSet(DataSet): 'sequence'] # Fields displayed in the search results table header - self.header_fields = ['', + self.header_fields = ['Index', 'ID', 'Location'] @@ -828,7 +895,7 @@ class GenotypeDataSet(DataSet): else: trait_location_value = ord(str(this_trait.chr).upper()[0])*1000 + this_trait.mb - this_trait.location_repr = 'Chr%s: %.4f' % (this_trait.chr, float(this_trait.mb) ) + this_trait.location_repr = 'Chr%s: %.6f' % (this_trait.chr, float(this_trait.mb) ) this_trait.location_value = trait_location_value def retrieve_sample_data(self, trait): @@ -840,15 +907,17 @@ class GenotypeDataSet(DataSet): left join GenoSE on (GenoSE.DataId = GenoData.Id AND GenoSE.StrainId = GenoData.StrainId) WHERE - Geno.SpeciesId = %s AND Geno.Name = '%s' AND GenoXRef.GenoId = Geno.Id AND + Geno.SpeciesId = %s AND Geno.Name = %s AND GenoXRef.GenoId = Geno.Id AND GenoXRef.GenoFreezeId = GenoFreeze.Id AND - GenoFreeze.Name = '%s' AND + GenoFreeze.Name = %s AND GenoXRef.DataId = GenoData.Id AND GenoData.StrainId = Strain.Id Order BY Strain.Name - """ % (webqtlDatabaseFunction.retrieve_species_id(self.group.name), trait, self.name) - results = g.db.execute(query).fetchall() + """ + results = g.db.execute(query, + (webqtlDatabaseFunction.retrieve_species_id(self.group.name), + trait, self.name)).fetchall() return results @@ -893,7 +962,7 @@ class MrnaAssayDataSet(DataSet): 'flag'] # Fields displayed in the search results table header - self.header_fields = ['', + self.header_fields = ['Index', 'ID', 'Symbol', 'Description', @@ -1055,7 +1124,7 @@ class MrnaAssayDataSet(DataSet): # this_trait.mb) #ZS: Put this in function currently called "convert_location_to_value" - this_trait.location_repr = 'Chr %s: %.4f Mb' % (this_trait.chr, + this_trait.location_repr = 'Chr%s: %.6f' % (this_trait.chr, float(this_trait.mb)) this_trait.location_value = trait_location_value @@ -1111,7 +1180,7 @@ class MrnaAssayDataSet(DataSet): this_trait.LRS_score_repr = '%3.1f' % this_trait.lrs this_trait.LRS_score_value = this_trait.lrs - this_trait.LRS_location_repr = 'Chr %s: %.4f Mb' % (lrs_chr, float(lrs_mb)) + this_trait.LRS_location_repr = 'Chr%s: %.6f' % (lrs_chr, float(lrs_mb)) def convert_location_to_value(self, chromosome, mb): diff --git a/wqflask/base/mrna_assay_tissue_data.py b/wqflask/base/mrna_assay_tissue_data.py index 1a05fce7..b2c0448a 100755 --- a/wqflask/base/mrna_assay_tissue_data.py +++ b/wqflask/base/mrna_assay_tissue_data.py @@ -51,15 +51,15 @@ class MrnaAssayTissueData(object): query += ''' Symbol in {} group by Symbol) as x inner join TissueProbeSetXRef as t on t.Symbol = x.Symbol - and t.Mean = x.maxmean; + and t.Mean = x.maxmean;http://docs.python.org/2/library/string.html?highlight=lower#string.lower '''.format(in_clause) results = g.db.execute(query).fetchall() for result in results: symbol = result[0] - if symbol in gene_symbols: - #gene_symbols.append(symbol) + if symbol.lower() in [gene_symbol.lower() for gene_symbol in gene_symbols]: + #gene_symbols.append(symbol) symbol = symbol.lower() self.data[symbol].gene_id = result.GeneId diff --git a/wqflask/base/trait.py b/wqflask/base/trait.py index 8930c917..ff80795c 100755 --- a/wqflask/base/trait.py +++ b/wqflask/base/trait.py @@ -251,14 +251,7 @@ class GeneralTrait(object): # Todo: is this necessary? If not remove self.data.clear() - if self.dataset.group.parlist: - all_samples_ordered = (self.dataset.group.parlist + - self.dataset.group.f1list + - self.dataset.group.samplelist) - elif self.dataset.group.f1list: - all_samples_ordered = self.dataset.group.f1list + self.dataset.group.samplelist - else: - all_samples_ordered = self.dataset.group.samplelist + all_samples_ordered = self.dataset.group.all_samples_ordered() if results: for item in results: @@ -298,7 +291,7 @@ class GeneralTrait(object): PublishFreeze.Id = %s """ % (self.name, self.dataset.id) - print("query is:", query) + print("query is:", query) trait_info = g.db.execute(query).fetchone() #XZ, 05/08/2009: Xiaodong add this block to use ProbeSet.Id to find the probeset instead of just using ProbeSet.Name @@ -337,10 +330,10 @@ class GeneralTrait(object): trait_info = g.db.execute(query).fetchone() #print("trait_info is: ", pf(trait_info)) else: #Temp type - query = """SELECT %s FROM %s WHERE Name = %s - """ % (string.join(self.dataset.display_fields,','), - self.dataset.type, self.name) - trait_info = g.db.execute(query).fetchone() + query = """SELECT %s FROM %s WHERE Name = %s""" + trait_info = g.db.execute(query, + (string.join(self.dataset.display_fields,','), + self.dataset.type, self.name)).fetchone() if trait_info: self.haveinfo = True @@ -423,6 +416,8 @@ class GeneralTrait(object): if result: self.locus_chr = result[0] self.locus_mb = result[1] + else: + self.locus = self.locus_chr = self.locus_mb = "" else: self.locus = self.locus_chr = self.locus_mb = "" else: diff --git a/wqflask/base/webqtlCaseData.py b/wqflask/base/webqtlCaseData.py index 5927b0f4..42763aed 100755 --- a/wqflask/base/webqtlCaseData.py +++ b/wqflask/base/webqtlCaseData.py @@ -34,8 +34,6 @@ class webqtlCaseData(object): self.value = value # Trait Value self.variance = variance # Trait Variance self.num_cases = num_cases # Number of individuals/cases - self.prob_plot_value = None # Ordered value for probability plot; this is sort of wrong but not sure how else to do this - self.z_score = None self.extra_attributes = None self.this_id = None # Set a sane default (can't be just "id" cause that's a reserved word) self.outlier = None # Not set to True/False until later diff --git a/wqflask/base/webqtlConfig.py b/wqflask/base/webqtlConfig.py index 48d8cd0a..330fec56 100755 --- a/wqflask/base/webqtlConfig.py +++ b/wqflask/base/webqtlConfig.py @@ -53,7 +53,7 @@ GNROOT = "/home/zas1024/gene/" # Will remove this and dependent items later SECUREDIR = GNROOT + 'secure/' COMMON_LIB = GNROOT + 'support/admin' HTMLPATH = GNROOT + 'genotype_files/' -PYLMM_PATH = '/home/zas1024/plink/' +PYLMM_PATH = '/home/zas1024/plink_gemma/' SNP_PATH = '/home/zas1024/snps/' IMGDIR = GNROOT + '/wqflask/wqflask/images/' IMAGESPATH = HTMLPATH + 'images/' diff --git a/wqflask/maintenance/get_group_samplelists.py b/wqflask/maintenance/get_group_samplelists.py index c9ec3872..b8397b47 100755 --- a/wqflask/maintenance/get_group_samplelists.py +++ b/wqflask/maintenance/get_group_samplelists.py @@ -17,8 +17,13 @@ def process_genofiles(geno_dir=webqtlConfig.GENODIR): sample_list = get_samplelist(geno_file) -def get_samplelist(geno_file): - genofilename = os.path.join(webqtlConfig.GENODIR, geno_file) +def get_samplelist(file_type, geno_file): + if file_type == "geno": + return get_samplelist_from_geno(geno_file) + elif file_type == "plink": + return get_samplelist_from_plink(geno_file) + +def get_samplelist_from_geno(genofilename): if os.path.isfile(genofilename + '.gz'): genofilename += '.gz' genofile = gzip.open(genofilename) @@ -41,3 +46,12 @@ def get_samplelist(geno_file): samplelist = headers[3:] return samplelist +def get_samplelist_from_plink(genofilename): + genofile = open(genofilename) + + samplelist = [] + for line in genofile: + line = line.split(" ") + samplelist.append(line[0]) + + return samplelist
\ No newline at end of file diff --git a/wqflask/runserver.py b/wqflask/runserver.py index 5a76d1e2..59ebf0d4 100755 --- a/wqflask/runserver.py +++ b/wqflask/runserver.py @@ -30,6 +30,7 @@ logging_tree.printout() app.run(host='0.0.0.0', port=app.config['SERVER_PORT'], - use_debugger=False, + debug=True, + use_debugger=True, threaded=True, use_reloader=True) diff --git a/wqflask/secure_server.py b/wqflask/secure_server.py index 975c97c0..fc258578 100755 --- a/wqflask/secure_server.py +++ b/wqflask/secure_server.py @@ -66,11 +66,8 @@ def check_send_mail_running(): if __name__ == '__main__': #create_user() - - check_send_mail_running() - app.run(host='0.0.0.0', port=5002, use_debugger=True, diff --git a/wqflask/utility/tools.py b/wqflask/utility/tools.py new file mode 100644 index 00000000..760ded7c --- /dev/null +++ b/wqflask/utility/tools.py @@ -0,0 +1,84 @@ +# Tools/paths finder resolves external paths from settings and/or environment +# variables +# +# Currently supported: +# +# PYLMM_PATH finds the root of the git repository of the pylmm_gn2 tool + +import os +import sys +from wqflask import app + +def get_setting(id,default,guess,get_valid_path): + """ + Resolve a setting from the environment or the global settings in app.config + """ + # ---- Check whether environment exists + path = get_valid_path(os.environ.get(id)) + # ---- Check whether setting exists + setting = app.config.get(id) + if not path: + path = get_valid_path(setting) + # ---- Check whether default exists + if not path: + path = get_valid_path(default) + # ---- Guess directory + if not path: + if not setting: + setting = guess + path = get_valid_path(guess) + if not path: + raise Exception(id+' '+setting+' path unknown or faulty (update settings.py?). '+id+' should point to the root of the git repository') + + return path + +def pylmm_command(default=None): + """ + Return the path to the repository and the python command to call + """ + def get_valid_path(path): + """Test for a valid repository""" + if path: + sys.stderr.write("Trying PYLMM_PATH in "+path+"\n") + if path and os.path.isfile(path+'/pylmm_gn2/lmm.py'): + return path + else: + None + + guess = os.environ.get('HOME')+'/pylmm_gn2' + path = get_setting('PYLMM_PATH',default,guess,get_valid_path) + pylmm_command = 'python '+path+'/pylmm_gn2/lmm.py' + return path,pylmm_command + +def plink_command(default=None): + """ + Return the path to the repository and the python command to call + """ + def get_valid_path(path): + """Test for a valid repository""" + if path: + sys.stderr.write("Trying PLINK_PATH in "+path+"\n") + if path and os.path.isfile(path+'/plink'): + return path + else: + None + + guess = os.environ.get('HOME')+'/plink' + path = get_setting('PLINK_PATH',default,guess,get_valid_path) + plink_command = path+'/plink' + return path,plink_command + +def gemma_command(default=None): + def get_valid_path(path): + """Test for a valid repository""" + if path: + sys.stderr.write("Trying PLINK_PATH in "+path+"\n") + if path and os.path.isfile(path+'/plink'): + return path + else: + None + + guess = os.environ.get('HOME')+'/plink' + path = get_setting('PLINK_PATH',default,guess,get_valid_path) + gemma_command = path+'/gemma' + return path, gemma_command
\ No newline at end of file diff --git a/wqflask/utility/webqtlUtil.py b/wqflask/utility/webqtlUtil.py index 4d7981d9..f842dde0 100755 --- a/wqflask/utility/webqtlUtil.py +++ b/wqflask/utility/webqtlUtil.py @@ -43,6 +43,7 @@ ParInfo ={ 'BXH':['BHF1', 'HBF1', 'C57BL/6J', 'C3H/HeJ'], 'AKXD':['AKF1', 'KAF1', 'AKR/J', 'DBA/2J'], 'BXD':['B6D2F1', 'D2B6F1', 'C57BL/6J', 'DBA/2J'], +'C57BL-6JxC57BL-6NJF2':['', '', 'C57BL/6J', 'C57BL/6NJ'], 'BXD300':['B6D2F1', 'D2B6F1', 'C57BL/6J', 'DBA/2J'], 'B6BTBRF2':['B6BTBRF1', 'BTBRB6F1', 'C57BL/6J', 'BTBRT<+>tf/J'], 'BHHBF2':['B6HF2','HB6F2','C57BL/6J','C3H/HeJ'], @@ -880,22 +881,6 @@ def cmpGenoPos(A,B): except: return 0 -#XZhou: Must use "BINARY" to enable case sensitive comparison. -def authUser(name,password,db, encrypt=None): - try: - if encrypt: - query = 'SELECT privilege, id,name,password, grpName FROM User WHERE name= BINARY \'%s\' and password= BINARY \'%s\'' % (name,password) - else: - query = 'SELECT privilege, id,name,password, grpName FROM User WHERE name= BINARY \'%s\' and password= BINARY SHA(\'%s\')' % (name,password) - db.execute(query) - records = db.fetchone() - if not records: - raise ValueError - return records#(privilege,id,name,password,grpName) - except: - return (None, None, None, None, None) - - def hasAccessToConfidentialPhenotypeTrait(privilege, userName, authorized_users): access_to_confidential_phenotype_trait = 0 if webqtlConfig.USERDICT[privilege] > webqtlConfig.USERDICT['user']: diff --git a/wqflask/wqflask/database.py b/wqflask/wqflask/database.py index e55f06a7..159c5d6c 100755 --- a/wqflask/wqflask/database.py +++ b/wqflask/wqflask/database.py @@ -13,6 +13,11 @@ db_session = scoped_session(sessionmaker(autocommit=False, Base = declarative_base() Base.query = db_session.query_property() +#import logging +# +#logging.basicConfig() +#logging.getLogger('sqlalchemy.engine').setLevel(logging.INFO) + def init_db(): # import all modules here that might define models so that # they will be registered properly on the metadata. Otherwise @@ -23,4 +28,4 @@ def init_db(): Base.metadata.create_all(bind=engine) print("Done creating all...") -init_db()
\ No newline at end of file +init_db() diff --git a/wqflask/wqflask/do_search.py b/wqflask/wqflask/do_search.py index 921a4a47..02e73bb0 100755 --- a/wqflask/wqflask/do_search.py +++ b/wqflask/wqflask/do_search.py @@ -3,6 +3,8 @@ from __future__ import print_function, division +import string + from flask import Flask, g from MySQLdb import escape_string as escape @@ -40,6 +42,13 @@ class DoSearch(object): results = g.db.execute(query, no_parameters=True).fetchall() return results + def handle_wildcard(self, str): + keyword = str.strip() + keyword.replace("*",".*") + keyword.replace("?",".") + + return keyword + #def escape(self, stringy): # """Shorter name than self.db_conn.escape_string""" # return escape(str(stringy)) @@ -58,7 +67,17 @@ class DoSearch(object): @classmethod def get_search(cls, search_type): print("search_types are:", pf(cls.search_types)) - return cls.search_types[search_type] + + search_type_string = search_type['dataset_type'] + if 'key' in search_type: + search_type_string += '_' + search_type['key'] + + print("search_type_string is:", search_type_string) + + if search_type_string in cls.search_types: + return cls.search_types[search_type_string] + else: + return None class QuickMrnaAssaySearch(DoSearch): """A general search for mRNA assays""" @@ -73,7 +92,7 @@ class QuickMrnaAssaySearch(DoSearch): ProbeSet.name_num as ProbeSet_name_num FROM ProbeSet """ - header_fields = ['', + header_fields = ['Index', 'Record', 'Symbol', 'Location'] @@ -99,7 +118,7 @@ class MrnaAssaySearch(DoSearch): DoSearch.search_types['ProbeSet'] = "MrnaAssaySearch" - base_query = """SELECT ProbeSet.Name as TNAME, + base_query = """SELECT distinct ProbeSet.Name as TNAME, 0 as thistable, ProbeSetXRef.Mean as TMEAN, ProbeSetXRef.LRS as TLRS, @@ -110,7 +129,7 @@ class MrnaAssaySearch(DoSearch): ProbeSet.name_num as TNAME_NUM FROM ProbeSetXRef, ProbeSet """ - header_fields = ['', + header_fields = ['Index', 'Record', 'Symbol', 'Description', @@ -120,6 +139,28 @@ class MrnaAssaySearch(DoSearch): 'Max LRS Location', 'Additive Effect'] + def get_where_clause(self): + + if self.search_term[0] != "*": + match_clause = """(MATCH (ProbeSet.Name, + ProbeSet.description, + ProbeSet.symbol, + alias, + GenbankId, + UniGeneId, + Probe_Target_Description) + AGAINST ('%s' IN BOOLEAN MODE)) and + """ % (escape(self.search_term[0])) + else: + match_clause = "" + + where_clause = (match_clause + + """ProbeSet.Id = ProbeSetXRef.ProbeSetId + and ProbeSetXRef.ProbeSetFreezeId = %s + """ % (escape(str(self.dataset.id)))) + + return where_clause + def compile_final_query(self, from_clause = '', where_clause = ''): """Generates the final query string""" @@ -138,23 +179,7 @@ class MrnaAssaySearch(DoSearch): return query - def get_where_clause(self): - where_clause = """(MATCH (ProbeSet.Name, - ProbeSet.description, - ProbeSet.symbol, - alias, - GenbankId, - UniGeneId, - Probe_Target_Description) - AGAINST ('%s' IN BOOLEAN MODE)) - and ProbeSet.Id = ProbeSetXRef.ProbeSetId - and ProbeSetXRef.ProbeSetFreezeId = %s - """ % (escape(self.search_term[0]), - escape(str(self.dataset.id))) - - return where_clause - - def run_combined(self, from_clause, where_clause): + def run_combined(self, from_clause = '', where_clause = ''): """Generates and runs a combined search of an mRNA expression dataset""" print("Running ProbeSetSearch") @@ -173,7 +198,6 @@ class MrnaAssaySearch(DoSearch): print("final query is:", pf(query)) - return self.execute(query) def run(self): @@ -210,7 +234,7 @@ class PhenotypeSearch(DoSearch): 'Publication.Authors', 'PublishXRef.Id') - header_fields = ['', + header_fields = ['Index', 'Record', 'Description', 'Authors', @@ -219,28 +243,62 @@ class PhenotypeSearch(DoSearch): 'Max LRS Location', 'Additive Effect'] - def get_fields_clause(self): + def get_where_clause(self): """Generate clause for WHERE portion of query""" #Todo: Zach will figure out exactly what both these lines mean #and comment here if "'" not in self.search_term[0]: - search_term = "[[:<:]]" + self.search_term[0] + "[[:>:]]" + search_term = "[[:<:]]" + self.handle_wildcard(self.search_term[0]) + "[[:>:]]" # This adds a clause to the query that matches the search term # against each field in the search_fields tuple - fields_clause = [] + where_clause_list = [] for field in self.search_fields: - fields_clause.append('''%s REGEXP "%s"''' % (field, search_term)) - fields_clause = "(%s) and " % ' OR '.join(fields_clause) + where_clause_list.append('''%s REGEXP "%s"''' % (field, search_term)) + where_clause = "(%s) " % ' OR '.join(where_clause_list) - return fields_clause + return where_clause def compile_final_query(self, from_clause = '', where_clause = ''): """Generates the final query string""" from_clause = self.normalize_spaces(from_clause) + if self.search_term[0] == "*": + query = (self.base_query + + """%s + WHERE PublishXRef.InbredSetId = %s + and PublishXRef.PhenotypeId = Phenotype.Id + and PublishXRef.PublicationId = Publication.Id + and PublishFreeze.Id = %s""" % ( + from_clause, + escape(str(self.dataset.group.id)), + escape(str(self.dataset.id)))) + else: + query = (self.base_query + + """%s + WHERE %s + and PublishXRef.InbredSetId = %s + and PublishXRef.PhenotypeId = Phenotype.Id + and PublishXRef.PublicationId = Publication.Id + and PublishFreeze.Id = %s""" % ( + from_clause, + where_clause, + escape(str(self.dataset.group.id)), + escape(str(self.dataset.id)))) + + print("query is:", pf(query)) + + return query + + def run_combined(self, from_clause, where_clause): + """Generates and runs a combined search of an phenotype dataset""" + + print("Running PhenotypeSearch") + + from_clause = self.normalize_spaces(from_clause) + query = (self.base_query + """%s WHERE %s @@ -253,14 +311,15 @@ class PhenotypeSearch(DoSearch): escape(str(self.dataset.group.id)), escape(str(self.dataset.id)))) - print("query is:", pf(query)) + print("final query is:", pf(query)) - return query + + return self.execute(query) def run(self): """Generates and runs a simple search of a phenotype dataset""" - query = self.compile_final_query(where_clause = self.get_fields_clause()) + query = self.compile_final_query(where_clause = self.get_where_clause()) return self.execute(query) @@ -310,7 +369,7 @@ class QuickPhenotypeSearch(PhenotypeSearch): def run(self): """Generates and runs a search across all phenotype datasets""" - query = self.compile_final_query(where_clause = self.get_fields_clause()) + query = self.compile_final_query(where_clause = self.get_where_clause()) return self.execute(query) @@ -329,40 +388,47 @@ class GenotypeSearch(DoSearch): search_fields = ('Name', 'Chr') - header_fields = ['', + header_fields = ['Index', 'Record', 'Location'] - def get_fields_clause(self): + def get_where_clause(self): """Generate clause for part of the WHERE portion of query""" # This adds a clause to the query that matches the search term # against each field in search_fields (above) - fields_clause = [] + where_clause = [] if "'" not in self.search_term[0]: self.search_term = "[[:<:]]" + self.search_term[0] + "[[:>:]]" for field in self.search_fields: - fields_clause.append('''%s REGEXP "%s"''' % ("%s.%s" % self.mescape(self.dataset.type, + where_clause.append('''%s REGEXP "%s"''' % ("%s.%s" % self.mescape(self.dataset.type, field), self.search_term)) - print("hello ;where_clause is:", pf(fields_clause)) - fields_clause = "(%s)" % ' OR '.join(fields_clause) + print("hello ;where_clause is:", pf(where_clause)) + where_clause = "(%s) " % ' OR '.join(where_clause) - return fields_clause + return where_clause def compile_final_query(self, from_clause = '', where_clause = ''): """Generates the final query string""" from_clause = self.normalize_spaces(from_clause) - query = (self.base_query + - """WHERE %s and - Geno.Id = GenoXRef.GenoId and - GenoXRef.GenoFreezeId = GenoFreeze.Id and - GenoFreeze.Id = %s"""% (where_clause, - escape(str(self.dataset.id)))) + + if self.search_term[0] == "*": + query = (self.base_query + + """WHERE Geno.Id = GenoXRef.GenoId + and GenoXRef.GenoFreezeId = GenoFreeze.Id + and GenoFreeze.Id = %s"""% (escape(str(self.dataset.id)))) + else: + query = (self.base_query + + """WHERE %s + and Geno.Id = GenoXRef.GenoId + and GenoXRef.GenoFreezeId = GenoFreeze.Id + and GenoFreeze.Id = %s"""% (where_clause, + escape(str(self.dataset.id)))) print("query is:", pf(query)) @@ -373,14 +439,20 @@ class GenotypeSearch(DoSearch): #Todo: Zach will figure out exactly what both these lines mean #and comment here - self.query = self.compile_final_query(where_clause = self.get_fields_clause()) + if self.search_term[0] == "*": + self.query = self.compile_final_query() + else: + self.query = self.compile_final_query(where_clause = self.get_where_clause()) return self.execute(self.query) class RifSearch(MrnaAssaySearch): """Searches for traits with a Gene RIF entry including the search term.""" - DoSearch.search_types['RIF'] = "RifSearch" + DoSearch.search_types['ProbeSet_RIF'] = "RifSearch" + + def get_from_clause(self): + return ", GeneRIF_BASIC " def get_where_clause(self): where_clause = """( %s.symbol = GeneRIF_BASIC.symbol and @@ -390,13 +462,9 @@ class RifSearch(MrnaAssaySearch): return where_clause def run(self): - #where_clause = """( %s.symbol = GeneRIF_BASIC.symbol and - # MATCH (GeneRIF_BASIC.comment) - # AGAINST ('+%s' IN BOOLEAN MODE)) """ % (self.dataset.type, self.search_term[0]) - + from_clause = self.get_from_clause() where_clause = self.get_where_clause() - from_clause = ", GeneRIF_BASIC " query = self.compile_final_query(from_clause, where_clause) return self.execute(query) @@ -404,7 +472,10 @@ class RifSearch(MrnaAssaySearch): class WikiSearch(MrnaAssaySearch): """Searches GeneWiki for traits other people have annotated""" - DoSearch.search_types['WIKI'] = "WikiSearch" + DoSearch.search_types['ProbeSet_WIKI'] = "WikiSearch" + + def get_from_clause(self): + return ", GeneRIF " def get_where_clause(self): where_clause = """%s.symbol = GeneRIF.symbol @@ -416,16 +487,9 @@ class WikiSearch(MrnaAssaySearch): return where_clause def run(self): - #where_clause = """%s.symbol = GeneRIF.symbol - # and GeneRIF.versionId=0 and GeneRIF.display>0 - # and (GeneRIF.comment REGEXP '%s' or GeneRIF.initial = '%s') - # """ % (self.dataset.type, - # "[[:<:]]"+str(self.search_term[0])+"[[:>:]]", - # str(self.search_term[0])) - + from_clause = self.get_from_clause() where_clause = self.get_where_clause() - from_clause = ", GeneRIF " query = self.compile_final_query(from_clause, where_clause) return self.execute(query) @@ -433,7 +497,7 @@ class WikiSearch(MrnaAssaySearch): class GoSearch(MrnaAssaySearch): """Searches for synapse-associated genes listed in the Gene Ontology.""" - DoSearch.search_types['GO'] = "GoSearch" + DoSearch.search_types['ProbeSet_GO'] = "GoSearch" def run(self): field = 'GOterm.acc' @@ -455,7 +519,7 @@ class GoSearch(MrnaAssaySearch): return self.execute(query) #ZS: Not sure what the best way to deal with LRS searches is -class LrsSearch(MrnaAssaySearch): +class LrsSearch(DoSearch): """Searches for genes with a QTL within the given LRS values LRS searches can take 3 different forms: @@ -470,163 +534,162 @@ class LrsSearch(MrnaAssaySearch): DoSearch.search_types['LRS'] = 'LrsSearch' def get_from_clause(self): - if self.search_operator == "=": - return ", Geno" - else: - return "" + #If the user typed, for example "Chr4", the "Chr" substring needs to be removed so that all search elements can be converted to floats + if len(self.search_term) > 2 and "Chr" in self.search_term[2]: + chr_num = self.search_term[2].replace("Chr", "") + self.search_term[2] = chr_num - def get_where_clause(self): self.search_term = [float(value) for value in self.search_term] + if len(self.search_term) > 2: + from_clause = ", Geno" + else: + from_clause = "" + + return from_clause + + def get_where_clause(self): if self.search_operator == "=": assert isinstance(self.search_term, (list, tuple)) - self.lrs_min, self.lrs_max = self.search_term[:2] + lrs_min, lrs_max = self.search_term[:2] - self.sub_clause = """ %sXRef.LRS > %s and - %sXRef.LRS < %s and """ % self.mescape(self.dataset.type, - min(self.lrs_min, self.lrs_max), + where_clause = """ %sXRef.LRS > %s and + %sXRef.LRS < %s """ % self.mescape(self.dataset.type, + min(lrs_min, lrs_max), self.dataset.type, - max(self.lrs_min, self.lrs_max)) + max(lrs_min, lrs_max)) if len(self.search_term) > 2: - self.chr_num = self.search_term[2] - self.sub_clause += """ Geno.Chr = %s and """ % (self.chr_num) + chr_num = self.search_term[2] + where_clause += """ and Geno.Chr = %s """ % (chr_num) if len(self.search_term) == 5: - self.mb_low, self.mb_high = self.search_term[3:] - self.sub_clause += """ Geno.Mb > %s and - Geno.Mb < %s and - """ % self.mescape(min(self.mb_low, self.mb_high), - max(self.mb_low, self.mb_high)) - print("self.sub_clause is:", pf(self.sub_clause)) - - #%s.Chr = Geno.Chr - where_clause = self.sub_clause + """ %sXRef.Locus = Geno.name and + mb_low, mb_high = self.search_term[3:] + where_clause += """ and Geno.Mb > %s and + Geno.Mb < %s + """ % self.mescape(min(mb_low, mb_high), + max(mb_low, mb_high)) + + where_clause += """ and %sXRef.Locus = Geno.name and Geno.SpeciesId = %s """ % self.mescape(self.dataset.type, self.species_id) else: # Deal with >, <, >=, and <= print("self.search_term is:", self.search_term) - self.sub_clause = """ %sXRef.LRS %s %s """ % self.mescape(self.dataset.type, + where_clause = """ %sXRef.LRS %s %s """ % self.mescape(self.dataset.type, self.search_operator, self.search_term[0]) - where_clause = self.sub_clause - - + return where_clause - def get_final_query(self): + + def run(self): + self.from_clause = self.get_from_clause() self.where_clause = self.get_where_clause() + self.query = self.compile_final_query(self.from_clause, self.where_clause) - return self.query + return self.execute(self.query) + +class MrnaLrsSearch(LrsSearch, MrnaAssaySearch): + + DoSearch.search_types['ProbeSet_LRS'] = 'MrnaLrsSearch' def run(self): self.from_clause = self.get_from_clause() + self.where_clause = self.get_where_clause() - #self.search_term = [float(value) for value in self.search_term] - # - #if self.search_operator == "=": - # assert isinstance(self.search_term, (list, tuple)) - # self.lrs_min, self.lrs_max = self.search_term[:2] - # - # self.sub_clause = """ %sXRef.LRS > %s and - # %sXRef.LRS < %s and """ % self.mescape(self.dataset.type, - # min(self.lrs_min, self.lrs_max), - # self.dataset.type, - # max(self.lrs_min, self.lrs_max)) - # - # if len(self.search_term) > 2: - # self.chr_num = self.search_term[2] - # self.sub_clause += """ Geno.Chr = %s and """ % (escape(self.chr_num)) - # if len(self.search_term) == 5: - # self.mb_low, self.mb_high = self.search_term[3:] - # self.sub_clause += """ Geno.Mb > %s and - # Geno.Mb < %s and - # """ % self.mescape(min(self.mb_low, self.mb_high), - # max(self.mb_low, self.mb_high)) - # print("self.sub_clause is:", pf(self.sub_clause)) - #else: - # # Deal with >, <, >=, and <= - # print("self.search_term is:", self.search_term) - # self.sub_clause = """ %sXRef.LRS %s %s and """ % self.mescape(self.dataset.type, - # self.search_operator, - # self.search_term[0]) - # - #self.where_clause = self.sub_clause + """ %sXRef.Locus = Geno.name and - # Geno.SpeciesId = %s and - # %s.Chr = Geno.Chr - # """ % self.mescape(self.dataset.type, - # self.species_id, - # self.dataset.type) + self.query = self.compile_final_query(from_clause = self.from_clause, where_clause = self.where_clause) + return self.execute(self.query) + +class PhenotypeLrsSearch(LrsSearch, PhenotypeSearch): + + DoSearch.search_types['Publish_LRS'] = 'PhenotypeLrsSearch' + + def run(self): + + self.from_clause = self.get_from_clause() self.where_clause = self.get_where_clause() - self.query = self.compile_final_query(self.from_clause, self.where_clause) + self.query = self.compile_final_query(from_clause = self.from_clause, where_clause = self.where_clause) return self.execute(self.query) -class CisTransLrsSearch(LrsSearch): - def real_run(self, the_operator): - #if isinstance(self.search_term, basestring): - # self.search_term = [self.search_term] - print("self.search_term is:", self.search_term) +class CisTransLrsSearch(DoSearch): + + def get_from_clause(self): + return ", Geno" + + def get_where_clause(self, cis_trans): self.search_term = [float(value) for value in self.search_term] self.mb_buffer = 5 # default - - self.from_clause = ", Geno " + if cis_trans == "cis": + the_operator = "<" + else: + the_operator = ">" if self.search_operator == "=": if len(self.search_term) == 2: - self.lrs_min, self.lrs_max = self.search_term + lrs_min, lrs_max = self.search_term #[int(value) for value in self.search_term] elif len(self.search_term) == 3: - self.lrs_min, self.lrs_max, self.mb_buffer = self.search_term + lrs_min, lrs_max, self.mb_buffer = self.search_term else: SomeError - self.sub_clause = """ %sXRef.LRS > %s and + sub_clause = """ %sXRef.LRS > %s and %sXRef.LRS < %s and """ % ( escape(self.dataset.type), - escape(min(self.lrs_min, self.lrs_max)), + escape(str(min(lrs_min, lrs_max))), escape(self.dataset.type), - escape(max(self.lrs_min, self.lrs_max)) + escape(str(max(lrs_min, lrs_max))) ) else: # Deal with >, <, >=, and <= - self.sub_clause = """ %sXRef.LRS %s %s and """ % ( + sub_clause = """ %sXRef.LRS %s %s and """ % ( escape(self.dataset.type), escape(self.search_operator), escape(self.search_term[0]) ) - self.where_clause = self.sub_clause + """ - ABS(%s.Mb-Geno.Mb) %s %s and - %sXRef.Locus = Geno.name and - Geno.SpeciesId = %s and - %s.Chr = Geno.Chr""" % ( - escape(self.dataset.type), - the_operator, - escape(self.mb_buffer), - escape(self.dataset.type), - escape(self.species_id), - escape(self.dataset.type) - ) - - print("where_clause is:", pf(self.where_clause)) - - self.query = self.compile_final_query(self.from_clause, self.where_clause) - - return self.execute(self.query) - + if cis_trans == "cis": + where_clause = sub_clause + """ + ABS(%s.Mb-Geno.Mb) %s %s and + %sXRef.Locus = Geno.name and + Geno.SpeciesId = %s and + %s.Chr = Geno.Chr""" % ( + escape(self.dataset.type), + the_operator, + escape(str(self.mb_buffer)), + escape(self.dataset.type), + escape(str(self.species_id)), + escape(self.dataset.type) + ) + else: + where_clause = sub_clause + """ + %sXRef.Locus = Geno.name and + Geno.SpeciesId = %s and + ((ABS(%s.Mb-Geno.Mb) %s %s and %s.Chr = Geno.Chr) or + (%s.Chr != Geno.Chr))""" % ( + escape(self.dataset.type), + escape(str(self.species_id)), + escape(self.dataset.type), + the_operator, + escape(str(self.mb_buffer)), + escape(self.dataset.type), + escape(self.dataset.type) + ) -class CisLrsSearch(CisTransLrsSearch): + return where_clause + +class CisLrsSearch(CisTransLrsSearch, MrnaAssaySearch): """ Searches for genes on a particular chromosome with a cis-eQTL within the given LRS values @@ -643,12 +706,20 @@ class CisLrsSearch(CisTransLrsSearch): """ - DoSearch.search_types['CISLRS'] = "CisLrsSearch" + DoSearch.search_types['ProbeSet_CISLRS'] = 'CisLrsSearch' + + def get_where_clause(self): + return CisTransLrsSearch.get_where_clause(self, "cis") def run(self): - return self.real_run("<") + self.from_clause = self.get_from_clause() + self.where_clause = self.get_where_clause() + + self.query = self.compile_final_query(self.from_clause, self.where_clause) -class TransLrsSearch(CisTransLrsSearch): + return self.execute(self.query) + +class TransLrsSearch(CisTransLrsSearch, MrnaAssaySearch): """Searches for genes on a particular chromosome with a cis-eQTL within the given LRS values A transLRS search can take 3 forms: @@ -664,16 +735,24 @@ class TransLrsSearch(CisTransLrsSearch): """ - DoSearch.search_types['TRANSLRS'] = "TransLrsSearch" + DoSearch.search_types['ProbeSet_TRANSLRS'] = 'TransLrsSearch' + + def get_where_clause(self): + return CisTransLrsSearch.get_where_clause(self, "trans") def run(self): - return self.real_run(">") + self.from_clause = self.get_from_clause() + self.where_clause = self.get_where_clause() + + self.query = self.compile_final_query(self.from_clause, self.where_clause) + + return self.execute(self.query) class MeanSearch(MrnaAssaySearch): """Searches for genes expressed within an interval (log2 units) determined by the user""" - DoSearch.search_types['MEAN'] = "MeanSearch" + DoSearch.search_types['ProbeSet_MEAN'] = "MeanSearch" def get_where_clause(self): self.search_term = [float(value) for value in self.search_term] @@ -704,24 +783,6 @@ class MeanSearch(MrnaAssaySearch): return self.query def run(self): - - #self.search_term = [float(value) for value in self.search_term] - # - #if self.search_operator == "=": - # assert isinstance(self.search_term, (list, tuple)) - # self.mean_min, self.mean_max = self.search_term[:2] - # - # self.where_clause = """ %sXRef.mean > %s and - # %sXRef.mean < %s """ % self.mescape(self.dataset.type, - # min(self.mean_min, self.mean_max), - # self.dataset.type, - # max(self.mean_min, self.mean_max)) - #else: - # # Deal with >, <, >=, and <= - # self.where_clause = """ %sXRef.mean %s %s """ % self.mescape(self.dataset.type, - # self.search_operator, - # self.search_term[0]) - self.where_clause = self.get_where_clause() print("where_clause is:", pf(self.where_clause)) @@ -732,7 +793,7 @@ class MeanSearch(MrnaAssaySearch): class RangeSearch(MrnaAssaySearch): """Searches for genes with a range of expression varying between two values""" - DoSearch.search_types['RANGE'] = "RangeSearch" + DoSearch.search_types['ProbeSet_RANGE'] = "RangeSearch" def get_where_clause(self): if self.search_operator == "=": @@ -758,27 +819,6 @@ class RangeSearch(MrnaAssaySearch): return where_clause def run(self): - - #self.search_term = [float(value) for value in self.search_term] - # - #if self.search_operator == "=": - # assert isinstance(self.search_term, (list, tuple)) - # self.range_min, self.range_max = self.search_term[:2] - # self.where_clause = """ (SELECT Pow(2, max(value) -min(value)) - # FROM ProbeSetData - # WHERE ProbeSetData.Id = ProbeSetXRef.dataId) > %s AND - # (SELECT Pow(2, max(value) -min(value)) - # FROM ProbeSetData - # WHERE ProbeSetData.Id = ProbeSetXRef.dataId) < %s - # """ % self.mescape(min(self.range_min, self.range_max), - # max(self.range_min, self.range_max)) - #else: - # # Deal with >, <, >=, and <= - # self.where_clause = """ (SELECT Pow(2, max(value) -min(value)) - # FROM ProbeSetData - # WHERE ProbeSetData.Id = ProbeSetXRef.dataId) > %s - # """ % (escape(self.search_term[0])) - self.where_clause = self.get_where_clause() self.query = self.compile_final_query(where_clause = self.where_clause) @@ -791,10 +831,12 @@ class PositionSearch(DoSearch): for search_key in ('POSITION', 'POS', 'MB'): DoSearch.search_types[search_key] = "PositionSearch" - def setup(self): - self.search_term = [float(value) for value in self.search_term] + def get_where_clause(self): + self.search_term = [float(value) if is_number(value) else value for value in self.search_term] self.chr, self.mb_min, self.mb_max = self.search_term[:3] - self.where_clause = """ %s.Chr = '%s' and + self.get_chr() + + where_clause = """ %s.Chr = %s and %s.Mb > %s and %s.Mb < %s """ % self.mescape(self.dataset.type, self.chr, @@ -803,29 +845,44 @@ class PositionSearch(DoSearch): self.dataset.type, max(self.mb_min, self.mb_max)) + + return where_clause + + def get_chr(self): + try: + self.chr = int(self.chr) + except: + self.chr = int(self.chr.replace('chr', '')) + def run(self): - self.setup() + self.get_where_clause() self.query = self.compile_final_query(where_clause = self.where_clause) return self.execute(self.query) -class MrnaPositionSearch(MrnaAssaySearch, PositionSearch): +class MrnaPositionSearch(PositionSearch, MrnaAssaySearch): """Searches for genes located within a specified range on a specified chromosome""" + for search_key in ('POSITION', 'POS', 'MB'): + DoSearch.search_types['ProbeSet_'+search_key] = "MrnaPositionSearch" + def run(self): - self.setup() + self.where_clause = self.get_where_clause() self.query = self.compile_final_query(where_clause = self.where_clause) return self.execute(self.query) -class GenotypePositionSearch(GenotypeSearch, PositionSearch): +class GenotypePositionSearch(PositionSearch, GenotypeSearch): """Searches for genes located within a specified range on a specified chromosome""" + for search_key in ('POSITION', 'POS', 'MB'): + DoSearch.search_types['Geno_'+search_key] = "GenotypePositionSearch" + def run(self): - self.setup() + self.where_clause = self.get_where_clause() self.query = self.compile_final_query(where_clause = self.where_clause) return self.execute(self.query) @@ -833,6 +890,8 @@ class GenotypePositionSearch(GenotypeSearch, PositionSearch): class PvalueSearch(MrnaAssaySearch): """Searches for traits with a permutationed p-value between low and high""" + DoSearch.search_types['ProbeSet_PVALUE'] = "PvalueSearch" + def run(self): self.search_term = [float(value) for value in self.search_term] @@ -863,7 +922,7 @@ class PvalueSearch(MrnaAssaySearch): class AuthorSearch(PhenotypeSearch): """Searches for phenotype traits with specified author(s)""" - DoSearch.search_types["NAME"] = "AuthorSearch" + DoSearch.search_types["Publish_NAME"] = "AuthorSearch" def run(self): @@ -875,6 +934,12 @@ class AuthorSearch(PhenotypeSearch): return self.execute(self.query) +def is_number(s): + try: + float(s) + return True + except ValueError: + return False if __name__ == "__main__": ### Usually this will be used as a library, but call it from the command line for testing diff --git a/wqflask/wqflask/docs.py b/wqflask/wqflask/docs.py index 07b0b81a..a8363a1f 100755 --- a/wqflask/wqflask/docs.py +++ b/wqflask/wqflask/docs.py @@ -8,9 +8,9 @@ class Docs(object): sql = """ SELECT Docs.title, Docs.content FROM Docs - WHERE Docs.entry LIKE '%s' + WHERE Docs.entry LIKE %s """ - result = g.db.execute(sql % (entry)).fetchone() + result = g.db.execute(sql, str(entry)).fetchone() self.entry = entry self.title = result[0] self.content = result[1] diff --git a/wqflask/wqflask/interval_mapping/interval_mapping.py b/wqflask/wqflask/interval_mapping/interval_mapping.py index 4b4b7f73..672a9401 100755 --- a/wqflask/wqflask/interval_mapping/interval_mapping.py +++ b/wqflask/wqflask/interval_mapping/interval_mapping.py @@ -74,6 +74,7 @@ class IntervalMapping(object): json.dumps(self.json_data, webqtlConfig.TMPDIR + json_filename) self.js_data = dict( + result_score_type = "LRS", manhattan_plot = self.manhattan_plot, additive = self.additive, chromosomes = chromosome_mb_lengths, @@ -85,7 +86,10 @@ class IntervalMapping(object): def set_options(self, start_vars): """Sets various options (physical/genetic mapping, # permutations, which chromosome""" - self.num_permutations = int(start_vars['num_perm']) + if start_vars['num_perm'] == "": + self.num_permutations = 0 + else: + self.num_permutations = int(start_vars['num_perm']) if start_vars['manhattan_plot'] == "true": self.manhattan_plot = True else: diff --git a/wqflask/wqflask/marker_regression/marker_regression.py b/wqflask/wqflask/marker_regression/marker_regression.py index 999a32b8..c003f5e8 100755 --- a/wqflask/wqflask/marker_regression/marker_regression.py +++ b/wqflask/wqflask/marker_regression/marker_regression.py @@ -25,7 +25,6 @@ from redis import Redis Redis = Redis() from flask import Flask, g -from wqflask import app from base.trait import GeneralTrait from base import data_set @@ -36,23 +35,14 @@ from utility import webqtlUtil #from wqflask.marker_regression import plink_mapping from wqflask.marker_regression import gemma_mapping #from wqflask.marker_regression import rqtl_mapping -from wqflask.my_pylmm.data import prep_data -# from wqflask.my_pylmm.pyLMM import lmm -# from wqflask.my_pylmm.pyLMM import input from utility import helper_functions from utility import Plot, Bunch from utility import temp_data - from utility.benchmark import Bench +from utility.tools import pylmm_command, plink_command -import os -if os.environ.get('PYLMM_PATH') is None: - PYLMM_PATH=app.config.get('PYLMM_PATH') - if PYLMM_PATH is None: - PYLMM_PATH=os.environ['HOME']+'/gene/wqflask/wqflask/my_pylmm/pyLMM' -if not os.path.isfile(PYLMM_PATH+'/lmm.py'): - raise Exception('PYLMM_PATH '+PYLMM_PATH+' unknown or faulty') -PYLMM_COMMAND= 'python '+PYLMM_PATH+'/lmm.py' +PYLMM_PATH,PYLMM_COMMAND = pylmm_command() +PLINK_PATH,PLINK_COMMAND = plink_command() class MarkerRegression(object): @@ -189,6 +179,7 @@ class MarkerRegression(object): self.js_data = dict( + result_score_type = "LOD", json_data = self.json_data, this_trait = self.this_trait.name, data_set = self.dataset.name, @@ -469,29 +460,16 @@ class MarkerRegression(object): def run_plink(self): - - os.chdir("/home/zas1024/plink") - plink_output_filename = webqtlUtil.genRandStr("%s_%s_"%(self.dataset.group.name, self.this_trait.name)) self.gen_pheno_txt_file_plink(pheno_filename = plink_output_filename) - plink_command = './plink --noweb --ped %s.ped --no-fid --no-parents --no-sex --no-pheno --map %s.map --pheno %s/%s.txt --pheno-name %s --maf %s --missing-phenotype -9999 --out %s%s --assoc ' % (self.dataset.group.name, self.dataset.group.name, webqtlConfig.TMPDIR, plink_output_filename, self.this_trait.name, self.maf, webqtlConfig.TMPDIR, plink_output_filename) - + plink_command = PLINK_COMMAND + ' --noweb --ped %s/%s.ped --no-fid --no-parents --no-sex --no-pheno --map %s/%s.map --pheno %s%s.txt --pheno-name %s --maf %s --missing-phenotype -9999 --out %s%s --assoc ' % (PLINK_PATH, self.dataset.group.name, PLINK_PATH, self.dataset.group.name, webqtlConfig.TMPDIR, plink_output_filename, self.this_trait.name, self.maf, webqtlConfig.TMPDIR, plink_output_filename) + print("plink_command:", plink_command) + os.system(plink_command) count, p_values = self.parse_plink_output(plink_output_filename) - #gemma_command = './gemma -bfile %s -k output_%s.cXX.txt -lmm 1 -o %s_output' % ( - # self.dataset.group.name, - # self.dataset.group.name, - # self.dataset.group.name) - #print("gemma_command:" + gemma_command) - # - #os.system(gemma_command) - # - #included_markers, p_values = self.parse_gemma_output() - # - #self.dataset.group.get_specified_markers(markers = included_markers) #for marker in self.dataset.group.markers.markers: # if marker['name'] not in included_markers: @@ -578,10 +556,7 @@ class MarkerRegression(object): # get strain name from ped file in order def get_samples_from_ped_file(self): - - os.chdir("/home/zas1024/plink") - - ped_file= open("{}.ped".format(self.dataset.group.name),"r") + ped_file= open("{}/{}.ped".format(PLINK_PATH, self.dataset.group.name),"r") line = ped_file.readline() sample_list=[] diff --git a/wqflask/wqflask/search_results.py b/wqflask/wqflask/search_results.py index df1edb13..01d65278 100755 --- a/wqflask/wqflask/search_results.py +++ b/wqflask/wqflask/search_results.py @@ -77,10 +77,15 @@ class SearchResultPage(object): self.trait_type = kw['trait_type'] self.quick_search() else: - self.results = [] print("kw is:", kw) - #self.quick_search = False - self.search_terms = kw['search_terms'] + if kw['search_terms_or']: + self.and_or = "or" + self.search_terms = kw['search_terms_or'] + else: + self.and_or = "and" + self.search_terms = kw['search_terms_and'] + self.search_term_exists = True + self.results = [] if kw['type'] == "Phenotypes": dataset_type = "Publish" elif kw['type'] == "Genotypes": @@ -222,60 +227,61 @@ class SearchResultPage(object): if len(self.search_terms) > 1: combined_from_clause = "" combined_where_clause = "" + previous_from_clauses = [] #The same table can't be referenced twice in the from clause for i, a_search in enumerate(self.search_terms): - print("[kodak] item is:", pf(a_search)) - search_term = a_search['search_term'] - search_operator = a_search['separator'] - if a_search['key']: - search_type = a_search['key'].upper() + the_search = self.get_search_ob(a_search) + if the_search != None: + get_from_clause = getattr(the_search, "get_from_clause", None) + if callable(get_from_clause): + from_clause = the_search.get_from_clause() + if from_clause in previous_from_clauses: + pass + else: + previous_from_clauses.append(from_clause) + combined_from_clause += from_clause + where_clause = the_search.get_where_clause() + combined_where_clause += "(" + where_clause + ")" + if (i+1) < len(self.search_terms): + if self.and_or == "and": + combined_where_clause += "AND" + else: + combined_where_clause += "OR" else: - # We fall back to the dataset type as the key to get the right object - search_type = self.dataset.type - - print("search_type is:", pf(search_type)) - - search_ob = do_search.DoSearch.get_search(search_type) - search_class = getattr(do_search, search_ob) - the_search = search_class(search_term, - search_operator, - self.dataset, - ) - - #search_query = the_search.get_final_query() - - get_from_clause = getattr(the_search, "get_from_clause", None) - if callable(get_from_clause): - from_clause = the_search.get_from_clause() - combined_from_clause += from_clause - where_clause = the_search.get_where_clause() - combined_where_clause += "(" + where_clause + ")" - if (i+1) < len(self.search_terms): - combined_where_clause += "AND" - - results = the_search.run_combined(combined_from_clause, combined_where_clause) - self.results.extend(results) - + self.search_term_exists = False + if self.search_term_exists: + combined_where_clause = "(" + combined_where_clause + ")" + final_query = the_search.compile_final_query(combined_from_clause, combined_where_clause) + results = the_search.execute(final_query) + self.results.extend(results) else: for a_search in self.search_terms: - print("[kodak] item is:", pf(a_search)) - search_term = a_search['search_term'] - search_operator = a_search['separator'] - if a_search['key']: - search_type = a_search['key'].upper() + the_search = self.get_search_ob(a_search) + if the_search != None: + self.results.extend(the_search.run()) else: - # We fall back to the dataset type as the key to get the right object - search_type = self.dataset.type - - print("search_type is:", pf(search_type)) - - search_ob = do_search.DoSearch.get_search(search_type) - search_class = getattr(do_search, search_ob) - print("search_class is: ", pf(search_class)) - the_search = search_class(search_term, - search_operator, - self.dataset, - ) - self.results.extend(the_search.run()) - #print("in the search results are:", self.results) - - self.header_fields = the_search.header_fields + self.search_term_exists = False + + if the_search != None: + self.header_fields = the_search.header_fields + + def get_search_ob(self, a_search): + print("[kodak] item is:", pf(a_search)) + search_term = a_search['search_term'] + search_operator = a_search['separator'] + search_type = {} + search_type['dataset_type'] = self.dataset.type + if a_search['key']: + search_type['key'] = a_search['key'].upper() + print("search_type is:", pf(search_type)) + + search_ob = do_search.DoSearch.get_search(search_type) + if search_ob: + search_class = getattr(do_search, search_ob) + print("search_class is: ", pf(search_class)) + the_search = search_class(search_term, + search_operator, + self.dataset, + ) + return the_search + else: + return None diff --git a/wqflask/wqflask/show_trait/SampleList.py b/wqflask/wqflask/show_trait/SampleList.py index 060cb519..367bd2dd 100755 --- a/wqflask/wqflask/show_trait/SampleList.py +++ b/wqflask/wqflask/show_trait/SampleList.py @@ -10,6 +10,8 @@ import numpy as np from scipy import stats from pprint import pformat as pf +import itertools + class SampleList(object): def __init__(self, dataset, @@ -24,15 +26,14 @@ class SampleList(object): self.header = header self.sample_list = [] # The actual list + self.sample_attribute_values = {} - try: - self.get_attributes() - except Exception: - print("failed to get attributes") - self.attributes = {} - + self.get_attributes() print("camera: attributes are:", pf(self.attributes)) + if self.this_trait and self.dataset and self.dataset.type == 'ProbeSet': + self.get_extra_attribute_values() + for counter, sample_name in enumerate(sample_names, 1): sample_name = sample_name.replace("_2nd_", "") @@ -59,15 +60,14 @@ class SampleList(object): sample.this_id = "Other_" + str(counter) #### For extra attribute columns; currently only used by several datasets - Zach - if self.this_trait and self.dataset and self.dataset.type == 'ProbeSet': - sample.extra_attributes = self.get_extra_attribute_values(sample_name) + if self.sample_attribute_values: + sample.extra_attributes = self.sample_attribute_values.get(sample_name, {}) print("sample.extra_attributes is", pf(sample.extra_attributes)) self.sample_list.append(sample) print("self.attributes is", pf(self.attributes)) - self.get_z_scores() self.do_outliers() #do_outliers(the_samples) print("*the_samples are [%i]: %s" % (len(self.sample_list), pf(self.sample_list))) @@ -78,50 +78,6 @@ class SampleList(object): def __repr__(self): return "<SampleList> --> %s" % (pf(self.__dict__)) - #def get_z_scores(self): - # values = [sample.value for sample in self.sample_list if sample.value != None] - # dataX = values[:] - # dataX.sort(webqtlUtil.cmpOrder) - # dataY=webqtlUtil.U(len(dataX)) - # z_scores=map(webqtlUtil.inverseCumul, dataY) - # - # print("self.sample_list:", [sample for sample in self.sample_list if sample.value != None]) - # print("z_scores:", len(z_scores)) - # for i, sample in enumerate([sample for sample in self.sample_list if sample.value != None]): - # print("sample is:", sample) - # sample.z_score = z_scores[i] - - - #def get_z_scores(self): - # values = [sample.value for sample in self.sample_list if sample.value != None] - # z_scores = z_score(values) - # - # print("self.sample_list:", [sample for sample in self.sample_list if sample.value != None]) - # print("z_scores:", len(z_scores)) - # for i, sample in enumerate([sample for sample in self.sample_list if sample.value != None]): - # print("sample is:", sample) - # sample.z_score = z_scores[i] - - - def get_z_scores(self): - - - values = [sample.value for sample in self.sample_list if sample.value != None] - numpy_array = np.array(values) - prob_plot = stats.probplot(numpy_array)[0] - print("prob_plot:", prob_plot) - - values = prob_plot[1] - z_scores = prob_plot[0] - print("z_scores:", z_scores) - - - print("self.sample_list:", [sample for sample in self.sample_list if sample.value != None]) - for i, sample in enumerate([sample for sample in self.sample_list if sample.value != None]): - print("sample is:", sample) - sample.z_score = z_scores[i] - sample.prob_plot_value = values[i] - def do_outliers(self): values = [sample.value for sample in self.sample_list if sample.value != None] upper_bound, lower_bound = Plot.find_outliers(values) @@ -135,75 +91,54 @@ class SampleList(object): else: sample.outlier = False - def get_attributes(self): """Finds which extra attributes apply to this dataset""" - - #ZS: Id and name values for this trait's extra attributes - case_attributes = g.db.execute('''SELECT CaseAttribute.Id, CaseAttribute.Name - FROM CaseAttribute, CaseAttributeXRef - WHERE CaseAttributeXRef.ProbeSetFreezeId = %s AND - CaseAttribute.Id = CaseAttributeXRef.CaseAttributeId - group by CaseAttributeXRef.CaseAttributeId''', - (str(self.dataset.id),)) + # Get attribute names and distinct values for each attribute + results = g.db.execute(''' + SELECT DISTINCT CaseAttribute.Id, CaseAttribute.Name, CaseAttributeXRef.Value + FROM CaseAttribute, CaseAttributeXRef + WHERE CaseAttributeXRef.CaseAttributeId = CaseAttribute.Id + AND CaseAttributeXRef.ProbeSetFreezeId = %s + ORDER BY CaseAttribute.Name''', (str(self.dataset.id),)) self.attributes = {} - for key, value in case_attributes.fetchall(): - print("radish: %s - %s" % (key, value)) + for attr, values in itertools.groupby(results.fetchall(), lambda row: (row.Id, row.Name)): + key, name = attr + print("radish: %s - %s" % (key, name)) self.attributes[key] = Bunch() - self.attributes[key].name = value - - attribute_values = g.db.execute('''SELECT DISTINCT CaseAttributeXRef.Value - FROM CaseAttribute, CaseAttributeXRef - WHERE CaseAttribute.Name = %s AND - CaseAttributeXRef.CaseAttributeId = CaseAttribute.Id''', (value,)) - - self.attributes[key].distinct_values = [item[0] for item in attribute_values.fetchall()] + self.attributes[key].name = name + self.attributes[key].distinct_values = [item.Value for item in values] self.attributes[key].distinct_values.sort(key=natural_sort_key) - - def get_extra_attribute_values(self, sample_name): - - attribute_values = {} - + def get_extra_attribute_values(self): if self.attributes: + results = g.db.execute(''' + SELECT Strain.Name AS SampleName, CaseAttributeId AS Id, CaseAttributeXRef.Value + FROM Strain, StrainXRef, InbredSet, CaseAttributeXRef + WHERE StrainXRef.StrainId = Strain.Id + AND InbredSet.Id = StrainXRef.InbredSetId + AND CaseAttributeXRef.StrainId = Strain.Id + AND InbredSet.Name = %s + AND CaseAttributeXRef.ProbeSetFreezeId = %s + ORDER BY SampleName''', + (self.dataset.group.name, self.this_trait.dataset.id)) + + for sample_name, items in itertools.groupby(results.fetchall(), lambda row: row.SampleName): + attribute_values = {} + for item in items: + attribute_value = item.Value + + #ZS: If it's an int, turn it into one for sorting + #(for example, 101 would be lower than 80 if they're strings instead of ints) + try: + attribute_value = int(attribute_value) + except ValueError: + pass + + attribute_values[self.attributes[item.Id].name] = attribute_value + self.sample_attribute_values[sample_name] = attribute_values - #ZS: Get StrainId value for the next query - result = g.db.execute("""SELECT Strain.Id - FROM Strain, StrainXRef, InbredSet - WHERE Strain.Name = %s and - StrainXRef.StrainId = Strain.Id and - InbredSet.Id = StrainXRef.InbredSetId and - InbredSet.Name = %s""", (sample_name, - self.dataset.group.name)) - - sample_id = result.fetchone().Id - - for attribute in self.attributes: - - #ZS: Add extra case attribute values (if any) - result = g.db.execute("""SELECT Value - FROM CaseAttributeXRef - WHERE ProbeSetFreezeId = %s AND - StrainId = %s AND - CaseAttributeId = %s - group by CaseAttributeXRef.CaseAttributeId""", ( - self.this_trait.dataset.id, sample_id, str(attribute))) - - attribute_value = result.fetchone().Value #Trait-specific attributes, if any - - #ZS: If it's an int, turn it into one for sorting - #(for example, 101 would be lower than 80 if they're strings instead of ints) - try: - attribute_value = int(attribute_value) - except ValueError: - pass - - attribute_values[self.attributes[attribute].name] = attribute_value - - return attribute_values - def se_exists(self): """Returns true if SE values exist for any samples, otherwise false""" diff --git a/wqflask/wqflask/show_trait/show_trait.py b/wqflask/wqflask/show_trait/show_trait.py index b28c9b76..02472267 100755 --- a/wqflask/wqflask/show_trait/show_trait.py +++ b/wqflask/wqflask/show_trait/show_trait.py @@ -16,6 +16,7 @@ from base import webqtlConfig from base import webqtlCaseData from wqflask.show_trait.SampleList import SampleList from utility import webqtlUtil, Plot, Bunch, helper_functions +from utility.tools import pylmm_command, plink_command from base.trait import GeneralTrait from base import data_set from dbFunction import webqtlDatabaseFunction @@ -23,6 +24,9 @@ from basicStatistics import BasicStatisticsFunctions from pprint import pformat as pf +PYLMM_PATH,PYLMM_COMMAND = pylmm_command() +PLINK_PATH,PLINK_COMMAND = plink_command() + ############################################### # # Todo: Put in security to ensure that user has permission to access confidential data sets @@ -137,24 +141,36 @@ class ShowTrait(object): sample_lists = [group.sample_list for group in self.sample_groups] print("sample_lists is:", pf(sample_lists)) - probability_plot_data = [] - sample_vals = [] - sample_z_scores = [] - for sample_list in sample_lists: - for sample in sample_list: - sample_vals.append(sample.prob_plot_value) - sample_z_scores.append(sample.z_score) - probability_plot_data.append(sample_z_scores) - probability_plot_data.append(sample_vals) + self.get_mapping_methods() - js_data = dict(sample_group_types = self.sample_group_types, sample_lists = sample_lists, - probability_plot_data = probability_plot_data, attribute_names = self.sample_groups[0].attributes, temp_uuid = self.temp_uuid) self.js_data = js_data + def get_mapping_methods(self): + '''Only display mapping methods when the dataset group's genotype file exists''' + def check_plink_gemma(): + if (os.path.isfile(PLINK_PATH+"/"+self.dataset.group.name+".bed") and + os.path.isfile(PLINK_PATH+"/"+self.dataset.group.name+".bim") and + os.path.isfile(PLINK_PATH+"/"+self.dataset.group.name+".fam") and + os.path.isfile(PLINK_PATH+"/"+self.dataset.group.name+".map")): + + return True + else: + return False + + def check_pylmm_rqtl(): + if os.path.isfile(webqtlConfig.GENODIR+self.dataset.group.name+".geno"): + return True + else: + return False + + self.use_plink_gemma = check_plink_gemma() + self.use_pylmm_rqtl = check_pylmm_rqtl() + + def read_data(self, include_f1=False): '''read user input data or from trait data and analysis form''' @@ -237,7 +253,7 @@ class ShowTrait(object): def dispTraitInformation(self, args, title1Body, hddn, this_trait): - _Species = webqtlDatabaseFunction.retrieve_species(group=self.dataset.group.name) + self.species_name = webqtlDatabaseFunction.retrieve_species(group=self.dataset.group.name) #tbl = HT.TableLite(cellpadding=2, Class="collap", style="margin-left:20px;", width="840", valign="top", id="target1") @@ -311,7 +327,7 @@ class ShowTrait(object): #XZ: Gene Symbol if this_trait.symbol: #XZ: Show SNP Browser only for mouse - if _Species == 'mouse': + if self.species_name == 'mouse': geneName = g.db.execute("SELECT geneSymbol FROM GeneList WHERE geneSymbol = %s", this_trait.symbol).fetchone() if geneName: snpurl = os.path.join(webqtlConfig.CGIDIR, "main.py?FormID=SnpBrowserResultPage&submitStatus=1&diffAlleles=True&customStrain=True") + "&geneName=%s" % geneName[0] @@ -334,8 +350,8 @@ class ShowTrait(object): #XZ: display similar traits in other selected datasets if this_trait and this_trait.dataset and this_trait.dataset.type=="ProbeSet" and this_trait.symbol: - if _Species in ("mouse", "rat", "human"): - similarUrl = "%s?cmd=sch&gene=%s&alias=1&species=%s" % (os.path.join(webqtlConfig.CGIDIR, webqtlConfig.SCRIPTFILE), this_trait.symbol, _Species) + if self.species_name in ("mouse", "rat", "human"): + similarUrl = "%s?cmd=sch&gene=%s&alias=1&species=%s" % (os.path.join(webqtlConfig.CGIDIR, webqtlConfig.SCRIPTFILE), this_trait.symbol, self.species_name) similarButton = HT.Href(url="#redirect", onClick="openNewWin('%s')" % similarUrl) similarButton_img = HT.Image("/images/find_icon.jpg", name="similar", alt=" Find similar expression data ", title=" Find similar expression data ", style="border:none;") similarText = "Find" @@ -374,40 +390,41 @@ class ShowTrait(object): #--------Hongqiang add this part in order to not only blat ProbeSet, but also blat Probe blatsequence = '%3E'+this_trait.name+'%0A'+blatsequence+'%0A' #XZ, 06/03/2009: ProbeSet name is not unique among platforms. We should use ProbeSet Id instead. - self.cursor.execute("""SELECT Probe.Sequence, Probe.Name + query = """SELECT Probe.Sequence, Probe.Name FROM Probe, ProbeSet, ProbeSetFreeze, ProbeSetXRef WHERE ProbeSetXRef.ProbeSetFreezeId = ProbeSetFreeze.Id AND ProbeSetXRef.ProbeSetId = ProbeSet.Id AND - ProbeSetFreeze.Name = '%s' AND - ProbeSet.Name = '%s' AND - Probe.ProbeSetId = ProbeSet.Id order by Probe.SerialOrder""" % (this_trait.dataset.name, this_trait.name) ) + ProbeSetFreeze.Name = '{}' AND + ProbeSet.Name = '{}' AND + Probe.ProbeSetId = ProbeSet.Id order by Probe.SerialOrder""".format(this_trait.dataset.name, this_trait.name) + + seqs = g.db.execute(query).fetchall() - seqs = self.cursor.fetchall() for seqt in seqs: if int(seqt[1][-1]) %2 == 1: blatsequence += '%3EProbe_'+string.strip(seqt[1])+'%0A'+string.strip(seqt[0])+'%0A' #XZ: Pay attention to the parameter of version (rn, mm, hg). They need to be changed if necessary. - if _Species == "rat": - UCSC_BLAT_URL = webqtlConfig.UCSC_BLAT % ('rat', 'rn3', blatsequence) - UTHSC_BLAT_URL = "" - elif _Species == "mouse": - UCSC_BLAT_URL = webqtlConfig.UCSC_BLAT % ('mouse', 'mm9', blatsequence) - UTHSC_BLAT_URL = webqtlConfig.UTHSC_BLAT % ('mouse', 'mm9', blatsequence) - elif _Species == "human": - UCSC_BLAT_URL = webqtlConfig.UCSC_BLAT % ('human', 'hg19', blatsequence) - UTHSC_BLAT_URL = "" + if self.species_name == "rat": + self.UCSC_BLAT_URL = webqtlConfig.UCSC_BLAT % ('rat', 'rn3', blatsequence) + self.UTHSC_BLAT_URL = "" + elif self.species_name == "mouse": + self.UCSC_BLAT_URL = webqtlConfig.UCSC_BLAT % ('mouse', 'mm9', blatsequence) + self.UTHSC_BLAT_URL = webqtlConfig.UTHSC_BLAT % ('mouse', 'mm9', blatsequence) + elif self.species_name == "human": + self.UCSC_BLAT_URL = webqtlConfig.UCSC_BLAT % ('human', 'hg19', blatsequence) + self.UTHSC_BLAT_URL = "" else: - UCSC_BLAT_URL = "" - UTHSC_BLAT_URL = "" + self.UCSC_BLAT_URL = "" + self.UTHSC_BLAT_URL = "" - if UCSC_BLAT_URL: + if self.UCSC_BLAT_URL != "": verifyButton = HT.Href(url="#", onClick="javascript:openNewWin('%s'); return false;" % UCSC_BLAT_URL) verifyButtonImg = HT.Image("/images/verify_icon.jpg", name="verify", alt=" Check probe locations at UCSC ", title=" Check probe locations at UCSC ", style="border:none;") verifyButton.append(verifyButtonImg) verifyText = 'Verify' - if UTHSC_BLAT_URL: + if self.UTHSC_BLAT_URL != "": rnaseqButton = HT.Href(url="#", onClick="javascript:openNewWin('%s'); return false;" % UTHSC_BLAT_URL) rnaseqButtonImg = HT.Image("/images/rnaseq_icon.jpg", name="rnaseq", alt=" View probes, SNPs, and RNA-seq at UTHSC ", title=" View probes, SNPs, and RNA-seq at UTHSC ", style="border:none;") @@ -427,6 +444,7 @@ class ShowTrait(object): #query database for number of probes associated with trait; if count > 0, set probe tool button and text probeResult = g.db.execute(query).fetchone() if probeResult[0] > 0: + self.show_probes = "True" probeurl = "%s?FormID=showProbeInfo&database=%s&ProbeSetID=%s&CellID=%s&group=%s&incparentsf1=ON" \ % (os.path.join(webqtlConfig.CGIDIR, webqtlConfig.SCRIPTFILE), this_trait.dataset, this_trait.name, this_trait.cellid, self.dataset.group.name) probeButton = HT.Href(url="#", onClick="javascript:openNewWin('%s'); return false;" % probeurl) @@ -434,7 +452,7 @@ class ShowTrait(object): #probeButton.append(probeButton_img) probeText = "Probes" - this_trait.species = _Species # We need this in the template, so we tuck it into this_trait + this_trait.species = self.species_name # We need this in the template, so we tuck it into this_trait this_trait.database = this_trait.get_database() #XZ: ID links @@ -473,14 +491,14 @@ class ShowTrait(object): #XZ,12/26/2008: Gene symbol may contain single quotation mark. #For example, Affymetrix, mouse430v2, 1440338_at, the symbol is 2'-Pde (geneid 211948) #I debug this by using double quotation marks. - if _Species == "rat": + if self.species_name == "rat": result = g.db.execute("SELECT kgID, chromosome,txStart,txEnd FROM GeneList_rn33 WHERE geneSymbol = %s", (this_trait.symbol)).fetchone() if result != None: kgId, chr, txst, txen = result[0], result[1], result[2], result[3] if chr and txst and txen and kgId: txst = int(txst*1000000) txen = int(txen*1000000) - if _Species == "mouse": + if self.species_name == "mouse": print("this_trait.symbol:", this_trait.symbol) result = g.db.execute("SELECT chromosome,txStart,txEnd FROM GeneList WHERE geneSymbol = %s", (this_trait.symbol)).fetchone() if result != None: @@ -495,10 +513,10 @@ class ShowTrait(object): # url="http://symatlas.gnf.org/SymAtlas/bioentry?querytext=%s&query=14&species=%s&type=Expression" \ # % (this_trait.symbol,symatlas_species),Class="fs14 fwn", \ # title="Expression across many tissues and cell types"), style=linkStyle), " "*2) - if this_trait.geneid and (_Species == "mouse" or _Species == "rat" or _Species == "human"): + if this_trait.geneid and (self.species_name == "mouse" or self.species_name == "rat" or self.species_name == "human"): #tSpan.append(HT.Span(HT.Href(text= 'BioGPS',target="mainFrame",\ # url="http://biogps.gnf.org/?org=%s#goto=genereport&id=%s" \ - # % (_Species, this_trait.geneid),Class="fs14 fwn", \ + # % (self.species_name, this_trait.geneid),Class="fs14 fwn", \ # title="Expression across many tissues and cell types"), style=linkStyle), " "*2) pass #tSpan.append(HT.Span(HT.Href(text= 'STRING',target="mainFrame",\ @@ -508,13 +526,13 @@ class ShowTrait(object): if this_trait.symbol: #ZS: The "species scientific" converts the plain English species names we're using to their scientific names, which are needed for PANTHER's input #We should probably use the scientific name along with the English name (if not instead of) elsewhere as well, given potential non-English speaking users - if _Species == "mouse": + if self.species_name == "mouse": species_scientific = "Mus%20musculus" - elif _Species == "rat": + elif self.species_name == "rat": species_scientific = "Rattus%20norvegicus" - elif _Species == "human": + elif self.species_name == "human": species_scientific = "Homo%20sapiens" - elif _Species == "drosophila": + elif self.species_name == "drosophila": species_scientific = "Drosophila%20melanogaster" else: species_scientific = "all" @@ -530,7 +548,7 @@ class ShowTrait(object): # url="http://bind.ca/?textquery=%s" \ # % this_trait.symbol,Class="fs14 fwn", \ # title="Protein interactions"), style=linkStyle), " "*2) - #if this_trait.geneid and (_Species == "mouse" or _Species == "rat" or _Species == "human"): + #if this_trait.geneid and (self.species_name == "mouse" or self.species_name == "rat" or self.species_name == "human"): # tSpan.append(HT.Span(HT.Href(text= 'Gemma',target="mainFrame",\ # url="http://www.chibi.ubc.ca/Gemma/gene/showGene.html?ncbiid=%s" \ # % this_trait.geneid, Class="fs14 fwn", \ @@ -539,19 +557,19 @@ class ShowTrait(object): # url="http://lily.uthsc.edu:8080/20091027_GNInterfaces/20091027_redirectSynDB.jsp?query=%s" \ # % this_trait.symbol, Class="fs14 fwn", \ # title="Brain synapse database"), style=linkStyle), " "*2) - #if _Species == "mouse": + #if self.species_name == "mouse": # tSpan.append(HT.Span(HT.Href(text= 'ABA',target="mainFrame",\ # url="http://mouse.brain-map.org/brain/%s.html" \ # % this_trait.symbol, Class="fs14 fwn", \ # title="Allen Brain Atlas"), style=linkStyle), " "*2) if this_trait.geneid: - #if _Species == "mouse": + #if self.species_name == "mouse": # tSpan.append(HT.Span(HT.Href(text= 'ABA',target="mainFrame",\ # url="http://www.brain-map.org/search.do?queryText=egeneid=%s" \ # % this_trait.geneid, Class="fs14 fwn", \ # title="Allen Brain Atlas"), style=linkStyle), " "*2) - if _Species == "human": + if self.species_name == "human": #tSpan.append(HT.Span(HT.Href(text= 'ABA',target="mainFrame",\ # url="http://humancortex.alleninstitute.org/has/human/imageseries/search/1.html?searchSym=t&searchAlt=t&searchName=t&gene_term=&entrez_term=%s" \ # % this_trait.geneid, Class="fs14 fwn", \ @@ -654,13 +672,13 @@ class ShowTrait(object): location = "not available" #if this_trait.sequence and len(this_trait.sequence) > 100: - # if _Species == "rat": + # if self.species_name == "rat": # UCSC_BLAT_URL = webqtlConfig.UCSC_BLAT % ('rat', 'rn3', this_trait.sequence) # UTHSC_BLAT_URL = webqtlConfig.UTHSC_BLAT % ('rat', 'rn3', this_trait.sequence) - # elif _Species == "mouse": + # elif self.species_name == "mouse": # UCSC_BLAT_URL = webqtlConfig.UCSC_BLAT % ('mouse', 'mm9', this_trait.sequence) # UTHSC_BLAT_URL = webqtlConfig.UTHSC_BLAT % ('mouse', 'mm9', this_trait.sequence) - # elif _Species == "human": + # elif self.species_name == "human": # UCSC_BLAT_URL = webqtlConfig.UCSC_BLAT % ('human', 'hg19', blatsequence) # UTHSC_BLAT_URL = webqtlConfig.UTHSC_BLAT % ('human', 'hg19', this_trait.sequence) # else: @@ -888,40 +906,7 @@ class ShowTrait(object): this_group = 'BXD' if this_group: - - dataset_menu = [] - print("[tape4] webqtlConfig.PUBLICTHRESH:", webqtlConfig.PUBLICTHRESH) - print("[tape4] type webqtlConfig.PUBLICTHRESH:", type(webqtlConfig.PUBLICTHRESH)) - results = g.db.execute("""SELECT PublishFreeze.FullName,PublishFreeze.Name FROM - PublishFreeze,InbredSet WHERE PublishFreeze.InbredSetId = InbredSet.Id - and InbredSet.Name = %s and PublishFreeze.public > %s""", - (this_group, webqtlConfig.PUBLICTHRESH)) - for item in results.fetchall(): - dataset_menu.append(dict(tissue=None, - datasets=[item])) - - results = g.db.execute("""SELECT GenoFreeze.FullName,GenoFreeze.Name FROM GenoFreeze, - InbredSet WHERE GenoFreeze.InbredSetId = InbredSet.Id and InbredSet.Name = - %s and GenoFreeze.public > %s""", - (this_group, webqtlConfig.PUBLICTHRESH)) - for item in results.fetchall(): - dataset_menu.append(dict(tissue=None, - datasets=[item])) - - #03/09/2009: Xiaodong changed the SQL query to order by Name as requested by Rob. - tissues = g.db.execute("SELECT Id, Name FROM Tissue order by Name") - for item in tissues.fetchall(): - tissue_id, tissue_name = item - data_sets = g.db.execute('''SELECT ProbeSetFreeze.FullName,ProbeSetFreeze.Name FROM ProbeSetFreeze, ProbeFreeze, - InbredSet WHERE ProbeSetFreeze.ProbeFreezeId = ProbeFreeze.Id and ProbeFreeze.TissueId = %s and - ProbeSetFreeze.public > %s and ProbeFreeze.InbredSetId = InbredSet.Id and InbredSet.Name like %s - order by ProbeSetFreeze.CreateTime desc, ProbeSetFreeze.AvgId ''', - (tissue_id, webqtlConfig.PUBLICTHRESH, "%" + this_group + "%")) - dataset_sub_menu = [item for item in data_sets.fetchall() if item] - if dataset_sub_menu: - dataset_menu.append(dict(tissue=tissue_name, - datasets=dataset_sub_menu)) - + dataset_menu = self.dataset.group.datasets() dataset_menu_selected = None if len(dataset_menu): if this_trait and this_trait.dataset: @@ -939,7 +924,7 @@ class ShowTrait(object): def build_mapping_tools(self, this_trait): - _Species = webqtlDatabaseFunction.retrieveSpecies(cursor=self.cursor, group=fd.group) + #_Species = webqtlDatabaseFunction.retrieveSpecies(cursor=self.cursor, group=fd.group) this_group = fd.group if this_group[:3] == 'BXD': @@ -1191,18 +1176,9 @@ class ShowTrait(object): def make_sample_lists(self, this_trait): - if self.dataset.group.parlist: - all_samples_ordered = (self.dataset.group.parlist + - self.dataset.group.f1list + - self.dataset.group.samplelist) - elif self.dataset.group.f1list: - all_samples_ordered = self.dataset.group.f1list + self.dataset.group.samplelist - else: - all_samples_ordered = self.dataset.group.samplelist + all_samples_ordered = self.dataset.group.all_samples_ordered() - this_trait_samples = set(this_trait.data.keys()) - - primary_sample_names = all_samples_ordered + primary_sample_names = list(all_samples_ordered) print("self.dataset.group", pf(self.dataset.group.__dict__)) print("-*- primary_samplelist is:", pf(primary_sample_names)) @@ -1213,14 +1189,10 @@ class ShowTrait(object): all_samples_ordered.append(sample) other_sample_names.append(sample) - other_sample_names, all_samples_ordered = get_samplelist_from_trait_data(this_trait, - all_samples_ordered) - - print("species:", self.dataset.group.species) if self.dataset.group.species == "human": primary_sample_names += other_sample_names - + primary_samples = SampleList(dataset = self.dataset, sample_names=primary_sample_names, this_trait=this_trait, @@ -1256,16 +1228,6 @@ class ShowTrait(object): # print("hjs") self.dataset.group.allsamples = all_samples_ordered - -def get_samplelist_from_trait_data(this_trait, all_samples_ordered): - other_sample_names = [] - for sample in this_trait.data.keys(): - if sample not in all_samples_ordered: - all_samples_ordered.append(sample) - other_sample_names.append(sample) - - return other_sample_names, all_samples_ordered - def get_nearest_marker(this_trait, this_db): this_chr = this_trait.locus_chr print("this_chr:", this_chr) diff --git a/wqflask/wqflask/static/new/css/bar_chart.css b/wqflask/wqflask/static/new/css/bar_chart.css index 78d31eee..4da090b9 100755 --- a/wqflask/wqflask/static/new/css/bar_chart.css +++ b/wqflask/wqflask/static/new/css/bar_chart.css @@ -9,7 +9,15 @@ fill: steelblue;
shape-rendering: crispEdges;
}
-/*
-.x.axis path {
+
+#legend-left, #legend-right {
+ font-size: 20px;
+}
+
+.nvd3 .nv-group {
+ fill-opacity: .9 !important;
+}
+
+.nvtooltip table td.legend-color-guide div {
display: none;
-}*/
\ No newline at end of file +}
\ No newline at end of file diff --git a/wqflask/wqflask/static/new/css/charts.css b/wqflask/wqflask/static/new/css/charts.css index 5f2d4d23..0ce88c15 100644 --- a/wqflask/wqflask/static/new/css/charts.css +++ b/wqflask/wqflask/static/new/css/charts.css @@ -25,4 +25,4 @@ p { p#caption {
margin-left: 100px;
width: 500px;
-}
+}
\ No newline at end of file diff --git a/wqflask/wqflask/static/new/css/prob_plot.css b/wqflask/wqflask/static/new/css/prob_plot.css new file mode 100644 index 00000000..2a1f8f53 --- /dev/null +++ b/wqflask/wqflask/static/new/css/prob_plot.css @@ -0,0 +1,3 @@ +#prob_plot_title { + text-align: center; +}
\ No newline at end of file diff --git a/wqflask/wqflask/static/new/css/show_trait.css b/wqflask/wqflask/static/new/css/show_trait.css index 9fc82a85..1e9fd4df 100644 --- a/wqflask/wqflask/static/new/css/show_trait.css +++ b/wqflask/wqflask/static/new/css/show_trait.css @@ -4,4 +4,8 @@ tr .outlier { #bar_chart_container { overflow-x:scroll; +} + +div.sample_group { + overflow: auto; # needed because it contains float dataTable wrapper }
\ No newline at end of file diff --git a/wqflask/wqflask/static/new/javascript/bar_chart.coffee b/wqflask/wqflask/static/new/javascript/bar_chart.coffee index c83de64e..7558de80 100755 --- a/wqflask/wqflask/static/new/javascript/bar_chart.coffee +++ b/wqflask/wqflask/static/new/javascript/bar_chart.coffee @@ -1,191 +1,159 @@ root = exports ? this class Bar_Chart - constructor: (@sample_list) -> + constructor: (sample_lists) -> + @sample_lists = {} + l1 = @sample_lists['samples_primary'] = sample_lists[0] or [] + l2 = @sample_lists['samples_other'] = sample_lists[1] or [] + + l1_names = (x.name for x in l1) + l3 = l1.concat((x for x in l2 when x.name not in l1_names)) + @sample_lists['samples_all'] = l3 + longest_sample_name_len = d3.max(sample.name.length for sample in l3) + @margin = { + top: 20, + right: 20, + bottom: longest_sample_name_len * 6, + left: 40 + } + + @attributes = (key for key of sample_lists[0][0]["extra_attributes"]) + @sample_attr_vals = (@extra(s) for s in @sample_lists['samples_all']\ + when s.value != null) + @get_distinct_attr_vals() + @get_attr_color_dict(@distinct_attr_vals) + @attribute_name = "None" + @sort_by = "name" - @get_samples() - console.log("sample names:", @sample_names) - if @sample_attr_vals.length > 0 - @get_distinct_attr_vals() - @get_attr_color_dict(@distinct_attr_vals) - - #Used to calculate the bottom margin so sample names aren't cut off - longest_sample_name = d3.max(sample.length for sample in @sample_names) - - @margin = {top: 20, right: 20, bottom: longest_sample_name * 7, left: 40} - @plot_width = @sample_vals.length * 20 - @margin.left - @margin.right - @range = @sample_vals.length * 20 - @plot_height = 500 - @margin.top - @margin.bottom - - @x_buffer = @plot_width/20 - @y_buffer = @plot_height/20 - - @y_min = d3.min(@sample_vals) - @y_max = d3.max(@sample_vals) * 1.1 - - @svg = @create_svg() - - @plot_height -= @y_buffer - @create_scales() - @create_graph() + @chart = null + + @select_attribute_box = $ "#color_attribute" d3.select("#color_attribute").on("change", => - @attribute = $("#color_attribute").val() - console.log("attr_color_dict:", @attr_color_dict) - #if $("#update_bar_chart").html() == 'Sort By Name' - if @sort_by = "name" - @svg.selectAll(".bar") - .data(@sorted_samples()) - .transition() - .duration(1000) - .style("fill", (d) => - if @attribute == "None" - return "steelblue" - else - return @attr_color_dict[@attribute][d[2][@attribute]] - ) - .select("title") - .text((d) => - return d[1] - ) - else - @svg.selectAll(".bar") - .data(@samples) - .transition() - .duration(1000) - .style("fill", (d) => - if @attribute == "None" - return "steelblue" - else - return @attr_color_dict[@attribute][d[2][@attribute]] - ) - @draw_legend() - @add_legend(@attribute, @distinct_attr_vals[@attribute]) + @attribute_name = @select_attribute_box.val() + @rebuild_bar_graph() ) $(".sort_by_value").on("click", => console.log("sorting by value") @sort_by = "value" - if @attributes.length > 0 - @attribute = $("#color_attribute").val() - #sortItems = (a, b) -> - # return a[1] - b[1] - @rebuild_bar_graph(@sorted_samples()) - #@svg.selectAll(".bar") - # .data(@sorted_samples()) - # .transition() - # .duration(1000) - # .attr("y", (d) => - # return @y_scale(d[1]) - # ) - # .attr("height", (d) => - # return @plot_height - @y_scale(d[1]) - # ) - # .select("title") - # .text((d) => - # return d[1] - # ) - # #.style("fill", (d) => - # # if @attributes.length > 0 - # # return @attr_color_dict[attribute][d[2][attribute]] - # # else - # # return "steelblue" - # #) - #sorted_sample_names = (sample[0] for sample in @sorted_samples()) - #x_scale = d3.scale.ordinal() - # .domain(sorted_sample_names) - # .rangeBands([0, @plot_width], .1) - #$('.x.axis').remove() - #@add_x_axis(x_scale) + @rebuild_bar_graph() ) - + $(".sort_by_name").on("click", => console.log("sorting by name") @sort_by = "name" - if @attributes.length > 0 - @attribute = $("#color_attribute").val() - @rebuild_bar_graph(@samples) - #@svg.selectAll(".bar") - # .data(@samples) - # .transition() - # .duration(1000) - # .attr("y", (d) => - # return @y_scale(d[1]) - # ) - # .attr("height", (d) => - # return @plot_height - @y_scale(d[1]) - # ) - # .select("title") - # .text((d) => - # return d[1] - # ) - # .style("fill", (d) => - # if @attributes.length > 0 - # return @attr_color_dict[attribute][d[2][attribute]] - # else - # return "steelblue" - # ) - #x_scale = d3.scale.ordinal() - # .domain(@sample_names) - # .rangeBands([0, @plot_width], .1) - #$('.x.axis').remove() - #@add_x_axis(x_scale) + @rebuild_bar_graph() ) d3.select("#color_by_trait").on("click", => @open_trait_selection() ) - rebuild_bar_graph: (samples) -> - console.log("samples:", samples) - @svg.selectAll(".bar") - .data(samples) - .transition() - .duration(1000) - .style("fill", (d) => - if @attributes.length == 0 and @trait_color_dict? - console.log("SAMPLE:", d[0]) - console.log("CHECKING:", @trait_color_dict[d[0]]) - #return "steelblue" - return @trait_color_dict[d[0]] - else if @attributes.length > 0 and @attribute != "None" - console.log("@attribute:", @attribute) - console.log("d[2]", d[2]) - console.log("the_color:", @attr_color_dict[@attribute][d[2][@attribute]]) - return @attr_color_dict[@attribute][d[2][@attribute]] - else - return "steelblue" - ) - .attr("y", (d) => - return @y_scale(d[1]) - ) - .attr("height", (d) => - return @plot_height - @y_scale(d[1]) + value: (sample) -> + @value_dict[sample.name].value + + variance: (sample) -> + @value_dict[sample.name].variance + + extra: (sample) -> + attr_vals = {} + for attribute in @attributes + attr_vals[attribute] = sample["extra_attributes"][attribute] + attr_vals + + # takes a dict: name -> value and rebuilds the graph + redraw: (samples_dict, selected_group) -> + @value_dict = samples_dict[selected_group] + @raw_data = (x for x in @sample_lists[selected_group] when\ + x.name of @value_dict and @value(x) != null) + @rebuild_bar_graph() + + rebuild_bar_graph: -> + raw_data = @raw_data.slice() + if @sort_by == 'value' + raw_data = raw_data.sort((x, y) => @value(x) - @value(y)) + console.log("raw_data: ", raw_data) + + h = 600 + container = $("#bar_chart_container") + container.height(h + @margin.top + @margin.bottom) + if @chart is null + @chart = nv.models.multiBarChart() + .height(h) + .errorBarColor(=> 'red') + .reduceXTicks(false) + .staggerLabels(false) + .showControls(false) + .showLegend(false) + + # show value, sd, and attributes in tooltip + @chart.multibar.dispatch.on('elementMouseover.tooltip', + (evt) => + evt.value = @chart.x()(evt.data); + evt['series'] = [{ + key: 'Value', + value: evt.data.y, + color: evt.color + }]; + if evt.data.yErr + evt['series'].push({key: 'SE', value: evt.data.yErr}) + if evt.data.attr + for k, v of evt.data.attr + evt['series'].push({key: k, value: v}) + @chart.tooltip.data(evt).hidden(false); ) - .select("title") - .text((d) => - return d[1] + + @chart.tooltip.valueFormatter((d, i) -> d) + + nv.addGraph(() => + @remove_legend() + values = ({x: s.name, y: @value(s), yErr: @variance(s) or 0,\ + attr: s.extra_attributes}\ + for s in raw_data) + + if @attribute_name != "None" + @color_dict = @attr_color_dict[@attribute_name] + @chart.barColor((d) => @color_dict[d.attr[@attribute_name]]) + @add_legend() + else + @chart.barColor(=> 'steelblue') + + @chart.width(raw_data.length * 20) + @chart.yDomain([0.9 * _.min((d.y - 1.5 * d.yErr for d in values)), + 1.05 * _.max((d.y + 1.5 * d.yErr for d in values))]) + console.log("values: ", values) + d3.select("#bar_chart_container svg") + .datum([{values: values}]) + .style('width', raw_data.length * 20 + 'px') + .transition().duration(1000) + .call(@chart) + + d3.select("#bar_chart_container .nv-x") + .selectAll('.tick text') + .style("font-size", "12px") + .style("text-anchor", "end") + .attr("dx", "-.8em") + .attr("dy", "-.3em") + .attr("transform", (d) => + return "rotate(-90)" + ) + + return @chart ) - #.style("fill", (d) => - # return @trait_color_dict[d[0]] - # #return @attr_color_dict["collection_trait"][trimmed_samples[d[0]]] - #) - sample_names = (sample[0] for sample in samples) - console.log("sample_names2:", sample_names) - x_scale = d3.scale.ordinal() - .domain(sample_names) - .rangeRoundBands([0, @range], 0.1, 0) - $('.bar_chart').find('.x.axis').remove() - @add_x_axis(x_scale) get_attr_color_dict: (vals) -> @attr_color_dict = {} + @is_discrete = {} + @minimum_values = {} + @maximum_values = {} console.log("vals:", vals) for own key, distinct_vals of vals @min_val = d3.min(distinct_vals) @max_val = d3.max(distinct_vals) this_color_dict = {} - if distinct_vals.length < 10 + discrete = distinct_vals.length < 10 + if discrete color = d3.scale.category10() for value, i in distinct_vals this_color_dict[value] = color(i) @@ -200,8 +168,7 @@ class Bar_Chart return true ) color_range = d3.scale.linear() - .domain([min_val, - max_val]) + .domain([@min_val, @max_val]) .range([0,255]) for value, i in distinct_vals console.log("color_range(value):", parseInt(color_range(value))) @@ -209,79 +176,12 @@ class Bar_Chart #this_color_dict[value] = d3.rgb("lightblue").darker(color_range(parseInt(value))) #this_color_dict[value] = "rgb(0, 0, " + color_range(parseInt(value)) + ")" @attr_color_dict[key] = this_color_dict - - - - draw_legend: () -> - $('#legend-left').html(@min_val) - $('#legend-right').html(@max_val) - svg_html = '<svg height="10" width="90"> \ - <rect x="0" width="15" height="10" style="fill: rgb(0, 0, 0);"></rect> \ - <rect x="15" width="15" height="10" style="fill: rgb(50, 0, 0);"></rect> \ - <rect x="30" width="15" height="10" style="fill: rgb(100, 0, 0);"></rect> \ - <rect x="45" width="15" height="10" style="fill: rgb(150, 0, 0);"></rect> \ - <rect x="60" width="15" height="10" style="fill: rgb(200, 0, 0);"></rect> \ - <rect x="75" width="15" height="10" style="fill: rgb(255, 0, 0);"></rect> \ - </svg>' - console.log("svg_html:", svg_html) - $('#legend-colors').html(svg_html) - - get_trait_color_dict: (samples, vals) -> - @trait_color_dict = {} - console.log("vals:", vals) - for own key, distinct_vals of vals - this_color_dict = {} - @min_val = d3.min(distinct_vals) - @max_val = d3.max(distinct_vals) - if distinct_vals.length < 10 - color = d3.scale.category10() - for value, i in distinct_vals - this_color_dict[value] = color(i) - else - console.log("distinct_values:", distinct_vals) - #Check whether all values are numbers, and if they are get a corresponding - #color gradient - if _.every(distinct_vals, (d) => - if isNaN(d) - return false - else - return true - ) - color_range = d3.scale.linear() - .domain([d3.min(distinct_vals), - d3.max(distinct_vals)]) - .range([0,255]) - for value, i in distinct_vals - console.log("color_range(value):", parseInt(color_range(value))) - this_color_dict[value] = d3.rgb(parseInt(color_range(value)),0, 0) - for own sample, value of samples - @trait_color_dict[sample] = this_color_dict[value] - - convert_into_colors: (values) -> - color_range = d3.scale.linear() - .domain([d3.min(values), - d3.max(values)]) - .range([0,255]) - for value, i in values - console.log("color_range(value):", color_range(parseInt(value))) - this_color_dict[value] = d3.rgb(color_range(parseInt(value)),0, 0) - #this_color_dict[value] = d3.rgb("lightblue").darker(color_range(parseInt(value))) - - get_samples: () -> - @sample_names = (sample.name for sample in @sample_list when sample.value != null) - @sample_vals = (sample.value for sample in @sample_list when sample.value != null) - @attributes = (key for key of @sample_list[0]["extra_attributes"]) - console.log("attributes:", @attributes) - @sample_attr_vals = [] - if @attributes.length > 0 - for sample in @sample_list - attr_vals = {} - for attribute in @attributes - attr_vals[attribute] = sample["extra_attributes"][attribute] - @sample_attr_vals.push(attr_vals) - @samples = _.zip(@sample_names, @sample_vals, @sample_attr_vals) + @is_discrete[key] = discrete + @minimum_values[key] = @min_val + @maximum_values[key] = @max_val get_distinct_attr_vals: () -> + # FIXME: this has quadratic behaviour, may cause issues with many samples and continuous attributes @distinct_attr_vals = {} for sample in @sample_attr_vals for attribute of sample @@ -290,134 +190,45 @@ class Bar_Chart if sample[attribute] not in @distinct_attr_vals[attribute] @distinct_attr_vals[attribute].push(sample[attribute]) #console.log("distinct_attr_vals:", @distinct_attr_vals) - - create_svg: () -> - svg = d3.select("#bar_chart") - .append("svg") - .attr("class", "bar_chart") - .attr("width", @plot_width + @margin.left + @margin.right) - .attr("height", @plot_height + @margin.top + @margin.bottom) - .append("g") - .attr("transform", "translate(" + @margin.left + "," + @margin.top + ")") - - return svg - - create_scales: () -> - @x_scale = d3.scale.ordinal() - .domain(@sample_names) - .rangeRoundBands([0, @range], 0.1, 0) - - @y_scale = d3.scale.linear() - .domain([@y_min * 0.75, @y_max]) - .range([@plot_height, @y_buffer]) - - create_graph: () -> - - #@add_border() - @add_x_axis(@x_scale) - @add_y_axis() - - @add_bars() - - add_x_axis: (scale) -> - xAxis = d3.svg.axis() - .scale(scale) - .orient("bottom"); - - @svg.append("g") - .attr("class", "x axis") - .attr("transform", "translate(0," + @plot_height + ")") - .call(xAxis) - .selectAll("text") - .style("text-anchor", "end") - .style("font-size", "12px") - .attr("dx", "-.8em") - .attr("dy", "-.3em") - .attr("transform", (d) => - return "rotate(-90)" - ) - add_y_axis: () -> - yAxis = d3.svg.axis() - .scale(@y_scale) - .orient("left") - .ticks(5) - - @svg.append("g") - .attr("class", "y axis") - .call(yAxis) - .append("text") - .attr("transform", "rotate(-90)") - .attr("y", 6) - .attr("dy", ".71em") - .style("text-anchor", "end") - - add_bars: () -> - @svg.selectAll(".bar") - .data(@samples) - .enter().append("rect") - .style("fill", "steelblue") - .attr("class", "bar") - .attr("x", (d) => - return @x_scale(d[0]) - ) - .attr("width", @x_scale.rangeBand()) - .attr("y", (d) => - return @y_scale(d[1]) - ) - .attr("height", (d) => - return @plot_height - @y_scale(d[1]) - ) - .append("svg:title") - .text((d) => - return d[1] + add_legend: => + if @is_discrete[@attribute_name] + @add_legend_discrete() + else + @add_legend_continuous() + + remove_legend: => + $(".legend").remove() + $("#legend-left,#legend-right,#legend-colors").empty() + + add_legend_continuous: => + $('#legend-left').html(@minimum_values[@attribute_name]) + $('#legend-right').html(@maximum_values[@attribute_name]) + svg_html = '<svg height="15" width="120"> \ + <rect x="0" width="20" height="15" style="fill: rgb(0, 0, 0);"></rect> \ + <rect x="20" width="20" height="15" style="fill: rgb(50, 0, 0);"></rect> \ + <rect x="40" width="20" height="15" style="fill: rgb(100, 0, 0);"></rect> \ + <rect x="60" width="20" height="15" style="fill: rgb(150, 0, 0);"></rect> \ + <rect x="80" width="20" height="15" style="fill: rgb(200, 0, 0);"></rect> \ + <rect x="100" width="20" height="15" style="fill: rgb(255, 0, 0);"></rect> \ + </svg>' + console.log("svg_html:", svg_html) + $('#legend-colors').html(svg_html) + + add_legend_discrete: => + legend_span = d3.select('#bar_chart_legend') + .append('div').style('word-wrap', 'break-word') + .attr('class', 'legend').selectAll('span') + .data(@distinct_attr_vals[@attribute_name]) + .enter().append('span').style({'word-wrap': 'normal', 'display': 'inline-block'}) + + legend_span.append('span') + .style("background-color", (d) => + return @attr_color_dict[@attribute_name][d] ) - - - sorted_samples: () -> - #if @sample_attr_vals.length > 0 - sample_list = _.zip(@sample_names, @sample_vals, @sample_attr_vals) - #else - # sample_list = _.zip(@sample_names, @sample_vals) - sorted = _.sortBy(sample_list, (sample) => - return sample[1] - ) - console.log("sorted:", sorted) - return sorted - - add_legend: (attribute, distinct_vals) -> - legend = @svg.append("g") - .attr("class", "legend") - .attr("height", 100) - .attr("width", 100) - .attr('transform', 'translate(-20,50)') - - legend_rect = legend.selectAll('rect') - .data(distinct_vals) - .enter() - .append("rect") - .attr("x", @plot_width - 65) - .attr("width", 10) - .attr("height", 10) - .attr("y", (d, i) => - return i * 20 - ) - .style("fill", (d) => - console.log("TEST:", @attr_color_dict[attribute][d]) - return @attr_color_dict[attribute][d] - ) - - legend_text = legend.selectAll('text') - .data(distinct_vals) - .enter() - .append("text") - .attr("x", @plot_width - 52) - .attr("y", (d, i) => - return i*20 + 9 - ) - .text((d) => - return d - ) + .style({'display': 'inline-block', 'width': '15px', 'height': '15px',\ + 'margin': '0px 5px 0px 15px'}) + legend_span.append('span').text((d) => return d).style('font-size', '20px') open_trait_selection: () -> $('#collections_holder').load('/collections/list?color_by_trait #collections_list', => @@ -432,7 +243,7 @@ class Bar_Chart # console.log("contents:", $(element).contents()) # $(element).contents().unwrap() ) - + color_by_trait: (trait_sample_data) -> console.log("BXD1:", trait_sample_data["BXD1"]) console.log("trait_sample_data:", trait_sample_data) @@ -443,34 +254,7 @@ class Bar_Chart @get_trait_color_dict(trimmed_samples, distinct_values) console.log("TRAIT_COLOR_DICT:", @trait_color_dict) console.log("SAMPLES:", @samples) - if @sort_by = "value" - @svg.selectAll(".bar") - .data(@samples) - .transition() - .duration(1000) - .style("fill", (d) => - console.log("this color:", @trait_color_dict[d[0]]) - return @trait_color_dict[d[0]] - ) - .select("title") - .text((d) => - return d[1] - ) - @draw_legend() - else - @svg.selectAll(".bar") - .data(@sorted_samples()) - .transition() - .duration(1000) - .style("fill", (d) => - console.log("this color:", @trait_color_dict[d[0]]) - return @trait_color_dict[d[0]] - ) - .select("title") - .text((d) => - return d[1] - ) - @draw_legend() + # TODO trim_values: (trait_sample_data) -> trimmed_samples = {} @@ -488,4 +272,4 @@ class Bar_Chart #for sample in samples # if samples[sample] in distinct_values -root.Bar_Chart = Bar_Chart
\ No newline at end of file +root.Bar_Chart = Bar_Chart diff --git a/wqflask/wqflask/static/new/javascript/bar_chart.js b/wqflask/wqflask/static/new/javascript/bar_chart.js index f5ed544b..0ab50b4e 100755 --- a/wqflask/wqflask/static/new/javascript/bar_chart.js +++ b/wqflask/wqflask/static/new/javascript/bar_chart.js @@ -1,494 +1,433 @@ -// Generated by CoffeeScript 1.8.0 -var Bar_Chart, root, - __hasProp = {}.hasOwnProperty, - __indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }; +// Generated by CoffeeScript 1.9.2 +(function() { + var Bar_Chart, root, + bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }, + indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }, + hasProp = {}.hasOwnProperty; -root = typeof exports !== "undefined" && exports !== null ? exports : this; + root = typeof exports !== "undefined" && exports !== null ? exports : this; -Bar_Chart = (function() { - function Bar_Chart(sample_list) { - var longest_sample_name, sample; - this.sample_list = sample_list; - this.sort_by = "name"; - this.get_samples(); - console.log("sample names:", this.sample_names); - if (this.sample_attr_vals.length > 0) { - this.get_distinct_attr_vals(); - this.get_attr_color_dict(this.distinct_attr_vals); - } - longest_sample_name = d3.max((function() { - var _i, _len, _ref, _results; - _ref = this.sample_names; - _results = []; - for (_i = 0, _len = _ref.length; _i < _len; _i++) { - sample = _ref[_i]; - _results.push(sample.length); - } - return _results; - }).call(this)); - this.margin = { - top: 20, - right: 20, - bottom: longest_sample_name * 7, - left: 40 - }; - this.plot_width = this.sample_vals.length * 20 - this.margin.left - this.margin.right; - this.range = this.sample_vals.length * 20; - this.plot_height = 500 - this.margin.top - this.margin.bottom; - this.x_buffer = this.plot_width / 20; - this.y_buffer = this.plot_height / 20; - this.y_min = d3.min(this.sample_vals); - this.y_max = d3.max(this.sample_vals) * 1.1; - this.svg = this.create_svg(); - this.plot_height -= this.y_buffer; - this.create_scales(); - this.create_graph(); - d3.select("#color_attribute").on("change", (function(_this) { - return function() { - _this.attribute = $("#color_attribute").val(); - console.log("attr_color_dict:", _this.attr_color_dict); - if (_this.sort_by = "name") { - _this.svg.selectAll(".bar").data(_this.sorted_samples()).transition().duration(1000).style("fill", function(d) { - if (_this.attribute === "None") { - return "steelblue"; - } else { - return _this.attr_color_dict[_this.attribute][d[2][_this.attribute]]; - } - }).select("title").text(function(d) { - return d[1]; - }); - } else { - _this.svg.selectAll(".bar").data(_this.samples).transition().duration(1000).style("fill", function(d) { - if (_this.attribute === "None") { - return "steelblue"; - } else { - return _this.attr_color_dict[_this.attribute][d[2][_this.attribute]]; - } - }); + Bar_Chart = (function() { + function Bar_Chart(sample_lists) { + this.add_legend_discrete = bind(this.add_legend_discrete, this); + this.add_legend_continuous = bind(this.add_legend_continuous, this); + this.remove_legend = bind(this.remove_legend, this); + this.add_legend = bind(this.add_legend, this); + var key, l1, l1_names, l2, l3, longest_sample_name_len, s, sample, x; + this.sample_lists = {}; + l1 = this.sample_lists['samples_primary'] = sample_lists[0] || []; + l2 = this.sample_lists['samples_other'] = sample_lists[1] || []; + l1_names = (function() { + var j, len, results; + results = []; + for (j = 0, len = l1.length; j < len; j++) { + x = l1[j]; + results.push(x.name); } - _this.draw_legend(); - return _this.add_legend(_this.attribute, _this.distinct_attr_vals[_this.attribute]); - }; - })(this)); - $(".sort_by_value").on("click", (function(_this) { - return function() { - console.log("sorting by value"); - _this.sort_by = "value"; - if (_this.attributes.length > 0) { - _this.attribute = $("#color_attribute").val(); + return results; + })(); + l3 = l1.concat((function() { + var j, len, ref, results; + results = []; + for (j = 0, len = l2.length; j < len; j++) { + x = l2[j]; + if (ref = x.name, indexOf.call(l1_names, ref) < 0) { + results.push(x); + } } - return _this.rebuild_bar_graph(_this.sorted_samples()); - }; - })(this)); - $(".sort_by_name").on("click", (function(_this) { - return function() { - console.log("sorting by name"); - _this.sort_by = "name"; - if (_this.attributes.length > 0) { - _this.attribute = $("#color_attribute").val(); + return results; + })()); + this.sample_lists['samples_all'] = l3; + longest_sample_name_len = d3.max((function() { + var j, len, results; + results = []; + for (j = 0, len = l3.length; j < len; j++) { + sample = l3[j]; + results.push(sample.name.length); } - return _this.rebuild_bar_graph(_this.samples); + return results; + })()); + this.margin = { + top: 20, + right: 20, + bottom: longest_sample_name_len * 6, + left: 40 }; - })(this)); - d3.select("#color_by_trait").on("click", (function(_this) { - return function() { - return _this.open_trait_selection(); - }; - })(this)); - } - - Bar_Chart.prototype.rebuild_bar_graph = function(samples) { - var sample, sample_names, x_scale; - console.log("samples:", samples); - this.svg.selectAll(".bar").data(samples).transition().duration(1000).style("fill", (function(_this) { - return function(d) { - if (_this.attributes.length === 0 && (_this.trait_color_dict != null)) { - console.log("SAMPLE:", d[0]); - console.log("CHECKING:", _this.trait_color_dict[d[0]]); - return _this.trait_color_dict[d[0]]; - } else if (_this.attributes.length > 0 && _this.attribute !== "None") { - console.log("@attribute:", _this.attribute); - console.log("d[2]", d[2]); - console.log("the_color:", _this.attr_color_dict[_this.attribute][d[2][_this.attribute]]); - return _this.attr_color_dict[_this.attribute][d[2][_this.attribute]]; - } else { - return "steelblue"; - } - }; - })(this)).attr("y", (function(_this) { - return function(d) { - return _this.y_scale(d[1]); - }; - })(this)).attr("height", (function(_this) { - return function(d) { - return _this.plot_height - _this.y_scale(d[1]); - }; - })(this)).select("title").text((function(_this) { - return function(d) { - return d[1]; - }; - })(this)); - sample_names = (function() { - var _i, _len, _results; - _results = []; - for (_i = 0, _len = samples.length; _i < _len; _i++) { - sample = samples[_i]; - _results.push(sample[0]); - } - return _results; - })(); - console.log("sample_names2:", sample_names); - x_scale = d3.scale.ordinal().domain(sample_names).rangeRoundBands([0, this.range], 0.1, 0); - $('.bar_chart').find('.x.axis').remove(); - return this.add_x_axis(x_scale); - }; - - Bar_Chart.prototype.get_attr_color_dict = function(vals) { - var color, color_range, distinct_vals, i, key, this_color_dict, value, _i, _j, _len, _len1, _results; - this.attr_color_dict = {}; - console.log("vals:", vals); - _results = []; - for (key in vals) { - if (!__hasProp.call(vals, key)) continue; - distinct_vals = vals[key]; - this.min_val = d3.min(distinct_vals); - this.max_val = d3.max(distinct_vals); - this_color_dict = {}; - if (distinct_vals.length < 10) { - color = d3.scale.category10(); - for (i = _i = 0, _len = distinct_vals.length; _i < _len; i = ++_i) { - value = distinct_vals[i]; - this_color_dict[value] = color(i); + this.attributes = (function() { + var results; + results = []; + for (key in sample_lists[0][0]["extra_attributes"]) { + results.push(key); } - } else { - console.log("distinct_values:", distinct_vals); - if (_.every(distinct_vals, (function(_this) { - return function(d) { - if (isNaN(d)) { - return false; - } else { - return true; - } - }; - })(this))) { - color_range = d3.scale.linear().domain([min_val, max_val]).range([0, 255]); - for (i = _j = 0, _len1 = distinct_vals.length; _j < _len1; i = ++_j) { - value = distinct_vals[i]; - console.log("color_range(value):", parseInt(color_range(value))); - this_color_dict[value] = d3.rgb(parseInt(color_range(value)), 0, 0); + return results; + })(); + this.sample_attr_vals = (function() { + var j, len, ref, results; + ref = this.sample_lists['samples_all']; + results = []; + for (j = 0, len = ref.length; j < len; j++) { + s = ref[j]; + if (s.value !== null) { + results.push(this.extra(s)); } } - } - _results.push(this.attr_color_dict[key] = this_color_dict); + return results; + }).call(this); + this.get_distinct_attr_vals(); + this.get_attr_color_dict(this.distinct_attr_vals); + this.attribute_name = "None"; + this.sort_by = "name"; + this.chart = null; + this.select_attribute_box = $("#color_attribute"); + d3.select("#color_attribute").on("change", (function(_this) { + return function() { + _this.attribute_name = _this.select_attribute_box.val(); + return _this.rebuild_bar_graph(); + }; + })(this)); + $(".sort_by_value").on("click", (function(_this) { + return function() { + console.log("sorting by value"); + _this.sort_by = "value"; + return _this.rebuild_bar_graph(); + }; + })(this)); + $(".sort_by_name").on("click", (function(_this) { + return function() { + console.log("sorting by name"); + _this.sort_by = "name"; + return _this.rebuild_bar_graph(); + }; + })(this)); + d3.select("#color_by_trait").on("click", (function(_this) { + return function() { + return _this.open_trait_selection(); + }; + })(this)); } - return _results; - }; - Bar_Chart.prototype.draw_legend = function() { - var svg_html; - $('#legend-left').html(this.min_val); - $('#legend-right').html(this.max_val); - svg_html = '<svg height="10" width="90"> <rect x="0" width="15" height="10" style="fill: rgb(0, 0, 0);"></rect> <rect x="15" width="15" height="10" style="fill: rgb(50, 0, 0);"></rect> <rect x="30" width="15" height="10" style="fill: rgb(100, 0, 0);"></rect> <rect x="45" width="15" height="10" style="fill: rgb(150, 0, 0);"></rect> <rect x="60" width="15" height="10" style="fill: rgb(200, 0, 0);"></rect> <rect x="75" width="15" height="10" style="fill: rgb(255, 0, 0);"></rect> </svg>'; - console.log("svg_html:", svg_html); - return $('#legend-colors').html(svg_html); - }; - - Bar_Chart.prototype.get_trait_color_dict = function(samples, vals) { - var color, color_range, distinct_vals, i, key, sample, this_color_dict, value, _i, _j, _len, _len1, _results; - this.trait_color_dict = {}; - console.log("vals:", vals); - for (key in vals) { - if (!__hasProp.call(vals, key)) continue; - distinct_vals = vals[key]; - this_color_dict = {}; - this.min_val = d3.min(distinct_vals); - this.max_val = d3.max(distinct_vals); - if (distinct_vals.length < 10) { - color = d3.scale.category10(); - for (i = _i = 0, _len = distinct_vals.length; _i < _len; i = ++_i) { - value = distinct_vals[i]; - this_color_dict[value] = color(i); - } - } else { - console.log("distinct_values:", distinct_vals); - if (_.every(distinct_vals, (function(_this) { - return function(d) { - if (isNaN(d)) { - return false; - } else { - return true; - } - }; - })(this))) { - color_range = d3.scale.linear().domain([d3.min(distinct_vals), d3.max(distinct_vals)]).range([0, 255]); - for (i = _j = 0, _len1 = distinct_vals.length; _j < _len1; i = ++_j) { - value = distinct_vals[i]; - console.log("color_range(value):", parseInt(color_range(value))); - this_color_dict[value] = d3.rgb(parseInt(color_range(value)), 0, 0); - } - } - } - } - _results = []; - for (sample in samples) { - if (!__hasProp.call(samples, sample)) continue; - value = samples[sample]; - _results.push(this.trait_color_dict[sample] = this_color_dict[value]); - } - return _results; - }; + Bar_Chart.prototype.value = function(sample) { + return this.value_dict[sample.name].value; + }; - Bar_Chart.prototype.convert_into_colors = function(values) { - var color_range, i, value, _i, _len, _results; - color_range = d3.scale.linear().domain([d3.min(values), d3.max(values)]).range([0, 255]); - _results = []; - for (i = _i = 0, _len = values.length; _i < _len; i = ++_i) { - value = values[i]; - console.log("color_range(value):", color_range(parseInt(value))); - _results.push(this_color_dict[value] = d3.rgb(color_range(parseInt(value)), 0, 0)); - } - return _results; - }; + Bar_Chart.prototype.variance = function(sample) { + return this.value_dict[sample.name].variance; + }; - Bar_Chart.prototype.get_samples = function() { - var attr_vals, attribute, key, sample, _i, _j, _len, _len1, _ref, _ref1; - this.sample_names = (function() { - var _i, _len, _ref, _results; - _ref = this.sample_list; - _results = []; - for (_i = 0, _len = _ref.length; _i < _len; _i++) { - sample = _ref[_i]; - if (sample.value !== null) { - _results.push(sample.name); - } + Bar_Chart.prototype.extra = function(sample) { + var attr_vals, attribute, j, len, ref; + attr_vals = {}; + ref = this.attributes; + for (j = 0, len = ref.length; j < len; j++) { + attribute = ref[j]; + attr_vals[attribute] = sample["extra_attributes"][attribute]; } - return _results; - }).call(this); - this.sample_vals = (function() { - var _i, _len, _ref, _results; - _ref = this.sample_list; - _results = []; - for (_i = 0, _len = _ref.length; _i < _len; _i++) { - sample = _ref[_i]; - if (sample.value !== null) { - _results.push(sample.value); + return attr_vals; + }; + + Bar_Chart.prototype.redraw = function(samples_dict, selected_group) { + var x; + this.value_dict = samples_dict[selected_group]; + this.raw_data = (function() { + var j, len, ref, results; + ref = this.sample_lists[selected_group]; + results = []; + for (j = 0, len = ref.length; j < len; j++) { + x = ref[j]; + if (x.name in this.value_dict && this.value(x) !== null) { + results.push(x); + } } + return results; + }).call(this); + return this.rebuild_bar_graph(); + }; + + Bar_Chart.prototype.rebuild_bar_graph = function() { + var container, h, raw_data; + raw_data = this.raw_data.slice(); + if (this.sort_by === 'value') { + raw_data = raw_data.sort((function(_this) { + return function(x, y) { + return _this.value(x) - _this.value(y); + }; + })(this)); } - return _results; - }).call(this); - this.attributes = (function() { - var _results; - _results = []; - for (key in this.sample_list[0]["extra_attributes"]) { - _results.push(key); - } - return _results; - }).call(this); - console.log("attributes:", this.attributes); - this.sample_attr_vals = []; - if (this.attributes.length > 0) { - _ref = this.sample_list; - for (_i = 0, _len = _ref.length; _i < _len; _i++) { - sample = _ref[_i]; - attr_vals = {}; - _ref1 = this.attributes; - for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) { - attribute = _ref1[_j]; - attr_vals[attribute] = sample["extra_attributes"][attribute]; - } - this.sample_attr_vals.push(attr_vals); + console.log("raw_data: ", raw_data); + h = 600; + container = $("#bar_chart_container"); + container.height(h + this.margin.top + this.margin.bottom); + if (this.chart === null) { + this.chart = nv.models.multiBarChart().height(h).errorBarColor((function(_this) { + return function() { + return 'red'; + }; + })(this)).reduceXTicks(false).staggerLabels(false).showControls(false).showLegend(false); + this.chart.multibar.dispatch.on('elementMouseover.tooltip', (function(_this) { + return function(evt) { + var k, ref, v; + evt.value = _this.chart.x()(evt.data); + evt['series'] = [ + { + key: 'Value', + value: evt.data.y, + color: evt.color + } + ]; + if (evt.data.yErr) { + evt['series'].push({ + key: 'SE', + value: evt.data.yErr + }); + } + if (evt.data.attr) { + ref = evt.data.attr; + for (k in ref) { + v = ref[k]; + evt['series'].push({ + key: k, + value: v + }); + } + } + return _this.chart.tooltip.data(evt).hidden(false); + }; + })(this)); + this.chart.tooltip.valueFormatter(function(d, i) { + return d; + }); } - } - return this.samples = _.zip(this.sample_names, this.sample_vals, this.sample_attr_vals); - }; + return nv.addGraph((function(_this) { + return function() { + var d, s, values; + _this.remove_legend(); + values = (function() { + var j, len, results; + results = []; + for (j = 0, len = raw_data.length; j < len; j++) { + s = raw_data[j]; + results.push({ + x: s.name, + y: this.value(s), + yErr: this.variance(s) || 0, + attr: s.extra_attributes + }); + } + return results; + }).call(_this); + if (_this.attribute_name !== "None") { + _this.color_dict = _this.attr_color_dict[_this.attribute_name]; + _this.chart.barColor(function(d) { + return _this.color_dict[d.attr[_this.attribute_name]]; + }); + _this.add_legend(); + } else { + _this.chart.barColor(function() { + return 'steelblue'; + }); + } + _this.chart.width(raw_data.length * 20); + _this.chart.yDomain([ + 0.9 * _.min((function() { + var j, len, results; + results = []; + for (j = 0, len = values.length; j < len; j++) { + d = values[j]; + results.push(d.y - 1.5 * d.yErr); + } + return results; + })()), 1.05 * _.max((function() { + var j, len, results; + results = []; + for (j = 0, len = values.length; j < len; j++) { + d = values[j]; + results.push(d.y + 1.5 * d.yErr); + } + return results; + })()) + ]); + console.log("values: ", values); + d3.select("#bar_chart_container svg").datum([ + { + values: values + } + ]).style('width', raw_data.length * 20 + 'px').transition().duration(1000).call(_this.chart); + d3.select("#bar_chart_container .nv-x").selectAll('.tick text').style("font-size", "12px").style("text-anchor", "end").attr("dx", "-.8em").attr("dy", "-.3em").attr("transform", function(d) { + return "rotate(-90)"; + }); + return _this.chart; + }; + })(this)); + }; - Bar_Chart.prototype.get_distinct_attr_vals = function() { - var attribute, sample, _i, _len, _ref, _results; - this.distinct_attr_vals = {}; - _ref = this.sample_attr_vals; - _results = []; - for (_i = 0, _len = _ref.length; _i < _len; _i++) { - sample = _ref[_i]; - _results.push((function() { - var _ref1, _results1; - _results1 = []; - for (attribute in sample) { - if (!this.distinct_attr_vals[attribute]) { - this.distinct_attr_vals[attribute] = []; + Bar_Chart.prototype.get_attr_color_dict = function(vals) { + var color, color_range, discrete, distinct_vals, i, j, key, l, len, len1, results, this_color_dict, value; + this.attr_color_dict = {}; + this.is_discrete = {}; + this.minimum_values = {}; + this.maximum_values = {}; + console.log("vals:", vals); + results = []; + for (key in vals) { + if (!hasProp.call(vals, key)) continue; + distinct_vals = vals[key]; + this.min_val = d3.min(distinct_vals); + this.max_val = d3.max(distinct_vals); + this_color_dict = {}; + discrete = distinct_vals.length < 10; + if (discrete) { + color = d3.scale.category10(); + for (i = j = 0, len = distinct_vals.length; j < len; i = ++j) { + value = distinct_vals[i]; + this_color_dict[value] = color(i); } - if (_ref1 = sample[attribute], __indexOf.call(this.distinct_attr_vals[attribute], _ref1) < 0) { - _results1.push(this.distinct_attr_vals[attribute].push(sample[attribute])); - } else { - _results1.push(void 0); + } else { + console.log("distinct_values:", distinct_vals); + if (_.every(distinct_vals, (function(_this) { + return function(d) { + if (isNaN(d)) { + return false; + } else { + return true; + } + }; + })(this))) { + color_range = d3.scale.linear().domain([this.min_val, this.max_val]).range([0, 255]); + for (i = l = 0, len1 = distinct_vals.length; l < len1; i = ++l) { + value = distinct_vals[i]; + console.log("color_range(value):", parseInt(color_range(value))); + this_color_dict[value] = d3.rgb(parseInt(color_range(value)), 0, 0); + } } } - return _results1; - }).call(this)); - } - return _results; - }; - - Bar_Chart.prototype.create_svg = function() { - var svg; - svg = d3.select("#bar_chart").append("svg").attr("class", "bar_chart").attr("width", this.plot_width + this.margin.left + this.margin.right).attr("height", this.plot_height + this.margin.top + this.margin.bottom).append("g").attr("transform", "translate(" + this.margin.left + "," + this.margin.top + ")"); - return svg; - }; - - Bar_Chart.prototype.create_scales = function() { - this.x_scale = d3.scale.ordinal().domain(this.sample_names).rangeRoundBands([0, this.range], 0.1, 0); - return this.y_scale = d3.scale.linear().domain([this.y_min * 0.75, this.y_max]).range([this.plot_height, this.y_buffer]); - }; - - Bar_Chart.prototype.create_graph = function() { - this.add_x_axis(this.x_scale); - this.add_y_axis(); - return this.add_bars(); - }; - - Bar_Chart.prototype.add_x_axis = function(scale) { - var xAxis; - xAxis = d3.svg.axis().scale(scale).orient("bottom"); - return this.svg.append("g").attr("class", "x axis").attr("transform", "translate(0," + this.plot_height + ")").call(xAxis).selectAll("text").style("text-anchor", "end").style("font-size", "12px").attr("dx", "-.8em").attr("dy", "-.3em").attr("transform", (function(_this) { - return function(d) { - return "rotate(-90)"; - }; - })(this)); - }; - - Bar_Chart.prototype.add_y_axis = function() { - var yAxis; - yAxis = d3.svg.axis().scale(this.y_scale).orient("left").ticks(5); - return this.svg.append("g").attr("class", "y axis").call(yAxis).append("text").attr("transform", "rotate(-90)").attr("y", 6).attr("dy", ".71em").style("text-anchor", "end"); - }; + this.attr_color_dict[key] = this_color_dict; + this.is_discrete[key] = discrete; + this.minimum_values[key] = this.min_val; + results.push(this.maximum_values[key] = this.max_val); + } + return results; + }; - Bar_Chart.prototype.add_bars = function() { - return this.svg.selectAll(".bar").data(this.samples).enter().append("rect").style("fill", "steelblue").attr("class", "bar").attr("x", (function(_this) { - return function(d) { - return _this.x_scale(d[0]); - }; - })(this)).attr("width", this.x_scale.rangeBand()).attr("y", (function(_this) { - return function(d) { - return _this.y_scale(d[1]); - }; - })(this)).attr("height", (function(_this) { - return function(d) { - return _this.plot_height - _this.y_scale(d[1]); - }; - })(this)).append("svg:title").text((function(_this) { - return function(d) { - return d[1]; - }; - })(this)); - }; + Bar_Chart.prototype.get_distinct_attr_vals = function() { + var attribute, j, len, ref, results, sample; + this.distinct_attr_vals = {}; + ref = this.sample_attr_vals; + results = []; + for (j = 0, len = ref.length; j < len; j++) { + sample = ref[j]; + results.push((function() { + var ref1, results1; + results1 = []; + for (attribute in sample) { + if (!this.distinct_attr_vals[attribute]) { + this.distinct_attr_vals[attribute] = []; + } + if (ref1 = sample[attribute], indexOf.call(this.distinct_attr_vals[attribute], ref1) < 0) { + results1.push(this.distinct_attr_vals[attribute].push(sample[attribute])); + } else { + results1.push(void 0); + } + } + return results1; + }).call(this)); + } + return results; + }; - Bar_Chart.prototype.sorted_samples = function() { - var sample_list, sorted; - sample_list = _.zip(this.sample_names, this.sample_vals, this.sample_attr_vals); - sorted = _.sortBy(sample_list, (function(_this) { - return function(sample) { - return sample[1]; - }; - })(this)); - console.log("sorted:", sorted); - return sorted; - }; + Bar_Chart.prototype.add_legend = function() { + if (this.is_discrete[this.attribute_name]) { + return this.add_legend_discrete(); + } else { + return this.add_legend_continuous(); + } + }; - Bar_Chart.prototype.add_legend = function(attribute, distinct_vals) { - var legend, legend_rect, legend_text; - legend = this.svg.append("g").attr("class", "legend").attr("height", 100).attr("width", 100).attr('transform', 'translate(-20,50)'); - legend_rect = legend.selectAll('rect').data(distinct_vals).enter().append("rect").attr("x", this.plot_width - 65).attr("width", 10).attr("height", 10).attr("y", (function(_this) { - return function(d, i) { - return i * 20; - }; - })(this)).style("fill", (function(_this) { - return function(d) { - console.log("TEST:", _this.attr_color_dict[attribute][d]); - return _this.attr_color_dict[attribute][d]; - }; - })(this)); - return legend_text = legend.selectAll('text').data(distinct_vals).enter().append("text").attr("x", this.plot_width - 52).attr("y", (function(_this) { - return function(d, i) { - return i * 20 + 9; - }; - })(this)).text((function(_this) { - return function(d) { - return d; - }; - })(this)); - }; + Bar_Chart.prototype.remove_legend = function() { + $(".legend").remove(); + return $("#legend-left,#legend-right,#legend-colors").empty(); + }; - Bar_Chart.prototype.open_trait_selection = function() { - return $('#collections_holder').load('/collections/list?color_by_trait #collections_list', (function(_this) { - return function() { - $.colorbox({ - inline: true, - href: "#collections_holder" - }); - return $('a.collection_name').attr('onClick', 'return false'); - }; - })(this)); - }; + Bar_Chart.prototype.add_legend_continuous = function() { + var svg_html; + $('#legend-left').html(this.minimum_values[this.attribute_name]); + $('#legend-right').html(this.maximum_values[this.attribute_name]); + svg_html = '<svg height="15" width="120"> <rect x="0" width="20" height="15" style="fill: rgb(0, 0, 0);"></rect> <rect x="20" width="20" height="15" style="fill: rgb(50, 0, 0);"></rect> <rect x="40" width="20" height="15" style="fill: rgb(100, 0, 0);"></rect> <rect x="60" width="20" height="15" style="fill: rgb(150, 0, 0);"></rect> <rect x="80" width="20" height="15" style="fill: rgb(200, 0, 0);"></rect> <rect x="100" width="20" height="15" style="fill: rgb(255, 0, 0);"></rect> </svg>'; + console.log("svg_html:", svg_html); + return $('#legend-colors').html(svg_html); + }; - Bar_Chart.prototype.color_by_trait = function(trait_sample_data) { - var distinct_values, trimmed_samples; - console.log("BXD1:", trait_sample_data["BXD1"]); - console.log("trait_sample_data:", trait_sample_data); - trimmed_samples = this.trim_values(trait_sample_data); - distinct_values = {}; - distinct_values["collection_trait"] = this.get_distinct_values(trimmed_samples); - this.get_trait_color_dict(trimmed_samples, distinct_values); - console.log("TRAIT_COLOR_DICT:", this.trait_color_dict); - console.log("SAMPLES:", this.samples); - if (this.sort_by = "value") { - this.svg.selectAll(".bar").data(this.samples).transition().duration(1000).style("fill", (function(_this) { + Bar_Chart.prototype.add_legend_discrete = function() { + var legend_span; + legend_span = d3.select('#bar_chart_legend').append('div').style('word-wrap', 'break-word').attr('class', 'legend').selectAll('span').data(this.distinct_attr_vals[this.attribute_name]).enter().append('span').style({ + 'word-wrap': 'normal', + 'display': 'inline-block' + }); + legend_span.append('span').style("background-color", (function(_this) { return function(d) { - console.log("this color:", _this.trait_color_dict[d[0]]); - return _this.trait_color_dict[d[0]]; + return _this.attr_color_dict[_this.attribute_name][d]; }; - })(this)).select("title").text((function(_this) { + })(this)).style({ + 'display': 'inline-block', + 'width': '15px', + 'height': '15px', + 'margin': '0px 5px 0px 15px' + }); + return legend_span.append('span').text((function(_this) { return function(d) { - return d[1]; + return d; }; - })(this)); - return this.draw_legend(); - } else { - this.svg.selectAll(".bar").data(this.sorted_samples()).transition().duration(1000).style("fill", (function(_this) { - return function(d) { - console.log("this color:", _this.trait_color_dict[d[0]]); - return _this.trait_color_dict[d[0]]; - }; - })(this)).select("title").text((function(_this) { - return function(d) { - return d[1]; + })(this)).style('font-size', '20px'); + }; + + Bar_Chart.prototype.open_trait_selection = function() { + return $('#collections_holder').load('/collections/list?color_by_trait #collections_list', (function(_this) { + return function() { + $.colorbox({ + inline: true, + href: "#collections_holder" + }); + return $('a.collection_name').attr('onClick', 'return false'); }; })(this)); - return this.draw_legend(); - } - }; + }; + + Bar_Chart.prototype.color_by_trait = function(trait_sample_data) { + var distinct_values, trimmed_samples; + console.log("BXD1:", trait_sample_data["BXD1"]); + console.log("trait_sample_data:", trait_sample_data); + trimmed_samples = this.trim_values(trait_sample_data); + distinct_values = {}; + distinct_values["collection_trait"] = this.get_distinct_values(trimmed_samples); + this.get_trait_color_dict(trimmed_samples, distinct_values); + console.log("TRAIT_COLOR_DICT:", this.trait_color_dict); + return console.log("SAMPLES:", this.samples); + }; - Bar_Chart.prototype.trim_values = function(trait_sample_data) { - var sample, trimmed_samples, _i, _len, _ref; - trimmed_samples = {}; - _ref = this.sample_names; - for (_i = 0, _len = _ref.length; _i < _len; _i++) { - sample = _ref[_i]; - if (sample in trait_sample_data) { - trimmed_samples[sample] = trait_sample_data[sample]; + Bar_Chart.prototype.trim_values = function(trait_sample_data) { + var j, len, ref, sample, trimmed_samples; + trimmed_samples = {}; + ref = this.sample_names; + for (j = 0, len = ref.length; j < len; j++) { + sample = ref[j]; + if (sample in trait_sample_data) { + trimmed_samples[sample] = trait_sample_data[sample]; + } } - } - console.log("trimmed_samples:", trimmed_samples); - return trimmed_samples; - }; + console.log("trimmed_samples:", trimmed_samples); + return trimmed_samples; + }; + + Bar_Chart.prototype.get_distinct_values = function(samples) { + var distinct_values; + distinct_values = _.uniq(_.values(samples)); + console.log("distinct_values:", distinct_values); + return distinct_values; + }; - Bar_Chart.prototype.get_distinct_values = function(samples) { - var distinct_values; - distinct_values = _.uniq(_.values(samples)); - console.log("distinct_values:", distinct_values); - return distinct_values; - }; + return Bar_Chart; - return Bar_Chart; + })(); -})(); + root.Bar_Chart = Bar_Chart; -root.Bar_Chart = Bar_Chart; +}).call(this); diff --git a/wqflask/wqflask/static/new/javascript/chr_lod_chart.coffee b/wqflask/wqflask/static/new/javascript/chr_lod_chart.coffee index 321957b3..e00694be 100644 --- a/wqflask/wqflask/static/new/javascript/chr_lod_chart.coffee +++ b/wqflask/wqflask/static/new/javascript/chr_lod_chart.coffee @@ -193,18 +193,7 @@ class Chr_Lod_Chart .attr("fill", "black")
add_back_button: () ->
- @svg.append("text")
- .attr("class", "back")
- .text("Return to full view")
- .attr("x", @x_buffer*2)
- .attr("y", @y_buffer/2)
- .attr("dx", "0em")
- .attr("text-anchor", "middle")
- .attr("font-family", "sans-serif")
- .attr("font-size", "18px")
- .attr("cursor", "pointer")
- .attr("fill", "black")
- .on("click", @return_to_full_view)
+ $("#return_to_full_view").show().click => @return_to_full_view()
add_path: () ->
line_function = d3.svg.line()
@@ -281,9 +270,10 @@ class Chr_Lod_Chart )
return_to_full_view: () ->
+ $("#return_to_full_view").hide()
$('#topchart').remove()
$('#chart_container').append('<div class="qtlcharts" id="topchart"></div>')
- create_manhattan_plot()
+ create_lod_chart()
show_marker_in_table: (marker_info) ->
console.log("in show_marker_in_table")
diff --git a/wqflask/wqflask/static/new/javascript/chr_lod_chart.js b/wqflask/wqflask/static/new/javascript/chr_lod_chart.js index 95dbb4e2..c060d9d7 100644 --- a/wqflask/wqflask/static/new/javascript/chr_lod_chart.js +++ b/wqflask/wqflask/static/new/javascript/chr_lod_chart.js @@ -1,4 +1,4 @@ -// Generated by CoffeeScript 1.8.0 +// Generated by CoffeeScript 1.9.2 var Chr_Lod_Chart; Chr_Lod_Chart = (function() { @@ -38,28 +38,28 @@ Chr_Lod_Chart = (function() { } Chr_Lod_Chart.prototype.get_max_chr = function() { - var key, _results; + var key, results; this.max_chr = 0; - _results = []; + results = []; for (key in js_data.chromosomes) { console.log("key is:", key); if (parseInt(key) > this.max_chr) { - _results.push(this.max_chr = parseInt(key)); + results.push(this.max_chr = parseInt(key)); } else { - _results.push(void 0); + results.push(void 0); } } - return _results; + return results; }; Chr_Lod_Chart.prototype.filter_qtl_results = function() { - var result, this_chr, _i, _len, _ref, _results; + var i, len, ref, result, results, this_chr; this.these_results = []; this_chr = 100; - _ref = this.qtl_results; - _results = []; - for (_i = 0, _len = _ref.length; _i < _len; _i++) { - result = _ref[_i]; + ref = this.qtl_results; + results = []; + for (i = 0, len = ref.length; i < len; i++) { + result = ref[i]; if (result.chr === "X") { this_chr = this.max_chr; } else { @@ -71,20 +71,20 @@ Chr_Lod_Chart = (function() { break; } if (parseInt(this_chr) === parseInt(this.chr[0])) { - _results.push(this.these_results.push(result)); + results.push(this.these_results.push(result)); } else { - _results.push(void 0); + results.push(void 0); } } - return _results; + return results; }; Chr_Lod_Chart.prototype.get_qtl_count = function() { - var high_qtl_count, result, _i, _len, _ref; + var high_qtl_count, i, len, ref, result; high_qtl_count = 0; - _ref = this.these_results; - for (_i = 0, _len = _ref.length; _i < _len; _i++) { - result = _ref[_i]; + ref = this.these_results; + for (i = 0, len = ref.length; i < len; i++) { + result = ref[i]; if (result.lod_score > 1) { high_qtl_count += 1; } @@ -94,16 +94,16 @@ Chr_Lod_Chart = (function() { }; Chr_Lod_Chart.prototype.create_coordinates = function() { - var result, _i, _len, _ref, _results; - _ref = this.these_results; - _results = []; - for (_i = 0, _len = _ref.length; _i < _len; _i++) { - result = _ref[_i]; + var i, len, ref, result, results; + ref = this.these_results; + results = []; + for (i = 0, len = ref.length; i < len; i++) { + result = ref[i]; this.x_coords.push(parseFloat(result.Mb)); this.y_coords.push(result.lod_score); - _results.push(this.marker_names.push(result.name)); + results.push(this.marker_names.push(result.name)); } - return _results; + return results; }; Chr_Lod_Chart.prototype.create_svg = function() { @@ -189,7 +189,11 @@ Chr_Lod_Chart = (function() { }; Chr_Lod_Chart.prototype.add_back_button = function() { - return this.svg.append("text").attr("class", "back").text("Return to full view").attr("x", this.x_buffer * 2).attr("y", this.y_buffer / 2).attr("dx", "0em").attr("text-anchor", "middle").attr("font-family", "sans-serif").attr("font-size", "18px").attr("cursor", "pointer").attr("fill", "black").on("click", this.return_to_full_view); + return $("#return_to_full_view").show().click((function(_this) { + return function() { + return _this.return_to_full_view(); + }; + })(this)); }; Chr_Lod_Chart.prototype.add_path = function() { @@ -253,9 +257,10 @@ Chr_Lod_Chart = (function() { }; Chr_Lod_Chart.prototype.return_to_full_view = function() { + $("#return_to_full_view").hide(); $('#topchart').remove(); $('#chart_container').append('<div class="qtlcharts" id="topchart"></div>'); - return create_manhattan_plot(); + return create_lod_chart(); }; Chr_Lod_Chart.prototype.show_marker_in_table = function(marker_info) { diff --git a/wqflask/wqflask/static/new/javascript/create_lodchart.coffee b/wqflask/wqflask/static/new/javascript/create_lodchart.coffee index 76be5490..472ec12d 100644 --- a/wqflask/wqflask/static/new/javascript/create_lodchart.coffee +++ b/wqflask/wqflask/static/new/javascript/create_lodchart.coffee @@ -1,4 +1,4 @@ -create_manhattan_plot = -> +create_lod_chart = -> h = 500 w = 1200 margin = {left:60, top:40, right:40, bottom: 40, inner:5} @@ -18,7 +18,7 @@ create_manhattan_plot = -> .height(h) .width(w) .margin(margin) - .ylab("LOD score") + .ylab(js_data.result_score_type + " score") .manhattanPlot(js_data.manhattan_plot) #.additive(additive) @@ -44,44 +44,11 @@ create_manhattan_plot = -> d3.select(this) .transition().duration(500).attr("r", r*3) .transition().duration(500).attr("r", r) - -create_manhattan_plot() -$("#export").click => - #Get d3 SVG element - svg = $("#topchart").find("svg")[0] - - #Extract SVG text string - svg_xml = (new XMLSerializer).serializeToString(svg) - console.log("svg_xml:", svg_xml) - - #Set filename - filename = "manhattan_plot_" + js_data.this_trait - - #Make a form with the SVG data - form = $("#exportform") - form.find("#data").val(svg_xml) - form.find("#filename").val(filename) - form.submit() - -$("#export_pdf").click => - - #$('#topchart').remove() - #$('#chart_container').append('<div class="qtlcharts" id="topchart"></div>') - #create_interval_map() - - #Get d3 SVG element - svg = $("#topchart").find("svg")[0] - - #Extract SVG text string - svg_xml = (new XMLSerializer).serializeToString(svg) - console.log("svg_xml:", svg_xml) - - #Set filename - filename = "manhattan_plot_" + js_data.this_trait +$ -> + #window.setTimeout ( -> + # console.log(js_data) + #), 1000 + #window.setTimeout(create_lod_chart(), 1000) + root.create_lod_chart = create_lod_chart - #Make a form with the SVG data - form = $("#exportpdfform") - form.find("#data").val(svg_xml) - form.find("#filename").val(filename) - form.submit() diff --git a/wqflask/wqflask/static/new/javascript/create_lodchart.js b/wqflask/wqflask/static/new/javascript/create_lodchart.js index f61f05bd..2537e9e2 100644 --- a/wqflask/wqflask/static/new/javascript/create_lodchart.js +++ b/wqflask/wqflask/static/new/javascript/create_lodchart.js @@ -1,73 +1,50 @@ -// Generated by CoffeeScript 1.8.0 -var create_manhattan_plot; +// Generated by CoffeeScript 1.9.2 +(function() { + var create_lod_chart; -create_manhattan_plot = function() { - var additive, chrrect, data, h, halfh, margin, mychart, totalh, totalw, w; - h = 500; - w = 1200; - margin = { - left: 60, - top: 40, - right: 40, - bottom: 40, - inner: 5 - }; - halfh = h + margin.top + margin.bottom; - totalh = halfh * 2; - totalw = w + margin.left + margin.right; - if ('additive' in js_data) { - additive = js_data.additive; - } else { - additive = false; - } - console.log("js_data:", js_data); - mychart = lodchart().lodvarname("lod.hk").height(h).width(w).margin(margin).ylab("LOD score").manhattanPlot(js_data.manhattan_plot); - data = js_data.json_data; - d3.select("div#topchart").datum(data).call(mychart); - chrrect = mychart.chrSelect(); - chrrect.on("mouseover", function() { - return d3.select(this).attr("fill", "#E9CFEC"); - }).on("mouseout", function(d, i) { - return d3.select(this).attr("fill", function() { - if (i % 2) { - return "#F1F1F9"; - } - return "#FBFBFF"; + create_lod_chart = function() { + var additive, chrrect, data, h, halfh, margin, mychart, totalh, totalw, w; + h = 500; + w = 1200; + margin = { + left: 60, + top: 40, + right: 40, + bottom: 40, + inner: 5 + }; + halfh = h + margin.top + margin.bottom; + totalh = halfh * 2; + totalw = w + margin.left + margin.right; + if ('additive' in js_data) { + additive = js_data.additive; + } else { + additive = false; + } + console.log("js_data:", js_data); + mychart = lodchart().lodvarname("lod.hk").height(h).width(w).margin(margin).ylab(js_data.result_score_type + " score").manhattanPlot(js_data.manhattan_plot); + data = js_data.json_data; + d3.select("div#topchart").datum(data).call(mychart); + chrrect = mychart.chrSelect(); + chrrect.on("mouseover", function() { + return d3.select(this).attr("fill", "#E9CFEC"); + }).on("mouseout", function(d, i) { + return d3.select(this).attr("fill", function() { + if (i % 2) { + return "#F1F1F9"; + } + return "#FBFBFF"; + }); + }); + return mychart.markerSelect().on("click", function(d) { + var r; + r = d3.select(this).attr("r"); + return d3.select(this).transition().duration(500).attr("r", r * 3).transition().duration(500).attr("r", r); }); - }); - return mychart.markerSelect().on("click", function(d) { - var r; - r = d3.select(this).attr("r"); - return d3.select(this).transition().duration(500).attr("r", r * 3).transition().duration(500).attr("r", r); - }); -}; - -create_manhattan_plot(); - -$("#export").click((function(_this) { - return function() { - var filename, form, svg, svg_xml; - svg = $("#topchart").find("svg")[0]; - svg_xml = (new XMLSerializer).serializeToString(svg); - console.log("svg_xml:", svg_xml); - filename = "manhattan_plot_" + js_data.this_trait; - form = $("#exportform"); - form.find("#data").val(svg_xml); - form.find("#filename").val(filename); - return form.submit(); }; -})(this)); -$("#export_pdf").click((function(_this) { - return function() { - var filename, form, svg, svg_xml; - svg = $("#topchart").find("svg")[0]; - svg_xml = (new XMLSerializer).serializeToString(svg); - console.log("svg_xml:", svg_xml); - filename = "manhattan_plot_" + js_data.this_trait; - form = $("#exportpdfform"); - form.find("#data").val(svg_xml); - form.find("#filename").val(filename); - return form.submit(); - }; -})(this)); + $(function() { + return root.create_lod_chart = create_lod_chart; + }); + +}).call(this); diff --git a/wqflask/wqflask/static/new/javascript/dataset_select_menu.js b/wqflask/wqflask/static/new/javascript/dataset_select_menu.js index 5bd54359..fb9fdcf3 100644 --- a/wqflask/wqflask/static/new/javascript/dataset_select_menu.js +++ b/wqflask/wqflask/static/new/javascript/dataset_select_menu.js @@ -134,10 +134,12 @@ $(function() { return _results; }; check_search_term = function() { - var search_term; - search_term = $('#tfor').val(); - console.log("search_term:", search_term); - if (search_term === "") { + var or_search_term, and_search_term; + or_search_term = $('#or_search').val(); + and_search_term = $('#and_search').val(); + console.log("or_search_term:", or_search_term); + console.log("and_search_term:", and_search_term); + if (or_search_term === "" && and_search_term === "") { alert("Please enter one or more search terms or search equations."); return false; } diff --git a/wqflask/wqflask/static/new/javascript/draw_probability_plot.coffee b/wqflask/wqflask/static/new/javascript/draw_probability_plot.coffee index a2345dcb..09ab3bc4 100644 --- a/wqflask/wqflask/static/new/javascript/draw_probability_plot.coffee +++ b/wqflask/wqflask/static/new/javascript/draw_probability_plot.coffee @@ -1,35 +1,94 @@ -# illustration of use of the scatterplot function
-
-h = 400
-w = 500
-margin = {left:60, top:40, right:40, bottom: 40, inner:5}
-halfh = (h+margin.top+margin.bottom)
-totalh = halfh*2
-halfw = (w+margin.left+margin.right)
-totalw = halfw*2
-
-# Example 1: simplest use
-#d3.json "data.json", (data) ->
-mychart = scatterplot().xvar(0)
- .yvar(1)
- .height(h)
- .width(w)
- .margin(margin)
-
-data = js_data.probability_plot_data
-#indID = js_data.indIDs
-#slope = js_data.slope
-#intercept = js_data.intercept
-
-console.log("THE DATA IS:", data)
-
-d3.select("div#prob_plot")
- .datum({data:data})
- .call(mychart)
-
-# animate points
-mychart.pointsSelect()
- .on "mouseover", (d) ->
- d3.select(this).attr("r", mychart.pointsize()*3)
- .on "mouseout", (d) ->
- d3.select(this).attr("r", mychart.pointsize())
+root = exports ? this
+
+# calculations here use the same formulae as scipy.stats.probplot
+get_z_scores = (n) ->
+ # order statistic medians (Filliben's approximation)
+ osm_uniform = new Array(n)
+ osm_uniform[n - 1] = Math.pow(0.5, 1.0 / n)
+ osm_uniform[0] = 1 - osm_uniform[n - 1]
+ for i in [1 .. (n - 2)]
+ osm_uniform[i] = (i + 1 - 0.3175) / (n + 0.365)
+
+ return (jStat.normal.inv(x, 0, 1) for x in osm_uniform)
+
+# samples is {'samples_primary': {'name1': value1, ...},
+# 'samples_other': {'nameN': valueN, ...},
+# 'samples_all': {'name1': value1, ...}}
+redraw_prob_plot = (samples, sample_group) ->
+ h = 600
+ w = 600
+ margin = {left:60, top:40, right:40, bottom: 40, inner:5}
+ totalh = h + margin.top + margin.bottom
+ totalw = w + margin.left + margin.right
+
+ container = $("#prob_plot_container")
+ container.width(totalw)
+ container.height(totalh)
+
+ nv.addGraph(() =>
+ chart = nv.models.scatterChart()
+ .width(w)
+ .height(h)
+ .showLegend(true)
+ .color(d3.scale.category10().range())
+
+ # size settings are quite counter-intuitive in NVD3!
+ chart.pointRange([50,50]) # (50 is the area, not radius)
+
+ chart.legend.updateState(false)
+
+ chart.xAxis
+ .axisLabel("Theoretical quantiles")
+ .tickFormat(d3.format('.02f'))
+
+ chart.yAxis
+ .axisLabel("Sample quantiles")
+ .tickFormat(d3.format('.02f'))
+
+ chart.tooltipContent((obj) =>
+ return '<b style="font-size: 20px">' + obj.point.name + '</b>'
+ )
+
+ all_samples = samples[sample_group]
+ names = (x for x in _.keys(all_samples) when all_samples[x] != null)
+ sorted_names = names.sort((x, y) => all_samples[x].value - all_samples[y].value)
+ sorted_values = (all_samples[x].value for x in sorted_names)
+ sw_result = ShapiroWilkW(sorted_values)
+ W = sw_result.w.toFixed(3)
+ pvalue = sw_result.p.toFixed(3)
+ pvalue_str = if pvalue > 0.05 then\
+ pvalue.toString()\
+ else\
+ "<span style='color:red'>"+pvalue+"</span>"
+ test_str = "Shapiro-Wilk test statistic is #{W} (p = #{pvalue_str})"
+
+ z_scores = get_z_scores(sorted_values.length)
+ slope = jStat.stdev(sorted_values)
+ intercept = jStat.mean(sorted_values)
+
+ make_data = (group_name) ->
+ return {
+ key: js_data.sample_group_types[group_name],
+ slope: slope,
+ intercept: intercept,
+ values: ({x: z_score, y: value, name: sample}\
+ for [z_score, value, sample] in\
+ _.zip(get_z_scores(sorted_values.length),
+ sorted_values,
+ sorted_names)\
+ when sample of samples[group_name])
+ }
+
+ data = [make_data('samples_primary'), make_data('samples_other')]
+ console.log("THE DATA IS:", data)
+ d3.select("#prob_plot_container svg")
+ .datum(data)
+ .call(chart)
+
+ $("#prob_plot_title").html("<h3>Normal probability plot</h3>"+test_str)
+ $("#prob_plot_container .nv-legendWrap").toggle(sample_group == "samples_all")
+
+ return chart
+ )
+
+root.redraw_prob_plot_impl = redraw_prob_plot
diff --git a/wqflask/wqflask/static/new/javascript/draw_probability_plot.js b/wqflask/wqflask/static/new/javascript/draw_probability_plot.js index f2bf09e6..3eb6295f 100644 --- a/wqflask/wqflask/static/new/javascript/draw_probability_plot.js +++ b/wqflask/wqflask/static/new/javascript/draw_probability_plot.js @@ -1,38 +1,122 @@ -// Generated by CoffeeScript 1.8.0 -var data, h, halfh, halfw, margin, mychart, totalh, totalw, w; +// Generated by CoffeeScript 1.9.2 +(function() { + var get_z_scores, redraw_prob_plot, root; -h = 400; + root = typeof exports !== "undefined" && exports !== null ? exports : this; -w = 500; + get_z_scores = function(n) { + var i, j, osm_uniform, ref, x; + osm_uniform = new Array(n); + osm_uniform[n - 1] = Math.pow(0.5, 1.0 / n); + osm_uniform[0] = 1 - osm_uniform[n - 1]; + for (i = j = 1, ref = n - 2; 1 <= ref ? j <= ref : j >= ref; i = 1 <= ref ? ++j : --j) { + osm_uniform[i] = (i + 1 - 0.3175) / (n + 0.365); + } + return (function() { + var k, len, results; + results = []; + for (k = 0, len = osm_uniform.length; k < len; k++) { + x = osm_uniform[k]; + results.push(jStat.normal.inv(x, 0, 1)); + } + return results; + })(); + }; -margin = { - left: 60, - top: 40, - right: 40, - bottom: 40, - inner: 5 -}; + redraw_prob_plot = function(samples, sample_group) { + var container, h, margin, totalh, totalw, w; + h = 600; + w = 600; + margin = { + left: 60, + top: 40, + right: 40, + bottom: 40, + inner: 5 + }; + totalh = h + margin.top + margin.bottom; + totalw = w + margin.left + margin.right; + container = $("#prob_plot_container"); + container.width(totalw); + container.height(totalh); + return nv.addGraph((function(_this) { + return function() { + var W, all_samples, chart, data, intercept, make_data, names, pvalue, pvalue_str, slope, sorted_names, sorted_values, sw_result, test_str, x, z_scores; + chart = nv.models.scatterChart().width(w).height(h).showLegend(true).color(d3.scale.category10().range()); + chart.pointRange([50, 50]); + chart.legend.updateState(false); + chart.xAxis.axisLabel("Theoretical quantiles").tickFormat(d3.format('.02f')); + chart.yAxis.axisLabel("Sample quantiles").tickFormat(d3.format('.02f')); + chart.tooltipContent(function(obj) { + return '<b style="font-size: 20px">' + obj.point.name + '</b>'; + }); + all_samples = samples[sample_group]; + names = (function() { + var j, len, ref, results; + ref = _.keys(all_samples); + results = []; + for (j = 0, len = ref.length; j < len; j++) { + x = ref[j]; + if (all_samples[x] !== null) { + results.push(x); + } + } + return results; + })(); + sorted_names = names.sort(function(x, y) { + return all_samples[x].value - all_samples[y].value; + }); + sorted_values = (function() { + var j, len, results; + results = []; + for (j = 0, len = sorted_names.length; j < len; j++) { + x = sorted_names[j]; + results.push(all_samples[x].value); + } + return results; + })(); + sw_result = ShapiroWilkW(sorted_values); + W = sw_result.w.toFixed(3); + pvalue = sw_result.p.toFixed(3); + pvalue_str = pvalue > 0.05 ? pvalue.toString() : "<span style='color:red'>" + pvalue + "</span>"; + test_str = "Shapiro-Wilk test statistic is " + W + " (p = " + pvalue_str + ")"; + z_scores = get_z_scores(sorted_values.length); + slope = jStat.stdev(sorted_values); + intercept = jStat.mean(sorted_values); + make_data = function(group_name) { + var sample, value, z_score; + return { + key: js_data.sample_group_types[group_name], + slope: slope, + intercept: intercept, + values: (function() { + var j, len, ref, ref1, results; + ref = _.zip(get_z_scores(sorted_values.length), sorted_values, sorted_names); + results = []; + for (j = 0, len = ref.length; j < len; j++) { + ref1 = ref[j], z_score = ref1[0], value = ref1[1], sample = ref1[2]; + if (sample in samples[group_name]) { + results.push({ + x: z_score, + y: value, + name: sample + }); + } + } + return results; + })() + }; + }; + data = [make_data('samples_primary'), make_data('samples_other')]; + console.log("THE DATA IS:", data); + d3.select("#prob_plot_container svg").datum(data).call(chart); + $("#prob_plot_title").html("<h3>Normal probability plot</h3>" + test_str); + $("#prob_plot_container .nv-legendWrap").toggle(sample_group === "samples_all"); + return chart; + }; + })(this)); + }; -halfh = h + margin.top + margin.bottom; + root.redraw_prob_plot_impl = redraw_prob_plot; -totalh = halfh * 2; - -halfw = w + margin.left + margin.right; - -totalw = halfw * 2; - -mychart = scatterplot().xvar(0).yvar(1).height(h).width(w).margin(margin); - -data = js_data.probability_plot_data; - -console.log("THE DATA IS:", data); - -d3.select("div#prob_plot").datum({ - data: data -}).call(mychart); - -mychart.pointsSelect().on("mouseover", function(d) { - return d3.select(this).attr("r", mychart.pointsize() * 3); -}).on("mouseout", function(d) { - return d3.select(this).attr("r", mychart.pointsize()); -}); +}).call(this); diff --git a/wqflask/wqflask/static/new/javascript/lod_chart.coffee b/wqflask/wqflask/static/new/javascript/lod_chart.coffee index b0f4b2f5..55ffdce0 100644 --- a/wqflask/wqflask/static/new/javascript/lod_chart.coffee +++ b/wqflask/wqflask/static/new/javascript/lod_chart.coffee @@ -1,514 +1,517 @@ -lodchart = () ->
- width = 800
- height = 500
- margin = {left:60, top:40, right:40, bottom: 40, inner:5}
- axispos = {xtitle:25, ytitle:30, xlabel:5, ylabel:5}
- titlepos = 20
- manhattanPlot = false
- additive = false
- ylim = null
- additive_ylim = null
- nyticks = 5
- yticks = null
- additive_yticks = null
- chrGap = 8
- darkrect = "#F1F1F9"
- lightrect = "#FBFBFF"
- lodlinecolor = "darkslateblue"
- additivelinecolor = "red"
- linewidth = 2
- suggestivecolor = "gainsboro"
- significantcolor = "#EBC7C7"
- pointcolor = "#E9CFEC" # pink
- pointsize = 0 # default = no visible points at markers
- pointstroke = "black"
- title = ""
- xlab = "Chromosome"
- ylab = "LRS score"
- additive_ylab = "Additive Effect"
- rotate_ylab = null
- yscale = d3.scale.linear()
- additive_yscale = d3.scale.linear()
- xscale = null
- pad4heatmap = false
- lodcurve = null
- lodvarname = null
- markerSelect = null
- chrSelect = null
- pointsAtMarkers = true
-
-
- ## the main function
- chart = (selection) ->
- selection.each (data) ->
-
- #console.log("data:", data)
-
- if manhattanPlot == true
- pointcolor = "darkslateblue"
- pointsize = 2
-
- lodvarname = lodvarname ? data.lodnames[0]
- data[lodvarname] = (Math.abs(x) for x in data[lodvarname]) # take absolute values
- ylim = ylim ? [0, d3.max(data[lodvarname])]
- if additive
- data['additive'] = (Math.abs(x) for x in data['additive'])
- additive_ylim = additive_ylim ? [0, d3.max(data['additive'])]
-
- lodvarnum = data.lodnames.indexOf(lodvarname)
-
- # Select the svg element, if it exists.
- svg = d3.select(this).selectAll("svg").data([data])
-
- # Otherwise, create the skeletal chart.
- gEnter = svg.enter().append("svg").append("g")
-
- # Update the outer dimensions.
- svg.attr("width", width+margin.left+margin.right)
- .attr("height", height+margin.top+margin.bottom)
-
- # Update the inner dimensions.
- g = svg.select("g")
-
- # box
- g.append("rect")
- .attr("x", margin.left)
- .attr("y", margin.top)
- .attr("height", height)
- .attr("width", width)
- .attr("fill", darkrect)
- .attr("stroke", "none")
-
- yscale.domain(ylim)
- .range([height+margin.top, margin.top+margin.inner])
-
- # if yticks not provided, use nyticks to choose pretty ones
- yticks = yticks ? yscale.ticks(nyticks)
-
- #if data['additive'].length > 0
- if additive
- additive_yscale.domain(additive_ylim)
- .range([height+margin.top, margin.top+margin.inner + height/2])
-
- additive_yticks = additive_yticks ? additive_yscale.ticks(nyticks)
-
- # reorganize lod,pos by chromosomes
- reorgLodData(data, lodvarname)
-
- # add chromosome scales (for x-axis)
- data = chrscales(data, width, chrGap, margin.left, pad4heatmap)
- xscale = data.xscale
-
- # chr rectangles
- chrSelect =
- g.append("g").attr("class", "chrRect")
- .selectAll("empty")
- .data(data.chrnames)
- .enter()
- .append("rect")
- .attr("id", (d) -> "chrrect#{d[0]}")
- .attr("x", (d,i) ->
- return data.chrStart[i] if i==0 and pad4heatmap
- data.chrStart[i]-chrGap/2)
- .attr("width", (d,i) ->
- return data.chrEnd[i] - data.chrStart[i]+chrGap/2 if (i==0 or i+1 == data.chrnames.length) and pad4heatmap
- data.chrEnd[i] - data.chrStart[i]+chrGap)
- .attr("y", margin.top)
- .attr("height", height)
- .attr("fill", (d,i) ->
- return darkrect if i % 2
- lightrect)
- .attr("stroke", "none")
- .on("click", (d) ->
- console.log("d is:", d)
- redraw_plot(d)
- )
-
- # x-axis labels
- xaxis = g.append("g").attr("class", "x axis")
- xaxis.selectAll("empty")
- .data(data.chrnames)
- .enter()
- .append("text")
- .text((d) -> d[0])
- .attr("x", (d,i) -> (data.chrStart[i]+data.chrEnd[i])/2)
- .attr("y", margin.top+height+axispos.xlabel)
- .attr("dominant-baseline", "hanging")
- .attr("text-anchor", "middle")
- .attr("cursor", "pointer")
- .on("click", (d) ->
- redraw_plot(d)
- )
-
- xaxis.append("text").attr("class", "title")
- .attr("y", margin.top+height+axispos.xtitle)
- .attr("x", margin.left+width/2)
- .attr("fill", "slateblue")
- .text(xlab)
-
-
- redraw_plot = (chr_ob) ->
- #console.log("chr_name is:", chr_ob[0])
- #console.log("chr_length is:", chr_ob[1])
- $('#topchart').remove()
- $('#chart_container').append('<div class="qtlcharts" id="topchart"></div>')
- chr_plot = new Chr_Lod_Chart(600, 1200, chr_ob, manhattanPlot)
-
- # y-axis
- rotate_ylab = rotate_ylab ? (ylab.length > 1)
- yaxis = g.append("g").attr("class", "y axis")
- yaxis.selectAll("empty")
- .data(yticks)
- .enter()
- .append("line")
- .attr("y1", (d) -> yscale(d))
- .attr("y2", (d) -> yscale(d))
- .attr("x1", margin.left)
- .attr("x2", margin.left+7)
- .attr("fill", "none")
- .attr("stroke", "white")
- .attr("stroke-width", 1)
- .style("pointer-events", "none")
-
- yaxis.selectAll("empty")
- .data(yticks)
- .enter()
- .append("text")
- .attr("y", (d) -> yscale(d))
- .attr("x", margin.left-axispos.ylabel)
- .attr("fill", "blue")
- .attr("dominant-baseline", "middle")
- .attr("text-anchor", "end")
- .text((d) -> formatAxis(yticks)(d))
-
- yaxis.append("text").attr("class", "title")
- .attr("y", margin.top+height/2)
- .attr("x", margin.left-axispos.ytitle)
- .text(ylab)
- .attr("transform", if rotate_ylab then "rotate(270,#{margin.left-axispos.ytitle},#{margin.top+height/2})" else "")
- .attr("text-anchor", "middle")
- .attr("fill", "slateblue")
-
- #if data['additive'].length > 0
- if additive
- rotate_additive_ylab = rotate_additive_ylab ? (additive_ylab.length > 1)
- additive_yaxis = g.append("g").attr("class", "y axis")
- additive_yaxis.selectAll("empty")
- .data(additive_yticks)
- .enter()
- .append("line")
- .attr("y1", (d) -> additive_yscale(d))
- .attr("y2", (d) -> additive_yscale(d))
- .attr("x1", margin.left + width)
- .attr("x2", margin.left + width - 7)
- .attr("fill", "none")
- .attr("stroke", "white")
- .attr("stroke-width", 1)
- .style("pointer-events", "none")
-
- additive_yaxis.selectAll("empty")
- .data(additive_yticks)
- .enter()
- .append("text")
- .attr("y", (d) -> additive_yscale(d))
- .attr("x", (d) -> margin.left + width + axispos.ylabel + 20)
- .attr("fill", "green")
- .attr("dominant-baseline", "middle")
- .attr("text-anchor", "end")
- .text((d) -> formatAxis(additive_yticks)(d))
-
- additive_yaxis.append("text").attr("class", "title")
- .attr("y", margin.top+1.5*height)
- .attr("x", margin.left + width + axispos.ytitle)
- .text(additive_ylab)
- .attr("transform", if rotate_additive_ylab then "rotate(270,#{margin.left + width + axispos.ytitle}, #{margin.top+height*1.5})" else "")
- .attr("text-anchor", "middle")
- .attr("fill", "green")
-
- if 'suggestive' of data
- suggestive_bar = g.append("g").attr("class", "suggestive")
- suggestive_bar.selectAll("empty")
- .data([data.suggestive])
- .enter()
- .append("line")
- .attr("y1", (d) -> yscale(d))
- .attr("y2", (d) -> yscale(d))
- .attr("x1", margin.left)
- .attr("x2", margin.left+width)
- .attr("fill", "none")
- .attr("stroke", suggestivecolor)
- .attr("stroke-width", 5)
- .style("pointer-events", "none")
-
- suggestive_bar = g.append("g").attr("class", "significant")
- suggestive_bar.selectAll("empty")
- .data([data.significant])
- .enter()
- .append("line")
- .attr("y1", (d) -> yscale(d))
- .attr("y2", (d) -> yscale(d))
- .attr("x1", margin.left)
- .attr("x2", margin.left+width)
- .attr("fill", "none")
- .attr("stroke", significantcolor)
- .attr("stroke-width", 5)
- .style("pointer-events", "none")
-
- if manhattanPlot == false
- # lod curves by chr
- lodcurve = (chr, lodcolumn) ->
- d3.svg.line()
- .x((d) -> xscale[chr](d))
- .y((d,i) -> yscale(data.lodByChr[chr][i][lodcolumn]))
-
- if additive
- additivecurve = (chr, lodcolumn) ->
- d3.svg.line()
- .x((d) -> xscale[chr](d))
- .y((d,i) -> additive_yscale(data.additiveByChr[chr][i][lodcolumn]))
-
- curves = g.append("g").attr("id", "curves")
-
- for chr in data.chrnames
- curves.append("path")
- .datum(data.posByChr[chr[0]])
- .attr("d", lodcurve(chr[0], lodvarnum))
- .attr("stroke", lodlinecolor)
- .attr("fill", "none")
- .attr("stroke-width", linewidth)
- .style("pointer-events", "none")
-
- if additive
- for chr in data.chrnames
- curves.append("path")
- .datum(data.posByChr[chr[0]])
- .attr("d", additivecurve(chr[0], lodvarnum))
- .attr("stroke", additivelinecolor)
- .attr("fill", "none")
- .attr("stroke-width", 1)
- .style("pointer-events", "none")
-
- # points at markers
- console.log("before pointsize")
- if pointsize > 0
- console.log("pointsize > 0 !!!")
- markerpoints = g.append("g").attr("id", "markerpoints_visible")
- markerpoints.selectAll("empty")
- .data(data.markers)
- .enter()
- .append("circle")
- .attr("cx", (d) -> xscale[d.chr](d.pos))
- .attr("cy", (d) -> yscale(d.lod))
- .attr("r", pointsize)
- .attr("fill", pointcolor)
- .attr("stroke", pointstroke)
- .attr("pointer-events", "hidden")
-
- # title
- titlegrp = g.append("g").attr("class", "title")
- .append("text")
- .attr("x", margin.left+width/2)
- .attr("y", margin.top-titlepos)
- .text(title)
-
- # another box around edge
- g.append("rect")
- .attr("x", margin.left)
- .attr("y", margin.top)
- .attr("height", height)
- .attr("width", () ->
- return(data.chrEnd[-1..][0]-margin.left) if pad4heatmap
- data.chrEnd[-1..][0]-margin.left+chrGap/2)
- .attr("fill", "none")
- .attr("stroke", "black")
- .attr("stroke-width", "none")
-
- if pointsAtMarkers
- # these hidden points are what gets selected...a bit larger
- hiddenpoints = g.append("g").attr("id", "markerpoints_hidden")
-
- markertip = d3.tip()
- .attr('class', 'd3-tip')
- .html((d) ->
- [d.name, " LRS = #{d3.format('.2f')(d.lod)}"])
- .direction("e")
- .offset([0,10])
- svg.call(markertip)
-
- markerSelect =
- hiddenpoints.selectAll("empty")
- .data(data.markers)
- .enter()
- .append("circle")
- .attr("cx", (d) -> xscale[d.chr](d.pos))
- .attr("cy", (d) -> yscale(d.lod))
- .attr("id", (d) -> d.name)
- .attr("r", d3.max([pointsize*2, 3]))
- .attr("opacity", 0)
- .attr("fill", pointcolor)
- .attr("stroke", pointstroke)
- .attr("stroke-width", "1")
- .on "mouseover.paneltip", (d) ->
- d3.select(this).attr("opacity", 1)
- markertip.show(d)
- .on "mouseout.paneltip", ->
- d3.select(this).attr("opacity", 0)
- .call(markertip.hide)
-
- ## configuration parameters
- chart.width = (value) ->
- return width unless arguments.length
- width = value
- chart
-
- chart.height = (value) ->
- return height unless arguments.length
- height = value
- chart
-
- chart.margin = (value) ->
- return margin unless arguments.length
- margin = value
- chart
-
- chart.titlepos = (value) ->
- return titlepos unless arguments.length
- titlepos
- chart
-
- chart.axispos = (value) ->
- return axispos unless arguments.length
- axispos = value
- chart
-
- chart.manhattanPlot = (value) ->
- return manhattanPlot unless arguments.length
- manhattanPlot = value
- chart
-
- chart.ylim = (value) ->
- return ylim unless arguments.length
- ylim = value
- chart
-
- #if data['additive'].length > 0
- chart.additive_ylim = (value) ->
- return additive_ylim unless arguments.length
- additive_ylim = value
- chart
-
- chart.nyticks = (value) ->
- return nyticks unless arguments.length
- nyticks = value
- chart
-
- chart.yticks = (value) ->
- return yticks unless arguments.length
- yticks = value
- chart
-
- chart.chrGap = (value) ->
- return chrGap unless arguments.length
- chrGap = value
- chart
-
- chart.darkrect = (value) ->
- return darkrect unless arguments.length
- darkrect = value
- chart
-
- chart.lightrect = (value) ->
- return lightrect unless arguments.length
- lightrect = value
- chart
-
- chart.linecolor = (value) ->
- return linecolor unless arguments.length
- linecolor = value
- chart
-
- chart.linewidth = (value) ->
- return linewidth unless arguments.length
- linewidth = value
- chart
-
- chart.pointcolor = (value) ->
- return pointcolor unless arguments.length
- pointcolor = value
- chart
-
- chart.pointsize = (value) ->
- return pointsize unless arguments.length
- pointsize = value
- chart
-
- chart.pointstroke = (value) ->
- return pointstroke unless arguments.length
- pointstroke = value
- chart
-
- chart.title = (value) ->
- return title unless arguments.length
- title = value
- chart
-
- chart.xlab = (value) ->
- return xlab unless arguments.length
- xlab = value
- chart
-
- chart.ylab = (value) ->
- return ylab unless arguments.length
- ylab = value
- chart
-
- chart.rotate_ylab = (value) ->
- return rotate_ylab if !arguments.length
- rotate_ylab = value
- chart
-
- chart.lodvarname = (value) ->
- return lodvarname unless arguments.length
- lodvarname = value
- chart
-
- chart.pad4heatmap = (value) ->
- return pad4heatmap unless arguments.length
- pad4heatmap = value
- chart
-
- chart.pointsAtMarkers = (value) ->
- return pointsAtMarkers unless arguments.length
- pointsAtMarkers = value
- chart
-
- chart.yscale = () ->
- return yscale
-
- chart.additive = () ->
- return additive
-
- #if data['additive'].length > 0
- chart.additive_yscale = () ->
- return additive_yscale
-
- chart.xscale = () ->
- return xscale
-
- if manhattanPlot == false
- chart.lodcurve = () ->
- return lodcurve
-
- #if data['additive'].length > 0
- chart.additivecurve = () ->
- return additivecurve
-
- chart.markerSelect = () ->
- return markerSelect
-
- chart.chrSelect = () ->
- return chrSelect
-
- # return the chart function
- chart
-
+lodchart = () -> + width = 800 + height = 500 + margin = {left:60, top:40, right:40, bottom: 40, inner:5} + axispos = {xtitle:25, ytitle:30, xlabel:5, ylabel:5} + titlepos = 20 + manhattanPlot = false + additive = false + ylim = null + additive_ylim = null + nyticks = 5 + yticks = null + additive_yticks = null + chrGap = 8 + darkrect = "#F1F1F9" + lightrect = "#FBFBFF" + lodlinecolor = "darkslateblue" + additivelinecolor = "red" + linewidth = 2 + suggestivecolor = "gainsboro" + significantcolor = "#EBC7C7" + pointcolor = "#E9CFEC" # pink + pointsize = 0 # default = no visible points at markers + pointstroke = "black" + title = "" + xlab = "Chromosome" + ylab = "LRS score" + additive_ylab = "Additive Effect" + rotate_ylab = null + yscale = d3.scale.linear() + additive_yscale = d3.scale.linear() + xscale = null + pad4heatmap = false + lodcurve = null + lodvarname = null + markerSelect = null + chrSelect = null + pointsAtMarkers = true + + + ## the main function + chart = (selection) -> + selection.each (data) -> + + #console.log("data:", data) + + if manhattanPlot == true + pointcolor = "darkslateblue" + pointsize = 2 + + lodvarname = lodvarname ? data.lodnames[0] + data[lodvarname] = (Math.abs(x) for x in data[lodvarname]) # take absolute values + ylim = ylim ? [0, d3.max(data[lodvarname])] + if additive + data['additive'] = (Math.abs(x) for x in data['additive']) + additive_ylim = additive_ylim ? [0, d3.max(data['additive'])] + + lodvarnum = data.lodnames.indexOf(lodvarname) + + # Select the svg element, if it exists. + svg = d3.select(this).selectAll("svg").data([data]) + + # Otherwise, create the skeletal chart. + gEnter = svg.enter().append("svg").append("g") + + # Update the outer dimensions. + svg.attr("width", width+margin.left+margin.right) + .attr("height", height+margin.top+margin.bottom) + + # Update the inner dimensions. + g = svg.select("g") + + # box + g.append("rect") + .attr("x", margin.left) + .attr("y", margin.top) + .attr("height", height) + .attr("width", width) + .attr("fill", darkrect) + .attr("stroke", "none") + + yscale.domain(ylim) + .range([height+margin.top, margin.top+margin.inner]) + + # if yticks not provided, use nyticks to choose pretty ones + yticks = yticks ? yscale.ticks(nyticks) + + #if data['additive'].length > 0 + if additive + additive_yscale.domain(additive_ylim) + .range([height+margin.top, margin.top+margin.inner + height/2]) + + additive_yticks = additive_yticks ? additive_yscale.ticks(nyticks) + + # reorganize lod,pos by chromosomes + reorgLodData(data, lodvarname) + + # add chromosome scales (for x-axis) + data = chrscales(data, width, chrGap, margin.left, pad4heatmap) + xscale = data.xscale + + # chr rectangles + chrSelect = + g.append("g").attr("class", "chrRect") + .selectAll("empty") + .data(data.chrnames) + .enter() + .append("rect") + .attr("id", (d) -> "chrrect#{d[0]}") + .attr("x", (d,i) -> + return data.chrStart[i] if i==0 and pad4heatmap + data.chrStart[i]-chrGap/2) + .attr("width", (d,i) -> + return data.chrEnd[i] - data.chrStart[i]+chrGap/2 if (i==0 or i+1 == data.chrnames.length) and pad4heatmap + data.chrEnd[i] - data.chrStart[i]+chrGap) + .attr("y", margin.top) + .attr("height", height) + .attr("fill", (d,i) -> + return darkrect if i % 2 + + lightrect) + .attr("stroke", "none") + .on("click", (d) -> + console.log("d is:", d) + redraw_plot(d) + ) + + # x-axis labels + xaxis = g.append("g").attr("class", "x axis") + xaxis.selectAll("empty") + .data(data.chrnames) + .enter() + .append("text") + .text((d) -> d[0]) + .attr("x", (d,i) -> (data.chrStart[i]+data.chrEnd[i])/2) + .attr("y", margin.top+height+axispos.xlabel) + .attr("dominant-baseline", "hanging") + .attr("text-anchor", "middle") + .attr("cursor", "pointer") + .on("click", (d) -> + redraw_plot(d) + ) + + xaxis.append("text").attr("class", "title") + .attr("y", margin.top+height+axispos.xtitle) + .attr("x", margin.left+width/2) + .attr("fill", "slateblue") + .text(xlab) + + + redraw_plot = (chr_ob) -> + #console.log("chr_name is:", chr_ob[0]) + #console.log("chr_length is:", chr_ob[1]) + $('#topchart').remove() + $('#chart_container').append('<div class="qtlcharts" id="topchart"></div>') + chr_plot = new Chr_Lod_Chart(600, 1200, chr_ob, manhattanPlot) + + # y-axis + rotate_ylab = rotate_ylab ? (ylab.length > 1) + yaxis = g.append("g").attr("class", "y axis") + yaxis.selectAll("empty") + .data(yticks) + .enter() + .append("line") + .attr("y1", (d) -> yscale(d)) + .attr("y2", (d) -> yscale(d)) + .attr("x1", margin.left) + .attr("x2", margin.left+7) + .attr("fill", "none") + .attr("stroke", "white") + .attr("stroke-width", 1) + .style("pointer-events", "none") + + yaxis.selectAll("empty") + .data(yticks) + .enter() + .append("text") + .attr("y", (d) -> yscale(d)) + .attr("x", margin.left-axispos.ylabel) + .attr("fill", "blue") + .attr("dominant-baseline", "middle") + .attr("text-anchor", "end") + .text((d) -> formatAxis(yticks)(d)) + + yaxis.append("text").attr("class", "title") + .attr("y", margin.top+height/2) + .attr("x", margin.left-axispos.ytitle) + .text(ylab) + .attr("transform", if rotate_ylab then "rotate(270,#{margin.left-axispos.ytitle},#{margin.top+height/2})" else "") + .attr("text-anchor", "middle") + .attr("fill", "slateblue") + + #if data['additive'].length > 0 + if additive + rotate_additive_ylab = rotate_additive_ylab ? (additive_ylab.length > 1) + additive_yaxis = g.append("g").attr("class", "y axis") + additive_yaxis.selectAll("empty") + .data(additive_yticks) + .enter() + .append("line") + .attr("y1", (d) -> additive_yscale(d)) + .attr("y2", (d) -> additive_yscale(d)) + .attr("x1", margin.left + width) + .attr("x2", margin.left + width - 7) + .attr("fill", "none") + .attr("stroke", "white") + .attr("stroke-width", 1) + .style("pointer-events", "none") + + additive_yaxis.selectAll("empty") + .data(additive_yticks) + .enter() + .append("text") + .attr("y", (d) -> additive_yscale(d)) + .attr("x", (d) -> margin.left + width + axispos.ylabel + 20) + .attr("fill", "green") + .attr("dominant-baseline", "middle") + .attr("text-anchor", "end") + .text((d) -> formatAxis(additive_yticks)(d)) + + additive_yaxis.append("text").attr("class", "title") + .attr("y", margin.top+1.5*height) + .attr("x", margin.left + width + axispos.ytitle) + .text(additive_ylab) + .attr("transform", if rotate_additive_ylab then "rotate(270,#{margin.left + width + axispos.ytitle}, #{margin.top+height*1.5})" else "") + .attr("text-anchor", "middle") + .attr("fill", "green") + + if 'suggestive' of data + suggestive_bar = g.append("g").attr("class", "suggestive") + suggestive_bar.selectAll("empty") + .data([data.suggestive]) + .enter() + .append("line") + .attr("y1", (d) -> yscale(d)) + .attr("y2", (d) -> yscale(d)) + .attr("x1", margin.left) + .attr("x2", margin.left+width) + .attr("fill", "none") + .attr("stroke", suggestivecolor) + .attr("stroke-width", 5) + .style("pointer-events", "none") + + suggestive_bar = g.append("g").attr("class", "significant") + suggestive_bar.selectAll("empty") + .data([data.significant]) + .enter() + .append("line") + .attr("y1", (d) -> yscale(d)) + .attr("y2", (d) -> yscale(d)) + .attr("x1", margin.left) + .attr("x2", margin.left+width) + .attr("fill", "none") + .attr("stroke", significantcolor) + .attr("stroke-width", 5) + .style("pointer-events", "none") + + if manhattanPlot == false + # lod curves by chr + lodcurve = (chr, lodcolumn) -> + d3.svg.line() + .x((d) -> xscale[chr](d)) + .y((d,i) -> yscale(data.lodByChr[chr][i][lodcolumn])) + + if additive + additivecurve = (chr, lodcolumn) -> + d3.svg.line() + .x((d) -> xscale[chr](d)) + .y((d,i) -> additive_yscale(data.additiveByChr[chr][i][lodcolumn])) + + curves = g.append("g").attr("id", "curves") + + for chr in data.chrnames + if chr.indexOf(data['chr']) + curves.append("path") + .datum(data.posByChr[chr[0]]) + .attr("d", lodcurve(chr[0], lodvarnum)) + .attr("stroke", lodlinecolor) + .attr("fill", "none") + .attr("stroke-width", linewidth) + .style("pointer-events", "none") + + if additive + for chr in data.chrnames + if chr.indexOf(data['chr']) + curves.append("path") + .datum(data.posByChr[chr[0]]) + .attr("d", additivecurve(chr[0], lodvarnum)) + .attr("stroke", additivelinecolor) + .attr("fill", "none") + .attr("stroke-width", 1) + .style("pointer-events", "none") + + # points at markers + console.log("before pointsize") + if pointsize > 0 + console.log("pointsize > 0 !!!") + markerpoints = g.append("g").attr("id", "markerpoints_visible") + markerpoints.selectAll("empty") + .data(data.markers) + .enter() + .append("circle") + .attr("cx", (d) -> xscale[d.chr](d.pos)) + .attr("cy", (d) -> yscale(d.lod)) + .attr("r", pointsize) + .attr("fill", pointcolor) + .attr("stroke", pointstroke) + .attr("pointer-events", "hidden") + + # title + titlegrp = g.append("g").attr("class", "title") + .append("text") + .attr("x", margin.left+width/2) + .attr("y", margin.top-titlepos) + .text(title) + + # another box around edge + g.append("rect") + .attr("x", margin.left) + .attr("y", margin.top) + .attr("height", height) + .attr("width", () -> + return(data.chrEnd[-1..][0]-margin.left) if pad4heatmap + data.chrEnd[-1..][0]-margin.left+chrGap/2) + .attr("fill", "none") + .attr("stroke", "black") + .attr("stroke-width", "none") + + if pointsAtMarkers + # these hidden points are what gets selected...a bit larger + hiddenpoints = g.append("g").attr("id", "markerpoints_hidden") + + markertip = d3.tip() + .attr('class', 'd3-tip') + .html((d) -> + [d.name, " LRS = #{d3.format('.2f')(d.lod)}"]) + .direction("e") + .offset([0,10]) + svg.call(markertip) + + markerSelect = + hiddenpoints.selectAll("empty") + .data(data.markers) + .enter() + .append("circle") + .attr("cx", (d) -> xscale[d.chr](d.pos)) + .attr("cy", (d) -> yscale(d.lod)) + .attr("id", (d) -> d.name) + .attr("r", d3.max([pointsize*2, 3])) + .attr("opacity", 0) + .attr("fill", pointcolor) + .attr("stroke", pointstroke) + .attr("stroke-width", "1") + .on "mouseover.paneltip", (d) -> + d3.select(this).attr("opacity", 1) + markertip.show(d) + .on "mouseout.paneltip", -> + d3.select(this).attr("opacity", 0) + .call(markertip.hide) + + ## configuration parameters + chart.width = (value) -> + return width unless arguments.length + width = value + chart + + chart.height = (value) -> + return height unless arguments.length + height = value + chart + + chart.margin = (value) -> + return margin unless arguments.length + margin = value + chart + + chart.titlepos = (value) -> + return titlepos unless arguments.length + titlepos + chart + + chart.axispos = (value) -> + return axispos unless arguments.length + axispos = value + chart + + chart.manhattanPlot = (value) -> + return manhattanPlot unless arguments.length + manhattanPlot = value + chart + + chart.ylim = (value) -> + return ylim unless arguments.length + ylim = value + chart + + #if data['additive'].length > 0 + chart.additive_ylim = (value) -> + return additive_ylim unless arguments.length + additive_ylim = value + chart + + chart.nyticks = (value) -> + return nyticks unless arguments.length + nyticks = value + chart + + chart.yticks = (value) -> + return yticks unless arguments.length + yticks = value + chart + + chart.chrGap = (value) -> + return chrGap unless arguments.length + chrGap = value + chart + + chart.darkrect = (value) -> + return darkrect unless arguments.length + darkrect = value + chart + + chart.lightrect = (value) -> + return lightrect unless arguments.length + lightrect = value + chart + + chart.linecolor = (value) -> + return linecolor unless arguments.length + linecolor = value + chart + + chart.linewidth = (value) -> + return linewidth unless arguments.length + linewidth = value + chart + + chart.pointcolor = (value) -> + return pointcolor unless arguments.length + pointcolor = value + chart + + chart.pointsize = (value) -> + return pointsize unless arguments.length + pointsize = value + chart + + chart.pointstroke = (value) -> + return pointstroke unless arguments.length + pointstroke = value + chart + + chart.title = (value) -> + return title unless arguments.length + title = value + chart + + chart.xlab = (value) -> + return xlab unless arguments.length + xlab = value + chart + + chart.ylab = (value) -> + return ylab unless arguments.length + ylab = value + chart + + chart.rotate_ylab = (value) -> + return rotate_ylab if !arguments.length + rotate_ylab = value + chart + + chart.lodvarname = (value) -> + return lodvarname unless arguments.length + lodvarname = value + chart + + chart.pad4heatmap = (value) -> + return pad4heatmap unless arguments.length + pad4heatmap = value + chart + + chart.pointsAtMarkers = (value) -> + return pointsAtMarkers unless arguments.length + pointsAtMarkers = value + chart + + chart.yscale = () -> + return yscale + + chart.additive = () -> + return additive + + #if data['additive'].length > 0 + chart.additive_yscale = () -> + return additive_yscale + + chart.xscale = () -> + return xscale + + if manhattanPlot == false + chart.lodcurve = () -> + return lodcurve + + #if data['additive'].length > 0 + chart.additivecurve = () -> + return additivecurve + + chart.markerSelect = () -> + return markerSelect + + chart.chrSelect = () -> + return chrSelect + + # return the chart function + chart + diff --git a/wqflask/wqflask/static/new/javascript/lod_chart.js b/wqflask/wqflask/static/new/javascript/lod_chart.js index 92289bfe..631d8632 100644 --- a/wqflask/wqflask/static/new/javascript/lod_chart.js +++ b/wqflask/wqflask/static/new/javascript/lod_chart.js @@ -1,4 +1,4 @@ -// Generated by CoffeeScript 1.8.0 +// Generated by CoffeeScript 1.9.2 var lodchart; lodchart = function() { @@ -53,33 +53,33 @@ lodchart = function() { pointsAtMarkers = true; chart = function(selection) { return selection.each(function(data) { - var additive_yaxis, additivecurve, chr, curves, g, gEnter, hiddenpoints, lodvarnum, markerpoints, markertip, redraw_plot, rotate_additive_ylab, suggestive_bar, svg, titlegrp, x, xaxis, yaxis, _i, _j, _len, _len1, _ref, _ref1; + var additive_yaxis, additivecurve, chr, curves, g, gEnter, hiddenpoints, j, k, len, len1, lodvarnum, markerpoints, markertip, redraw_plot, ref, ref1, rotate_additive_ylab, suggestive_bar, svg, titlegrp, x, xaxis, yaxis; if (manhattanPlot === true) { pointcolor = "darkslateblue"; pointsize = 2; } lodvarname = lodvarname != null ? lodvarname : data.lodnames[0]; data[lodvarname] = (function() { - var _i, _len, _ref, _results; - _ref = data[lodvarname]; - _results = []; - for (_i = 0, _len = _ref.length; _i < _len; _i++) { - x = _ref[_i]; - _results.push(Math.abs(x)); + var j, len, ref, results; + ref = data[lodvarname]; + results = []; + for (j = 0, len = ref.length; j < len; j++) { + x = ref[j]; + results.push(Math.abs(x)); } - return _results; + return results; })(); ylim = ylim != null ? ylim : [0, d3.max(data[lodvarname])]; if (additive) { data['additive'] = (function() { - var _i, _len, _ref, _results; - _ref = data['additive']; - _results = []; - for (_i = 0, _len = _ref.length; _i < _len; _i++) { - x = _ref[_i]; - _results.push(Math.abs(x)); + var j, len, ref, results; + ref = data['additive']; + results = []; + for (j = 0, len = ref.length; j < len; j++) { + x = ref[j]; + results.push(Math.abs(x)); } - return _results; + return results; })(); additive_ylim = additive_ylim != null ? additive_ylim : [0, d3.max(data['additive'])]; } @@ -196,16 +196,20 @@ lodchart = function() { }; } curves = g.append("g").attr("id", "curves"); - _ref = data.chrnames; - for (_i = 0, _len = _ref.length; _i < _len; _i++) { - chr = _ref[_i]; - curves.append("path").datum(data.posByChr[chr[0]]).attr("d", lodcurve(chr[0], lodvarnum)).attr("stroke", lodlinecolor).attr("fill", "none").attr("stroke-width", linewidth).style("pointer-events", "none"); + ref = data.chrnames; + for (j = 0, len = ref.length; j < len; j++) { + chr = ref[j]; + if (chr.indexOf(data['chr'])) { + curves.append("path").datum(data.posByChr[chr[0]]).attr("d", lodcurve(chr[0], lodvarnum)).attr("stroke", lodlinecolor).attr("fill", "none").attr("stroke-width", linewidth).style("pointer-events", "none"); + } } if (additive) { - _ref1 = data.chrnames; - for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) { - chr = _ref1[_j]; - curves.append("path").datum(data.posByChr[chr[0]]).attr("d", additivecurve(chr[0], lodvarnum)).attr("stroke", additivelinecolor).attr("fill", "none").attr("stroke-width", 1).style("pointer-events", "none"); + ref1 = data.chrnames; + for (k = 0, len1 = ref1.length; k < len1; k++) { + chr = ref1[k]; + if (chr.indexOf(data['chr'])) { + curves.append("path").datum(data.posByChr[chr[0]]).attr("d", additivecurve(chr[0], lodvarnum)).attr("stroke", additivelinecolor).attr("fill", "none").attr("stroke-width", 1).style("pointer-events", "none"); + } } } } @@ -450,4 +454,4 @@ lodchart = function() { return chrSelect; }; return chart; -}; +};
\ No newline at end of file diff --git a/wqflask/wqflask/static/new/javascript/panelutil.coffee b/wqflask/wqflask/static/new/javascript/panelutil.coffee index a3bc0b44..f7b51457 100644 --- a/wqflask/wqflask/static/new/javascript/panelutil.coffee +++ b/wqflask/wqflask/static/new/javascript/panelutil.coffee @@ -30,16 +30,15 @@ reorgLodData = (data, lodvarname=null) -> data.lodByChr = {} for chr,i in data.chrnames - #console.log("chr:", chr) - data.posByChr[chr[0]] = [] - data.lodByChr[chr[0]] = [] - for pos, j in data.pos - if data.chr[j].toString() == chr[0] - #console.log(data.chr[j] + " AND " + chr[0]) - data.posByChr[chr[0]].push(pos) - data.lodnames = [data.lodnames] unless Array.isArray(data.lodnames) - lodval = (data[lodcolumn][j] for lodcolumn in data.lodnames) - data.lodByChr[chr[0]].push(lodval) + if data.chr.indexOf(chr[0]) + data.posByChr[chr[0]] = [] + data.lodByChr[chr[0]] = [] + for pos, j in data.pos + if data.chr[j].toString() == chr[0] + data.posByChr[chr[0]].push(pos) + data.lodnames = [data.lodnames] unless Array.isArray(data.lodnames) + lodval = (data[lodcolumn][j] for lodcolumn in data.lodnames) + data.lodByChr[chr[0]].push(lodval) #console.log("data.posByChr:", data.posByChr) diff --git a/wqflask/wqflask/static/new/javascript/panelutil.js b/wqflask/wqflask/static/new/javascript/panelutil.js index 3a180e60..7c14f4de 100644 --- a/wqflask/wqflask/static/new/javascript/panelutil.js +++ b/wqflask/wqflask/static/new/javascript/panelutil.js @@ -106,10 +106,13 @@ chrscales = function(data, width, chrGap, leftMargin, pad4heatmap) { if (d > maxd) { maxd = d; } - rng = d3.extent(data.posByChr[chr[0]]); - chrStart.push(rng[0]); - chrEnd.push(rng[1]); - L = rng[1] - rng[0]; + //rng = d3.extent(data.posByChr[chr[0]]); + //chrStart.push(rng[0]); + //chrEnd.push(rng[1]); + //L = rng[1] - rng[0]; + chrStart.push(0); + chrEnd.push(chr[1]); + L = chr[1] chrLength.push(L); totalChrLength += L; } @@ -436,4 +439,4 @@ abs = function(x) { return x; } return Math.abs(x); -}; +};
\ No newline at end of file diff --git a/wqflask/wqflask/static/new/javascript/probability_plot.coffee b/wqflask/wqflask/static/new/javascript/probability_plot.coffee deleted file mode 100644 index c1f26941..00000000 --- a/wqflask/wqflask/static/new/javascript/probability_plot.coffee +++ /dev/null @@ -1,424 +0,0 @@ -probability_plot = () ->
- width = 800
- height = 600
- margin = {left:60, top:40, right:40, bottom: 40, inner:5}
- axispos = {xtitle:25, ytitle:45, xlabel:5, ylabel:5}
- titlepos = 20
- xNA = {handle:true, force:false, width:15, gap:10}
- yNA = {handle:true, force:false, width:15, gap:10}
- xlim = null
- ylim = null
- nxticks = 5
- xticks = null
- nyticks = 5
- yticks = null
- rectcolor = d3.rgb(230, 230, 230)
- pointcolor = null
- pointstroke = "black"
- pointsize = 3 # default = no visible points at markers
- title = "Correlation Scatterplot"
- xlab = "X"
- ylab = "Y"
- rotate_ylab = null
- yscale = d3.scale.linear()
- xscale = d3.scale.linear()
- xvar = 0
- yvar = 1
- pointsSelect = null
- dataByInd = false
-
- ## the main function
- chart = (selection) ->
- selection.each (data) ->
-
- if dataByInd
- x = data.data.map (d) -> d[xvar]
- y = data.data.map (d) -> d[yvar]
- else # reorganize data
- x = data.data[xvar]
- y = data.data[yvar]
-
- console.log("x:", x)
- console.log("y:", y)
-
- # grab indID if it's there
- # if no indID, create a vector of them
- indID = data?.indID ? null
- indID = indID ? [1..x.length]
-
- console.log("indID:", indID)
-
- # groups of colors
- group = data?.group ? (1 for i in x)
- ngroup = d3.max(group)
- group = (g-1 for g in group) # changed from (1,2,3,...) to (0,1,2,...)
-
- # colors of the points in the different groups
- pointcolor = pointcolor ? selectGroupColors(ngroup, "dark")
- pointcolor = expand2vector(pointcolor, ngroup)
-
- # if all (x,y) not null
- xNA.handle = false if x.every (v) -> (v?) and !xNA.force
- yNA.handle = false if y.every (v) -> (v?) and !yNA.force
- if xNA.handle
- paneloffset = xNA.width + xNA.gap
- panelwidth = width - paneloffset
- else
- paneloffset = 0
- panelwidth = width
- if yNA.handle
- panelheight = height - (yNA.width + yNA.gap)
- else
- panelheight = height
-
- xlim = xlim ? d3.extent(x)
- ylim = ylim ? d3.extent(y)
-
- # I'll replace missing values something smaller than what's observed
- na_value = d3.min(x.concat y) - 100
-
- # Select the svg element, if it exists.
- svg = d3.select(this).selectAll("svg").data([data])
-
- # Otherwise, create the skeletal chart.
- gEnter = svg.enter().append("svg").append("g")
-
- # Update the outer dimensions.
- svg.attr("width", width+margin.left+margin.right)
- .attr("height", height+margin.top+margin.bottom)
-
- g = svg.select("g")
-
- # box
- g.append("rect")
- .attr("x", paneloffset+margin.left)
- .attr("y", margin.top)
- .attr("height", panelheight)
- .attr("width", panelwidth)
- .attr("fill", rectcolor)
- .attr("stroke", "none")
- if xNA.handle
- g.append("rect")
- .attr("x", margin.left)
- .attr("y", margin.top)
- .attr("height", panelheight)
- .attr("width", xNA.width)
- .attr("fill", rectcolor)
- .attr("stroke", "none")
- if xNA.handle and yNA.handle
- g.append("rect")
- .attr("x", margin.left)
- .attr("y", margin.top+height - yNA.width)
- .attr("height", yNA.width)
- .attr("width", xNA.width)
- .attr("fill", rectcolor)
- .attr("stroke", "none")
- if yNA.handle
- g.append("rect")
- .attr("x", margin.left+paneloffset)
- .attr("y", margin.top+height-yNA.width)
- .attr("height", yNA.width)
- .attr("width", panelwidth)
- .attr("fill", rectcolor)
- .attr("stroke", "none")
-
- # simple scales (ignore NA business)
- xrange = [margin.left+paneloffset+margin.inner, margin.left+paneloffset+panelwidth-margin.inner]
- yrange = [margin.top+panelheight-margin.inner, margin.top+margin.inner]
- xscale.domain(xlim).range(xrange)
- yscale.domain(ylim).range(yrange)
- xs = d3.scale.linear().domain(xlim).range(xrange)
- ys = d3.scale.linear().domain(ylim).range(yrange)
-
- # "polylinear" scales to handle missing values
- if xNA.handle
- xscale.domain([na_value].concat xlim)
- .range([margin.left + xNA.width/2].concat xrange)
- x = x.map (e) -> if e? then e else na_value
- if yNA.handle
- yscale.domain([na_value].concat ylim)
- .range([height+margin.top-yNA.width/2].concat yrange)
- y = y.map (e) -> if e? then e else na_value
-
- # if yticks not provided, use nyticks to choose pretty ones
- yticks = yticks ? ys.ticks(nyticks)
- xticks = xticks ? xs.ticks(nxticks)
-
- # title
- titlegrp = g.append("g").attr("class", "title")
- .append("text")
- .attr("x", margin.left + width/2)
- .attr("y", margin.top - titlepos)
- .text(title)
-
- # x-axis
- xaxis = g.append("g").attr("class", "x axis")
- xaxis.selectAll("empty")
- .data(xticks)
- .enter()
- .append("line")
- .attr("x1", (d) -> xscale(d))
- .attr("x2", (d) -> xscale(d))
- .attr("y1", margin.top)
- .attr("y2", margin.top+height)
- .attr("fill", "none")
- .attr("stroke", "white")
- .attr("stroke-width", 1)
- .style("pointer-events", "none")
- xaxis.selectAll("empty")
- .data(xticks)
- .enter()
- .append("text")
- .attr("x", (d) -> xscale(d))
- .attr("y", margin.top+height+axispos.xlabel)
- .text((d) -> formatAxis(xticks)(d))
- xaxis.append("text").attr("class", "title")
- .attr("x", margin.left+width/2)
- .attr("y", margin.top+height+axispos.xtitle)
- .text(xlab)
- if xNA.handle
- xaxis.append("text")
- .attr("x", margin.left+xNA.width/2)
- .attr("y", margin.top+height+axispos.xlabel)
- .text("N/A")
-
- # y-axis
- rotate_ylab = rotate_ylab ? (ylab.length > 1)
- yaxis = g.append("g").attr("class", "y axis")
- yaxis.selectAll("empty")
- .data(yticks)
- .enter()
- .append("line")
- .attr("y1", (d) -> yscale(d))
- .attr("y2", (d) -> yscale(d))
- .attr("x1", margin.left)
- .attr("x2", margin.left+width)
- .attr("fill", "none")
- .attr("stroke", "white")
- .attr("stroke-width", 1)
- .style("pointer-events", "none")
- yaxis.selectAll("empty")
- .data(yticks)
- .enter()
- .append("text")
- .attr("y", (d) -> yscale(d))
- .attr("x", margin.left-axispos.ylabel)
- .text((d) -> formatAxis(yticks)(d))
- yaxis.append("text").attr("class", "title")
- .attr("y", margin.top+height/2)
- .attr("x", margin.left-axispos.ytitle)
- .text(ylab)
- .attr("transform", if rotate_ylab then "rotate(270,#{margin.left-axispos.ytitle},#{margin.top+height/2})" else "")
- if yNA.handle
- yaxis.append("text")
- .attr("x", margin.left-axispos.ylabel)
- .attr("y", margin.top+height-yNA.width/2)
- .text("N/A")
-
- indtip = d3.tip()
- .attr('class', 'd3-tip')
- .html((d,i) -> indID[i])
- .direction('e')
- .offset([0,10])
- svg.call(indtip)
-
- #g.append("line")
- # .attr("x1")
- #
- #g.append("line")
- # .attr("x1", xscale(minx))
- # .attr("x2", xscale(maxx*0.995))
- # .attr("y2", yscale(slope*maxx*0.995+intercept))
- # .style("stroke", "black")
- # .style("stroke-width", 2);
-
- points = g.append("g").attr("id", "points")
- pointsSelect =
- points.selectAll("empty")
- .data(d3.range(x.length))
- .enter()
- .append("circle")
- .attr("cx", (d,i) -> xscale(x[i]))
- .attr("cy", (d,i) -> yscale(y[i]))
- .attr("class", (d,i) -> "pt#{i}")
- .attr("r", pointsize)
- .attr("fill", (d,i) -> pointcolor[group[i]])
- .attr("stroke", pointstroke)
- .attr("stroke-width", "1")
- .attr("opacity", (d,i) ->
- return 1 if (x[i]? or xNA.handle) and (y[i]? or yNA.handle)
- return 0)
- .on("mouseover.paneltip", indtip.show)
- .on("mouseout.paneltip", indtip.hide)
-
- # box
- g.append("rect")
- .attr("x", margin.left+paneloffset)
- .attr("y", margin.top)
- .attr("height", panelheight)
- .attr("width", panelwidth)
- .attr("fill", "none")
- .attr("stroke", "black")
- .attr("stroke-width", "none")
- if xNA.handle
- g.append("rect")
- .attr("x", margin.left)
- .attr("y", margin.top)
- .attr("height", panelheight)
- .attr("width", xNA.width)
- .attr("fill", "none")
- .attr("stroke", "black")
- .attr("stroke-width", "none")
- if xNA.handle and yNA.handle
- g.append("rect")
- .attr("x", margin.left)
- .attr("y", margin.top+height - yNA.width)
- .attr("height", yNA.width)
- .attr("width", xNA.width)
- .attr("fill", "none")
- .attr("stroke", "black")
- .attr("stroke-width", "none")
- if yNA.handle
- g.append("rect")
- .attr("x", margin.left+paneloffset)
- .attr("y", margin.top+height-yNA.width)
- .attr("height", yNA.width)
- .attr("width", panelwidth)
- .attr("fill", "none")
- .attr("stroke", "black")
- .attr("stroke-width", "none")
-
- ## configuration parameters
- chart.width = (value) ->
- return width if !arguments.length
- width = value
- chart
-
- chart.height = (value) ->
- return height if !arguments.length
- height = value
- chart
-
- chart.margin = (value) ->
- return margin if !arguments.length
- margin = value
- chart
-
- chart.axispos = (value) ->
- return axispos if !arguments.length
- axispos = value
- chart
-
- chart.titlepos = (value) ->
- return titlepos if !arguments.length
- titlepos
- chart
-
- chart.xlim = (value) ->
- return xlim if !arguments.length
- xlim = value
- chart
-
- chart.nxticks = (value) ->
- return nxticks if !arguments.length
- nxticks = value
- chart
-
- chart.xticks = (value) ->
- return xticks if !arguments.length
- xticks = value
- chart
-
- chart.ylim = (value) ->
- return ylim if !arguments.length
- ylim = value
- chart
-
- chart.nyticks = (value) ->
- return nyticks if !arguments.length
- nyticks = value
- chart
-
- chart.yticks = (value) ->
- return yticks if !arguments.length
- yticks = value
- chart
-
- chart.rectcolor = (value) ->
- return rectcolor if !arguments.length
- rectcolor = value
- chart
-
- chart.pointcolor = (value) ->
- return pointcolor if !arguments.length
- pointcolor = value
- chart
-
- chart.pointsize = (value) ->
- return pointsize if !arguments.length
- pointsize = value
- chart
-
- chart.pointstroke = (value) ->
- return pointstroke if !arguments.length
- pointstroke = value
- chart
-
- chart.dataByInd = (value) ->
- return dataByInd if !arguments.length
- dataByInd = value
- chart
-
- chart.title = (value) ->
- return title if !arguments.length
- title = value
- chart
-
- chart.xlab = (value) ->
- return xlab if !arguments.length
- xlab = value
- chart
-
- chart.ylab = (value) ->
- return ylab if !arguments.length
- ylab = value
- chart
-
- chart.rotate_ylab = (value) ->
- return rotate_ylab if !arguments.length
- rotate_ylab = value
- chart
-
- chart.xvar = (value) ->
- return xvar if !arguments.length
- xvar = value
- chart
-
- chart.yvar = (value) ->
- return yvar if !arguments.length
- yvar = value
- chart
-
- chart.xNA = (value) ->
- return xNA if !arguments.length
- xNA = value
- chart
-
- chart.yNA = (value) ->
- return yNA if !arguments.length
- yNA = value
- chart
-
- chart.yscale = () ->
- return yscale
-
- chart.xscale = () ->
- return xscale
-
- chart.pointsSelect = () ->
- return pointsSelect
-
- # return the chart function
- chart
-
-probability_plot()
\ No newline at end of file diff --git a/wqflask/wqflask/static/new/javascript/probability_plot.js b/wqflask/wqflask/static/new/javascript/probability_plot.js deleted file mode 100644 index af0f787b..00000000 --- a/wqflask/wqflask/static/new/javascript/probability_plot.js +++ /dev/null @@ -1,408 +0,0 @@ -// Generated by CoffeeScript 1.8.0 -var probability_plot; - -probability_plot = function() { - var axispos, chart, dataByInd, height, margin, nxticks, nyticks, pointcolor, pointsSelect, pointsize, pointstroke, rectcolor, rotate_ylab, title, titlepos, width, xNA, xlab, xlim, xscale, xticks, xvar, yNA, ylab, ylim, yscale, yticks, yvar; - width = 800; - height = 600; - margin = { - left: 60, - top: 40, - right: 40, - bottom: 40, - inner: 5 - }; - axispos = { - xtitle: 25, - ytitle: 45, - xlabel: 5, - ylabel: 5 - }; - titlepos = 20; - xNA = { - handle: true, - force: false, - width: 15, - gap: 10 - }; - yNA = { - handle: true, - force: false, - width: 15, - gap: 10 - }; - xlim = null; - ylim = null; - nxticks = 5; - xticks = null; - nyticks = 5; - yticks = null; - rectcolor = d3.rgb(230, 230, 230); - pointcolor = null; - pointstroke = "black"; - pointsize = 3; - title = "Correlation Scatterplot"; - xlab = "X"; - ylab = "Y"; - rotate_ylab = null; - yscale = d3.scale.linear(); - xscale = d3.scale.linear(); - xvar = 0; - yvar = 1; - pointsSelect = null; - dataByInd = false; - chart = function(selection) { - return selection.each(function(data) { - var g, gEnter, group, i, indID, indtip, na_value, ngroup, panelheight, paneloffset, panelwidth, points, svg, titlegrp, x, xaxis, xrange, xs, y, yaxis, yrange, ys, _i, _ref, _ref1, _ref2, _results; - if (dataByInd) { - x = data.data.map(function(d) { - return d[xvar]; - }); - y = data.data.map(function(d) { - return d[yvar]; - }); - } else { - x = data.data[xvar]; - y = data.data[yvar]; - } - console.log("x:", x); - console.log("y:", y); - indID = (_ref = data != null ? data.indID : void 0) != null ? _ref : null; - indID = indID != null ? indID : (function() { - _results = []; - for (var _i = 1, _ref1 = x.length; 1 <= _ref1 ? _i <= _ref1 : _i >= _ref1; 1 <= _ref1 ? _i++ : _i--){ _results.push(_i); } - return _results; - }).apply(this); - console.log("indID:", indID); - group = (_ref2 = data != null ? data.group : void 0) != null ? _ref2 : (function() { - var _j, _len, _results1; - _results1 = []; - for (_j = 0, _len = x.length; _j < _len; _j++) { - i = x[_j]; - _results1.push(1); - } - return _results1; - })(); - ngroup = d3.max(group); - group = (function() { - var _j, _len, _results1; - _results1 = []; - for (_j = 0, _len = group.length; _j < _len; _j++) { - g = group[_j]; - _results1.push(g - 1); - } - return _results1; - })(); - pointcolor = pointcolor != null ? pointcolor : selectGroupColors(ngroup, "dark"); - pointcolor = expand2vector(pointcolor, ngroup); - if (x.every(function(v) { - return (v != null) && !xNA.force; - })) { - xNA.handle = false; - } - if (y.every(function(v) { - return (v != null) && !yNA.force; - })) { - yNA.handle = false; - } - if (xNA.handle) { - paneloffset = xNA.width + xNA.gap; - panelwidth = width - paneloffset; - } else { - paneloffset = 0; - panelwidth = width; - } - if (yNA.handle) { - panelheight = height - (yNA.width + yNA.gap); - } else { - panelheight = height; - } - xlim = xlim != null ? xlim : d3.extent(x); - ylim = ylim != null ? ylim : d3.extent(y); - na_value = d3.min(x.concat(y)) - 100; - svg = d3.select(this).selectAll("svg").data([data]); - gEnter = svg.enter().append("svg").append("g"); - svg.attr("width", width + margin.left + margin.right).attr("height", height + margin.top + margin.bottom); - g = svg.select("g"); - g.append("rect").attr("x", paneloffset + margin.left).attr("y", margin.top).attr("height", panelheight).attr("width", panelwidth).attr("fill", rectcolor).attr("stroke", "none"); - if (xNA.handle) { - g.append("rect").attr("x", margin.left).attr("y", margin.top).attr("height", panelheight).attr("width", xNA.width).attr("fill", rectcolor).attr("stroke", "none"); - } - if (xNA.handle && yNA.handle) { - g.append("rect").attr("x", margin.left).attr("y", margin.top + height - yNA.width).attr("height", yNA.width).attr("width", xNA.width).attr("fill", rectcolor).attr("stroke", "none"); - } - if (yNA.handle) { - g.append("rect").attr("x", margin.left + paneloffset).attr("y", margin.top + height - yNA.width).attr("height", yNA.width).attr("width", panelwidth).attr("fill", rectcolor).attr("stroke", "none"); - } - xrange = [margin.left + paneloffset + margin.inner, margin.left + paneloffset + panelwidth - margin.inner]; - yrange = [margin.top + panelheight - margin.inner, margin.top + margin.inner]; - xscale.domain(xlim).range(xrange); - yscale.domain(ylim).range(yrange); - xs = d3.scale.linear().domain(xlim).range(xrange); - ys = d3.scale.linear().domain(ylim).range(yrange); - if (xNA.handle) { - xscale.domain([na_value].concat(xlim)).range([margin.left + xNA.width / 2].concat(xrange)); - x = x.map(function(e) { - if (e != null) { - return e; - } else { - return na_value; - } - }); - } - if (yNA.handle) { - yscale.domain([na_value].concat(ylim)).range([height + margin.top - yNA.width / 2].concat(yrange)); - y = y.map(function(e) { - if (e != null) { - return e; - } else { - return na_value; - } - }); - } - yticks = yticks != null ? yticks : ys.ticks(nyticks); - xticks = xticks != null ? xticks : xs.ticks(nxticks); - titlegrp = g.append("g").attr("class", "title").append("text").attr("x", margin.left + width / 2).attr("y", margin.top - titlepos).text(title); - xaxis = g.append("g").attr("class", "x axis"); - xaxis.selectAll("empty").data(xticks).enter().append("line").attr("x1", function(d) { - return xscale(d); - }).attr("x2", function(d) { - return xscale(d); - }).attr("y1", margin.top).attr("y2", margin.top + height).attr("fill", "none").attr("stroke", "white").attr("stroke-width", 1).style("pointer-events", "none"); - xaxis.selectAll("empty").data(xticks).enter().append("text").attr("x", function(d) { - return xscale(d); - }).attr("y", margin.top + height + axispos.xlabel).text(function(d) { - return formatAxis(xticks)(d); - }); - xaxis.append("text").attr("class", "title").attr("x", margin.left + width / 2).attr("y", margin.top + height + axispos.xtitle).text(xlab); - if (xNA.handle) { - xaxis.append("text").attr("x", margin.left + xNA.width / 2).attr("y", margin.top + height + axispos.xlabel).text("N/A"); - } - rotate_ylab = rotate_ylab != null ? rotate_ylab : ylab.length > 1; - yaxis = g.append("g").attr("class", "y axis"); - yaxis.selectAll("empty").data(yticks).enter().append("line").attr("y1", function(d) { - return yscale(d); - }).attr("y2", function(d) { - return yscale(d); - }).attr("x1", margin.left).attr("x2", margin.left + width).attr("fill", "none").attr("stroke", "white").attr("stroke-width", 1).style("pointer-events", "none"); - yaxis.selectAll("empty").data(yticks).enter().append("text").attr("y", function(d) { - return yscale(d); - }).attr("x", margin.left - axispos.ylabel).text(function(d) { - return formatAxis(yticks)(d); - }); - yaxis.append("text").attr("class", "title").attr("y", margin.top + height / 2).attr("x", margin.left - axispos.ytitle).text(ylab).attr("transform", rotate_ylab ? "rotate(270," + (margin.left - axispos.ytitle) + "," + (margin.top + height / 2) + ")" : ""); - if (yNA.handle) { - yaxis.append("text").attr("x", margin.left - axispos.ylabel).attr("y", margin.top + height - yNA.width / 2).text("N/A"); - } - indtip = d3.tip().attr('class', 'd3-tip').html(function(d, i) { - return indID[i]; - }).direction('e').offset([0, 10]); - svg.call(indtip); - points = g.append("g").attr("id", "points"); - pointsSelect = points.selectAll("empty").data(d3.range(x.length)).enter().append("circle").attr("cx", function(d, i) { - return xscale(x[i]); - }).attr("cy", function(d, i) { - return yscale(y[i]); - }).attr("class", function(d, i) { - return "pt" + i; - }).attr("r", pointsize).attr("fill", function(d, i) { - return pointcolor[group[i]]; - }).attr("stroke", pointstroke).attr("stroke-width", "1").attr("opacity", function(d, i) { - if (((x[i] != null) || xNA.handle) && ((y[i] != null) || yNA.handle)) { - return 1; - } - return 0; - }).on("mouseover.paneltip", indtip.show).on("mouseout.paneltip", indtip.hide); - g.append("rect").attr("x", margin.left + paneloffset).attr("y", margin.top).attr("height", panelheight).attr("width", panelwidth).attr("fill", "none").attr("stroke", "black").attr("stroke-width", "none"); - if (xNA.handle) { - g.append("rect").attr("x", margin.left).attr("y", margin.top).attr("height", panelheight).attr("width", xNA.width).attr("fill", "none").attr("stroke", "black").attr("stroke-width", "none"); - } - if (xNA.handle && yNA.handle) { - g.append("rect").attr("x", margin.left).attr("y", margin.top + height - yNA.width).attr("height", yNA.width).attr("width", xNA.width).attr("fill", "none").attr("stroke", "black").attr("stroke-width", "none"); - } - if (yNA.handle) { - return g.append("rect").attr("x", margin.left + paneloffset).attr("y", margin.top + height - yNA.width).attr("height", yNA.width).attr("width", panelwidth).attr("fill", "none").attr("stroke", "black").attr("stroke-width", "none"); - } - }); - }; - chart.width = function(value) { - if (!arguments.length) { - return width; - } - width = value; - return chart; - }; - chart.height = function(value) { - if (!arguments.length) { - return height; - } - height = value; - return chart; - }; - chart.margin = function(value) { - if (!arguments.length) { - return margin; - } - margin = value; - return chart; - }; - chart.axispos = function(value) { - if (!arguments.length) { - return axispos; - } - axispos = value; - return chart; - }; - chart.titlepos = function(value) { - if (!arguments.length) { - return titlepos; - } - titlepos; - return chart; - }; - chart.xlim = function(value) { - if (!arguments.length) { - return xlim; - } - xlim = value; - return chart; - }; - chart.nxticks = function(value) { - if (!arguments.length) { - return nxticks; - } - nxticks = value; - return chart; - }; - chart.xticks = function(value) { - if (!arguments.length) { - return xticks; - } - xticks = value; - return chart; - }; - chart.ylim = function(value) { - if (!arguments.length) { - return ylim; - } - ylim = value; - return chart; - }; - chart.nyticks = function(value) { - if (!arguments.length) { - return nyticks; - } - nyticks = value; - return chart; - }; - chart.yticks = function(value) { - if (!arguments.length) { - return yticks; - } - yticks = value; - return chart; - }; - chart.rectcolor = function(value) { - if (!arguments.length) { - return rectcolor; - } - rectcolor = value; - return chart; - }; - chart.pointcolor = function(value) { - if (!arguments.length) { - return pointcolor; - } - pointcolor = value; - return chart; - }; - chart.pointsize = function(value) { - if (!arguments.length) { - return pointsize; - } - pointsize = value; - return chart; - }; - chart.pointstroke = function(value) { - if (!arguments.length) { - return pointstroke; - } - pointstroke = value; - return chart; - }; - chart.dataByInd = function(value) { - if (!arguments.length) { - return dataByInd; - } - dataByInd = value; - return chart; - }; - chart.title = function(value) { - if (!arguments.length) { - return title; - } - title = value; - return chart; - }; - chart.xlab = function(value) { - if (!arguments.length) { - return xlab; - } - xlab = value; - return chart; - }; - chart.ylab = function(value) { - if (!arguments.length) { - return ylab; - } - ylab = value; - return chart; - }; - chart.rotate_ylab = function(value) { - if (!arguments.length) { - return rotate_ylab; - } - rotate_ylab = value; - return chart; - }; - chart.xvar = function(value) { - if (!arguments.length) { - return xvar; - } - xvar = value; - return chart; - }; - chart.yvar = function(value) { - if (!arguments.length) { - return yvar; - } - yvar = value; - return chart; - }; - chart.xNA = function(value) { - if (!arguments.length) { - return xNA; - } - xNA = value; - return chart; - }; - chart.yNA = function(value) { - if (!arguments.length) { - return yNA; - } - yNA = value; - return chart; - }; - chart.yscale = function() { - return yscale; - }; - chart.xscale = function() { - return xscale; - }; - chart.pointsSelect = function() { - return pointsSelect; - }; - return chart; -}; - -probability_plot(); diff --git a/wqflask/wqflask/static/new/javascript/scatterplot.coffee b/wqflask/wqflask/static/new/javascript/scatterplot.coffee index 6a96e50f..008ab925 100644 --- a/wqflask/wqflask/static/new/javascript/scatterplot.coffee +++ b/wqflask/wqflask/static/new/javascript/scatterplot.coffee @@ -427,4 +427,4 @@ scatterplot = () -> # return the chart function
chart
-root.scatterplot = scatterplot
\ No newline at end of file +root.scatterplot = scatterplot
diff --git a/wqflask/wqflask/static/new/javascript/show_trait.coffee b/wqflask/wqflask/static/new/javascript/show_trait.coffee index 1d3123ba..91aa15ba 100755 --- a/wqflask/wqflask/static/new/javascript/show_trait.coffee +++ b/wqflask/wqflask/static/new/javascript/show_trait.coffee @@ -61,38 +61,16 @@ Stat_Table_Rows = [ ] $ -> - sample_lists = js_data.sample_lists - sample_group_types = js_data.sample_group_types - #if $("#update_bar_chart").length - # $("#update_bar_chart.btn-group").button() - root.bar_chart = new Bar_Chart(sample_lists[0]) - root.histogram = new Histogram(sample_lists[0]) - new Box_Plot(sample_lists[0]) + add = -> + trait = $("input[name=trait_hmac]").val() + console.log("trait is:", trait) + $.colorbox({href:"/collections/add?traits=#{trait}"}) - $('.bar_chart_samples_group').change -> - $('#bar_chart').remove() - $('#bar_chart_container').append('<div id="bar_chart"></div>') - group = $(this).val() - if group == "samples_primary" - root.bar_chart = new Bar_Chart(sample_lists[0]) - else if group == "samples_other" - root.bar_chart = new Bar_Chart(sample_lists[1]) - else if group == "samples_all" - all_samples = sample_lists[0].concat sample_lists[1] - root.bar_chart = new Bar_Chart(all_samples) - - $('.box_plot_samples_group').change -> - $('#box_plot').remove() - $('#box_plot_container').append('<div id="box_plot"></div>') - group = $(this).val() - if group == "samples_primary" - new Box_Plot(sample_lists[0]) - else if group == "samples_other" - new Box_Plot(sample_lists[1]) - else if group == "samples_all" - all_samples = sample_lists[0].concat sample_lists[1] - new Box_Plot(all_samples) + $('#add_to_collection').click(add) + + sample_lists = js_data.sample_lists + sample_group_types = js_data.sample_group_types d3.select("#select_compare_trait").on("click", => $('.scatter-matrix-container').remove() @@ -126,7 +104,7 @@ $ -> $("#stats_tabs" + selected).show() - change_stats_value = (sample_sets, category, value_type, decimal_places)-> + change_stats_value = (sample_sets, category, value_type, decimal_places, effects) -> id = "#" + process_id(category, value_type) console.log("the_id:", id) in_box = $(id).html @@ -145,7 +123,10 @@ $ -> console.log("*-* current_value:", current_value) if the_value != current_value console.log("object:", $(id).html(the_value)) - $(id).html(the_value).effect("highlight") + if effects + $(id).html(the_value).effect("highlight") + else + $(id).html(the_value) # We go ahead and always change the title value if we have it if title_value @@ -153,11 +134,20 @@ $ -> update_stat_values = (sample_sets)-> + show_effects = $(".tab-pane.active").attr("id") == "stats_tab" for category in ['samples_primary', 'samples_other', 'samples_all'] for row in Stat_Table_Rows console.log("Calling change_stats_value") - change_stats_value(sample_sets, category, row.vn, row.digits) + change_stats_value(sample_sets, category, row.vn, row.digits, show_effects) + + redraw_histogram = -> + root.histogram.redraw((x.value for x in _.values(root.selected_samples[root.histogram_group]))) + + redraw_bar_chart = -> + root.bar_chart.redraw(root.selected_samples, root.bar_chart_group) + redraw_prob_plot = -> + root.redraw_prob_plot_impl(root.selected_samples, root.prob_plot_group) make_table = -> header = "<thead><tr><th> </th>" @@ -213,6 +203,11 @@ $ -> samples_other: new Stats([]) samples_all: new Stats([]) + root.selected_samples = # maps: sample name -> value + samples_primary: {} + samples_other: {} + samples_all: {} + console.log("at beginning:", sample_sets) tables = ['samples_primary', 'samples_other'] @@ -222,25 +217,43 @@ $ -> name = $(row).find('.edit_sample_sample_name').html() name = $.trim(name) real_value = $(row).find('.edit_sample_value').val() - console.log("real_value:", real_value) + #console.log("real_value:", real_value) checkbox = $(row).find(".edit_sample_checkbox") - checked = $(checkbox).attr('checked') + checked = $(checkbox).prop('checked') if checked and is_number(real_value) and real_value != "" - console.log("in the iffy if") + #console.log("in the iffy if") real_value = parseFloat(real_value) sample_sets[table].add_value(real_value) - console.log("checking name of:", name) + + real_variance = $(row).find('.edit_sample_se').val() + if (is_number(real_variance)) + real_variance = parseFloat(real_variance) + else + real_variance = null + real_dict = {value: real_value, variance: real_variance} + root.selected_samples[table][name] = real_dict + + #console.log("checking name of:", name) if not (name of already_seen) - console.log("haven't seen") + #console.log("haven't seen") sample_sets['samples_all'].add_value(real_value) + root.selected_samples['samples_all'][name] = real_dict already_seen[name] = true console.log("towards end:", sample_sets) - root.histogram.redraw(sample_sets['samples_primary'].the_values) update_stat_values(sample_sets) + console.log("redrawing histogram") + redraw_histogram() + + console.log("redrawing bar chart") + redraw_bar_chart() + + console.log("redrawing probability plot") + redraw_prob_plot() + show_hide_outliers = -> console.log("FOOBAR in beginning of show_hide_outliers") label = $('#show_hide_outliers').val() @@ -427,11 +440,37 @@ $ -> console.log("after registering block_outliers") _.mixin(_.str.exports()); # Add string fuctions directly to underscore - $('#edit_sample_lists').change(edit_data_change) - console.log("loaded") - #console.log("basic_table is:", basic_table) - # Add back following two lines later + + root.histogram_group = 'samples_primary' + root.histogram = new Histogram(sample_lists[0]) + $('.histogram_samples_group').val(root.histogram_group) + $('.histogram_samples_group').change -> + root.histogram_group = $(this).val() + redraw_histogram() + + root.bar_chart_group = 'samples_primary' + root.bar_chart = new Bar_Chart(sample_lists) + $('.bar_chart_samples_group').val(root.bar_chart_group) + $('.bar_chart_samples_group').change -> + root.bar_chart_group = $(this).val() + redraw_bar_chart() + + root.prob_plot_group = 'samples_primary' + $('.prob_plot_samples_group').val(root.prob_plot_group) + $('.prob_plot_samples_group').change -> + root.prob_plot_group = $(this).val() + redraw_prob_plot() + make_table() edit_data_change() # Set the values at the beginning + + $('#edit_sample_lists').change(edit_data_change) + + # bind additional handlers for pushing data updates + $('#block_by_index').click(edit_data_change) + $('#exclude_group').click(edit_data_change) + $('#block_outliers').click(edit_data_change) + $('#reset').click(edit_data_change) + #$("#all-mean").html('foobar8') console.log("end") diff --git a/wqflask/wqflask/static/new/javascript/show_trait.js b/wqflask/wqflask/static/new/javascript/show_trait.js index 9323862a..302d5ec4 100755 --- a/wqflask/wqflask/static/new/javascript/show_trait.js +++ b/wqflask/wqflask/static/new/javascript/show_trait.js @@ -1,8 +1,8 @@ -// Generated by CoffeeScript 1.9.2 +// Generated by CoffeeScript 1.8.0 (function() { var Stat_Table_Rows, is_number, - hasProp = {}.hasOwnProperty, - slice = [].slice; + __hasProp = {}.hasOwnProperty, + __slice = [].slice; console.log("start_b"); @@ -56,40 +56,18 @@ ]; $(function() { - var block_by_attribute_value, block_by_index, block_outliers, change_stats_value, create_value_dropdown, edit_data_change, export_sample_table_data, get_sample_table_data, hide_no_value, hide_tabs, make_table, on_corr_method_change, open_trait_selection, populate_sample_attributes_values_dropdown, process_id, reset_samples_table, sample_group_types, sample_lists, show_hide_outliers, stats_mdp_change, update_stat_values; + var add, block_by_attribute_value, block_by_index, block_outliers, change_stats_value, create_value_dropdown, edit_data_change, export_sample_table_data, get_sample_table_data, hide_no_value, hide_tabs, make_table, on_corr_method_change, open_trait_selection, populate_sample_attributes_values_dropdown, process_id, redraw_bar_chart, redraw_histogram, redraw_prob_plot, reset_samples_table, sample_group_types, sample_lists, show_hide_outliers, stats_mdp_change, update_stat_values; + add = function() { + var trait; + trait = $("input[name=trait_hmac]").val(); + console.log("trait is:", trait); + return $.colorbox({ + href: "/collections/add?traits=" + trait + }); + }; + $('#add_to_collection').click(add); sample_lists = js_data.sample_lists; sample_group_types = js_data.sample_group_types; - root.bar_chart = new Bar_Chart(sample_lists[0]); - root.histogram = new Histogram(sample_lists[0]); - new Box_Plot(sample_lists[0]); - $('.bar_chart_samples_group').change(function() { - var all_samples, group; - $('#bar_chart').remove(); - $('#bar_chart_container').append('<div id="bar_chart"></div>'); - group = $(this).val(); - if (group === "samples_primary") { - return root.bar_chart = new Bar_Chart(sample_lists[0]); - } else if (group === "samples_other") { - return root.bar_chart = new Bar_Chart(sample_lists[1]); - } else if (group === "samples_all") { - all_samples = sample_lists[0].concat(sample_lists[1]); - return root.bar_chart = new Bar_Chart(all_samples); - } - }); - $('.box_plot_samples_group').change(function() { - var all_samples, group; - $('#box_plot').remove(); - $('#box_plot_container').append('<div id="box_plot"></div>'); - group = $(this).val(); - if (group === "samples_primary") { - return new Box_Plot(sample_lists[0]); - } else if (group === "samples_other") { - return new Box_Plot(sample_lists[1]); - } else if (group === "samples_all") { - all_samples = sample_lists[0].concat(sample_lists[1]); - return new Box_Plot(all_samples); - } - }); d3.select("#select_compare_trait").on("click", (function(_this) { return function() { $('.scatter-matrix-container').remove(); @@ -113,12 +91,12 @@ })(this)); }; hide_tabs = function(start) { - var i, ref, results, x; - results = []; - for (x = i = ref = start; ref <= 10 ? i <= 10 : i >= 10; x = ref <= 10 ? ++i : --i) { - results.push($("#stats_tabs" + x).hide()); + var x, _i, _results; + _results = []; + for (x = _i = start; start <= 10 ? _i <= 10 : _i >= 10; x = start <= 10 ? ++_i : --_i) { + _results.push($("#stats_tabs" + x).hide()); } - return results; + return _results; }; stats_mdp_change = function() { var selected; @@ -126,7 +104,7 @@ hide_tabs(0); return $("#stats_tabs" + selected).show(); }; - change_stats_value = function(sample_sets, category, value_type, decimal_places) { + change_stats_value = function(sample_sets, category, value_type, decimal_places, effects) { var current_value, id, in_box, the_value, title_value; id = "#" + process_id(category, value_type); console.log("the_id:", id); @@ -144,39 +122,63 @@ console.log("*-* current_value:", current_value); if (the_value !== current_value) { console.log("object:", $(id).html(the_value)); - $(id).html(the_value).effect("highlight"); + if (effects) { + $(id).html(the_value).effect("highlight"); + } else { + $(id).html(the_value); + } } if (title_value) { return $(id).attr('title', title_value); } }; update_stat_values = function(sample_sets) { - var category, i, len, ref, results, row; - ref = ['samples_primary', 'samples_other', 'samples_all']; - results = []; - for (i = 0, len = ref.length; i < len; i++) { - category = ref[i]; - results.push((function() { - var j, len1, results1; - results1 = []; - for (j = 0, len1 = Stat_Table_Rows.length; j < len1; j++) { - row = Stat_Table_Rows[j]; + var category, row, show_effects, _i, _len, _ref, _results; + show_effects = $(".tab-pane.active").attr("id") === "stats_tab"; + _ref = ['samples_primary', 'samples_other', 'samples_all']; + _results = []; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + category = _ref[_i]; + _results.push((function() { + var _j, _len1, _results1; + _results1 = []; + for (_j = 0, _len1 = Stat_Table_Rows.length; _j < _len1; _j++) { + row = Stat_Table_Rows[_j]; console.log("Calling change_stats_value"); - results1.push(change_stats_value(sample_sets, category, row.vn, row.digits)); + _results1.push(change_stats_value(sample_sets, category, row.vn, row.digits, show_effects)); } - return results1; + return _results1; })()); } - return results; + return _results; + }; + redraw_histogram = function() { + var x; + return root.histogram.redraw((function() { + var _i, _len, _ref, _results; + _ref = _.values(root.selected_samples[root.histogram_group]); + _results = []; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + x = _ref[_i]; + _results.push(x.value); + } + return _results; + })()); + }; + redraw_bar_chart = function() { + return root.bar_chart.redraw(root.selected_samples, root.bar_chart_group); + }; + redraw_prob_plot = function() { + return root.redraw_prob_plot_impl(root.selected_samples, root.prob_plot_group); }; make_table = function() { - var header, i, key, len, ref, ref1, row, row_line, table, the_id, the_rows, value; + var header, key, row, row_line, table, the_id, the_rows, value, _i, _len, _ref, _ref1; header = "<thead><tr><th> </th>"; console.log("js_data.sample_group_types:", js_data.sample_group_types); - ref = js_data.sample_group_types; - for (key in ref) { - if (!hasProp.call(ref, key)) continue; - value = ref[key]; + _ref = js_data.sample_group_types; + for (key in _ref) { + if (!__hasProp.call(_ref, key)) continue; + value = _ref[key]; console.log("aa key:", key); console.log("aa value:", value); the_id = process_id("column", key); @@ -185,8 +187,8 @@ header += "</thead>"; console.log("windex header is:", header); the_rows = "<tbody>"; - for (i = 0, len = Stat_Table_Rows.length; i < len; i++) { - row = Stat_Table_Rows[i]; + for (_i = 0, _len = Stat_Table_Rows.length; _i < _len; _i++) { + row = Stat_Table_Rows[_i]; console.log("rowing"); row_line = "<tr>"; if (row.url != null) { @@ -195,10 +197,10 @@ row_line += "<td id=\"" + row.vn + "\">" + row.pretty + "</td>"; } console.log("box - js_data.sample_group_types:", js_data.sample_group_types); - ref1 = js_data.sample_group_types; - for (key in ref1) { - if (!hasProp.call(ref1, key)) continue; - value = ref1[key]; + _ref1 = js_data.sample_group_types; + for (key in _ref1) { + if (!__hasProp.call(_ref1, key)) continue; + value = _ref1[key]; console.log("apple key:", key); the_id = process_id(key, row.vn); console.log("the_id:", the_id); @@ -214,13 +216,13 @@ return $("#stats_table").append(table); }; process_id = function() { - var i, len, processed, value, values; - values = 1 <= arguments.length ? slice.call(arguments, 0) : []; + var processed, value, values, _i, _len; + values = 1 <= arguments.length ? __slice.call(arguments, 0) : []; /* Make an id or a class valid javascript by, for example, eliminating spaces */ processed = ""; - for (i = 0, len = values.length; i < len; i++) { - value = values[i]; + for (_i = 0, _len = values.length; _i < _len; _i++) { + value = values[_i]; console.log("value:", value); value = value.replace(" ", "_"); if (processed.length) { @@ -231,42 +233,60 @@ return processed; }; edit_data_change = function() { - var already_seen, checkbox, checked, i, j, len, len1, name, real_value, row, rows, sample_sets, table, tables; + var already_seen, checkbox, checked, name, real_dict, real_value, real_variance, row, rows, sample_sets, table, tables, _i, _j, _len, _len1; already_seen = {}; sample_sets = { samples_primary: new Stats([]), samples_other: new Stats([]), samples_all: new Stats([]) }; + root.selected_samples = { + samples_primary: {}, + samples_other: {}, + samples_all: {} + }; console.log("at beginning:", sample_sets); tables = ['samples_primary', 'samples_other']; - for (i = 0, len = tables.length; i < len; i++) { - table = tables[i]; + for (_i = 0, _len = tables.length; _i < _len; _i++) { + table = tables[_i]; rows = $("#" + table).find('tr'); - for (j = 0, len1 = rows.length; j < len1; j++) { - row = rows[j]; + for (_j = 0, _len1 = rows.length; _j < _len1; _j++) { + row = rows[_j]; name = $(row).find('.edit_sample_sample_name').html(); name = $.trim(name); real_value = $(row).find('.edit_sample_value').val(); - console.log("real_value:", real_value); checkbox = $(row).find(".edit_sample_checkbox"); - checked = $(checkbox).attr('checked'); + checked = $(checkbox).prop('checked'); if (checked && is_number(real_value) && real_value !== "") { - console.log("in the iffy if"); real_value = parseFloat(real_value); sample_sets[table].add_value(real_value); - console.log("checking name of:", name); + real_variance = $(row).find('.edit_sample_se').val(); + if (is_number(real_variance)) { + real_variance = parseFloat(real_variance); + } else { + real_variance = null; + } + real_dict = { + value: real_value, + variance: real_variance + }; + root.selected_samples[table][name] = real_dict; if (!(name in already_seen)) { - console.log("haven't seen"); sample_sets['samples_all'].add_value(real_value); + root.selected_samples['samples_all'][name] = real_dict; already_seen[name] = true; } } } } console.log("towards end:", sample_sets); - root.histogram.redraw(sample_sets['samples_primary'].the_values); - return update_stat_values(sample_sets); + update_stat_values(sample_sets); + console.log("redrawing histogram"); + redraw_histogram(); + console.log("redrawing bar chart"); + redraw_bar_chart(); + console.log("redrawing probability plot"); + return redraw_prob_plot(); }; show_hide_outliers = function() { var label; @@ -299,26 +319,26 @@ return "<option val=" + value + ">" + value + "</option>"; }; populate_sample_attributes_values_dropdown = function() { - var attribute_info, i, key, len, ref, ref1, results, sample_attributes, selected_attribute, value; + var attribute_info, key, sample_attributes, selected_attribute, value, _i, _len, _ref, _ref1, _results; console.log("in beginning of psavd"); $('#attribute_values').empty(); sample_attributes = {}; - ref = js_data.attribute_names; - for (key in ref) { - if (!hasProp.call(ref, key)) continue; - attribute_info = ref[key]; + _ref = js_data.attribute_names; + for (key in _ref) { + if (!__hasProp.call(_ref, key)) continue; + attribute_info = _ref[key]; sample_attributes[attribute_info.name] = attribute_info.distinct_values; } console.log("[visa] attributes is:", sample_attributes); selected_attribute = $('#exclude_menu').val().replace("_", " "); console.log("selected_attribute is:", selected_attribute); - ref1 = sample_attributes[selected_attribute]; - results = []; - for (i = 0, len = ref1.length; i < len; i++) { - value = ref1[i]; - results.push($(create_value_dropdown(value)).appendTo($('#attribute_values'))); + _ref1 = sample_attributes[selected_attribute]; + _results = []; + for (_i = 0, _len = _ref1.length; _i < _len; _i++) { + value = _ref1[_i]; + _results.push($(create_value_dropdown(value)).appendTo($('#attribute_values'))); } - return results; + return _results; }; if (js_data.attribute_names.length > 0) { populate_sample_attributes_values_dropdown(); @@ -341,17 +361,17 @@ }; $('#exclude_group').click(block_by_attribute_value); block_by_index = function() { - var end_index, error, i, index, index_list, index_set, index_string, j, k, len, len1, ref, ref1, ref2, results, start_index; + var end_index, error, index, index_list, index_set, index_string, start_index, _i, _j, _k, _len, _len1, _ref, _results; index_string = $('#remove_samples_field').val(); index_list = []; - ref = index_string.split(","); - for (i = 0, len = ref.length; i < len; i++) { - index_set = ref[i]; + _ref = index_string.split(","); + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + index_set = _ref[_i]; if (index_set.indexOf('-') !== -1) { try { start_index = parseInt(index_set.split("-")[0]); end_index = parseInt(index_set.split("-")[1]); - for (index = j = ref1 = start_index, ref2 = end_index; ref1 <= ref2 ? j <= ref2 : j >= ref2; index = ref1 <= ref2 ? ++j : --j) { + for (index = _j = start_index; start_index <= end_index ? _j <= end_index : _j >= end_index; index = start_index <= end_index ? ++_j : --_j) { index_list.push(index); } } catch (_error) { @@ -365,22 +385,22 @@ } } console.log("index_list:", index_list); - results = []; - for (k = 0, len1 = index_list.length; k < len1; k++) { - index = index_list[k]; + _results = []; + for (_k = 0, _len1 = index_list.length; _k < _len1; _k++) { + index = index_list[_k]; if ($('#block_group').val() === "primary") { console.log("block_group:", $('#block_group').val()); console.log("row:", $('#Primary_' + index.toString())); - results.push($('#Primary_' + index.toString()).find('.trait_value_input').val("x")); + _results.push($('#Primary_' + index.toString()).find('.trait_value_input').val("x")); } else if ($('#block_group').val() === "other") { console.log("block_group:", $('#block_group').val()); console.log("row:", $('#Other_' + index.toString())); - results.push($('#Other_' + index.toString()).find('.trait_value_input').val("x")); + _results.push($('#Other_' + index.toString()).find('.trait_value_input').val("x")); } else { - results.push(void 0); + _results.push(void 0); } } - return results; + return _results; }; $('#block_by_index').click(block_by_index); hide_no_value = function() { @@ -417,17 +437,17 @@ samples = []; $('#' + table_name).find('.value_se').each((function(_this) { return function(_index, element) { - var attribute_info, key, ref, row_data; + var attribute_info, key, row_data, _ref; row_data = {}; row_data.name = $.trim($(element).find('.column_name-Sample').text()); row_data.value = $(element).find('.edit_sample_value').val(); if ($(element).find('.edit_sample_se').length !== -1) { row_data.se = $(element).find('.edit_sample_se').val(); } - ref = js_data.attribute_names; - for (key in ref) { - if (!hasProp.call(ref, key)) continue; - attribute_info = ref[key]; + _ref = js_data.attribute_names; + for (key in _ref) { + if (!__hasProp.call(_ref, key)) continue; + attribute_info = _ref[key]; row_data[attribute_info.name] = $.trim($(element).find('.column_name-' + attribute_info.name.replace(" ", "_")).text()); } console.log("row_data is:", row_data); @@ -460,10 +480,33 @@ $('#block_outliers').click(block_outliers); console.log("after registering block_outliers"); _.mixin(_.str.exports()); - $('#edit_sample_lists').change(edit_data_change); - console.log("loaded"); + root.histogram_group = 'samples_primary'; + root.histogram = new Histogram(sample_lists[0]); + $('.histogram_samples_group').val(root.histogram_group); + $('.histogram_samples_group').change(function() { + root.histogram_group = $(this).val(); + return redraw_histogram(); + }); + root.bar_chart_group = 'samples_primary'; + root.bar_chart = new Bar_Chart(sample_lists); + $('.bar_chart_samples_group').val(root.bar_chart_group); + $('.bar_chart_samples_group').change(function() { + root.bar_chart_group = $(this).val(); + return redraw_bar_chart(); + }); + root.prob_plot_group = 'samples_primary'; + $('.prob_plot_samples_group').val(root.prob_plot_group); + $('.prob_plot_samples_group').change(function() { + root.prob_plot_group = $(this).val(); + return redraw_prob_plot(); + }); make_table(); edit_data_change(); + $('#edit_sample_lists').change(edit_data_change); + $('#block_by_index').click(edit_data_change); + $('#exclude_group').click(edit_data_change); + $('#block_outliers').click(edit_data_change); + $('#reset').click(edit_data_change); return console.log("end"); }); diff --git a/wqflask/wqflask/static/new/javascript/show_trait_mapping_tools.coffee b/wqflask/wqflask/static/new/javascript/show_trait_mapping_tools.coffee index 881ea74d..16ca1886 100755 --- a/wqflask/wqflask/static/new/javascript/show_trait_mapping_tools.coffee +++ b/wqflask/wqflask/static/new/javascript/show_trait_mapping_tools.coffee @@ -63,10 +63,12 @@ do_ajax_post = (url, form_data) -> console.log(xhr) clearInterval(this.my_timer) $('#progress_bar_container').modal('hide') + $('#static_progress_bar_container').modal('hide') $("body").html("We got an error.") success: (data) => clearInterval(this.my_timer) $('#progress_bar_container').modal('hide') + $('#static_progress_bar_container').modal('hide') open_mapping_results(data) #$("body").html(data) ) @@ -76,36 +78,44 @@ do_ajax_post = (url, form_data) -> return false open_mapping_results = (data) -> + #results_window = window.open("/mapping_results_container") + #results_window.onload = -> + # results_window.document.getElementById("mapping_results_container").innerHTML = data + $.colorbox( - html: data - href: "#mapping_results_holder" - height: "90%" - width: "90%" - ) + html: data + href: "#mapping_results_holder" + height: "90%" + width: "90%" + onComplete: => + root.create_lod_chart() + + #Set filename + filename = "lod_chart_" + js_data.this_trait + + getSvgXml = -> + svg = $("#topchart").find("svg")[0] + (new XMLSerializer).serializeToString(svg) + + $("#exportform > #export").click => + svg_xml = getSvgXml() + form = $("#exportform") + form.find("#data").val(svg_xml) + form.find("#filename").val(filename) + form.submit() + + $("#exportpdfform > #export_pdf").click => + svg_xml = getSvgXml() + form = $("#exportpdfform") + form.find("#data").val(svg_xml) + form.find("#filename").val(filename) + form.submit() + ) showalert = (message,alerttype) -> $('#alert_placeholder').append('<div id="alertdiv" class="alert ' + alerttype + '"><a class="close" data-dismiss="alert">×</a><span>'+message+'</span></div>') -$("#interval_mapping_compute").click(() => - showalert("One or more outliers exist in this data set. Please review values before mapping. \ - Including outliers when mapping may lead to misleading results. \ - We recommend <A HREF=\"http://en.wikipedia.org/wiki/Winsorising\">winsorising</A> the outliers \ - or simply deleting them.", "alert-success") - console.log("In interval mapping") - $("#progress_bar_container").modal() - url = "/interval_mapping" - - $('input[name=method]').val("reaper") - $('input[name=manhattan_plot]').val($('input[name=manhattan_plot_reaper]:checked').val()) - $('input[name=mapping_display_all]').val($('input[name=display_all_reaper]')) - $('input[name=suggestive]').val($('input[name=suggestive_reaper]')) - form_data = $('#trait_data_form').serialize() - console.log("form_data is:", form_data) - - do_ajax_post(url, form_data) -) - $('#suggestive').hide() $('input[name=display_all]').change(() => @@ -116,8 +126,8 @@ $('input[name=display_all]').change(() => $('#suggestive').hide() ) -$("#pylmm_compute").click(() => - $("#progress_bar_container").modal({show:true}) +$("#pylmm_compute").on("click", => + $("#progress_bar_container").modal() url = "/marker_regression" $('input[name=method]').val("pylmm") $('input[name=num_perm]').val($('input[name=num_perm_pylmm]').val()) @@ -133,7 +143,7 @@ $("#pylmm_compute").click(() => -$("#rqtl_geno_compute").click(() => +$("#rqtl_geno_compute").on("click", => $("#progress_bar_container").modal() url = "/marker_regression" $('input[name=method]').val("rqtl_geno") @@ -150,7 +160,7 @@ $("#rqtl_geno_compute").click(() => ) -$("#plink_compute").click(() => +$("#plink_compute").on("click", => $("#static_progress_bar_container").modal() url = "/marker_regression" $('input[name=method]').val("plink") @@ -163,7 +173,7 @@ $("#plink_compute").click(() => do_ajax_post(url, form_data) ) -$("#gemma_compute").click(() => +$("#gemma_compute").on("click", => console.log("RUNNING GEMMA") $("#static_progress_bar_container").modal() url = "/marker_regression" @@ -177,6 +187,25 @@ $("#gemma_compute").click(() => do_ajax_post(url, form_data) ) +$("#interval_mapping_compute").on("click", => + showalert("One or more outliers exist in this data set. Please review values before mapping. \ + Including outliers when mapping may lead to misleading results. \ + We recommend <A HREF=\"http://en.wikipedia.org/wiki/Winsorising\">winsorising</A> the outliers \ + or simply deleting them.", "alert-success") + console.log("In interval mapping") + $("#progress_bar_container").modal() + url = "/interval_mapping" + + $('input[name=method]').val("reaper") + $('input[name=manhattan_plot]').val($('input[name=manhattan_plot_reaper]:checked').val()) + $('input[name=mapping_display_all]').val($('input[name=display_all_reaper]')) + $('input[name=suggestive]').val($('input[name=suggestive_reaper]')) + form_data = $('#trait_data_form').serialize() + console.log("form_data is:", form_data) + + do_ajax_post(url, form_data) +) + #$(".submit_special").click(submit_special) composite_mapping_fields = -> diff --git a/wqflask/wqflask/static/new/javascript/show_trait_mapping_tools.js b/wqflask/wqflask/static/new/javascript/show_trait_mapping_tools.js index 1779df4b..d6f4ba89 100755 --- a/wqflask/wqflask/static/new/javascript/show_trait_mapping_tools.js +++ b/wqflask/wqflask/static/new/javascript/show_trait_mapping_tools.js @@ -1,4 +1,4 @@ -// Generated by CoffeeScript 1.8.0 +// Generated by CoffeeScript 1.9.2 var block_outliers, composite_mapping_fields, do_ajax_post, get_progress, mapping_method_fields, open_mapping_results, showalert, submit_special, toggle_enable_disable, update_time_remaining; submit_special = function() { @@ -79,6 +79,7 @@ do_ajax_post = function(url, form_data) { console.log(xhr); clearInterval(_this.my_timer); $('#progress_bar_container').modal('hide'); + $('#static_progress_bar_container').modal('hide'); return $("body").html("We got an error."); }; })(this), @@ -86,6 +87,7 @@ do_ajax_post = function(url, form_data) { return function(data) { clearInterval(_this.my_timer); $('#progress_bar_container').modal('hide'); + $('#static_progress_bar_container').modal('hide'); return open_mapping_results(data); }; })(this) @@ -100,7 +102,35 @@ open_mapping_results = function(data) { html: data, href: "#mapping_results_holder", height: "90%", - width: "90%" + width: "90%", + onComplete: (function(_this) { + return function() { + var filename, getSvgXml; + root.create_lod_chart(); + filename = "lod_chart_" + js_data.this_trait; + getSvgXml = function() { + var svg; + svg = $("#topchart").find("svg")[0]; + return (new XMLSerializer).serializeToString(svg); + }; + $("#exportform > #export").click(function() { + var form, svg_xml; + svg_xml = getSvgXml(); + form = $("#exportform"); + form.find("#data").val(svg_xml); + form.find("#filename").val(filename); + return form.submit(); + }); + return $("#exportpdfform > #export_pdf").click(function() { + var form, svg_xml; + svg_xml = getSvgXml(); + form = $("#exportpdfform"); + form.find("#data").val(svg_xml); + form.find("#filename").val(filename); + return form.submit(); + }); + }; + })(this) }); }; @@ -108,23 +138,6 @@ showalert = function(message, alerttype) { return $('#alert_placeholder').append('<div id="alertdiv" class="alert ' + alerttype + '"><a class="close" data-dismiss="alert">�</a><span>' + message + '</span></div>'); }; -$("#interval_mapping_compute").click((function(_this) { - return function() { - var form_data, url; - showalert("One or more outliers exist in this data set. Please review values before mapping. Including outliers when mapping may lead to misleading results. We recommend <A HREF=\"http://en.wikipedia.org/wiki/Winsorising\">winsorising</A> the outliers or simply deleting them.", "alert-success"); - console.log("In interval mapping"); - $("#progress_bar_container").modal(); - url = "/interval_mapping"; - $('input[name=method]').val("reaper"); - $('input[name=manhattan_plot]').val($('input[name=manhattan_plot_reaper]:checked').val()); - $('input[name=mapping_display_all]').val($('input[name=display_all_reaper]')); - $('input[name=suggestive]').val($('input[name=suggestive_reaper]')); - form_data = $('#trait_data_form').serialize(); - console.log("form_data is:", form_data); - return do_ajax_post(url, form_data); - }; -})(this)); - $('#suggestive').hide(); $('input[name=display_all]').change((function(_this) { @@ -138,12 +151,10 @@ $('input[name=display_all]').change((function(_this) { }; })(this)); -$("#pylmm_compute").click((function(_this) { +$("#pylmm_compute").on("click", (function(_this) { return function() { var form_data, url; - $("#progress_bar_container").modal({ - show: true - }); + $("#progress_bar_container").modal(); url = "/marker_regression"; $('input[name=method]').val("pylmm"); $('input[name=num_perm]').val($('input[name=num_perm_pylmm]').val()); @@ -154,7 +165,7 @@ $("#pylmm_compute").click((function(_this) { }; })(this)); -$("#rqtl_geno_compute").click((function(_this) { +$("#rqtl_geno_compute").on("click", (function(_this) { return function() { var form_data, url; $("#progress_bar_container").modal(); @@ -169,14 +180,12 @@ $("#rqtl_geno_compute").click((function(_this) { }; })(this)); -$("#plink_compute").click((function(_this) { +$("#plink_compute").on("click", (function(_this) { return function() { var form_data, url; $("#static_progress_bar_container").modal(); url = "/marker_regression"; $('input[name=method]').val("plink"); - $('input[name=mapping_display_all]').val($('input[name=display_all_plink]').val()); - $('input[name=suggestive]').val($('input[name=suggestive_plink]').val()); $('input[name=maf]').val($('input[name=maf_plink]').val()); form_data = $('#trait_data_form').serialize(); console.log("form_data is:", form_data); @@ -184,7 +193,7 @@ $("#plink_compute").click((function(_this) { }; })(this)); -$("#gemma_compute").click((function(_this) { +$("#gemma_compute").on("click", (function(_this) { return function() { var form_data, url; console.log("RUNNING GEMMA"); @@ -198,6 +207,23 @@ $("#gemma_compute").click((function(_this) { }; })(this)); +$("#interval_mapping_compute").on("click", (function(_this) { + return function() { + var form_data, url; + showalert("One or more outliers exist in this data set. Please review values before mapping. Including outliers when mapping may lead to misleading results. We recommend <A HREF=\"http://en.wikipedia.org/wiki/Winsorising\">winsorising</A> the outliers or simply deleting them.", "alert-success"); + console.log("In interval mapping"); + $("#progress_bar_container").modal(); + url = "/interval_mapping"; + $('input[name=method]').val("reaper"); + $('input[name=manhattan_plot]').val($('input[name=manhattan_plot_reaper]:checked').val()); + $('input[name=mapping_display_all]').val($('input[name=display_all_reaper]')); + $('input[name=suggestive]').val($('input[name=suggestive_reaper]')); + form_data = $('#trait_data_form').serialize(); + console.log("form_data is:", form_data); + return do_ajax_post(url, form_data); + }; +})(this)); + composite_mapping_fields = function() { return $(".composite_fields").toggle(); }; diff --git a/wqflask/wqflask/static/new/js_external/jstat.min.js b/wqflask/wqflask/static/new/js_external/jstat.min.js new file mode 100644 index 00000000..28de9c1a --- /dev/null +++ b/wqflask/wqflask/static/new/js_external/jstat.min.js @@ -0,0 +1,2 @@ +this.j$=this.jStat=function(a,b){function f(b,c){var d=b>c?b:c;return a.pow(10,17-~~(a.log(d>0?d:-d)*a.LOG10E))}function h(a){return e.call(a)==="[object Function]"}function i(a){return typeof a=="number"&&a===a}function j(a){return c.apply([],a)}function k(){return new k._init(arguments)}function l(){return 0}function m(){return 1}function n(a,b){return a===b?1:0}var c=Array.prototype.concat,d=Array.prototype.slice,e=Object.prototype.toString,g=Array.isArray||function(b){return e.call(b)==="[object Array]"};k.fn=k.prototype,k._init=function(b){var c;if(g(b[0]))if(g(b[0][0])){h(b[1])&&(b[0]=k.map(b[0],b[1]));for(c=0;c<b[0].length;c++)this[c]=b[0][c];this.length=b[0].length}else this[0]=h(b[1])?k.map(b[0],b[1]):b[0],this.length=1;else if(i(b[0]))this[0]=k.seq.apply(null,b),this.length=1;else{if(b[0]instanceof k)return k(b[0].toArray());this[0]=[],this.length=1}return this},k._init.prototype=k.prototype,k._init.constructor=k,k.utils={calcRdx:f,isArray:g,isFunction:h,isNumber:i,toVector:j},k.extend=function(b){var c,d;if(arguments.length===1){for(d in b)k[d]=b[d];return this}for(c=1;c<arguments.length;c++)for(d in arguments[c])b[d]=arguments[c][d];return b},k.rows=function(b){return b.length||1},k.cols=function(b){return b[0].length||1},k.dimensions=function(b){return{rows:k.rows(b),cols:k.cols(b)}},k.row=function(b,c){return b[c]},k.col=function(b,c){var d=new Array(b.length);for(var e=0;e<b.length;e++)d[e]=[b[e][c]];return d},k.diag=function(b){var c=k.rows(b),d=new Array(c);for(var e=0;e<c;e++)d[e]=[b[e][e]];return d},k.antidiag=function(b){var c=k.rows(b)-1,d=new Array(c);for(var e=0;c>=0;c--,e++)d[e]=[b[e][c]];return d},k.transpose=function(b){var c=[],d,e,f,h,i;g(b[0])||(b=[b]),e=b.length,f=b[0].length;for(i=0;i<f;i++){d=new Array(e);for(h=0;h<e;h++)d[h]=b[h][i];c.push(d)}return c.length===1?c[0]:c},k.map=function(b,c,d){var e,f,h,i,j;g(b[0])||(b=[b]),f=b.length,h=b[0].length,i=d?b:new Array(f);for(e=0;e<f;e++){i[e]||(i[e]=new Array(h));for(j=0;j<h;j++)i[e][j]=c(b[e][j],e,j)}return i.length===1?i[0]:i},k.cumreduce=function(b,c,d){var e,f,h,i,j;g(b[0])||(b=[b]),f=b.length,h=b[0].length,i=d?b:new Array(f);for(e=0;e<f;e++){i[e]||(i[e]=new Array(h)),h>0&&(i[e][0]=b[e][0]);for(j=1;j<h;j++)i[e][j]=c(i[e][j-1],b[e][j])}return i.length===1?i[0]:i},k.alter=function(b,c){return k.map(b,c,!0)},k.create=function(b,c,d){var e=new Array(b),f,g;h(c)&&(d=c,c=b);for(f=0;f<b;f++){e[f]=new Array(c);for(g=0;g<c;g++)e[f][g]=d(f,g)}return e},k.zeros=function(b,c){return i(c)||(c=b),k.create(b,c,l)},k.ones=function(b,c){return i(c)||(c=b),k.create(b,c,m)},k.rand=function(c,d){return i(d)||(d=c),k.create(c,d,a.random)},k.identity=function(b,c){return i(c)||(c=b),k.create(b,c,n)},k.symmetric=function(b){var c=!0,d=b.length,e,f;if(b.length!==b[0].length)return!1;for(e=0;e<d;e++)for(f=0;f<d;f++)if(b[f][e]!==b[e][f])return!1;return!0},k.clear=function(b){return k.alter(b,l)},k.seq=function(b,c,d,e){h(e)||(e=!1);var g=[],i=f(b,c),j=(c*i-b*i)/((d-1)*i),k=b,l;for(l=0;k<=c;l++,k=(b*i+j*i*l)/i)g.push(e?e(k,l):k);return g};var o=k.prototype;return o.length=0,o.push=Array.prototype.push,o.sort=Array.prototype.sort,o.splice=Array.prototype.splice,o.slice=Array.prototype.slice,o.toArray=function(){return this.length>1?d.call(this):d.call(this)[0]},o.map=function(b,c){return k(k.map(this,b,c))},o.cumreduce=function(b,c){return k(k.cumreduce(this,b,c))},o.alter=function(b){return k.alter(this,b),this},function(a){for(var b=0;b<a.length;b++)(function(a){o[a]=function(b){var c=this,d;return b?(setTimeout(function(){b.call(c,o[a].call(c))}),this):(d=k[a](this),g(d)?k(d):d)}})(a[b])}("transpose clear symmetric rows cols dimensions diag antidiag".split(" ")),function(a){for(var b=0;b<a.length;b++)(function(a){o[a]=function(b,c){var d=this;return c?(setTimeout(function(){c.call(d,o[a].call(d,b))}),this):k(k[a](this,b))}})(a[b])}("row col".split(" ")),function(a){for(var b=0;b<a.length;b++)(function(a){o[a]=new Function("return jStat(jStat."+a+".apply(null, arguments));")})(a[b])}("create zeros ones rand identity".split(" ")),k}(Math),function(a,b){function d(a,b){return a-b}function e(a,c,d){return b.max(c,b.min(a,d))}var c=a.utils.isFunction;a.sum=function g(a){var g=0,b=a.length,c;while(--b>=0)g+=a[b];return g},a.sumsqrd=function(b){var c=0,d=b.length;while(--d>=0)c+=b[d]*b[d];return c},a.sumsqerr=function(c){var d=a.mean(c),e=0,f=c.length,g;while(--f>=0)g=c[f]-d,e+=g*g;return e},a.product=function(b){var c=1,d=b.length;while(--d>=0)c*=b[d];return c},a.min=function(b){var c=b[0],d=0;while(++d<b.length)b[d]<c&&(c=b[d]);return c},a.max=function(b){var c=b[0],d=0;while(++d<b.length)b[d]>c&&(c=b[d]);return c},a.mean=function(c){return a.sum(c)/c.length},a.meansqerr=function(c){return a.sumsqerr(c)/c.length},a.geomean=function(d){return b.pow(a.product(d),1/d.length)},a.median=function(b){var c=b.length,e=b.slice().sort(d);return c&1?e[c/2|0]:(e[c/2-1]+e[c/2])/2},a.cumsum=function(c){return a.cumreduce(c,function(a,b){return a+b})},a.cumprod=function(c){return a.cumreduce(c,function(a,b){return a*b})},a.diff=function(b){var c=[],d=b.length,e;for(e=1;e<d;e++)c.push(b[e]-b[e-1]);return c},a.mode=function(b){var c=b.length,e=b.slice().sort(d),f=1,g=0,h=0,i=[],j;for(j=0;j<c;j++)e[j]===e[j+1]?f++:(f>g?(i=[e[j]],g=f,h=0):f===g&&(i.push(e[j]),h++),f=1);return h===0?i[0]:i},a.range=function(c){return a.max(c)-a.min(c)},a.variance=function(c,d){return a.sumsqerr(c)/(c.length-(d?1:0))},a.stdev=function(d,e){return b.sqrt(a.variance(d,e))},a.meandev=function(d){var e=0,f=a.mean(d),g;for(g=d.length-1;g>=0;g--)e+=b.abs(d[g]-f);return e/d.length},a.meddev=function(d){var e=0,f=a.median(d),g;for(g=d.length-1;g>=0;g--)e+=b.abs(d[g]-f);return e/d.length},a.coeffvar=function(c){return a.stdev(c)/a.mean(c)},a.quartiles=function(c){var e=c.length,f=c.slice().sort(d);return[f[b.round(e/4)-1],f[b.round(e/2)-1],f[b.round(e*3/4)-1]]},a.quantiles=function(c,f,g,h){var i=c.slice().sort(d),j=[f.length],k=c.length,l,m,n,o,p,q;typeof g=="undefined"&&(g=3/8),typeof h=="undefined"&&(h=3/8);for(l=0;l<f.length;l++)m=f[l],n=g+m*(1-g-h),o=k*m+n,p=b.floor(e(o,1,k-1)),q=e(o-p,0,1),j[l]=(1-q)*i[p-1]+q*i[p];return j},a.percentileOfScore=function(b,c,d){var e=0,f=b.length,g=!1,h,i;d==="strict"&&(g=!0);for(i=0;i<f;i++)h=b[i],(g&&h<c||!g&&h<=c)&&e++;return e/f},a.histogram=function(d,e){var f=a.min(d),g=e||4,h=(a.max(d)-f)/g,i=d.length,e=[],j;for(j=0;j<g;j++)e[j]=0;for(j=0;j<i;j++)e[b.min(b.floor((d[j]-f)/h),g-1)]+=1;return e},a.covariance=function(c,d){var e=a.mean(c),f=a.mean(d),g=c.length,h=new Array(g),i;for(i=0;i<g;i++)h[i]=(c[i]-e)*(d[i]-f);return a.sum(h)/(g-1)},a.corrcoeff=function(c,d){return a.covariance(c,d)/a.stdev(c,1)/a.stdev(d,1)},a.stanMoment=function(d,e){var f=a.mean(d),g=a.stdev(d),h=d.length,j=0;for(i=0;i<h;i++)j+=b.pow((d[i]-f)/g,e);return j/d.length},a.skewness=function(c){return a.stanMoment(c,3)},a.kurtosis=function(c){return a.stanMoment(c,4)-3};var f=a.prototype;(function(b){for(var d=0;d<b.length;d++)(function(b){f[b]=function(d,e){var g=[],h=0,i=this;c(d)&&(e=d,d=!1);if(e)return setTimeout(function(){e.call(i,f[b].call(i,d))}),this;if(this.length>1){i=d===!0?this:this.transpose();for(;h<i.length;h++)g[h]=a[b](i[h]);return g}return a[b](this[0],d)}})(b[d])})("cumsum cumprod".split(" ")),function(b){for(var d=0;d<b.length;d++)(function(b){f[b]=function(d,e){var g=[],h=0,i=this;c(d)&&(e=d,d=!1);if(e)return setTimeout(function(){e.call(i,f[b].call(i,d))}),this;if(this.length>1){i=d===!0?this:this.transpose();for(;h<i.length;h++)g[h]=a[b](i[h]);return d===!0?a[b](a.utils.toVector(g)):g}return a[b](this[0],d)}})(b[d])}("sum sumsqrd sumsqerr product min max mean meansqerr geomean median diff mode range variance stdev meandev meddev coeffvar quartiles histogram skewness kurtosis".split(" ")),function(b){for(var d=0;d<b.length;d++)(function(b){f[b]=function(){var d=[],e=0,g=this,h=Array.prototype.slice.call(arguments);if(c(h[h.length-1])){var i=h[h.length-1],j=h.slice(0,h.length-1);return setTimeout(function(){i.call(g,f[b].apply(g,j))}),this}var i=undefined,k=function(d){return a[b].apply(g,[d].concat(h))};if(this.length>1){g=g.transpose();for(;e<g.length;e++)d[e]=k(g[e]);return d}return k(this[0])}})(b[d])}("quantiles percentileOfScore".split(" "))}(this.jStat,Math),function(a,b){a.gammaln=function(c){var d=0,e=[76.18009172947146,-86.50532032941678,24.01409824083091,-1.231739572450155,.001208650973866179,-0.000005395239384953],f=1.000000000190015,g,h,i;i=(h=g=c)+5.5,i-=(g+.5)*b.log(i);for(;d<6;d++)f+=e[d]/++h;return b.log(2.5066282746310007*f/g)-i},a.gammafn=function(c){var d=[-1.716185138865495,24.76565080557592,-379.80425647094563,629.3311553128184,866.9662027904133,-31451.272968848367,-36144.413418691176,66456.14382024054],e=[-30.8402300119739,315.35062697960416,-1015.1563674902192,-3107.771671572311,22538.11842098015,4755.846277527881,-134659.9598649693,-115132.2596755535],f=!1,g=0,h=0,i=0,j=c,k,l,m,n,o,p;if(j<=0){n=j%1+3.6e-16;if(n)f=(j&1?-1:1)*b.PI/b.sin(b.PI*n),j=1-j;else return Infinity}m=j,j<1?l=j++:l=(j-=g=(j|0)-1)-1;for(k=0;k<8;++k)i=(i+d[k])*l,h=h*l+e[k];n=i/h+1;if(m<j)n/=m;else if(m>j)for(k=0;k<g;++k)n*=j,j++;return f&&(n=f/n),n},a.gammap=function(c,d){return a.lowRegGamma(c,d)*a.gammafn(c)},a.lowRegGamma=function(d,e){var f=a.gammaln(d),g=d,h=1/d,i=h,j=e+1-d,k=1/1e-30,l=1/j,m=l,n=1,o=-~(b.log(d>=1?d:1/d)*8.5+d*.4+17),p,q;if(e<0||d<=0)return NaN;if(e<d+1){for(;n<=o;n++)h+=i*=e/++g;return h*b.exp(-e+d*b.log(e)-f)}for(;n<=o;n++)p=-n*(n-d),j+=2,l=p*l+j,k=j+p/k,l=1/l,m*=l*k;return 1-m*b.exp(-e+d*b.log(e)-f)},a.factorialln=function(c){return c<0?NaN:a.gammaln(c+1)},a.factorial=function(c){return c<0?NaN:a.gammafn(c+1)},a.combination=function(d,e){return d>170||e>170?b.exp(a.combinationln(d,e)):a.factorial(d)/a.factorial(e)/a.factorial(d-e)},a.combinationln=function(c,d){return a.factorialln(c)-a.factorialln(d)-a.factorialln(c-d)},a.permutation=function(c,d){return a.factorial(c)/a.factorial(c-d)},a.betafn=function(d,e){return d<=0||e<=0?undefined:d+e>170?b.exp(a.betaln(d,e)):a.gammafn(d)*a.gammafn(e)/a.gammafn(d+e)},a.betaln=function(c,d){return a.gammaln(c)+a.gammaln(d)-a.gammaln(c+d)},a.betacf=function(c,d,e){var f=1e-30,g=1,h=d+e,i=d+1,j=d-1,k=1,l=1-h*c/i,m,n,o,p;b.abs(l)<f&&(l=f),l=1/l,p=l;for(;g<=100;g++){m=2*g,n=g*(e-g)*c/((j+m)*(d+m)),l=1+n*l,b.abs(l)<f&&(l=f),k=1+n/k,b.abs(k)<f&&(k=f),l=1/l,p*=l*k,n=-(d+g)*(h+g)*c/((d+m)*(i+m)),l=1+n*l,b.abs(l)<f&&(l=f),k=1+n/k,b.abs(k)<f&&(k=f),l=1/l,o=l*k,p*=o;if(b.abs(o-1)<3e-7)break}return p},a.gammapinv=function(d,e){var f=0,g=e-1,h=1e-8,i=a.gammaln(e),j,k,l,m,n,o,p;if(d>=1)return b.max(100,e+100*b.sqrt(e));if(d<=0)return 0;e>1?(o=b.log(g),p=b.exp(g*(o-1)-i),n=d<.5?d:1-d,l=b.sqrt(-2*b.log(n)),j=(2.30753+l*.27061)/(1+l*(.99229+l*.04481))-l,d<.5&&(j=-j),j=b.max(.001,e*b.pow(1-1/(9*e)-j/(3*b.sqrt(e)),3))):(l=1-e*(.253+e*.12),d<l?j=b.pow(d/l,1/e):j=1-b.log(1-(d-l)/(1-l)));for(;f<12;f++){if(j<=0)return 0;k=a.lowRegGamma(e,j)-d,e>1?l=p*b.exp(-(j-g)+g*(b.log(j)-o)):l=b.exp(-j+g*b.log(j)-i),m=k/l,j-=l=m/(1-.5*b.min(1,m*((e-1)/j-1))),j<=0&&(j=.5*(j+l));if(b.abs(l)<h*j)break}return j},a.erf=function(c){var d=[-1.3026537197817094,.6419697923564902,.019476473204185836,-0.00956151478680863,-0.000946595344482036,.000366839497852761,42523324806907e-18,-0.000020278578112534,-0.000001624290004647,130365583558e-17,1.5626441722e-8,-8.5238095915e-8,6.529054439e-9,5.059343495e-9,-9.91364156e-10,-2.27365122e-10,9.6467911e-11,2.394038e-12,-6.886027e-12,8.94487e-13,3.13092e-13,-1.12708e-13,3.81e-16,7.106e-15,-1.523e-15,-9.4e-17,1.21e-16,-2.8e-17],e=d.length-1,f=!1,g=0,h=0,i,j,k,l;c<0&&(c=-c,f=!0),i=2/(2+c),j=4*i-2;for(;e>0;e--)k=g,g=j*g-h+d[e],h=k;return l=i*b.exp(-c*c+.5*(d[0]+j*g)-h),f?l-1:1-l},a.erfc=function(c){return 1-a.erf(c)},a.erfcinv=function(d){var e=0,f,g,h,i;if(d>=2)return-100;if(d<=0)return 100;i=d<1?d:2-d,h=b.sqrt(-2*b.log(i/2)),f=-0.70711*((2.30753+h*.27061)/(1+h*(.99229+h*.04481))-h);for(;e<2;e++)g=a.erfc(f)-i,f+=g/(1.1283791670955126*b.exp(-f*f)-f*g);return d<1?f:-f},a.ibetainv=function(d,e,f){var g=1e-8,h=e-1,i=f-1,j=0,k,l,m,n,o,p,q,r,s,t,u;if(d<=0)return 0;if(d>=1)return 1;e>=1&&f>=1?(m=d<.5?d:1-d,n=b.sqrt(-2*b.log(m)),q=(2.30753+n*.27061)/(1+n*(.99229+n*.04481))-n,d<.5&&(q=-q),r=(q*q-3)/6,s=2/(1/(2*e-1)+1/(2*f-1)),t=q*b.sqrt(r+s)/s-(1/(2*f-1)-1/(2*e-1))*(r+5/6-2/(3*s)),q=e/(e+f*b.exp(2*t))):(k=b.log(e/(e+f)),l=b.log(f/(e+f)),n=b.exp(e*k)/e,o=b.exp(f*l)/f,t=n+o,d<n/t?q=b.pow(e*t*d,1/e):q=1-b.pow(f*t*(1-d),1/f)),u=-a.gammaln(e)-a.gammaln(f)+a.gammaln(e+f);for(;j<10;j++){if(q===0||q===1)return q;p=a.ibeta(q,e,f)-d,n=b.exp(h*b.log(q)+i*b.log(1-q)+u),o=p/n,q-=n=o/(1-.5*b.min(1,o*(h/q-i/(1-q)))),q<=0&&(q=.5*(q+n)),q>=1&&(q=.5*(q+n+1));if(b.abs(n)<g*q&&j>0)break}return q},a.ibeta=function(d,e,f){var g=d===0||d===1?0:b.exp(a.gammaln(e+f)-a.gammaln(e)-a.gammaln(f)+e*b.log(d)+f*b.log(1-d));return d<0||d>1?!1:d<(e+1)/(e+f+2)?g*a.betacf(d,e,f)/e:1-g*a.betacf(1-d,f,e)/f},a.randn=function(d,e){var f,g,h,i,j,k;e||(e=d);if(d)return a.create(d,e,function(){return a.randn()});do f=b.random(),g=1.7156*(b.random()-.5),h=f-.449871,i=b.abs(g)+.386595,j=h*h+i*(.196*i-.25472*h);while(j>.27597&&(j>.27846||g*g>-4*b.log(f)*f*f));return g/f},a.randg=function(d,e,f){var g=d,h,i,j,k,l,m;f||(f=e),d||(d=1);if(e)return m=a.zeros(e,f),m.alter(function(){return a.randg(d)}),m;d<1&&(d+=1),h=d-1/3,i=1/b.sqrt(9*h);do{do l=a.randn(),k=1+i*l;while(k<=0);k=k*k*k,j=b.random()}while(j>1-.331*b.pow(l,4)&&b.log(j)>.5*l*l+h*(1-k+b.log(k)));if(d==g)return h*k;do j=b.random();while(j===0);return b.pow(j,1/g)*h*k},function(b){for(var c=0;c<b.length;c++)(function(b){a.fn[b]=function(){return a(a.map(this,function(c){return a[b](c)}))}})(b[c])}("gammaln gammafn factorial factorialln".split(" ")),function(b){for(var c=0;c<b.length;c++)(function(b){a.fn[b]=function(){return a(a[b].apply(null,arguments))}})(b[c])}("randn".split(" "))}(this.jStat,Math),function(a,b){(function(b){for(var c=0;c<b.length;c++)(function(b){a[b]=function(a,b,c){return this instanceof arguments.callee?(this._a=a,this._b=b,this._c=c,this):new arguments.callee(a,b,c)},a.fn[b]=function(c,d,e){var f=a[b](c,d,e);return f.data=this,f},a[b].prototype.sample=function(c){var d=this._a,e=this._b,f=this._c;return c?a.alter(c,function(){return a[b].sample(d,e,f)}):a[b].sample(d,e,f)},function(c){for(var d=0;d<c.length;d++)(function(c){a[b].prototype[c]=function(d){var e=this._a,f=this._b,g=this._c;return!d&&d!==0&&(d=this.data),typeof d!="number"?a.fn.map.call(d,function(d){return a[b][c](d,e,f,g)}):a[b][c](d,e,f,g)}})(c[d])}("pdf cdf inv".split(" ")),function(c){for(var d=0;d<c.length;d++)(function(c){a[b].prototype[c]=function(){return a[b][c](this._a,this._b,this._c)}})(c[d])}("mean median mode variance".split(" "))})(b[c])})("beta centralF cauchy chisquare exponential gamma invgamma kumaraswamy lognormal noncentralt normal pareto studentt weibull uniform binomial negbin hypgeom poisson triangular".split(" ")),a.extend(a.beta,{pdf:function(d,e,f){return d>1||d<0?0:e==1&&f==1?1:e<512||f<512?b.pow(d,e-1)*b.pow(1-d,f-1)/a.betafn(e,f):b.exp((e-1)*b.log(d)+(f-1)*b.log(1-d)-a.betaln(e,f))},cdf:function(c,d,e){return c>1||c<0?(c>1)*1:a.ibeta(c,d,e)},inv:function(c,d,e){return a.ibetainv(c,d,e)},mean:function(b,c){return b/(b+c)},median:function(b,c){throw new Error("median not yet implemented")},mode:function(b,c){return(b-1)/(b+c-2)},sample:function(c,d){var e=a.randg(c);return e/(e+a.randg(d))},variance:function(c,d){return c*d/(b.pow(c+d,2)*(c+d+1))}}),a.extend(a.centralF,{pdf:function(d,e,f){var g,h,i;return d<0?undefined:e<=2?e===1&&f===1?Infinity:e===2&&f===1?1:b.sqrt(b.pow(e*d,e)*b.pow(f,f)/b.pow(e*d+f,e+f))/(d*a.betafn(e/2,f/2)):(g=e*d/(f+d*e),h=f/(f+d*e),i=e*h/2,i*a.binomial.pdf((e-2)/2,(e+f-2)/2,g))},cdf:function(c,d,e){return a.ibeta(d*c/(d*c+e),d/2,e/2)},inv:function(c,d,e){return e/(d*(1/a.ibetainv(c,d/2,e/2)-1))},mean:function(b,c){return c>2?c/(c-2):undefined},mode:function(b,c){return b>2?c*(b-2)/(b*(c+2)):undefined},sample:function(c,d){var e=a.randg(c/2)*2,f=a.randg(d/2)*2;return e/c/(f/d)},variance:function(b,c){return c<=4?undefined:2*c*c*(b+c-2)/(b*(c-2)*(c-2)*(c-4))}}),a.extend(a.cauchy,{pdf:function(c,d,e){return e/(b.pow(c-d,2)+b.pow(e,2))/b.PI},cdf:function(c,d,e){return b.atan((c-d)/e)/b.PI+.5},inv:function(a,c,d){return c+d*b.tan(b.PI*(a-.5))},median:function(b,c){return b},mode:function(b,c){return b},sample:function(d,e){return a.randn()*b.sqrt(1/(2*a.randg(.5)))*e+d}}),a.extend(a.chisquare,{pdf:function(d,e){return d===0?0:b.exp((e/2-1)*b.log(d)-d/2-e/2*b.log(2)-a.gammaln(e/2))},cdf:function(c,d){return a.lowRegGamma(d/2,c/2)},inv:function(b,c){return 2*a.gammapinv(b,.5*c)},mean:function(a){return a},median:function(c){return c*b.pow(1-2/(9*c),3)},mode:function(b){return b-2>0?b-2:0},sample:function(c){return a.randg(c/2)*2},variance:function(b){return 2*b}}),a.extend(a.exponential,{pdf:function(c,d){return c<0?0:d*b.exp(-d*c)},cdf:function(c,d){return c<0?0:1-b.exp(-d*c)},inv:function(a,c){return-b.log(1-a)/c},mean:function(a){return 1/a},median:function(a){return 1/a*b.log(2)},mode:function(b){return 0},sample:function(c){return-1/c*b.log(b.random())},variance:function(a){return b.pow(a,-2)}}),a.extend(a.gamma,{pdf:function(d,e,f){return b.exp((e-1)*b.log(d)-d/f-a.gammaln(e)-e*b.log(f))},cdf:function(c,d,e){return a.lowRegGamma(d,c/e)},inv:function(b,c,d){return a.gammapinv(b,c)*d},mean:function(a,b){return a*b},mode:function(b,c){return b>1?(b-1)*c:undefined},sample:function(c,d){return a.randg(c)*d},variance:function(b,c){return b*c*c}}),a.extend(a.invgamma,{pdf:function(d,e,f){return b.exp(-(e+1)*b.log(d)-f/d-a.gammaln(e)+e*b.log(f))},cdf:function(c,d,e){return 1-a.lowRegGamma(d,e/c)},inv:function(b,c,d){return d/a.gammapinv(1-b,c)},mean:function(a,b){return a>1?b/(a-1):undefined},mode:function(b,c){return c/(b+1)},sample:function(c,d){return d/a.randg(c)},variance:function(b,c){return b<=2?undefined:c*c/((b-1)*(b-1)*(b-2))}}),a.extend(a.kumaraswamy,{pdf:function(c,d,e){return b.exp(b.log(d)+b.log(e)+(d-1)*b.log(c)+(e-1)*b.log(1-b.pow(c,d)))},cdf:function(c,d,e){return 1-b.pow(1-b.pow(c,d),e)},mean:function(b,c){return c*a.gammafn(1+1/b)*a.gammafn(c)/a.gammafn(1+1/b+c)},median:function(c,d){return b.pow(1-b.pow(2,-1/d),1/c)},mode:function(c,d){return c>=1&&d>=1&&c!==1&&d!==1?b.pow((c-1)/(c*d-1),1/c):undefined},variance:function(b,c){throw new Error("variance not yet implemented")}}),a.extend(a.lognormal,{pdf:function(c,d,e){return b.exp(-b.log(c)-.5*b.log(2*b.PI)-b.log(e)-b.pow(b.log(c)-d,2)/(2*e*e))},cdf:function(d,e,f){return.5+.5*a.erf((b.log(d)-e)/b.sqrt(2*f*f))},inv:function(c,d,e){return b.exp(-1.4142135623730951*e*a.erfcinv(2*c)+d)},mean:function(c,d){return b.exp(c+d*d/2)},median:function(c,d){return b.exp(c)},mode:function(c,d){return b.exp(c-d*d)},sample:function(d,e){return b.exp(a.randn()*e+d)},variance:function(c,d){return(b.exp(d*d)-1)*b.exp(2*c+d*d)}}),a.extend(a.noncentralt,{pdf:function(d,e,f){var g=1e-14;return b.abs(f)<g?a.studentt.pdf(d,e):b.abs(d)<g?b.exp(a.gammaln((e+1)/2)-f*f/2-.5*b.log(b.PI*e)-a.gammaln(e/2)):e/d*(a.noncentralt.cdf(d*b.sqrt(1+2/e),e+2,f)-a.noncentralt.cdf(d,e,f))},cdf:function(d,e,f){var g=1e-14,h=200;if(b.abs(f)<g)return a.studentt.cdf(d,e);var i=!1;d<0&&(i=!0,f=-f);var j=a.normal.cdf(-f,0,1),k=g+1,l=k,m=d*d/(d*d+e),n=0,o=b.exp(-f*f/2),p=b.exp(-f*f/2-.5*b.log(2)-a.gammaln(1.5))*f;while(n<h||l>g||k>g)l=k,n>0&&(o*=f*f/(2*n),p*=f*f/(2*(n+.5))),k=o*a.beta.cdf(m,n+.5,e/2)+p*a.beta.cdf(m,n+1,e/2),j+=.5*k,n++;return i?1-j:j}}),a.extend(a.normal,{pdf:function(c,d,e){return b.exp(-0.5*b.log(2*b.PI)-b.log(e)-b.pow(c-d,2)/(2*e*e))},cdf:function(d,e,f){return.5*(1+a.erf((d-e)/b.sqrt(2*f*f)))},inv:function(b,c,d){return-1.4142135623730951*d*a.erfcinv(2*b)+c},mean:function(a,b){return a},median:function(b,c){return b},mode:function(a,b){return a},sample:function(c,d){return a.randn()*d+c},variance:function(a,b){return b*b}}),a.extend(a.pareto,{pdf:function(c,d,e){return c<d?undefined:e*b.pow(d,e)/b.pow(c,e+1)},cdf:function(c,d,e){return 1-b.pow(d/c,e)},mean:function(c,d){return d<=1?undefined:d*b.pow(c,d)/(d-1)},median:function(c,d){return c*d*b.SQRT2},mode:function(b,c){return b},variance:function(a,c){return c<=2?undefined:a*a*c/(b.pow(c-1,2)*(c-2))}}),a.extend(a.studentt,{pdf:function(d,e){return e=e>1e+100?1e+100:e,1/(b.sqrt(e)*a.betafn(.5,e/2))*b.pow(1+d*d/e,-((e+1)/2))},cdf:function(d,e){var f=e/2;return a.ibeta((d+b.sqrt(d*d+e))/(2*b.sqrt(d*d+e)),f,f)},inv:function(c,d){var e=a.ibetainv(2*b.min(c,1-c),.5*d,.5);return e=b.sqrt(d*(1-e)/e),c>.5?e:-e},mean:function(b){return b>1?0:undefined},median:function(b){return 0},mode:function(b){return 0},sample:function(d){return a.randn()*b.sqrt(d/(2*a.randg(d/2)))},variance:function(b){return b>2?b/(b-2):b>1?Infinity:undefined}}),a.extend(a.weibull,{pdf:function(c,d,e){return c<0?0:e/d*b.pow(c/d,e-1)*b.exp(-b.pow(c/d,e))},cdf:function(c,d,e){return c<0?0:1-b.exp(-b.pow(c/d,e))},inv:function(a,c,d){return c*b.pow(-b.log(1-a),1/d)},mean:function(b,c){return b*a.gammafn(1+1/c)},median:function(c,d){return c*b.pow(b.log(2),1/d)},mode:function(c,d){return d<=1?undefined:c*b.pow((d-1)/d,1/d)},sample:function(c,d){return c*b.pow(-b.log(b.random()),1/d)},variance:function(d,e){return d*d*a.gammafn(1+2/e)-b.pow(this.mean(d,e),2)}}),a.extend(a.uniform,{pdf:function(b,c,d){return b<c||b>d?0:1/(d-c)},cdf:function(b,c,d){return b<c?0:b<d?(b-c)/(d-c):1},inv:function(a,b,c){return b+a*(c-b)},mean:function(b,c){return.5*(b+c)},median:function(c,d){return a.mean(c,d)},mode:function(b,c){throw new Error("mode is not yet implemented")},sample:function(c,d){return c/2+d/2+(d/2-c/2)*(2*b.random()-1)},variance:function(c,d){return b.pow(d-c,2)/12}}),a.extend(a.binomial,{pdf:function(d,e,f){return f===0||f===1?e*f===d?1:0:a.combination(e,d)*b.pow(f,d)*b.pow(1-f,e-d)},cdf:function(c,d,e){var f=[],g=0;if(c<0)return 0;if(c<d){for(;g<=c;g++)f[g]=a.binomial.pdf(g,d,e);return a.sum(f)}return 1}}),a.extend(a.negbin,{pdf:function(d,e,f){return d!==d|0?!1:d<0?0:a.combination(d+e-1,e-1)*b.pow(1-f,d)*b.pow(f,e)},cdf:function(c,d,e){var f=0,g=0;if(c<0)return 0;for(;g<=c;g++)f+=a.negbin.pdf(g,d,e);return f}}),a.extend(a.hypgeom,{pdf:function(d,e,f,g){if(d!==d|0)return!1;if(d<0||d<f-(e-g))return 0;if(d>g||d>f)return 0;if(f*2>e)return g*2>e?a.hypgeom.pdf(e-f-g+d,e,e-f,e-g):a.hypgeom.pdf(g-d,e,e-f,g);if(g*2>e)return a.hypgeom.pdf(f-d,e,f,e-g);if(f<g)return a.hypgeom.pdf(d,e,g,f);var h=1,i=0;for(var j=0;j<d;j++){while(h>1&&i<g)h*=1-f/(e-i),i++;h*=(g-j)*(f-j)/((j+1)*(e-f-g+j+1))}for(;i<g;i++)h*=1-f/(e-i);return b.min(1,b.max(0,h))},cdf:function(d,e,f,g){if(d<0||d<f-(e-g))return 0;if(d>=g||d>=f)return 1;if(f*2>e)return g*2>e?a.hypgeom.cdf(e-f-g+d,e,e-f,e-g):1-a.hypgeom.cdf(g-d-1,e,e-f,g);if(g*2>e)return 1-a.hypgeom.cdf(f-d-1,e,f,e-g);if(f<g)return a.hypgeom.cdf(d,e,g,f);var h=1,i=1,j=0;for(var k=0;k<d;k++){while(h>1&&j<g){var l=1-f/(e-j);i*=l,h*=l,j++}i*=(g-k)*(f-k)/((k+1)*(e-f-g+k+1)),h+=i}for(;j<g;j++)h*=1-f/(e-j);return b.min(1,b.max(0,h))}}),a.extend(a.poisson,{pdf:function(d,e){return b.pow(e,d)*b.exp(-e)/a.factorial(d)},cdf:function(c,d){var e=[],f=0;if(c<0)return 0;for(;f<=c;f++)e.push(a.poisson.pdf(f,d));return a.sum(e)},mean:function(a){return a},variance:function(a){return a},sample:function(c){var d=1,e=0,f=b.exp(-c);do e++,d*=b.random();while(d>f);return e-1}}),a.extend(a.triangular,{pdf:function(b,c,d,e){return d<=c||e<c||e>d?undefined:b<c||b>d?0:b<=e?e===c?1:2*(b-c)/((d-c)*(e-c)):e===d?1:2*(d-b)/((d-c)*(d-e))},cdf:function(c,d,e,f){return e<=d||f<d||f>e?undefined:c<d?0:c<=f?b.pow(c-d,2)/((e-d)*(f-d)):1-b.pow(e-c,2)/((e-d)*(e-f))},mean:function(b,c,d){return(b+c+d)/3},median:function(c,d,e){if(e<=(c+d)/2)return d-b.sqrt((d-c)*(d-e))/b.sqrt(2);if(e>(c+d)/2)return c+b.sqrt((d-c)*(e-c))/b.sqrt(2)},mode:function(b,c,d){return d},sample:function(c,d,e){var f=b.random();return f<(e-c)/(d-c)?c+b.sqrt(f*(d-c)*(e-c)):d-b.sqrt((1-f)*(d-c)*(d-e))},variance:function(b,c,d){return(b*b+c*c+d*d-b*c-b*d-c*d)/18}})}(this.jStat,Math),function(a,b){var d=Array.prototype.push,e=a.utils.isArray;a.extend({add:function(c,d){return e(d)?(e(d[0])||(d=[d]),a.map(c,function(a,b,c){return a+d[b][c]})):a.map(c,function(a){return a+d})},subtract:function(c,d){return e(d)?(e(d[0])||(d=[d]),a.map(c,function(a,b,c){return a-d[b][c]||0})):a.map(c,function(a){return a-d})},divide:function(c,d){return e(d)?(e(d[0])||(d=[d]),a.multiply(c,a.inv(d))):a.map(c,function(a){return a/d})},multiply:function(c,d){var f,g,h,i,j=c.length,k=c[0].length,l=a.zeros(j,h=e(d)?d[0].length:k),m=0;if(e(d)){for(;m<h;m++)for(f=0;f<j;f++){i=0;for(g=0;g<k;g++)i+=c[f][g]*d[g][m];l[f][m]=i}return j===1&&m===1?l[0][0]:l}return a.map(c,function(a){return a*d})},dot:function(c,d){e(c[0])||(c=[c]),e(d[0])||(d=[d]);var f=c[0].length===1&&c.length!==1?a.transpose(c):c,g=d[0].length===1&&d.length!==1?a.transpose(d):d,h=[],i=0,j=f.length,k=f[0].length,l,m;for(;i<j;i++){h[i]=[],l=0;for(m=0;m<k;m++)l+=f[i][m]*g[i][m];h[i]=l}return h.length===1?h[0]:h},pow:function(d,e){return a.map(d,function(a){return b.pow(a,e)})},exp:function(d){return a.map(d,function(a){return b.exp(a)})},log:function(d){return a.map(d,function(a){return b.log(a)})},abs:function(d){return a.map(d,function(a){return b.abs(a)})},norm:function(c,d){var f=0,g=0;isNaN(d)&&(d=2),e(c[0])&&(c=c[0]);for(;g<c.length;g++)f+=b.pow(b.abs(c[g]),d);return b.pow(f,1/d)},angle:function(d,e){return b.acos(a.dot(d,e)/(a.norm(d)*a.norm(e)))},aug:function(b,c){var e=b.slice(),f=0;for(;f<e.length;f++)d.apply(e[f],c[f]);return e},inv:function(c){var d=c.length,e=c[0].length,f=a.identity(d,e),g=a.gauss_jordan(c,f),h=[],i=0,j;for(;i<d;i++){h[i]=[];for(j=e;j<g[0].length;j++)h[i][j-e]=g[i][j]}return h},det:function(b){var c=b.length,d=c*2,e=new Array(d),f=c-1,g=d-1,h=f-c+1,i=g,j=0,k=0,l;if(c===2)return b[0][0]*b[1][1]-b[0][1]*b[1][0];for(;j<d;j++)e[j]=1;for(j=0;j<c;j++){for(l=0;l<c;l++)e[h<0?h+c:h]*=b[j][l],e[i<c?i+c:i]*=b[j][l],h++,i--;h=--f-c+1,i=--g}for(j=0;j<c;j++)k+=e[j];for(;j<d;j++)k-=e[j];return k},gauss_elimination:function(d,e){var f=0,g=0,h=d.length,i=d[0].length,j=1,k=0,l=[],m,n,o,p;d=a.aug(d,e),m=d[0].length;for(f=0;f<h;f++){n=d[f][f],g=f;for(p=f+1;p<i;p++)n<b.abs(d[p][f])&&(n=d[p][f],g=p);if(g!=f)for(p=0;p<m;p++)o=d[f][p],d[f][p]=d[g][p],d[g][p]=o;for(g=f+1;g<h;g++){j=d[g][f]/d[f][f];for(p=f;p<m;p++)d[g][p]=d[g][p]-j*d[f][p]}}for(f=h-1;f>=0;f--){k=0;for(g=f+1;g<=h-1;g++)k+=l[g]*d[f][g];l[f]=(d[f][m-1]-k)/d[f][f]}return l},gauss_jordan:function(e,f){var g=a.aug(e,f),h=g.length,i=g[0].length;for(var j=0;j<h;j++){var k=j;for(var l=j+1;l<h;l++)b.abs(g[l][j])>b.abs(g[k][j])&&(k=l);var m=g[j];g[j]=g[k],g[k]=m;for(var l=j+1;l<h;l++){c=g[l][j]/g[j][j];for(var n=j;n<i;n++)g[l][n]-=g[j][n]*c}}for(var j=h-1;j>=0;j--){c=g[j][j];for(var l=0;l<j;l++)for(var n=i-1;n>j-1;n--)g[l][n]-=g[j][n]*g[l][j]/c;g[j][j]/=c;for(var n=h;n<i;n++)g[j][n]/=c}return g},lu:function(b,c){throw new Error("lu not yet implemented")},cholesky:function(b,c){throw new Error("cholesky not yet implemented")},gauss_jacobi:function(d,e,f,g){var h=0,i=0,j=d.length,k=[],l=[],m=[],n,o,p,q;for(;h<j;h++){k[h]=[],l[h]=[],m[h]=[];for(i=0;i<j;i++)h>i?(k[h][i]=d[h][i],l[h][i]=m[h][i]=0):h<i?(l[h][i]=d[h][i],k[h][i]=m[h][i]=0):(m[h][i]=d[h][i],k[h][i]=l[h][i]=0)}p=a.multiply(a.multiply(a.inv(m),a.add(k,l)),-1),o=a.multiply(a.inv(m),e),n=f,q=a.add(a.multiply(p,f),o),h=2;while(b.abs(a.norm(a.subtract(q,n)))>g)n=q,q=a.add(a.multiply(p,n),o),h++;return q},gauss_seidel:function(d,e,f,g){var h=0,i=d.length,j=[],k=[],l=[],m,n,o,p,q;for(;h<i;h++){j[h]=[],k[h]=[],l[h]=[];for(m=0;m<i;m++)h>m?(j[h][m]=d[h][m],k[h][m]=l[h][m]=0):h<m?(k[h][m]=d[h][m],j[h][m]=l[h][m]=0):(l[h][m]=d[h][m],j[h][m]=k[h][m]=0)}p=a.multiply(a.multiply(a.inv(a.add(l,j)),k),-1),o=a.multiply(a.inv(a.add(l,j)),e),n=f,q=a.add(a.multiply(p,f),o),h=2;while(b.abs(a.norm(a.subtract(q,n)))>g)n=q,q=a.add(a.multiply(p,n),o),h+=1;return q},SOR:function(d,e,f,g,h){var i=0,j=d.length,k=[],l=[],m=[],n,o,p,q,r;for(;i<j;i++){k[i]=[],l[i]=[],m[i]=[];for(n=0;n<j;n++)i>n?(k[i][n]=d[i][n],l[i][n]=m[i][n]=0):i<n?(l[i][n]=d[i][n],k[i][n]=m[i][n]=0):(m[i][n]=d[i][n],k[i][n]=l[i][n]=0)}q=a.multiply(a.inv(a.add(m,a.multiply(k,h))),a.subtract(a.multiply(m,1-h),a.multiply(l,h))),p=a.multiply(a.multiply(a.inv(a.add(m,a.multiply(k,h))),e),h),o=f,r=a.add(a.multiply(q,f),p),i=2;while(b.abs(a.norm(a.subtract(r,o)))>g)o=r,r=a.add(a.multiply(q,o),p),i++;return r},householder:function(d){var e=d.length,f=d[0].length,g=0,h=[],i=[],j,k,l,m,n;for(;g<e-1;g++){j=0;for(m=g+1;m<f;m++)j+=d[m][g]*d[m][g];n=d[g+1][g]>0?-1:1,j=n*b.sqrt(j),k=b.sqrt((j*j-d[g+1][g]*j)/2),h=a.zeros(e,1),h[g+1][0]=(d[g+1][g]-j)/(2*k);for(l=g+2;l<e;l++)h[l][0]=d[l][g]/(2*k);i=a.subtract(a.identity(e,f),a.multiply(a.multiply(h,a.transpose(h)),2)),d=a.multiply(i,a.multiply(d,i))}return d},QR:function(d,e){var f=d.length,g=d[0].length,h=0,i=[],j=[],k=[],l,m,n,o,p,q;for(;h<f-1;h++){m=0;for(l=h+1;l<g;l++)m+=d[l][h]*d[l][h];p=d[h+1][h]>0?-1:1,m=p*b.sqrt(m),n=b.sqrt((m*m-d[h+1][h]*m)/2),i=a.zeros(f,1),i[h+1][0]=(d[h+1][h]-m)/(2*n);for(o=h+2;o<f;o++)i[o][0]=d[o][h]/(2*n);j=a.subtract(a.identity(f,g),a.multiply(a.multiply(i,a.transpose(i)),2)),d=a.multiply(j,d),e=a.multiply(j,e)}for(h=f-1;h>=0;h--){q=0;for(l=h+1;l<=g-1;l++)q=k[l]*d[h][l];k[h]=e[h][0]/d[h][h]}return k},jacobi:function(d){var e=1,f=0,g=d.length,h=a.identity(g,g),i=[],j,k,l,m,n,o,p,q;while(e===1){f++,o=d[0][1],m=0,n=1;for(k=0;k<g;k++)for(l=0;l<g;l++)k!=l&&o<b.abs(d[k][l])&&(o=b.abs(d[k][l]),m=k,n=l);d[m][m]===d[n][n]?p=d[m][n]>0?b.PI/4:-b.PI/4:p=b.atan(2*d[m][n]/(d[m][m]-d[n][n]))/2,q=a.identity(g,g),q[m][m]=b.cos(p),q[m][n]=-b.sin(p),q[n][m]=b.sin(p),q[n][n]=b.cos(p),h=a.multiply(h,q),j=a.multiply(a.multiply(a.inv(q),d),q),d=j,e=0;for(k=1;k<g;k++)for(l=1;l<g;l++)k!=l&&b.abs(d[k][l])>.001&&(e=1)}for(k=0;k<g;k++)i.push(d[k][k]);return[h,i]},rungekutta:function(b,c,d,e,f,g){var h,i,j,k,l;if(g===2)while(e<=d)h=c*b(e,f),i=c*b(e+c,f+h),j=f+(h+i)/2,f=j,e+=c;if(g===4)while(e<=d)h=c*b(e,f),i=c*b(e+c/2,f+h/2),k=c*b(e+c/2,f+i/2),l=c*b(e+c,f+k),j=f+(h+2*i+2*k+l)/6,f=j,e+=c;return f},romberg:function(c,d,e,f){var g=0,h=(e-d)/2,i=[],j=[],k=[],l,m,n,o,p,q;while(g<f/2){p=c(d);for(n=d,o=0;n<=e;n+=h,o++)i[o]=n;l=i.length;for(n=1;n<l-1;n++)p+=(n%2!==0?4:2)*c(i[n]);p=h/3*(p+c(e)),k[g]=p,h/=2,g++}m=k.length,l=1;while(m!==1){for(n=0;n<m-1;n++)j[n]=(b.pow(4,l)*k[n+1]-k[n])/(b.pow(4,l)-1);m=j.length,k=j,j=[],l++}return k},richardson:function(c,d,e,f){function g(a,b){var c=0,d=a.length,e;for(;c<d;c++)a[c]===b&&(e=c);return e}var h=c.length,i=b.abs(e-c[g(c,e)+1]),j=0,k=[],l=[],m,n,o,p,q;while(f>=i)m=g(c,e+f),n=g(c,e),k[j]=(d[m]-2*d[n]+d[2*n-m])/(f*f),f/=2,j++;p=k.length,o=1;while(p!=1){for(q=0;q<p-1;q++)l[q]=(b.pow(4,o)*k[q+1]-k[q])/(b.pow(4,o)-1);p=l.length,k=l,l=[],o++}return k},simpson:function(b,c,d,e){var f=(d-c)/e,g=b(c),h=[],i=c,j=0,k=1,l;for(;i<=d;i+=f,j++)h[j]=i;l=h.length;for(;k<l-1;k++)g+=(k%2!==0?4:2)*b(h[k]);return f/3*(g+b(d))},hermite:function(b,c,d,e){var f=b.length,g=0,h=0,i=[],j=[],k=[],l=[],m;for(;h<f;h++){i[h]=1;for(m=0;m<f;m++)h!=m&&(i[h]*=(e-b[m])/(b[h]-b[m]));j[h]=0;for(m=0;m<f;m++)h!=m&&(j[h]+=1/(b[h]-b[m]));k[h]=(1-2*(e-b[h])*j[h])*i[h]*i[h],l[h]=(e-b[h])*i[h]*i[h],g+=k[h]*c[h]+l[h]*d[h]}return g},lagrange:function(b,c,d){var e=0,f=0,g,h,i=b.length;for(;f<i;f++){h=c[f];for(g=0;g<i;g++)f!=g&&(h*=(d-b[g])/(b[f]-b[g]));e+=h}return e},cubic_spline:function(c,d,e){var f=c.length,g=0,h,i=[],j=[],k=[],l=[],m=[],n=[],o=[];for(;g<f-1;g++)m[g]=c[g+1]-c[g];k[0]=0;for(g=1;g<f-1;g++)k[g]=3/m[g]*(d[g+1]-d[g])-3/m[g-1]*(d[g]-d[g-1]);for(g=1;g<f-1;g++)i[g]=[],j[g]=[],i[g][g-1]=m[g-1],i[g][g]=2*(m[g-1]+m[g]),i[g][g+1]=m[g],j[g][0]=k[g];l=a.multiply(a.inv(i),j);for(h=0;h<f-1;h++)n[h]=(d[h+1]-d[h])/m[h]-m[h]*(l[h+1][0]+2*l[h][0])/3,o[h]=(l[h+1][0]-l[h][0])/(3*m[h]);for(h=0;h<f;h++)if(c[h]>e)break;return h-=1,d[h]+(e-c[h])*n[h]+a.sq(e-c[h])*l[h]+(e-c[h])*a.sq(e-c[h])*o[h]},gauss_quadrature:function(){throw new Error("gauss_quadrature not yet implemented")},PCA:function(c){var d=c.length,e=c[0].length,f=!1,g=0,h,i,j=[],k=[],l=[],m=[],n=[],o=[],p=[],q=[],r=[],s=[];for(g=0;g<d;g++)j[g]=a.sum(c[g])/e;for(g=0;g<e;g++){p[g]=[];for(h=0;h<d;h++)p[g][h]=c[h][g]-j[h]}p=a.transpose(p);for(g=0;g<d;g++){q[g]=[];for(h=0;h<d;h++)q[g][h]=a.dot([p[g]],[p[h]])/(e-1)}l=a.jacobi(q),r=l[0],k=l[1],s=a.transpose(r);for(g=0;g<k.length;g++)for(h=g;h<k.length;h++)k[g]<k[h]&&(i=k[g],k[g]=k[h],k[h]=i,m=s[g],s[g]=s[h],s[h]=m);o=a.transpose(p);for(g=0;g<d;g++){n[g]=[];for(h=0;h<o.length;h++)n[g][h]=a.dot([s[g]],[o[h]])}return[c,k,s,n]}}),function(b){for(var c=0;c<b.length;c++)(function(b){a.fn[b]=function(c,d){var e=this;return d?(setTimeout(function(){d.call(e,a.fn[b].call(e,c))},15),this):typeof a[b](this,c)=="number"?a[b](this,c):a(a[b](this,c))}})(b[c])}("add divide multiply subtract dot pow exp log abs norm angle".split +(" "))}(this.jStat,Math),function(a,b){var c=[].slice,d=a.utils.isNumber;a.extend({zscore:function(){var e=c.call(arguments);return d(e[1])?(e[0]-e[1])/e[2]:(e[0]-a.mean(e[1]))/a.stdev(e[1],e[2])},ztest:function(){var f=c.call(arguments);if(f.length===4){if(d(f[1])){var g=a.zscore(f[0],f[1],f[2]);return f[3]===1?a.normal.cdf(-b.abs(g),0,1):a.normal.cdf(-b.abs(g),0,1)*2}var g=f[0];return f[2]===1?a.normal.cdf(-b.abs(g),0,1):a.normal.cdf(-b.abs(g),0,1)*2}var g=a.zscore(f[0],f[1],f[3]);return f[1]===1?a.normal.cdf(-b.abs(g),0,1):a.normal.cdf(-b.abs(g),0,1)*2}}),a.extend(a.fn,{zscore:function(b,c){return(b-this.mean())/this.stdev(c)},ztest:function(d,e,f){var g=b.abs(this.zscore(d,f));return e===1?a.normal.cdf(-g,0,1):a.normal.cdf(-g,0,1)*2}}),a.extend({tscore:function(){var e=c.call(arguments);return e.length===4?(e[0]-e[1])/(e[2]/b.sqrt(e[3])):(e[0]-a.mean(e[1]))/(a.stdev(e[1],!0)/b.sqrt(e[1].length))},ttest:function(){var f=c.call(arguments),g;return f.length===5?(g=b.abs(a.tscore(f[0],f[1],f[2],f[3])),f[4]===1?a.studentt.cdf(-g,f[3]-1):a.studentt.cdf(-g,f[3]-1)*2):d(f[1])?(g=b.abs(f[0]),f[2]==1?a.studentt.cdf(-g,f[1]-1):a.studentt.cdf(-g,f[1]-1)*2):(g=b.abs(a.tscore(f[0],f[1])),f[2]==1?a.studentt.cdf(-g,f[1].length-1):a.studentt.cdf(-g,f[1].length-1)*2)}}),a.extend(a.fn,{tscore:function(c){return(c-this.mean())/(this.stdev(!0)/b.sqrt(this.cols()))},ttest:function(d,e){return e===1?1-a.studentt.cdf(b.abs(this.tscore(d)),this.cols()-1):a.studentt.cdf(-b.abs(this.tscore(d)),this.cols()-1)*2}}),a.extend({anovafscore:function(){var e=c.call(arguments),f,g,h,i,j,k,l,m;if(e.length===1){j=new Array(e[0].length);for(l=0;l<e[0].length;l++)j[l]=e[0][l];e=j}if(e.length===2)return a.variance(e[0])/a.variance(e[1]);g=new Array;for(l=0;l<e.length;l++)g=g.concat(e[l]);h=a.mean(g),f=0;for(l=0;l<e.length;l++)f+=e[l].length*b.pow(a.mean(e[l])-h,2);f/=e.length-1,k=0;for(l=0;l<e.length;l++){i=a.mean(e[l]);for(m=0;m<e[l].length;m++)k+=b.pow(e[l][m]-i,2)}return k/=g.length-e.length,f/k},anovaftest:function(){var e=c.call(arguments),f,g,h,i;if(d(e[0]))return 1-a.centralF.cdf(e[0],e[1],e[2]);anovafscore=a.anovafscore(e),f=e.length-1,h=0;for(i=0;i<e.length;i++)h+=e[i].length;return g=h-f-1,1-a.centralF.cdf(anovafscore,f,g)},ftest:function(c,d,e){return 1-a.centralF.cdf(c,d,e)}}),a.extend(a.fn,{anovafscore:function(){return a.anovafscore(this.toArray())},anovaftes:function(){var c=0,d;for(d=0;d<this.length;d++)c+=this[d].length;return a.ftest(this.anovafscore(),this.length-1,c-this.length)}}),a.extend({normalci:function(){var e=c.call(arguments),f=new Array(2),g;return e.length===4?g=b.abs(a.normal.inv(e[1]/2,0,1)*e[2]/b.sqrt(e[3])):g=b.abs(a.normal.inv(e[1]/2,0,1)*a.stdev(e[2])/b.sqrt(e[2].length)),f[0]=e[0]-g,f[1]=e[0]+g,f},tci:function(){var e=c.call(arguments),f=new Array(2),g;return e.length===4?g=b.abs(a.studentt.inv(e[1]/2,e[3]-1)*e[2]/b.sqrt(e[3])):g=b.abs(a.studentt.inv(e[1]/2,e[2].length-1)*a.stdev(e[2],!0)/b.sqrt(e[2].length)),f[0]=e[0]-g,f[1]=e[0]+g,f},significant:function(b,c){return b<c}}),a.extend(a.fn,{normalci:function(c,d){return a.normalci(c,d,this.toArray())},tci:function(c,d){return a.tci(c,d,this.toArray())}})}(this.jStat,Math);
\ No newline at end of file diff --git a/wqflask/wqflask/static/new/js_external/shapiro-wilk.js b/wqflask/wqflask/static/new/js_external/shapiro-wilk.js new file mode 100644 index 00000000..1084b9f5 --- /dev/null +++ b/wqflask/wqflask/static/new/js_external/shapiro-wilk.js @@ -0,0 +1,197 @@ +/* + * Ported from http://svn.r-project.org/R/trunk/src/library/stats/src/swilk.c + * + * R : A Computer Language for Statistical Data Analysis + * Copyright (C) 2000-12 The R Core Team. + * + * Based on Applied Statistics algorithms AS181, R94 + * (C) Royal Statistical Society 1982, 1995 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, a copy is available at + * http://www.r-project.org/Licenses/ + */ + +function sign(x) { + if (x == 0) + return 0; + return x > 0 ? 1 : -1; +} + +function ShapiroWilkW(x) +{ + function poly(cc, nord, x) + { + /* Algorithm AS 181.2 Appl. Statist. (1982) Vol. 31, No. 2 + Calculates the algebraic polynomial of order nord-1 with array of coefficients cc. + Zero order coefficient is cc(1) = cc[0] */ + var p; + var ret_val; + + ret_val = cc[0]; + if (nord > 1) { + p = x * cc[nord-1]; + for (j = nord - 2; j > 0; j--) + p = (p + cc[j]) * x; + ret_val += p; + } + return ret_val; + } + x = x.sort(function (a, b) { return a - b; }); + var n = x.length; + if (n < 3) + return undefined; + var nn2 = Math.floor(n / 2); + var a = new Array(Math.floor(nn2) + 1); /* 1-based */ + +/* ALGORITHM AS R94 APPL. STATIST. (1995) vol.44, no.4, 547-551. + + Calculates the Shapiro-Wilk W test and its significance level +*/ + var small = 1e-19; + + /* polynomial coefficients */ + var g = [ -2.273, 0.459 ]; + var c1 = [ 0, 0.221157, -0.147981, -2.07119, 4.434685, -2.706056 ]; + var c2 = [ 0, 0.042981, -0.293762, -1.752461, 5.682633, -3.582633 ]; + var c3 = [ 0.544, -0.39978, 0.025054, -6.714e-4 ]; + var c4 = [ 1.3822, -0.77857, 0.062767, -0.0020322 ]; + var c5 = [ -1.5861, -0.31082, -0.083751, 0.0038915 ]; + var c6 = [ -0.4803, -0.082676, 0.0030302 ]; + + /* Local variables */ + var i, j, i1; + + var ssassx, summ2, ssumm2, gamma, range; + var a1, a2, an, m, s, sa, xi, sx, xx, y, w1; + var fac, asa, an25, ssa, sax, rsn, ssx, xsx; + + var pw = 1; + an = n; + + if (n == 3) + a[1] = 0.70710678;/* = sqrt(1/2) */ + else { + an25 = an + 0.25; + summ2 = 0.0; + for (i = 1; i <= nn2; i++) { + a[i] = jStat.normal.inv((i - 0.375) / an25, 0, 1); // p(X <= x), + var r__1 = a[i]; + summ2 += r__1 * r__1; + } + summ2 *= 2; + ssumm2 = Math.sqrt(summ2); + rsn = 1 / Math.sqrt(an); + a1 = poly(c1, 6, rsn) - a[1] / ssumm2; + + /* Normalize a[] */ + if (n > 5) { + i1 = 3; + a2 = -a[2] / ssumm2 + poly(c2, 6, rsn); + fac = Math.sqrt((summ2 - 2 * (a[1] * a[1]) - 2 * (a[2] * a[2])) / (1 - 2 * (a1 * a1) - 2 * (a2 * a2))); + a[2] = a2; + } else { + i1 = 2; + fac = Math.sqrt((summ2 - 2 * (a[1] * a[1])) / ( 1 - 2 * (a1 * a1))); + } + a[1] = a1; + for (i = i1; i <= nn2; i++) + a[i] /= - fac; + } + +/* Check for zero range */ + + range = x[n - 1] - x[0]; + if (range < small) { + console.log('range is too small!') + return undefined; + } + + +/* Check for correct sort order on range - scaled X */ + + xx = x[0] / range; + sx = xx; + sa = -a[1]; + for (i = 1, j = n - 1; i < n; j--) { + xi = x[i] / range; + if (xx - xi > small) { + console.log("xx - xi is too big.", xx - xi); + return undefined; + } + sx += xi; + i++; + if (i != j) + sa += sign(i - j) * a[Math.min(i, j)]; + xx = xi; + } + if (n > 5000) { + console.log("n is too big!") + return undefined; + } + + +/* Calculate W statistic as squared correlation + between data and coefficients */ + + sa /= n; + sx /= n; + ssa = ssx = sax = 0.; + for (i = 0, j = n - 1; i < n; i++, j--) { + if (i != j) + asa = sign(i - j) * a[1 + Math.min(i, j)] - sa; + else + asa = -sa; + xsx = x[i] / range - sx; + ssa += asa * asa; + ssx += xsx * xsx; + sax += asa * xsx; + } + +/* W1 equals (1-W) calculated to avoid excessive rounding error + for W very near 1 (a potential problem in very large samples) */ + + ssassx = Math.sqrt(ssa * ssx); + w1 = (ssassx - sax) * (ssassx + sax) / (ssa * ssx); + var w = 1 - w1; + +/* Calculate significance level for W */ + + if (n == 3) {/* exact P value : */ + var pi6 = 1.90985931710274; /* = 6/pi */ + var stqr = 1.04719755119660; /* = asin(sqrt(3/4)) */ + pw = pi6 * (Math.asin(Math.sqrt(w)) - stqr); + if (pw < 0.) + pw = 0; + return w; + } + y = Math.log(w1); + xx = Math.log(an); + if (n <= 11) { + gamma = poly(g, 2, an); + if (y >= gamma) { + pw = 1e-99; /* an "obvious" value, was 'small' which was 1e-19f */ + return w; + } + y = -Math.log(gamma - y); + m = poly(c3, 4, an); + s = Math.exp(poly(c4, 4, an)); + } else { /* n >= 12 */ + m = poly(c5, 4, xx); + s = Math.exp(poly(c6, 3, xx)); + } + + pw = 0.5 - 0.5 * jStat.erf((y - m) / Math.sqrt(2) / s) + + return {w: w, p: pw}; +} diff --git a/wqflask/wqflask/static/new/packages/nvd3/nv.d3.css b/wqflask/wqflask/static/new/packages/nvd3/nv.d3.css new file mode 100644 index 00000000..726f76c3 --- /dev/null +++ b/wqflask/wqflask/static/new/packages/nvd3/nv.d3.css @@ -0,0 +1,641 @@ +/* nvd3 version 1.8.1 (https://github.com/novus/nvd3) 2015-05-25 */ +.nvd3 .nv-axis {
+ pointer-events:none;
+ opacity: 1;
+}
+
+.nvd3 .nv-axis path {
+ fill: none;
+ stroke: #000;
+ stroke-opacity: .75;
+ shape-rendering: crispEdges;
+}
+
+.nvd3 .nv-axis path.domain {
+ stroke-opacity: .75;
+}
+
+.nvd3 .nv-axis.nv-x path.domain {
+ stroke-opacity: 0;
+}
+
+.nvd3 .nv-axis line {
+ fill: none;
+ stroke: #e5e5e5;
+ shape-rendering: crispEdges;
+}
+
+.nvd3 .nv-axis .zero line,
+ /*this selector may not be necessary*/ .nvd3 .nv-axis line.zero {
+ stroke-opacity: .75;
+}
+
+.nvd3 .nv-axis .nv-axisMaxMin text {
+ font-weight: bold;
+}
+
+.nvd3 .x .nv-axis .nv-axisMaxMin text,
+.nvd3 .x2 .nv-axis .nv-axisMaxMin text,
+.nvd3 .x3 .nv-axis .nv-axisMaxMin text {
+ text-anchor: middle
+}
+
+.nvd3 .nv-axis.nv-disabled {
+ opacity: 0;
+}
+ +.nvd3 .nv-bars rect {
+ fill-opacity: .75;
+
+ transition: fill-opacity 250ms linear;
+ -moz-transition: fill-opacity 250ms linear;
+ -webkit-transition: fill-opacity 250ms linear;
+}
+
+.nvd3 .nv-bars rect.hover {
+ fill-opacity: 1;
+}
+
+.nvd3 .nv-bars .hover rect {
+ fill: lightblue;
+}
+
+.nvd3 .nv-bars text {
+ fill: rgba(0,0,0,0);
+}
+
+.nvd3 .nv-bars .hover text {
+ fill: rgba(0,0,0,1);
+}
+
+.nvd3 .nv-multibar .nv-groups rect,
+.nvd3 .nv-multibarHorizontal .nv-groups rect,
+.nvd3 .nv-discretebar .nv-groups rect {
+ stroke-opacity: 0;
+
+ transition: fill-opacity 250ms linear;
+ -moz-transition: fill-opacity 250ms linear;
+ -webkit-transition: fill-opacity 250ms linear;
+}
+
+.nvd3 .nv-multibar .nv-groups rect:hover,
+.nvd3 .nv-multibarHorizontal .nv-groups rect:hover,
+.nvd3 .nv-candlestickBar .nv-ticks rect:hover,
+.nvd3 .nv-discretebar .nv-groups rect:hover {
+ fill-opacity: 1;
+}
+
+.nvd3 .nv-discretebar .nv-groups text,
+.nvd3 .nv-multibarHorizontal .nv-groups text {
+ font-weight: bold;
+ fill: rgba(0,0,0,1);
+ stroke: rgba(0,0,0,0);
+}
+ +/* boxplot CSS */ +.nvd3 .nv-boxplot circle { + fill-opacity: 0.5; +} + +.nvd3 .nv-boxplot circle:hover { + fill-opacity: 1; +} + +.nvd3 .nv-boxplot rect:hover { + fill-opacity: 1; +} + +.nvd3 line.nv-boxplot-median { + stroke: black; +} + +.nv-boxplot-tick:hover { + stroke-width: 2.5px; +} +/* bullet */
+.nvd3.nv-bullet { font: 10px sans-serif; }
+.nvd3.nv-bullet .nv-measure { fill-opacity: .8; }
+.nvd3.nv-bullet .nv-measure:hover { fill-opacity: 1; }
+.nvd3.nv-bullet .nv-marker { stroke: #000; stroke-width: 2px; }
+.nvd3.nv-bullet .nv-markerTriangle { stroke: #000; fill: #fff; stroke-width: 1.5px; }
+.nvd3.nv-bullet .nv-tick line { stroke: #666; stroke-width: .5px; }
+.nvd3.nv-bullet .nv-range.nv-s0 { fill: #eee; }
+.nvd3.nv-bullet .nv-range.nv-s1 { fill: #ddd; }
+.nvd3.nv-bullet .nv-range.nv-s2 { fill: #ccc; }
+.nvd3.nv-bullet .nv-title { font-size: 14px; font-weight: bold; }
+.nvd3.nv-bullet .nv-subtitle { fill: #999; }
+
+
+.nvd3.nv-bullet .nv-range {
+ fill: #bababa;
+ fill-opacity: .4;
+}
+.nvd3.nv-bullet .nv-range:hover {
+ fill-opacity: .7;
+}
+ +.nvd3.nv-candlestickBar .nv-ticks .nv-tick {
+ stroke-width: 1px;
+}
+
+.nvd3.nv-candlestickBar .nv-ticks .nv-tick.hover {
+ stroke-width: 2px;
+}
+
+.nvd3.nv-candlestickBar .nv-ticks .nv-tick.positive rect {
+ stroke: #2ca02c;
+ fill: #2ca02c;
+}
+
+.nvd3.nv-candlestickBar .nv-ticks .nv-tick.negative rect {
+ stroke: #d62728;
+ fill: #d62728;
+}
+
+.with-transitions .nv-candlestickBar .nv-ticks .nv-tick {
+ transition: stroke-width 250ms linear, stroke-opacity 250ms linear;
+ -moz-transition: stroke-width 250ms linear, stroke-opacity 250ms linear;
+ -webkit-transition: stroke-width 250ms linear, stroke-opacity 250ms linear;
+
+}
+
+.nvd3.nv-candlestickBar .nv-ticks line {
+ stroke: #333;
+}
+
+ +.nvd3 .nv-legend .nv-disabled rect { + /*fill-opacity: 0;*/ +} + +.nvd3 .nv-check-box .nv-box { + fill-opacity:0; + stroke-width:2; +} + +.nvd3 .nv-check-box .nv-check { + fill-opacity:0; + stroke-width:4; +} + +.nvd3 .nv-series.nv-disabled .nv-check-box .nv-check { + fill-opacity:0; + stroke-opacity:0; +} + +.nvd3 .nv-controlsWrap .nv-legend .nv-check-box .nv-check { + opacity: 0; +} + +/* line plus bar */
+.nvd3.nv-linePlusBar .nv-bar rect {
+ fill-opacity: .75;
+}
+
+.nvd3.nv-linePlusBar .nv-bar rect:hover {
+ fill-opacity: 1;
+} +.nvd3 .nv-groups path.nv-line {
+ fill: none;
+}
+
+.nvd3 .nv-groups path.nv-area {
+ stroke: none;
+}
+
+.nvd3.nv-line .nvd3.nv-scatter .nv-groups .nv-point {
+ fill-opacity: 0;
+ stroke-opacity: 0;
+}
+
+.nvd3.nv-scatter.nv-single-point .nv-groups .nv-point {
+ fill-opacity: .5 !important;
+ stroke-opacity: .5 !important;
+}
+
+
+.with-transitions .nvd3 .nv-groups .nv-point {
+ transition: stroke-width 250ms linear, stroke-opacity 250ms linear;
+ -moz-transition: stroke-width 250ms linear, stroke-opacity 250ms linear;
+ -webkit-transition: stroke-width 250ms linear, stroke-opacity 250ms linear;
+
+}
+
+.nvd3.nv-scatter .nv-groups .nv-point.hover,
+.nvd3 .nv-groups .nv-point.hover {
+ stroke-width: 7px;
+ fill-opacity: .95 !important;
+ stroke-opacity: .95 !important;
+}
+
+
+.nvd3 .nv-point-paths path {
+ stroke: #aaa;
+ stroke-opacity: 0;
+ fill: #eee;
+ fill-opacity: 0;
+}
+
+
+
+.nvd3 .nv-indexLine {
+ cursor: ew-resize;
+}
+ +/********************
+ * SVG CSS
+ */
+
+/********************
+ Default CSS for an svg element nvd3 used
+*/
+svg.nvd3-svg {
+ -webkit-touch-callout: none;
+ -webkit-user-select: none;
+ -khtml-user-select: none;
+ -ms-user-select: none;
+ -moz-user-select: none;
+ user-select: none;
+ display: block;
+ width:100%;
+ height:100%;
+}
+
+/********************
+ Box shadow and border radius styling
+*/
+.nvtooltip.with-3d-shadow, .with-3d-shadow .nvtooltip {
+ -moz-box-shadow: 0 5px 10px rgba(0,0,0,.2);
+ -webkit-box-shadow: 0 5px 10px rgba(0,0,0,.2);
+ box-shadow: 0 5px 10px rgba(0,0,0,.2);
+
+ -webkit-border-radius: 5px;
+ -moz-border-radius: 5px;
+ border-radius: 5px;
+}
+
+
+.nvd3 text {
+ font: normal 12px Arial;
+}
+
+.nvd3 .title {
+ font: bold 14px Arial;
+}
+
+.nvd3 .nv-background {
+ fill: white;
+ fill-opacity: 0;
+}
+
+.nvd3.nv-noData {
+ font-size: 18px;
+ font-weight: bold;
+}
+
+
+/**********
+* Brush
+*/
+
+.nv-brush .extent {
+ fill-opacity: .125;
+ shape-rendering: crispEdges;
+}
+
+.nv-brush .resize path {
+ fill: #eee;
+ stroke: #666;
+}
+
+
+/**********
+* Legend
+*/
+
+.nvd3 .nv-legend .nv-series {
+ cursor: pointer;
+}
+
+.nvd3 .nv-legend .nv-disabled circle {
+ fill-opacity: 0;
+}
+
+/* focus */
+.nvd3 .nv-brush .extent {
+ fill-opacity: 0 !important;
+}
+
+.nvd3 .nv-brushBackground rect {
+ stroke: #000;
+ stroke-width: .4;
+ fill: #fff;
+ fill-opacity: .7;
+}
+
+ +.nvd3.nv-ohlcBar .nv-ticks .nv-tick {
+ stroke-width: 1px;
+}
+
+.nvd3.nv-ohlcBar .nv-ticks .nv-tick.hover {
+ stroke-width: 2px;
+}
+
+.nvd3.nv-ohlcBar .nv-ticks .nv-tick.positive {
+ stroke: #2ca02c;
+}
+
+.nvd3.nv-ohlcBar .nv-ticks .nv-tick.negative {
+ stroke: #d62728;
+}
+
+ +.nvd3 .background path {
+ fill: none;
+ stroke: #EEE;
+ stroke-opacity: .4;
+ shape-rendering: crispEdges;
+}
+
+.nvd3 .foreground path {
+ fill: none;
+ stroke-opacity: .7;
+}
+
+.nvd3 .nv-parallelCoordinates-brush .extent
+{
+ fill: #fff;
+ fill-opacity: .6;
+ stroke: gray;
+ shape-rendering: crispEdges;
+}
+
+.nvd3 .nv-parallelCoordinates .hover {
+ fill-opacity: 1;
+ stroke-width: 3px;
+}
+
+
+.nvd3 .missingValuesline line {
+ fill: none;
+ stroke: black;
+ stroke-width: 1;
+ stroke-opacity: 1;
+ stroke-dasharray: 5, 5;
+} +.nvd3.nv-pie path {
+ stroke-opacity: 0;
+ transition: fill-opacity 250ms linear, stroke-width 250ms linear, stroke-opacity 250ms linear;
+ -moz-transition: fill-opacity 250ms linear, stroke-width 250ms linear, stroke-opacity 250ms linear;
+ -webkit-transition: fill-opacity 250ms linear, stroke-width 250ms linear, stroke-opacity 250ms linear;
+
+}
+
+.nvd3.nv-pie .nv-pie-title {
+ font-size: 24px;
+ fill: rgba(19, 196, 249, 0.59);
+}
+
+.nvd3.nv-pie .nv-slice text {
+ stroke: #000;
+ stroke-width: 0;
+}
+
+.nvd3.nv-pie path {
+ stroke: #fff;
+ stroke-width: 1px;
+ stroke-opacity: 1;
+}
+
+.nvd3.nv-pie .hover path {
+ fill-opacity: .7;
+}
+.nvd3.nv-pie .nv-label {
+ pointer-events: none;
+}
+.nvd3.nv-pie .nv-label rect {
+ fill-opacity: 0;
+ stroke-opacity: 0;
+}
+ +/* scatter */
+.nvd3 .nv-groups .nv-point.hover {
+ stroke-width: 20px;
+ stroke-opacity: .5;
+}
+
+.nvd3 .nv-scatter .nv-point.hover {
+ fill-opacity: 1;
+}
+.nv-noninteractive {
+ pointer-events: none;
+}
+
+.nv-distx, .nv-disty {
+ pointer-events: none;
+}
+ +/* sparkline */
+.nvd3.nv-sparkline path {
+ fill: none;
+}
+
+.nvd3.nv-sparklineplus g.nv-hoverValue {
+ pointer-events: none;
+}
+
+.nvd3.nv-sparklineplus .nv-hoverValue line {
+ stroke: #333;
+ stroke-width: 1.5px;
+}
+
+.nvd3.nv-sparklineplus,
+.nvd3.nv-sparklineplus g {
+ pointer-events: all;
+}
+
+.nvd3 .nv-hoverArea {
+ fill-opacity: 0;
+ stroke-opacity: 0;
+}
+
+.nvd3.nv-sparklineplus .nv-xValue,
+.nvd3.nv-sparklineplus .nv-yValue {
+ stroke-width: 0;
+ font-size: .9em;
+ font-weight: normal;
+}
+
+.nvd3.nv-sparklineplus .nv-yValue {
+ stroke: #f66;
+}
+
+.nvd3.nv-sparklineplus .nv-maxValue {
+ stroke: #2ca02c;
+ fill: #2ca02c;
+}
+
+.nvd3.nv-sparklineplus .nv-minValue {
+ stroke: #d62728;
+ fill: #d62728;
+}
+
+.nvd3.nv-sparklineplus .nv-currentValue {
+ font-weight: bold;
+ font-size: 1.1em;
+} +/* stacked area */
+.nvd3.nv-stackedarea path.nv-area {
+ fill-opacity: .7;
+ stroke-opacity: 0;
+ transition: fill-opacity 250ms linear, stroke-opacity 250ms linear;
+ -moz-transition: fill-opacity 250ms linear, stroke-opacity 250ms linear;
+ -webkit-transition: fill-opacity 250ms linear, stroke-opacity 250ms linear;
+}
+
+.nvd3.nv-stackedarea path.nv-area.hover {
+ fill-opacity: .9;
+}
+
+
+.nvd3.nv-stackedarea .nv-groups .nv-point {
+ stroke-opacity: 0;
+ fill-opacity: 0;
+} +
+
+.nvtooltip {
+ position: absolute;
+ background-color: rgba(255,255,255,1.0);
+ color: rgba(0,0,0,1.0);
+ padding: 1px;
+ border: 1px solid rgba(0,0,0,.2);
+ z-index: 10000;
+ display: block;
+
+ font-family: Arial;
+ font-size: 13px;
+ text-align: left;
+ pointer-events: none;
+
+ white-space: nowrap;
+
+ -webkit-touch-callout: none;
+ -webkit-user-select: none;
+ -khtml-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ user-select: none;
+}
+
+.nvtooltip {
+ background: rgba(255,255,255, 0.8);
+ border: 1px solid rgba(0,0,0,0.5);
+ border-radius: 4px;
+}
+
+/*Give tooltips that old fade in transition by
+ putting a "with-transitions" class on the container div.
+*/
+.nvtooltip.with-transitions, .with-transitions .nvtooltip {
+ transition: opacity 50ms linear;
+ -moz-transition: opacity 50ms linear;
+ -webkit-transition: opacity 50ms linear;
+
+ transition-delay: 200ms;
+ -moz-transition-delay: 200ms;
+ -webkit-transition-delay: 200ms;
+}
+
+.nvtooltip.x-nvtooltip,
+.nvtooltip.y-nvtooltip {
+ padding: 8px;
+}
+
+.nvtooltip h3 {
+ margin: 0;
+ padding: 4px 14px;
+ line-height: 18px;
+ font-weight: normal;
+ background-color: rgba(247,247,247,0.75);
+ color: rgba(0,0,0,1.0);
+ text-align: center;
+
+ border-bottom: 1px solid #ebebeb;
+
+ -webkit-border-radius: 5px 5px 0 0;
+ -moz-border-radius: 5px 5px 0 0;
+ border-radius: 5px 5px 0 0;
+}
+
+.nvtooltip p {
+ margin: 0;
+ padding: 5px 14px;
+ text-align: center;
+}
+
+.nvtooltip span {
+ display: inline-block;
+ margin: 2px 0;
+}
+
+.nvtooltip table {
+ margin: 6px;
+ border-spacing:0;
+}
+
+
+.nvtooltip table td {
+ padding: 2px 9px 2px 0;
+ vertical-align: middle;
+}
+
+.nvtooltip table td.key {
+ font-weight:normal;
+}
+.nvtooltip table td.value {
+ text-align: right;
+ font-weight: bold;
+}
+
+.nvtooltip table tr.highlight td {
+ padding: 1px 9px 1px 0;
+ border-bottom-style: solid;
+ border-bottom-width: 1px;
+ border-top-style: solid;
+ border-top-width: 1px;
+}
+
+.nvtooltip table td.legend-color-guide div {
+ width: 8px;
+ height: 8px;
+ vertical-align: middle;
+}
+
+.nvtooltip table td.legend-color-guide div {
+ width: 12px;
+ height: 12px;
+ border: 1px solid #999;
+}
+
+.nvtooltip .footer {
+ padding: 3px;
+ text-align: center;
+}
+
+.nvtooltip-pending-removal {
+ pointer-events: none;
+ display: none;
+}
+
+
+/****
+Interactive Layer
+*/
+.nvd3 .nv-interactiveGuideLine {
+ pointer-events:none;
+}
+.nvd3 line.nv-guideline {
+ stroke: #ccc;
+}
\ No newline at end of file diff --git a/wqflask/wqflask/static/new/packages/nvd3/nv.d3.js b/wqflask/wqflask/static/new/packages/nvd3/nv.d3.js new file mode 100644 index 00000000..b11ce58f --- /dev/null +++ b/wqflask/wqflask/static/new/packages/nvd3/nv.d3.js @@ -0,0 +1,13362 @@ +/* nvd3 version 1.8.1 (https://github.com/novus/nvd3) 2015-05-25 */ +(function(){ + +// set up main nv object +var nv = {}; + +// the major global objects under the nv namespace +nv.dev = false; //set false when in production +nv.tooltip = nv.tooltip || {}; // For the tooltip system +nv.utils = nv.utils || {}; // Utility subsystem +nv.models = nv.models || {}; //stores all the possible models/components +nv.charts = {}; //stores all the ready to use charts +nv.logs = {}; //stores some statistics and potential error messages +nv.dom = {}; //DOM manipulation functions + +nv.dispatch = d3.dispatch('render_start', 'render_end'); + +// Function bind polyfill +// Needed ONLY for phantomJS as it's missing until version 2.0 which is unreleased as of this comment +// https://github.com/ariya/phantomjs/issues/10522 +// http://kangax.github.io/compat-table/es5/#Function.prototype.bind +// phantomJS is used for running the test suite +if (!Function.prototype.bind) { + Function.prototype.bind = function (oThis) { + if (typeof this !== "function") { + // closest thing possible to the ECMAScript 5 internal IsCallable function + throw new TypeError("Function.prototype.bind - what is trying to be bound is not callable"); + } + + var aArgs = Array.prototype.slice.call(arguments, 1), + fToBind = this, + fNOP = function () {}, + fBound = function () { + return fToBind.apply(this instanceof fNOP && oThis + ? this + : oThis, + aArgs.concat(Array.prototype.slice.call(arguments))); + }; + + fNOP.prototype = this.prototype; + fBound.prototype = new fNOP(); + return fBound; + }; +} + +// Development render timers - disabled if dev = false +if (nv.dev) { + nv.dispatch.on('render_start', function(e) { + nv.logs.startTime = +new Date(); + }); + + nv.dispatch.on('render_end', function(e) { + nv.logs.endTime = +new Date(); + nv.logs.totalTime = nv.logs.endTime - nv.logs.startTime; + nv.log('total', nv.logs.totalTime); // used for development, to keep track of graph generation times + }); +} + +// Logs all arguments, and returns the last so you can test things in place +// Note: in IE8 console.log is an object not a function, and if modernizr is used +// then calling Function.prototype.bind with with anything other than a function +// causes a TypeError to be thrown. +nv.log = function() { + if (nv.dev && window.console && console.log && console.log.apply) + console.log.apply(console, arguments); + else if (nv.dev && window.console && typeof console.log == "function" && Function.prototype.bind) { + var log = Function.prototype.bind.call(console.log, console); + log.apply(console, arguments); + } + return arguments[arguments.length - 1]; +}; + +// print console warning, should be used by deprecated functions +nv.deprecated = function(name, info) { + if (console && console.warn) { + console.warn('nvd3 warning: `' + name + '` has been deprecated. ', info || ''); + } +}; + +// The nv.render function is used to queue up chart rendering +// in non-blocking async functions. +// When all queued charts are done rendering, nv.dispatch.render_end is invoked. +nv.render = function render(step) { + // number of graphs to generate in each timeout loop + step = step || 1; + + nv.render.active = true; + nv.dispatch.render_start(); + + var renderLoop = function() { + var chart, graph; + + for (var i = 0; i < step && (graph = nv.render.queue[i]); i++) { + chart = graph.generate(); + if (typeof graph.callback == typeof(Function)) graph.callback(chart); + } + + nv.render.queue.splice(0, i); + + if (nv.render.queue.length) { + setTimeout(renderLoop); + } + else { + nv.dispatch.render_end(); + nv.render.active = false; + } + }; + + setTimeout(renderLoop); +}; + +nv.render.active = false; +nv.render.queue = []; + +/* +Adds a chart to the async rendering queue. This method can take arguments in two forms: +nv.addGraph({ + generate: <Function> + callback: <Function> +}) + +or + +nv.addGraph(<generate Function>, <callback Function>) + +The generate function should contain code that creates the NVD3 model, sets options +on it, adds data to an SVG element, and invokes the chart model. The generate function +should return the chart model. See examples/lineChart.html for a usage example. + +The callback function is optional, and it is called when the generate function completes. +*/ +nv.addGraph = function(obj) { + if (typeof arguments[0] === typeof(Function)) { + obj = {generate: arguments[0], callback: arguments[1]}; + } + + nv.render.queue.push(obj); + + if (!nv.render.active) { + nv.render(); + } +}; + +// Node/CommonJS exports +if (typeof(module) !== 'undefined' && typeof(exports) !== 'undefined') { + module.exports = nv; +} + +if (typeof(window) !== 'undefined') { + window.nv = nv; +} +/* Facade for queueing DOM write operations
+ * with Fastdom (https://github.com/wilsonpage/fastdom)
+ * if available.
+ * This could easily be extended to support alternate
+ * implementations in the future.
+ */
+nv.dom.write = function(callback) {
+ if (window.fastdom !== undefined) {
+ return fastdom.write(callback);
+ }
+ return callback();
+};
+
+/* Facade for queueing DOM read operations
+ * with Fastdom (https://github.com/wilsonpage/fastdom)
+ * if available.
+ * This could easily be extended to support alternate
+ * implementations in the future.
+ */
+nv.dom.read = function(callback) {
+ if (window.fastdom !== undefined) {
+ return fastdom.read(callback);
+ }
+ return callback();
+};/* Utility class to handle creation of an interactive layer. + This places a rectangle on top of the chart. When you mouse move over it, it sends a dispatch + containing the X-coordinate. It can also render a vertical line where the mouse is located. + + dispatch.elementMousemove is the important event to latch onto. It is fired whenever the mouse moves over + the rectangle. The dispatch is given one object which contains the mouseX/Y location. + It also has 'pointXValue', which is the conversion of mouseX to the x-axis scale. + */ +nv.interactiveGuideline = function() { + "use strict"; + + var tooltip = nv.models.tooltip(); + tooltip.duration(0).hideDelay(0)._isInteractiveLayer(true).hidden(false); + + //Public settings + var width = null; + var height = null; + + //Please pass in the bounding chart's top and left margins + //This is important for calculating the correct mouseX/Y positions. + var margin = {left: 0, top: 0} + , xScale = d3.scale.linear() + , dispatch = d3.dispatch('elementMousemove', 'elementMouseout', 'elementClick', 'elementDblclick') + , showGuideLine = true; + //Must pass in the bounding chart's <svg> container. + //The mousemove event is attached to this container. + var svgContainer = null; + + // check if IE by looking for activeX + var isMSIE = "ActiveXObject" in window; + + + function layer(selection) { + selection.each(function(data) { + var container = d3.select(this); + var availableWidth = (width || 960), availableHeight = (height || 400); + var wrap = container.selectAll("g.nv-wrap.nv-interactiveLineLayer") + .data([data]); + var wrapEnter = wrap.enter() + .append("g").attr("class", " nv-wrap nv-interactiveLineLayer"); + wrapEnter.append("g").attr("class","nv-interactiveGuideLine"); + + if (!svgContainer) { + return; + } + + function mouseHandler() { + var d3mouse = d3.mouse(this); + var mouseX = d3mouse[0]; + var mouseY = d3mouse[1]; + var subtractMargin = true; + var mouseOutAnyReason = false; + if (isMSIE) { + /* + D3.js (or maybe SVG.getScreenCTM) has a nasty bug in Internet Explorer 10. + d3.mouse() returns incorrect X,Y mouse coordinates when mouse moving + over a rect in IE 10. + However, d3.event.offsetX/Y also returns the mouse coordinates + relative to the triggering <rect>. So we use offsetX/Y on IE. + */ + mouseX = d3.event.offsetX; + mouseY = d3.event.offsetY; + + /* + On IE, if you attach a mouse event listener to the <svg> container, + it will actually trigger it for all the child elements (like <path>, <circle>, etc). + When this happens on IE, the offsetX/Y is set to where ever the child element + is located. + As a result, we do NOT need to subtract margins to figure out the mouse X/Y + position under this scenario. Removing the line below *will* cause + the interactive layer to not work right on IE. + */ + if(d3.event.target.tagName !== "svg") { + subtractMargin = false; + } + + if (d3.event.target.className.baseVal.match("nv-legend")) { + mouseOutAnyReason = true; + } + + } + + if(subtractMargin) { + mouseX -= margin.left; + mouseY -= margin.top; + } + + /* If mouseX/Y is outside of the chart's bounds, + trigger a mouseOut event. + */ + if (mouseX < 0 || mouseY < 0 + || mouseX > availableWidth || mouseY > availableHeight + || (d3.event.relatedTarget && d3.event.relatedTarget.ownerSVGElement === undefined) + || mouseOutAnyReason + ) { + + if (isMSIE) { + if (d3.event.relatedTarget + && d3.event.relatedTarget.ownerSVGElement === undefined + && (d3.event.relatedTarget.className === undefined + || d3.event.relatedTarget.className.match(tooltip.nvPointerEventsClass))) { + + return; + } + } + dispatch.elementMouseout({ + mouseX: mouseX, + mouseY: mouseY + }); + layer.renderGuideLine(null); //hide the guideline + tooltip.hidden(true); + return; + } else { + tooltip.hidden(false); + } + + var pointXValue = xScale.invert(mouseX); + dispatch.elementMousemove({ + mouseX: mouseX, + mouseY: mouseY, + pointXValue: pointXValue + }); + + //If user double clicks the layer, fire a elementDblclick + if (d3.event.type === "dblclick") { + dispatch.elementDblclick({ + mouseX: mouseX, + mouseY: mouseY, + pointXValue: pointXValue + }); + } + + // if user single clicks the layer, fire elementClick + if (d3.event.type === 'click') { + dispatch.elementClick({ + mouseX: mouseX, + mouseY: mouseY, + pointXValue: pointXValue + }); + } + } + + svgContainer + .on("touchmove",mouseHandler) + .on("mousemove",mouseHandler, true) + .on("mouseout" ,mouseHandler,true) + .on("dblclick" ,mouseHandler) + .on("click", mouseHandler) + ; + + layer.guideLine = null; + //Draws a vertical guideline at the given X postion. + layer.renderGuideLine = function(x) { + if (!showGuideLine) return; + if (layer.guideLine && layer.guideLine.attr("x1") === x) return; + nv.dom.write(function() { + var line = wrap.select(".nv-interactiveGuideLine") + .selectAll("line") + .data((x != null) ? [nv.utils.NaNtoZero(x)] : [], String); + line.enter() + .append("line") + .attr("class", "nv-guideline") + .attr("x1", function(d) { return d;}) + .attr("x2", function(d) { return d;}) + .attr("y1", availableHeight) + .attr("y2",0); + line.exit().remove(); + }); + } + }); + } + + layer.dispatch = dispatch; + layer.tooltip = tooltip; + + layer.margin = function(_) { + if (!arguments.length) return margin; + margin.top = typeof _.top != 'undefined' ? _.top : margin.top; + margin.left = typeof _.left != 'undefined' ? _.left : margin.left; + return layer; + }; + + layer.width = function(_) { + if (!arguments.length) return width; + width = _; + return layer; + }; + + layer.height = function(_) { + if (!arguments.length) return height; + height = _; + return layer; + }; + + layer.xScale = function(_) { + if (!arguments.length) return xScale; + xScale = _; + return layer; + }; + + layer.showGuideLine = function(_) { + if (!arguments.length) return showGuideLine; + showGuideLine = _; + return layer; + }; + + layer.svgContainer = function(_) { + if (!arguments.length) return svgContainer; + svgContainer = _; + return layer; + }; + + return layer; +}; + +/* Utility class that uses d3.bisect to find the index in a given array, where a search value can be inserted. + This is different from normal bisectLeft; this function finds the nearest index to insert the search value. + + For instance, lets say your array is [1,2,3,5,10,30], and you search for 28. + Normal d3.bisectLeft will return 4, because 28 is inserted after the number 10. But interactiveBisect will return 5 + because 28 is closer to 30 than 10. + + Unit tests can be found in: interactiveBisectTest.html + + Has the following known issues: + * Will not work if the data points move backwards (ie, 10,9,8,7, etc) or if the data points are in random order. + * Won't work if there are duplicate x coordinate values. + */ +nv.interactiveBisect = function (values, searchVal, xAccessor) { + "use strict"; + if (! (values instanceof Array)) { + return null; + } + var _xAccessor; + if (typeof xAccessor !== 'function') { + _xAccessor = function(d) { + return d.x; + } + } else { + _xAccessor = xAccessor; + } + var _cmp = function(d, v) { + // Accessors are no longer passed the index of the element along with + // the element itself when invoked by d3.bisector. + // + // Starting at D3 v3.4.4, d3.bisector() started inspecting the + // function passed to determine if it should consider it an accessor + // or a comparator. This meant that accessors that take two arguments + // (expecting an index as the second parameter) are treated as + // comparators where the second argument is the search value against + // which the first argument is compared. + return _xAccessor(d) - v; + }; + + var bisect = d3.bisector(_cmp).left; + var index = d3.max([0, bisect(values,searchVal) - 1]); + var currentValue = _xAccessor(values[index]); + + if (typeof currentValue === 'undefined') { + currentValue = index; + } + + if (currentValue === searchVal) { + return index; //found exact match + } + + var nextIndex = d3.min([index+1, values.length - 1]); + var nextValue = _xAccessor(values[nextIndex]); + + if (typeof nextValue === 'undefined') { + nextValue = nextIndex; + } + + if (Math.abs(nextValue - searchVal) >= Math.abs(currentValue - searchVal)) { + return index; + } else { + return nextIndex + } +}; + +/* + Returns the index in the array "values" that is closest to searchVal. + Only returns an index if searchVal is within some "threshold". + Otherwise, returns null. + */ +nv.nearestValueIndex = function (values, searchVal, threshold) { + "use strict"; + var yDistMax = Infinity, indexToHighlight = null; + values.forEach(function(d,i) { + var delta = Math.abs(searchVal - d); + if ( d != null && delta <= yDistMax && delta < threshold) { + yDistMax = delta; + indexToHighlight = i; + } + }); + return indexToHighlight; +}; +/* Tooltip rendering model for nvd3 charts. + window.nv.models.tooltip is the updated,new way to render tooltips. + + window.nv.tooltip.show is the old tooltip code. + window.nv.tooltip.* also has various helper methods. + */ +(function() { + "use strict"; + + /* Model which can be instantiated to handle tooltip rendering. + Example usage: + var tip = nv.models.tooltip().gravity('w').distance(23) + .data(myDataObject); + + tip(); //just invoke the returned function to render tooltip. + */ + nv.models.tooltip = function() { + + /* + Tooltip data. If data is given in the proper format, a consistent tooltip is generated. + Example Format of data: + { + key: "Date", + value: "August 2009", + series: [ + {key: "Series 1", value: "Value 1", color: "#000"}, + {key: "Series 2", value: "Value 2", color: "#00f"} + ] + } + */ + var data = null; + var gravity = 'w' //Can be 'n','s','e','w'. Determines how tooltip is positioned. + , distance = 25 //Distance to offset tooltip from the mouse location. + , snapDistance = 0 //Tolerance allowed before tooltip is moved from its current position (creates 'snapping' effect) + , fixedTop = null //If not null, this fixes the top position of the tooltip. + , classes = null //Attaches additional CSS classes to the tooltip DIV that is created. + , chartContainer = null //Parent dom element of the SVG that holds the chart. + , hidden = true // start off hidden, toggle with hide/show functions below + , hideDelay = 400 // delay before the tooltip hides after calling hide() + , tooltip = null // d3 select of tooltipElem below + , tooltipElem = null //actual DOM element representing the tooltip. + , position = {left: null, top: null} //Relative position of the tooltip inside chartContainer. + , offset = {left: 0, top: 0} //Offset of tooltip against the pointer + , enabled = true //True -> tooltips are rendered. False -> don't render tooltips. + , duration = 100 // duration for tooltip movement + , headerEnabled = true + ; + + // set to true by interactive layer to adjust tooltip positions + // eventually we should probably fix interactive layer to get the position better. + // for now this is needed if you want to set chartContainer for normal tooltips, else it "fixes" it to broken + var isInteractiveLayer = false; + + //Generates a unique id when you create a new tooltip() object + var id = "nvtooltip-" + Math.floor(Math.random() * 100000); + + //CSS class to specify whether element should not have mouse events. + var nvPointerEventsClass = "nv-pointer-events-none"; + + //Format function for the tooltip values column + var valueFormatter = function(d,i) { + return d; + }; + + //Format function for the tooltip header value. + var headerFormatter = function(d) { + return d; + }; + + var keyFormatter = function(d, i) { + return d; + }; + + //By default, the tooltip model renders a beautiful table inside a DIV. + //You can override this function if a custom tooltip is desired. + var contentGenerator = function(d) { + if (d === null) { + return ''; + } + + var table = d3.select(document.createElement("table")); + if (headerEnabled) { + var theadEnter = table.selectAll("thead") + .data([d]) + .enter().append("thead"); + + theadEnter.append("tr") + .append("td") + .attr("colspan", 3) + .append("strong") + .classed("x-value", true) + .html(headerFormatter(d.value)); + } + + var tbodyEnter = table.selectAll("tbody") + .data([d]) + .enter().append("tbody"); + + var trowEnter = tbodyEnter.selectAll("tr") + .data(function(p) { return p.series}) + .enter() + .append("tr") + .classed("highlight", function(p) { return p.highlight}); + + trowEnter.append("td") + .classed("legend-color-guide",true) + .append("div") + .style("background-color", function(p) { return p.color}); + + trowEnter.append("td") + .classed("key",true) + .html(function(p, i) {return keyFormatter(p.key, i)}); + + trowEnter.append("td") + .classed("value",true) + .html(function(p, i) { return valueFormatter(p.value, i) }); + + + trowEnter.selectAll("td").each(function(p) { + if (p.highlight) { + var opacityScale = d3.scale.linear().domain([0,1]).range(["#fff",p.color]); + var opacity = 0.6; + d3.select(this) + .style("border-bottom-color", opacityScale(opacity)) + .style("border-top-color", opacityScale(opacity)) + ; + } + }); + + var html = table.node().outerHTML; + if (d.footer !== undefined) + html += "<div class='footer'>" + d.footer + "</div>"; + return html; + + }; + + var dataSeriesExists = function(d) { + if (d && d.series) { + if (d.series instanceof Array) { + return !!d.series.length; + } + // if object, it's okay just convert to array of the object + if (d.series instanceof Object) { + d.series = [d.series]; + return true; + } + } + return false; + }; + + var calcTooltipPosition = function(pos) { + if (!tooltipElem) return; + + nv.dom.read(function() { + var height = parseInt(tooltipElem.offsetHeight, 10), + width = parseInt(tooltipElem.offsetWidth, 10), + windowWidth = nv.utils.windowSize().width, + windowHeight = nv.utils.windowSize().height, + scrollTop = window.pageYOffset, + scrollLeft = window.pageXOffset, + left, top; + + windowHeight = window.innerWidth >= document.body.scrollWidth ? windowHeight : windowHeight - 16; + windowWidth = window.innerHeight >= document.body.scrollHeight ? windowWidth : windowWidth - 16; + + + //Helper functions to find the total offsets of a given DOM element. + //Looks up the entire ancestry of an element, up to the first relatively positioned element. + var tooltipTop = function ( Elem ) { + var offsetTop = top; + do { + if( !isNaN( Elem.offsetTop ) ) { + offsetTop += (Elem.offsetTop); + } + Elem = Elem.offsetParent; + } while( Elem ); + return offsetTop; + }; + var tooltipLeft = function ( Elem ) { + var offsetLeft = left; + do { + if( !isNaN( Elem.offsetLeft ) ) { + offsetLeft += (Elem.offsetLeft); + } + Elem = Elem.offsetParent; + } while( Elem ); + return offsetLeft; + }; + + // calculate position based on gravity + var tLeft, tTop; + switch (gravity) { + case 'e': + left = pos[0] - width - distance; + top = pos[1] - (height / 2); + tLeft = tooltipLeft(tooltipElem); + tTop = tooltipTop(tooltipElem); + if (tLeft < scrollLeft) left = pos[0] + distance > scrollLeft ? pos[0] + distance : scrollLeft - tLeft + left; + if (tTop < scrollTop) top = scrollTop - tTop + top; + if (tTop + height > scrollTop + windowHeight) top = scrollTop + windowHeight - tTop + top - height; + break; + case 'w': + left = pos[0] + distance; + top = pos[1] - (height / 2); + tLeft = tooltipLeft(tooltipElem); + tTop = tooltipTop(tooltipElem); + if (tLeft + width > windowWidth) left = pos[0] - width - distance; + if (tTop < scrollTop) top = scrollTop + 5; + if (tTop + height > scrollTop + windowHeight) top = scrollTop + windowHeight - tTop + top - height; + break; + case 'n': + left = pos[0] - (width / 2) - 5; + top = pos[1] + distance; + tLeft = tooltipLeft(tooltipElem); + tTop = tooltipTop(tooltipElem); + if (tLeft < scrollLeft) left = scrollLeft + 5; + if (tLeft + width > windowWidth) left = left - width/2 + 5; + if (tTop + height > scrollTop + windowHeight) top = scrollTop + windowHeight - tTop + top - height; + break; + case 's': + left = pos[0] - (width / 2); + top = pos[1] - height - distance; + tLeft = tooltipLeft(tooltipElem); + tTop = tooltipTop(tooltipElem); + if (tLeft < scrollLeft) left = scrollLeft + 5; + if (tLeft + width > windowWidth) left = left - width/2 + 5; + if (scrollTop > tTop) top = scrollTop; + break; + case 'none': + left = pos[0]; + top = pos[1] - distance; + tLeft = tooltipLeft(tooltipElem); + tTop = tooltipTop(tooltipElem); + break; + } + + // adjust tooltip offsets + left -= offset.left; + top -= offset.top; + + // using tooltip.style('transform') returns values un-usable for tween + var box = tooltipElem.getBoundingClientRect(); + var scrollTop = window.pageYOffset || document.documentElement.scrollTop; + var scrollLeft = window.pageXOffset || document.documentElement.scrollLeft; + var old_translate = 'translate(' + (box.left + scrollLeft) + 'px, ' + (box.top + scrollTop) + 'px)'; + var new_translate = 'translate(' + left + 'px, ' + top + 'px)'; + var translateInterpolator = d3.interpolateString(old_translate, new_translate); + + var is_hidden = tooltip.style('opacity') < 0.1; + + // delay hiding a bit to avoid flickering + if (hidden) { + tooltip + .transition() + .delay(hideDelay) + .duration(0) + .style('opacity', 0); + } else { + tooltip + .interrupt() // cancel running transitions + .transition() + .duration(is_hidden ? 0 : duration) + // using tween since some versions of d3 can't auto-tween a translate on a div + .styleTween('transform', function (d) { + return translateInterpolator; + }, 'important') + // Safari has its own `-webkit-transform` and does not support `transform` + // transform tooltip without transition only in Safari + .style('-webkit-transform', new_translate) + .style('opacity', 1); + } + + + + }); + }; + + //In situations where the chart is in a 'viewBox', re-position the tooltip based on how far chart is zoomed. + function convertViewBoxRatio() { + if (chartContainer) { + var svg = d3.select(chartContainer); + if (svg.node().tagName !== "svg") { + svg = svg.select("svg"); + } + var viewBox = (svg.node()) ? svg.attr('viewBox') : null; + if (viewBox) { + viewBox = viewBox.split(' '); + var ratio = parseInt(svg.style('width'), 10) / viewBox[2]; + + position.left = position.left * ratio; + position.top = position.top * ratio; + } + } + } + + //Creates new tooltip container, or uses existing one on DOM. + function initTooltip() { + if (!tooltip) { + var body; + if (chartContainer) { + body = chartContainer; + } else { + body = document.body; + } + //Create new tooltip div if it doesn't exist on DOM. + tooltip = d3.select(body).append("div") + .attr("class", "nvtooltip " + (classes ? classes : "xy-tooltip")) + .attr("id", id); + tooltip.style("top", 0).style("left", 0); + tooltip.style('opacity', 0); + tooltip.selectAll("div, table, td, tr").classed(nvPointerEventsClass, true); + tooltip.classed(nvPointerEventsClass, true); + tooltipElem = tooltip.node(); + } + } + + //Draw the tooltip onto the DOM. + function nvtooltip() { + if (!enabled) return; + if (!dataSeriesExists(data)) return; + + convertViewBoxRatio(); + + var left = position.left; + var top = (fixedTop !== null) ? fixedTop : position.top; + + nv.dom.write(function () { + initTooltip(); + // generate data and set it into tooltip + // Bonus - If you override contentGenerator and return falsey you can use something like + // React or Knockout to bind the data for your tooltip + var newContent = contentGenerator(data); + if (newContent) { + tooltipElem.innerHTML = newContent; + } + + if (chartContainer && isInteractiveLayer) { + nv.dom.read(function() { + var svgComp = chartContainer.getElementsByTagName("svg")[0]; + var svgOffset = {left:0,top:0}; + if (svgComp) { + var svgBound = svgComp.getBoundingClientRect(); + var chartBound = chartContainer.getBoundingClientRect(); + var svgBoundTop = svgBound.top; + + //Defensive code. Sometimes, svgBoundTop can be a really negative + // number, like -134254. That's a bug. + // If such a number is found, use zero instead. FireFox bug only + if (svgBoundTop < 0) { + var containerBound = chartContainer.getBoundingClientRect(); + svgBoundTop = (Math.abs(svgBoundTop) > containerBound.height) ? 0 : svgBoundTop; + } + svgOffset.top = Math.abs(svgBoundTop - chartBound.top); + svgOffset.left = Math.abs(svgBound.left - chartBound.left); + } + //If the parent container is an overflow <div> with scrollbars, subtract the scroll offsets. + //You need to also add any offset between the <svg> element and its containing <div> + //Finally, add any offset of the containing <div> on the whole page. + left += chartContainer.offsetLeft + svgOffset.left - 2*chartContainer.scrollLeft; + top += chartContainer.offsetTop + svgOffset.top - 2*chartContainer.scrollTop; + + if (snapDistance && snapDistance > 0) { + top = Math.floor(top/snapDistance) * snapDistance; + } + calcTooltipPosition([left,top]); + }); + } else { + calcTooltipPosition([left,top]); + } + }); + + return nvtooltip; + } + + nvtooltip.nvPointerEventsClass = nvPointerEventsClass; + nvtooltip.options = nv.utils.optionsFunc.bind(nvtooltip); + + nvtooltip._options = Object.create({}, { + // simple read/write options + duration: {get: function(){return duration;}, set: function(_){duration=_;}}, + gravity: {get: function(){return gravity;}, set: function(_){gravity=_;}}, + distance: {get: function(){return distance;}, set: function(_){distance=_;}}, + snapDistance: {get: function(){return snapDistance;}, set: function(_){snapDistance=_;}}, + classes: {get: function(){return classes;}, set: function(_){classes=_;}}, + chartContainer: {get: function(){return chartContainer;}, set: function(_){chartContainer=_;}}, + fixedTop: {get: function(){return fixedTop;}, set: function(_){fixedTop=_;}}, + enabled: {get: function(){return enabled;}, set: function(_){enabled=_;}}, + hideDelay: {get: function(){return hideDelay;}, set: function(_){hideDelay=_;}}, + contentGenerator: {get: function(){return contentGenerator;}, set: function(_){contentGenerator=_;}}, + valueFormatter: {get: function(){return valueFormatter;}, set: function(_){valueFormatter=_;}}, + headerFormatter: {get: function(){return headerFormatter;}, set: function(_){headerFormatter=_;}}, + keyFormatter: {get: function(){return keyFormatter;}, set: function(_){keyFormatter=_;}}, + headerEnabled: {get: function(){return headerEnabled;}, set: function(_){headerEnabled=_;}}, + + // internal use only, set by interactive layer to adjust position. + _isInteractiveLayer: {get: function(){return isInteractiveLayer;}, set: function(_){isInteractiveLayer=!!_;}}, + + // options with extra logic + position: {get: function(){return position;}, set: function(_){ + position.left = _.left !== undefined ? _.left : position.left; + position.top = _.top !== undefined ? _.top : position.top; + }}, + offset: {get: function(){return offset;}, set: function(_){ + offset.left = _.left !== undefined ? _.left : offset.left; + offset.top = _.top !== undefined ? _.top : offset.top; + }}, + hidden: {get: function(){return hidden;}, set: function(_){ + if (hidden != _) { + hidden = !!_; + nvtooltip(); + } + }}, + data: {get: function(){return data;}, set: function(_){ + // if showing a single data point, adjust data format with that + if (_.point) { + _.value = _.point.x; + _.series = _.series || {}; + _.series.value = _.point.y; + _.series.color = _.point.color || _.series.color; + } + data = _; + }}, + + // read only properties + tooltipElem: {get: function(){return tooltipElem;}, set: function(_){}}, + id: {get: function(){return id;}, set: function(_){}} + }); + + nv.utils.initOptions(nvtooltip); + return nvtooltip; + }; + +})(); + + +/* +Gets the browser window size + +Returns object with height and width properties + */ +nv.utils.windowSize = function() { + // Sane defaults + var size = {width: 640, height: 480}; + + // Most recent browsers use + if (window.innerWidth && window.innerHeight) { + size.width = window.innerWidth; + size.height = window.innerHeight; + return (size); + } + + // IE can use depending on mode it is in + if (document.compatMode=='CSS1Compat' && + document.documentElement && + document.documentElement.offsetWidth ) { + + size.width = document.documentElement.offsetWidth; + size.height = document.documentElement.offsetHeight; + return (size); + } + + // Earlier IE uses Doc.body + if (document.body && document.body.offsetWidth) { + size.width = document.body.offsetWidth; + size.height = document.body.offsetHeight; + return (size); + } + + return (size); +}; + +/* +Binds callback function to run when window is resized + */ +nv.utils.windowResize = function(handler) { + if (window.addEventListener) { + window.addEventListener('resize', handler); + } else { + nv.log("ERROR: Failed to bind to window.resize with: ", handler); + } + // return object with clear function to remove the single added callback. + return { + callback: handler, + clear: function() { + window.removeEventListener('resize', handler); + } + } +}; + + +/* +Backwards compatible way to implement more d3-like coloring of graphs. +Can take in nothing, an array, or a function/scale +To use a normal scale, get the range and pass that because we must be able +to take two arguments and use the index to keep backward compatibility +*/ +nv.utils.getColor = function(color) { + //if you pass in nothing, get default colors back + if (color === undefined) { + return nv.utils.defaultColor(); + + //if passed an array, turn it into a color scale + // use isArray, instanceof fails if d3 range is created in an iframe + } else if(Array.isArray(color)) { + var color_scale = d3.scale.ordinal().range(color); + return function(d, i) { + var key = i === undefined ? d : i; + return d.color || color_scale(key); + }; + + //if passed a function or scale, return it, or whatever it may be + //external libs, such as angularjs-nvd3-directives use this + } else { + //can't really help it if someone passes rubbish as color + return color; + } +}; + + +/* +Default color chooser uses a color scale of 20 colors from D3 + https://github.com/mbostock/d3/wiki/Ordinal-Scales#categorical-colors + */ +nv.utils.defaultColor = function() { + // get range of the scale so we'll turn it into our own function. + return nv.utils.getColor(d3.scale.category20().range()); +}; + + +/* +Returns a color function that takes the result of 'getKey' for each series and +looks for a corresponding color from the dictionary +*/ +nv.utils.customTheme = function(dictionary, getKey, defaultColors) { + // use default series.key if getKey is undefined + getKey = getKey || function(series) { return series.key }; + defaultColors = defaultColors || d3.scale.category20().range(); + + // start at end of default color list and walk back to index 0 + var defIndex = defaultColors.length; + + return function(series, index) { + var key = getKey(series); + if (typeof dictionary[key] === 'function') { + return dictionary[key](); + } else if (dictionary[key] !== undefined) { + return dictionary[key]; + } else { + // no match in dictionary, use a default color + if (!defIndex) { + // used all the default colors, start over + defIndex = defaultColors.length; + } + defIndex = defIndex - 1; + return defaultColors[defIndex]; + } + }; +}; + + +/* +From the PJAX example on d3js.org, while this is not really directly needed +it's a very cool method for doing pjax, I may expand upon it a little bit, +open to suggestions on anything that may be useful +*/ +nv.utils.pjax = function(links, content) { + + var load = function(href) { + d3.html(href, function(fragment) { + var target = d3.select(content).node(); + target.parentNode.replaceChild( + d3.select(fragment).select(content).node(), + target); + nv.utils.pjax(links, content); + }); + }; + + d3.selectAll(links).on("click", function() { + history.pushState(this.href, this.textContent, this.href); + load(this.href); + d3.event.preventDefault(); + }); + + d3.select(window).on("popstate", function() { + if (d3.event.state) { + load(d3.event.state); + } + }); +}; + + +/* +For when we want to approximate the width in pixels for an SVG:text element. +Most common instance is when the element is in a display:none; container. +Forumla is : text.length * font-size * constant_factor +*/ +nv.utils.calcApproxTextWidth = function (svgTextElem) { + if (typeof svgTextElem.style === 'function' + && typeof svgTextElem.text === 'function') { + + var fontSize = parseInt(svgTextElem.style("font-size").replace("px",""), 10); + var textLength = svgTextElem.text().length; + return textLength * fontSize * 0.5; + } + return 0; +}; + + +/* +Numbers that are undefined, null or NaN, convert them to zeros. +*/ +nv.utils.NaNtoZero = function(n) { + if (typeof n !== 'number' + || isNaN(n) + || n === null + || n === Infinity + || n === -Infinity) { + + return 0; + } + return n; +}; + +/* +Add a way to watch for d3 transition ends to d3 +*/ +d3.selection.prototype.watchTransition = function(renderWatch){ + var args = [this].concat([].slice.call(arguments, 1)); + return renderWatch.transition.apply(renderWatch, args); +}; + + +/* +Helper object to watch when d3 has rendered something +*/ +nv.utils.renderWatch = function(dispatch, duration) { + if (!(this instanceof nv.utils.renderWatch)) { + return new nv.utils.renderWatch(dispatch, duration); + } + + var _duration = duration !== undefined ? duration : 250; + var renderStack = []; + var self = this; + + this.models = function(models) { + models = [].slice.call(arguments, 0); + models.forEach(function(model){ + model.__rendered = false; + (function(m){ + m.dispatch.on('renderEnd', function(arg){ + m.__rendered = true; + self.renderEnd('model'); + }); + })(model); + + if (renderStack.indexOf(model) < 0) { + renderStack.push(model); + } + }); + return this; + }; + + this.reset = function(duration) { + if (duration !== undefined) { + _duration = duration; + } + renderStack = []; + }; + + this.transition = function(selection, args, duration) { + args = arguments.length > 1 ? [].slice.call(arguments, 1) : []; + + if (args.length > 1) { + duration = args.pop(); + } else { + duration = _duration !== undefined ? _duration : 250; + } + selection.__rendered = false; + + if (renderStack.indexOf(selection) < 0) { + renderStack.push(selection); + } + + if (duration === 0) { + selection.__rendered = true; + selection.delay = function() { return this; }; + selection.duration = function() { return this; }; + return selection; + } else { + if (selection.length === 0) { + selection.__rendered = true; + } else if (selection.every( function(d){ return !d.length; } )) { + selection.__rendered = true; + } else { + selection.__rendered = false; + } + + var n = 0; + return selection + .transition() + .duration(duration) + .each(function(){ ++n; }) + .each('end', function(d, i) { + if (--n === 0) { + selection.__rendered = true; + self.renderEnd.apply(this, args); + } + }); + } + }; + + this.renderEnd = function() { + if (renderStack.every( function(d){ return d.__rendered; } )) { + renderStack.forEach( function(d){ d.__rendered = false; }); + dispatch.renderEnd.apply(this, arguments); + } + } + +}; + + +/* +Takes multiple objects and combines them into the first one (dst) +example: nv.utils.deepExtend({a: 1}, {a: 2, b: 3}, {c: 4}); +gives: {a: 2, b: 3, c: 4} +*/ +nv.utils.deepExtend = function(dst){ + var sources = arguments.length > 1 ? [].slice.call(arguments, 1) : []; + sources.forEach(function(source) { + for (var key in source) { + var isArray = dst[key] instanceof Array; + var isObject = typeof dst[key] === 'object'; + var srcObj = typeof source[key] === 'object'; + + if (isObject && !isArray && srcObj) { + nv.utils.deepExtend(dst[key], source[key]); + } else { + dst[key] = source[key]; + } + } + }); +}; + + +/* +state utility object, used to track d3 states in the models +*/ +nv.utils.state = function(){ + if (!(this instanceof nv.utils.state)) { + return new nv.utils.state(); + } + var state = {}; + var _self = this; + var _setState = function(){}; + var _getState = function(){ return {}; }; + var init = null; + var changed = null; + + this.dispatch = d3.dispatch('change', 'set'); + + this.dispatch.on('set', function(state){ + _setState(state, true); + }); + + this.getter = function(fn){ + _getState = fn; + return this; + }; + + this.setter = function(fn, callback) { + if (!callback) { + callback = function(){}; + } + _setState = function(state, update){ + fn(state); + if (update) { + callback(); + } + }; + return this; + }; + + this.init = function(state){ + init = init || {}; + nv.utils.deepExtend(init, state); + }; + + var _set = function(){ + var settings = _getState(); + + if (JSON.stringify(settings) === JSON.stringify(state)) { + return false; + } + + for (var key in settings) { + if (state[key] === undefined) { + state[key] = {}; + } + state[key] = settings[key]; + changed = true; + } + return true; + }; + + this.update = function(){ + if (init) { + _setState(init, false); + init = null; + } + if (_set.call(this)) { + this.dispatch.change(state); + } + }; + +}; + + +/* +Snippet of code you can insert into each nv.models.* to give you the ability to +do things like: +chart.options({ + showXAxis: true, + tooltips: true +}); + +To enable in the chart: +chart.options = nv.utils.optionsFunc.bind(chart); +*/ +nv.utils.optionsFunc = function(args) { + if (args) { + d3.map(args).forEach((function(key,value) { + if (typeof this[key] === "function") { + this[key](value); + } + }).bind(this)); + } + return this; +}; + + +/* +numTicks: requested number of ticks +data: the chart data + +returns the number of ticks to actually use on X axis, based on chart data +to avoid duplicate ticks with the same value +*/ +nv.utils.calcTicksX = function(numTicks, data) { + // find max number of values from all data streams + var numValues = 1; + var i = 0; + for (i; i < data.length; i += 1) { + var stream_len = data[i] && data[i].values ? data[i].values.length : 0; + numValues = stream_len > numValues ? stream_len : numValues; + } + nv.log("Requested number of ticks: ", numTicks); + nv.log("Calculated max values to be: ", numValues); + // make sure we don't have more ticks than values to avoid duplicates + numTicks = numTicks > numValues ? numTicks = numValues - 1 : numTicks; + // make sure we have at least one tick + numTicks = numTicks < 1 ? 1 : numTicks; + // make sure it's an integer + numTicks = Math.floor(numTicks); + nv.log("Calculating tick count as: ", numTicks); + return numTicks; +}; + + +/* +returns number of ticks to actually use on Y axis, based on chart data +*/ +nv.utils.calcTicksY = function(numTicks, data) { + // currently uses the same logic but we can adjust here if needed later + return nv.utils.calcTicksX(numTicks, data); +}; + + +/* +Add a particular option from an options object onto chart +Options exposed on a chart are a getter/setter function that returns chart +on set to mimic typical d3 option chaining, e.g. svg.option1('a').option2('b'); + +option objects should be generated via Object.create() to provide +the option of manipulating data via get/set functions. +*/ +nv.utils.initOption = function(chart, name) { + // if it's a call option, just call it directly, otherwise do get/set + if (chart._calls && chart._calls[name]) { + chart[name] = chart._calls[name]; + } else { + chart[name] = function (_) { + if (!arguments.length) return chart._options[name]; + chart._overrides[name] = true; + chart._options[name] = _; + return chart; + }; + // calling the option as _option will ignore if set by option already + // so nvd3 can set options internally but the stop if set manually + chart['_' + name] = function(_) { + if (!arguments.length) return chart._options[name]; + if (!chart._overrides[name]) { + chart._options[name] = _; + } + return chart; + } + } +}; + + +/* +Add all options in an options object to the chart +*/ +nv.utils.initOptions = function(chart) { + chart._overrides = chart._overrides || {}; + var ops = Object.getOwnPropertyNames(chart._options || {}); + var calls = Object.getOwnPropertyNames(chart._calls || {}); + ops = ops.concat(calls); + for (var i in ops) { + nv.utils.initOption(chart, ops[i]); + } +}; + + +/* +Inherit options from a D3 object +d3.rebind makes calling the function on target actually call it on source +Also use _d3options so we can track what we inherit for documentation and chained inheritance +*/ +nv.utils.inheritOptionsD3 = function(target, d3_source, oplist) { + target._d3options = oplist.concat(target._d3options || []); + oplist.unshift(d3_source); + oplist.unshift(target); + d3.rebind.apply(this, oplist); +}; + + +/* +Remove duplicates from an array +*/ +nv.utils.arrayUnique = function(a) { + return a.sort().filter(function(item, pos) { + return !pos || item != a[pos - 1]; + }); +}; + + +/* +Keeps a list of custom symbols to draw from in addition to d3.svg.symbol +Necessary since d3 doesn't let you extend its list -_- +Add new symbols by doing nv.utils.symbols.set('name', function(size){...}); +*/ +nv.utils.symbolMap = d3.map(); + + +/* +Replaces d3.svg.symbol so that we can look both there and our own map + */ +nv.utils.symbol = function() { + var type, + size = 64; + function symbol(d,i) { + var t = type.call(this,d,i); + var s = size.call(this,d,i); + if (d3.svg.symbolTypes.indexOf(t) !== -1) { + return d3.svg.symbol().type(t).size(s)(); + } else { + return nv.utils.symbolMap.get(t)(s); + } + } + symbol.type = function(_) { + if (!arguments.length) return type; + type = d3.functor(_); + return symbol; + }; + symbol.size = function(_) { + if (!arguments.length) return size; + size = d3.functor(_); + return symbol; + }; + return symbol; +}; + + +/* +Inherit option getter/setter functions from source to target +d3.rebind makes calling the function on target actually call it on source +Also track via _inherited and _d3options so we can track what we inherit +for documentation generation purposes and chained inheritance +*/ +nv.utils.inheritOptions = function(target, source) { + // inherit all the things + var ops = Object.getOwnPropertyNames(source._options || {}); + var calls = Object.getOwnPropertyNames(source._calls || {}); + var inherited = source._inherited || []; + var d3ops = source._d3options || []; + var args = ops.concat(calls).concat(inherited).concat(d3ops); + args.unshift(source); + args.unshift(target); + d3.rebind.apply(this, args); + // pass along the lists to keep track of them, don't allow duplicates + target._inherited = nv.utils.arrayUnique(ops.concat(calls).concat(inherited).concat(ops).concat(target._inherited || [])); + target._d3options = nv.utils.arrayUnique(d3ops.concat(target._d3options || [])); +}; + + +/* +Runs common initialize code on the svg before the chart builds +*/ +nv.utils.initSVG = function(svg) { + svg.classed({'nvd3-svg':true}); +}; + + +/* +Sanitize and provide default for the container height. +*/ +nv.utils.sanitizeHeight = function(height, container) { + return (height || parseInt(container.style('height'), 10) || 400); +}; + + +/* +Sanitize and provide default for the container width. +*/ +nv.utils.sanitizeWidth = function(width, container) { + return (width || parseInt(container.style('width'), 10) || 960); +}; + + +/* +Calculate the available height for a chart. +*/ +nv.utils.availableHeight = function(height, container, margin) { + return nv.utils.sanitizeHeight(height, container) - margin.top - margin.bottom; +}; + +/* +Calculate the available width for a chart. +*/ +nv.utils.availableWidth = function(width, container, margin) { + return nv.utils.sanitizeWidth(width, container) - margin.left - margin.right; +}; + +/* +Clear any rendered chart components and display a chart's 'noData' message +*/ +nv.utils.noData = function(chart, container) { + var opt = chart.options(), + margin = opt.margin(), + noData = opt.noData(), + data = (noData == null) ? ["No Data Available."] : [noData], + height = nv.utils.availableHeight(opt.height(), container, margin), + width = nv.utils.availableWidth(opt.width(), container, margin), + x = margin.left + width/2, + y = margin.top + height/2; + + //Remove any previously created chart components + container.selectAll('g').remove(); + + var noDataText = container.selectAll('.nv-noData').data(data); + + noDataText.enter().append('text') + .attr('class', 'nvd3 nv-noData') + .attr('dy', '-.7em') + .style('text-anchor', 'middle'); + + noDataText + .attr('x', x) + .attr('y', y) + .text(function(t){ return t; }); +}; + +nv.models.axis = function() { + "use strict"; + + //============================================================ + // Public Variables with Default Settings + //------------------------------------------------------------ + + var axis = d3.svg.axis(); + var scale = d3.scale.linear(); + + var margin = {top: 0, right: 0, bottom: 0, left: 0} + , width = 75 //only used for tickLabel currently + , height = 60 //only used for tickLabel currently + , axisLabelText = null + , showMaxMin = true //TODO: showMaxMin should be disabled on all ordinal scaled axes + , rotateLabels = 0 + , rotateYLabel = true + , staggerLabels = false + , isOrdinal = false + , ticks = null + , axisLabelDistance = 0 + , duration = 250 + , dispatch = d3.dispatch('renderEnd') + ; + axis + .scale(scale) + .orient('bottom') + .tickFormat(function(d) { return d }) + ; + + //============================================================ + // Private Variables + //------------------------------------------------------------ + + var scale0; + var renderWatch = nv.utils.renderWatch(dispatch, duration); + + function chart(selection) { + renderWatch.reset(); + selection.each(function(data) { + var container = d3.select(this); + nv.utils.initSVG(container); + + // Setup containers and skeleton of chart + var wrap = container.selectAll('g.nv-wrap.nv-axis').data([data]); + var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-axis'); + var gEnter = wrapEnter.append('g'); + var g = wrap.select('g'); + + if (ticks !== null) + axis.ticks(ticks); + else if (axis.orient() == 'top' || axis.orient() == 'bottom') + axis.ticks(Math.abs(scale.range()[1] - scale.range()[0]) / 100); + + //TODO: consider calculating width/height based on whether or not label is added, for reference in charts using this component + g.watchTransition(renderWatch, 'axis').call(axis); + + scale0 = scale0 || axis.scale(); + + var fmt = axis.tickFormat(); + if (fmt == null) { + fmt = scale0.tickFormat(); + } + + var axisLabel = g.selectAll('text.nv-axislabel') + .data([axisLabelText || null]); + axisLabel.exit().remove(); + + var xLabelMargin; + var axisMaxMin; + var w; + switch (axis.orient()) { + case 'top': + axisLabel.enter().append('text').attr('class', 'nv-axislabel'); + if (scale.range().length < 2) { + w = 0; + } else if (scale.range().length === 2) { + w = scale.range()[1]; + } else { + w = scale.range()[scale.range().length-1]+(scale.range()[1]-scale.range()[0]); + } + axisLabel + .attr('text-anchor', 'middle') + .attr('y', 0) + .attr('x', w/2); + if (showMaxMin) { + axisMaxMin = wrap.selectAll('g.nv-axisMaxMin') + .data(scale.domain()); + axisMaxMin.enter().append('g').attr('class',function(d,i){ + return ['nv-axisMaxMin','nv-axisMaxMin-x',(i == 0 ? 'nv-axisMin-x':'nv-axisMax-x')].join(' ') + }).append('text'); + axisMaxMin.exit().remove(); + axisMaxMin + .attr('transform', function(d,i) { + return 'translate(' + nv.utils.NaNtoZero(scale(d)) + ',0)' + }) + .select('text') + .attr('dy', '-0.5em') + .attr('y', -axis.tickPadding()) + .attr('text-anchor', 'middle') + .text(function(d,i) { + var v = fmt(d); + return ('' + v).match('NaN') ? '' : v; + }); + axisMaxMin.watchTransition(renderWatch, 'min-max top') + .attr('transform', function(d,i) { + return 'translate(' + nv.utils.NaNtoZero(scale.range()[i]) + ',0)' + }); + } + break; + case 'bottom': + xLabelMargin = axisLabelDistance + 36; + var maxTextWidth = 30; + var textHeight = 0; + var xTicks = g.selectAll('g').select("text"); + var rotateLabelsRule = ''; + if (rotateLabels%360) { + //Calculate the longest xTick width + xTicks.each(function(d,i){ + var box = this.getBoundingClientRect(); + var width = box.width; + textHeight = box.height; + if(width > maxTextWidth) maxTextWidth = width; + }); + rotateLabelsRule = 'rotate(' + rotateLabels + ' 0,' + (textHeight/2 + axis.tickPadding()) + ')'; + //Convert to radians before calculating sin. Add 30 to margin for healthy padding. + var sin = Math.abs(Math.sin(rotateLabels*Math.PI/180)); + xLabelMargin = (sin ? sin*maxTextWidth : maxTextWidth)+30; + //Rotate all xTicks + xTicks + .attr('transform', rotateLabelsRule) + .style('text-anchor', rotateLabels%360 > 0 ? 'start' : 'end'); + } + axisLabel.enter().append('text').attr('class', 'nv-axislabel'); + if (scale.range().length < 2) { + w = 0; + } else if (scale.range().length === 2) { + w = scale.range()[1]; + } else { + w = scale.range()[scale.range().length-1]+(scale.range()[1]-scale.range()[0]); + } + axisLabel + .attr('text-anchor', 'middle') + .attr('y', xLabelMargin) + .attr('x', w/2); + if (showMaxMin) { + //if (showMaxMin && !isOrdinal) { + axisMaxMin = wrap.selectAll('g.nv-axisMaxMin') + //.data(scale.domain()) + .data([scale.domain()[0], scale.domain()[scale.domain().length - 1]]); + axisMaxMin.enter().append('g').attr('class',function(d,i){ + return ['nv-axisMaxMin','nv-axisMaxMin-x',(i == 0 ? 'nv-axisMin-x':'nv-axisMax-x')].join(' ') + }).append('text'); + axisMaxMin.exit().remove(); + axisMaxMin + .attr('transform', function(d,i) { + return 'translate(' + nv.utils.NaNtoZero((scale(d) + (isOrdinal ? scale.rangeBand() / 2 : 0))) + ',0)' + }) + .select('text') + .attr('dy', '.71em') + .attr('y', axis.tickPadding()) + .attr('transform', rotateLabelsRule) + .style('text-anchor', rotateLabels ? (rotateLabels%360 > 0 ? 'start' : 'end') : 'middle') + .text(function(d,i) { + var v = fmt(d); + return ('' + v).match('NaN') ? '' : v; + }); + axisMaxMin.watchTransition(renderWatch, 'min-max bottom') + .attr('transform', function(d,i) { + return 'translate(' + nv.utils.NaNtoZero((scale(d) + (isOrdinal ? scale.rangeBand() / 2 : 0))) + ',0)' + }); + } + if (staggerLabels) + xTicks + .attr('transform', function(d,i) { + return 'translate(0,' + (i % 2 == 0 ? '0' : '12') + ')' + }); + + break; + case 'right': + axisLabel.enter().append('text').attr('class', 'nv-axislabel'); + axisLabel + .style('text-anchor', rotateYLabel ? 'middle' : 'begin') + .attr('transform', rotateYLabel ? 'rotate(90)' : '') + .attr('y', rotateYLabel ? (-Math.max(margin.right, width) + 12) : -10) //TODO: consider calculating this based on largest tick width... OR at least expose this on chart + .attr('x', rotateYLabel ? (d3.max(scale.range()) / 2) : axis.tickPadding()); + if (showMaxMin) { + axisMaxMin = wrap.selectAll('g.nv-axisMaxMin') + .data(scale.domain()); + axisMaxMin.enter().append('g').attr('class',function(d,i){ + return ['nv-axisMaxMin','nv-axisMaxMin-y',(i == 0 ? 'nv-axisMin-y':'nv-axisMax-y')].join(' ') + }).append('text') + .style('opacity', 0); + axisMaxMin.exit().remove(); + axisMaxMin + .attr('transform', function(d,i) { + return 'translate(0,' + nv.utils.NaNtoZero(scale(d)) + ')' + }) + .select('text') + .attr('dy', '.32em') + .attr('y', 0) + .attr('x', axis.tickPadding()) + .style('text-anchor', 'start') + .text(function(d, i) { + var v = fmt(d); + return ('' + v).match('NaN') ? '' : v; + }); + axisMaxMin.watchTransition(renderWatch, 'min-max right') + .attr('transform', function(d,i) { + return 'translate(0,' + nv.utils.NaNtoZero(scale.range()[i]) + ')' + }) + .select('text') + .style('opacity', 1); + } + break; + case 'left': + /* + //For dynamically placing the label. Can be used with dynamically-sized chart axis margins + var yTicks = g.selectAll('g').select("text"); + yTicks.each(function(d,i){ + var labelPadding = this.getBoundingClientRect().width + axis.tickPadding() + 16; + if(labelPadding > width) width = labelPadding; + }); + */ + axisLabel.enter().append('text').attr('class', 'nv-axislabel'); + axisLabel + .style('text-anchor', rotateYLabel ? 'middle' : 'end') + .attr('transform', rotateYLabel ? 'rotate(-90)' : '') + .attr('y', rotateYLabel ? (-Math.max(margin.left, width) + 25 - (axisLabelDistance || 0)) : -10) + .attr('x', rotateYLabel ? (-d3.max(scale.range()) / 2) : -axis.tickPadding()); + if (showMaxMin) { + axisMaxMin = wrap.selectAll('g.nv-axisMaxMin') + .data(scale.domain()); + axisMaxMin.enter().append('g').attr('class',function(d,i){ + return ['nv-axisMaxMin','nv-axisMaxMin-y',(i == 0 ? 'nv-axisMin-y':'nv-axisMax-y')].join(' ') + }).append('text') + .style('opacity', 0); + axisMaxMin.exit().remove(); + axisMaxMin + .attr('transform', function(d,i) { + return 'translate(0,' + nv.utils.NaNtoZero(scale0(d)) + ')' + }) + .select('text') + .attr('dy', '.32em') + .attr('y', 0) + .attr('x', -axis.tickPadding()) + .attr('text-anchor', 'end') + .text(function(d,i) { + var v = fmt(d); + return ('' + v).match('NaN') ? '' : v; + }); + axisMaxMin.watchTransition(renderWatch, 'min-max right') + .attr('transform', function(d,i) { + return 'translate(0,' + nv.utils.NaNtoZero(scale.range()[i]) + ')' + }) + .select('text') + .style('opacity', 1); + } + break; + } + axisLabel.text(function(d) { return d }); + + if (showMaxMin && (axis.orient() === 'left' || axis.orient() === 'right')) { + //check if max and min overlap other values, if so, hide the values that overlap + g.selectAll('g') // the g's wrapping each tick + .each(function(d,i) { + d3.select(this).select('text').attr('opacity', 1); + if (scale(d) < scale.range()[1] + 10 || scale(d) > scale.range()[0] - 10) { // 10 is assuming text height is 16... if d is 0, leave it! + if (d > 1e-10 || d < -1e-10) // accounts for minor floating point errors... though could be problematic if the scale is EXTREMELY SMALL + d3.select(this).attr('opacity', 0); + + d3.select(this).select('text').attr('opacity', 0); // Don't remove the ZERO line!! + } + }); + + //if Max and Min = 0 only show min, Issue #281 + if (scale.domain()[0] == scale.domain()[1] && scale.domain()[0] == 0) { + wrap.selectAll('g.nv-axisMaxMin').style('opacity', function (d, i) { + return !i ? 1 : 0 + }); + } + } + + if (showMaxMin && (axis.orient() === 'top' || axis.orient() === 'bottom')) { + var maxMinRange = []; + wrap.selectAll('g.nv-axisMaxMin') + .each(function(d,i) { + try { + if (i) // i== 1, max position + maxMinRange.push(scale(d) - this.getBoundingClientRect().width - 4); //assuming the max and min labels are as wide as the next tick (with an extra 4 pixels just in case) + else // i==0, min position + maxMinRange.push(scale(d) + this.getBoundingClientRect().width + 4) + }catch (err) { + if (i) // i== 1, max position + maxMinRange.push(scale(d) - 4); //assuming the max and min labels are as wide as the next tick (with an extra 4 pixels just in case) + else // i==0, min position + maxMinRange.push(scale(d) + 4); + } + }); + // the g's wrapping each tick + g.selectAll('g').each(function(d, i) { + if (scale(d) < maxMinRange[0] || scale(d) > maxMinRange[1]) { + if (d > 1e-10 || d < -1e-10) // accounts for minor floating point errors... though could be problematic if the scale is EXTREMELY SMALL + d3.select(this).remove(); + else + d3.select(this).select('text').remove(); // Don't remove the ZERO line!! + } + }); + } + + //Highlight zero tick line + g.selectAll('.tick') + .filter(function (d) { + /* + The filter needs to return only ticks at or near zero. + Numbers like 0.00001 need to count as zero as well, + and the arithmetic trick below solves that. + */ + return !parseFloat(Math.round(d * 100000) / 1000000) && (d !== undefined) + }) + .classed('zero', true); + + //store old scales for use in transitions on update + scale0 = scale.copy(); + + }); + + renderWatch.renderEnd('axis immediate'); + return chart; + } + + //============================================================ + // Expose Public Variables + //------------------------------------------------------------ + + // expose chart's sub-components + chart.axis = axis; + chart.dispatch = dispatch; + + chart.options = nv.utils.optionsFunc.bind(chart); + chart._options = Object.create({}, { + // simple options, just get/set the necessary values + axisLabelDistance: {get: function(){return axisLabelDistance;}, set: function(_){axisLabelDistance=_;}}, + staggerLabels: {get: function(){return staggerLabels;}, set: function(_){staggerLabels=_;}}, + rotateLabels: {get: function(){return rotateLabels;}, set: function(_){rotateLabels=_;}}, + rotateYLabel: {get: function(){return rotateYLabel;}, set: function(_){rotateYLabel=_;}}, + showMaxMin: {get: function(){return showMaxMin;}, set: function(_){showMaxMin=_;}}, + axisLabel: {get: function(){return axisLabelText;}, set: function(_){axisLabelText=_;}}, + height: {get: function(){return height;}, set: function(_){height=_;}}, + ticks: {get: function(){return ticks;}, set: function(_){ticks=_;}}, + width: {get: function(){return width;}, set: function(_){width=_;}}, + + // options that require extra logic in the setter + margin: {get: function(){return margin;}, set: function(_){ + margin.top = _.top !== undefined ? _.top : margin.top; + margin.right = _.right !== undefined ? _.right : margin.right; + margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom; + margin.left = _.left !== undefined ? _.left : margin.left; + }}, + duration: {get: function(){return duration;}, set: function(_){ + duration=_; + renderWatch.reset(duration); + }}, + scale: {get: function(){return scale;}, set: function(_){ + scale = _; + axis.scale(scale); + isOrdinal = typeof scale.rangeBands === 'function'; + nv.utils.inheritOptionsD3(chart, scale, ['domain', 'range', 'rangeBand', 'rangeBands']); + }} + }); + + nv.utils.initOptions(chart); + nv.utils.inheritOptionsD3(chart, axis, ['orient', 'tickValues', 'tickSubdivide', 'tickSize', 'tickPadding', 'tickFormat']); + nv.utils.inheritOptionsD3(chart, scale, ['domain', 'range', 'rangeBand', 'rangeBands']); + + return chart; +}; +nv.models.boxPlot = function() { + "use strict"; + + //============================================================ + // Public Variables with Default Settings + //------------------------------------------------------------ + + var margin = {top: 0, right: 0, bottom: 0, left: 0} + , width = 960 + , height = 500 + , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one + , x = d3.scale.ordinal() + , y = d3.scale.linear() + , getX = function(d) { return d.x } + , getY = function(d) { return d.y } + , color = nv.utils.defaultColor() + , container = null + , xDomain + , yDomain + , xRange + , yRange + , dispatch = d3.dispatch('elementMouseover', 'elementMouseout', 'elementMousemove', 'renderEnd') + , duration = 250 + , maxBoxWidth = null + ; + + //============================================================ + // Private Variables + //------------------------------------------------------------ + + var x0, y0; + var renderWatch = nv.utils.renderWatch(dispatch, duration); + + function chart(selection) { + renderWatch.reset(); + selection.each(function(data) { + var availableWidth = width - margin.left - margin.right, + availableHeight = height - margin.top - margin.bottom; + + container = d3.select(this); + nv.utils.initSVG(container); + + // Setup Scales + x .domain(xDomain || data.map(function(d,i) { return getX(d,i); })) + .rangeBands(xRange || [0, availableWidth], .1); + + // if we know yDomain, no need to calculate + var yData = [] + if (!yDomain) { + // (y-range is based on quartiles, whiskers and outliers) + + // lower values + var yMin = d3.min(data.map(function(d) { + var min_arr = []; + + min_arr.push(d.values.Q1); + if (d.values.hasOwnProperty('whisker_low') && d.values.whisker_low !== null) { min_arr.push(d.values.whisker_low); } + if (d.values.hasOwnProperty('outliers') && d.values.outliers !== null) { min_arr = min_arr.concat(d.values.outliers); } + + return d3.min(min_arr); + })); + + // upper values + var yMax = d3.max(data.map(function(d) { + var max_arr = []; + + max_arr.push(d.values.Q3); + if (d.values.hasOwnProperty('whisker_high') && d.values.whisker_high !== null) { max_arr.push(d.values.whisker_high); } + if (d.values.hasOwnProperty('outliers') && d.values.outliers !== null) { max_arr = max_arr.concat(d.values.outliers); } + + return d3.max(max_arr); + })); + + yData = [ yMin, yMax ] ; + } + + y.domain(yDomain || yData); + y.range(yRange || [availableHeight, 0]); + + //store old scales if they exist + x0 = x0 || x; + y0 = y0 || y.copy().range([y(0),y(0)]); + + // Setup containers and skeleton of chart + var wrap = container.selectAll('g.nv-wrap').data([data]); + var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap'); + wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); + + var boxplots = wrap.selectAll('.nv-boxplot').data(function(d) { return d }); + var boxEnter = boxplots.enter().append('g').style('stroke-opacity', 1e-6).style('fill-opacity', 1e-6); + boxplots + .attr('class', 'nv-boxplot') + .attr('transform', function(d,i,j) { return 'translate(' + (x(getX(d,i)) + x.rangeBand() * .05) + ', 0)'; }) + .classed('hover', function(d) { return d.hover }); + boxplots + .watchTransition(renderWatch, 'nv-boxplot: boxplots') + .style('stroke-opacity', 1) + .style('fill-opacity', .75) + .delay(function(d,i) { return i * duration / data.length }) + .attr('transform', function(d,i) { + return 'translate(' + (x(getX(d,i)) + x.rangeBand() * .05) + ', 0)'; + }); + boxplots.exit().remove(); + + // ----- add the SVG elements for each boxPlot ----- + + // conditionally append whisker lines + boxEnter.each(function(d,i) { + var box = d3.select(this); + + ['low', 'high'].forEach(function(key) { + if (d.values.hasOwnProperty('whisker_' + key) && d.values['whisker_' + key] !== null) { + box.append('line') + .style('stroke', (d.color) ? d.color : color(d,i)) + .attr('class', 'nv-boxplot-whisker nv-boxplot-' + key); + + box.append('line') + .style('stroke', (d.color) ? d.color : color(d,i)) + .attr('class', 'nv-boxplot-tick nv-boxplot-' + key); + } + }); + }); + + // outliers + // TODO: support custom colors here + var outliers = boxplots.selectAll('.nv-boxplot-outlier').data(function(d) { + if (d.values.hasOwnProperty('outliers') && d.values.outliers !== null) { return d.values.outliers; } + else { return []; } + }); + outliers.enter().append('circle') + .style('fill', function(d,i,j) { return color(d,j) }).style('stroke', function(d,i,j) { return color(d,j) }) + .on('mouseover', function(d,i,j) { + d3.select(this).classed('hover', true); + dispatch.elementMouseover({ + series: { key: d, color: color(d,j) }, + e: d3.event + }); + }) + .on('mouseout', function(d,i,j) { + d3.select(this).classed('hover', false); + dispatch.elementMouseout({ + series: { key: d, color: color(d,j) }, + e: d3.event + }); + }) + .on('mousemove', function(d,i) { + dispatch.elementMousemove({e: d3.event}); + }); + + outliers.attr('class', 'nv-boxplot-outlier'); + outliers + .watchTransition(renderWatch, 'nv-boxplot: nv-boxplot-outlier') + .attr('cx', x.rangeBand() * .45) + .attr('cy', function(d,i,j) { return y(d); }) + .attr('r', '3'); + outliers.exit().remove(); + + var box_width = function() { return (maxBoxWidth === null ? x.rangeBand() * .9 : Math.min(75, x.rangeBand() * .9)); }; + var box_left = function() { return x.rangeBand() * .45 - box_width()/2; }; + var box_right = function() { return x.rangeBand() * .45 + box_width()/2; }; + + // update whisker lines and ticks + ['low', 'high'].forEach(function(key) { + var endpoint = (key === 'low') ? 'Q1' : 'Q3'; + + boxplots.select('line.nv-boxplot-whisker.nv-boxplot-' + key) + .watchTransition(renderWatch, 'nv-boxplot: boxplots') + .attr('x1', x.rangeBand() * .45 ) + .attr('y1', function(d,i) { return y(d.values['whisker_' + key]); }) + .attr('x2', x.rangeBand() * .45 ) + .attr('y2', function(d,i) { return y(d.values[endpoint]); }); + + boxplots.select('line.nv-boxplot-tick.nv-boxplot-' + key) + .watchTransition(renderWatch, 'nv-boxplot: boxplots') + .attr('x1', box_left ) + .attr('y1', function(d,i) { return y(d.values['whisker_' + key]); }) + .attr('x2', box_right ) + .attr('y2', function(d,i) { return y(d.values['whisker_' + key]); }); + }); + + ['low', 'high'].forEach(function(key) { + boxEnter.selectAll('.nv-boxplot-' + key) + .on('mouseover', function(d,i,j) { + d3.select(this).classed('hover', true); + dispatch.elementMouseover({ + series: { key: d.values['whisker_' + key], color: color(d,j) }, + e: d3.event + }); + }) + .on('mouseout', function(d,i,j) { + d3.select(this).classed('hover', false); + dispatch.elementMouseout({ + series: { key: d.values['whisker_' + key], color: color(d,j) }, + e: d3.event + }); + }) + .on('mousemove', function(d,i) { + dispatch.elementMousemove({e: d3.event}); + }); + }); + + // boxes + boxEnter.append('rect') + .attr('class', 'nv-boxplot-box') + // tooltip events + .on('mouseover', function(d,i) { + d3.select(this).classed('hover', true); + dispatch.elementMouseover({ + key: d.label, + value: d.label, + series: [ + { key: 'Q3', value: d.values.Q3, color: d.color || color(d,i) }, + { key: 'Q2', value: d.values.Q2, color: d.color || color(d,i) }, + { key: 'Q1', value: d.values.Q1, color: d.color || color(d,i) } + ], + data: d, + index: i, + e: d3.event + }); + }) + .on('mouseout', function(d,i) { + d3.select(this).classed('hover', false); + dispatch.elementMouseout({ + key: d.label, + value: d.label, + series: [ + { key: 'Q3', value: d.values.Q3, color: d.color || color(d,i) }, + { key: 'Q2', value: d.values.Q2, color: d.color || color(d,i) }, + { key: 'Q1', value: d.values.Q1, color: d.color || color(d,i) } + ], + data: d, + index: i, + e: d3.event + }); + }) + .on('mousemove', function(d,i) { + dispatch.elementMousemove({e: d3.event}); + }); + + // box transitions + boxplots.select('rect.nv-boxplot-box') + .watchTransition(renderWatch, 'nv-boxplot: boxes') + .attr('y', function(d,i) { return y(d.values.Q3); }) + .attr('width', box_width) + .attr('x', box_left ) + + .attr('height', function(d,i) { return Math.abs(y(d.values.Q3) - y(d.values.Q1)) || 1 }) + .style('fill', function(d,i) { return d.color || color(d,i) }) + .style('stroke', function(d,i) { return d.color || color(d,i) }); + + // median line + boxEnter.append('line').attr('class', 'nv-boxplot-median'); + + boxplots.select('line.nv-boxplot-median') + .watchTransition(renderWatch, 'nv-boxplot: boxplots line') + .attr('x1', box_left) + .attr('y1', function(d,i) { return y(d.values.Q2); }) + .attr('x2', box_right) + .attr('y2', function(d,i) { return y(d.values.Q2); }); + + //store old scales for use in transitions on update + x0 = x.copy(); + y0 = y.copy(); + }); + + renderWatch.renderEnd('nv-boxplot immediate'); + return chart; + } + + //============================================================ + // Expose Public Variables + //------------------------------------------------------------ + + chart.dispatch = dispatch; + chart.options = nv.utils.optionsFunc.bind(chart); + + chart._options = Object.create({}, { + // simple options, just get/set the necessary values + width: {get: function(){return width;}, set: function(_){width=_;}}, + height: {get: function(){return height;}, set: function(_){height=_;}}, + maxBoxWidth: {get: function(){return maxBoxWidth;}, set: function(_){maxBoxWidth=_;}}, + x: {get: function(){return getX;}, set: function(_){getX=_;}}, + y: {get: function(){return getY;}, set: function(_){getY=_;}}, + xScale: {get: function(){return x;}, set: function(_){x=_;}}, + yScale: {get: function(){return y;}, set: function(_){y=_;}}, + xDomain: {get: function(){return xDomain;}, set: function(_){xDomain=_;}}, + yDomain: {get: function(){return yDomain;}, set: function(_){yDomain=_;}}, + xRange: {get: function(){return xRange;}, set: function(_){xRange=_;}}, + yRange: {get: function(){return yRange;}, set: function(_){yRange=_;}}, + id: {get: function(){return id;}, set: function(_){id=_;}}, + // rectClass: {get: function(){return rectClass;}, set: function(_){rectClass=_;}}, + + // options that require extra logic in the setter + margin: {get: function(){return margin;}, set: function(_){ + margin.top = _.top !== undefined ? _.top : margin.top; + margin.right = _.right !== undefined ? _.right : margin.right; + margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom; + margin.left = _.left !== undefined ? _.left : margin.left; + }}, + color: {get: function(){return color;}, set: function(_){ + color = nv.utils.getColor(_); + }}, + duration: {get: function(){return duration;}, set: function(_){ + duration = _; + renderWatch.reset(duration); + }} + }); + + nv.utils.initOptions(chart); + + return chart; +}; +nv.models.boxPlotChart = function() { + "use strict"; + + //============================================================ + // Public Variables with Default Settings + //------------------------------------------------------------ + + var boxplot = nv.models.boxPlot() + , xAxis = nv.models.axis() + , yAxis = nv.models.axis() + ; + + var margin = {top: 15, right: 10, bottom: 50, left: 60} + , width = null + , height = null + , color = nv.utils.getColor() + , showXAxis = true + , showYAxis = true + , rightAlignYAxis = false + , staggerLabels = false + , tooltip = nv.models.tooltip() + , x + , y + , noData = "No Data Available." + , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'beforeUpdate', 'renderEnd') + , duration = 250 + ; + + xAxis + .orient('bottom') + .showMaxMin(false) + .tickFormat(function(d) { return d }) + ; + yAxis + .orient((rightAlignYAxis) ? 'right' : 'left') + .tickFormat(d3.format(',.1f')) + ; + + tooltip.duration(0); + + //============================================================ + // Private Variables + //------------------------------------------------------------ + + var renderWatch = nv.utils.renderWatch(dispatch, duration); + + function chart(selection) { + renderWatch.reset(); + renderWatch.models(boxplot); + if (showXAxis) renderWatch.models(xAxis); + if (showYAxis) renderWatch.models(yAxis); + + selection.each(function(data) { + var container = d3.select(this), + that = this; + nv.utils.initSVG(container); + var availableWidth = (width || parseInt(container.style('width')) || 960) + - margin.left - margin.right, + availableHeight = (height || parseInt(container.style('height')) || 400) + - margin.top - margin.bottom; + + chart.update = function() { + dispatch.beforeUpdate(); + container.transition().duration(duration).call(chart); + }; + chart.container = this; + + // Display No Data message if there's nothing to show. (quartiles required at minimum) + if (!data || !data.length || + !data.filter(function(d) { return d.values.hasOwnProperty("Q1") && d.values.hasOwnProperty("Q2") && d.values.hasOwnProperty("Q3"); }).length) { + var noDataText = container.selectAll('.nv-noData').data([noData]); + + noDataText.enter().append('text') + .attr('class', 'nvd3 nv-noData') + .attr('dy', '-.7em') + .style('text-anchor', 'middle'); + + noDataText + .attr('x', margin.left + availableWidth / 2) + .attr('y', margin.top + availableHeight / 2) + .text(function(d) { return d }); + + return chart; + } else { + container.selectAll('.nv-noData').remove(); + } + + // Setup Scales + x = boxplot.xScale(); + y = boxplot.yScale().clamp(true); + + // Setup containers and skeleton of chart + var wrap = container.selectAll('g.nv-wrap.nv-boxPlotWithAxes').data([data]); + var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-boxPlotWithAxes').append('g'); + var defsEnter = gEnter.append('defs'); + var g = wrap.select('g'); + + gEnter.append('g').attr('class', 'nv-x nv-axis'); + gEnter.append('g').attr('class', 'nv-y nv-axis') + .append('g').attr('class', 'nv-zeroLine') + .append('line'); + + gEnter.append('g').attr('class', 'nv-barsWrap'); + + g.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); + + if (rightAlignYAxis) { + g.select(".nv-y.nv-axis") + .attr("transform", "translate(" + availableWidth + ",0)"); + } + + // Main Chart Component(s) + boxplot + .width(availableWidth) + .height(availableHeight); + + var barsWrap = g.select('.nv-barsWrap') + .datum(data.filter(function(d) { return !d.disabled })) + + barsWrap.transition().call(boxplot); + + + defsEnter.append('clipPath') + .attr('id', 'nv-x-label-clip-' + boxplot.id()) + .append('rect'); + + g.select('#nv-x-label-clip-' + boxplot.id() + ' rect') + .attr('width', x.rangeBand() * (staggerLabels ? 2 : 1)) + .attr('height', 16) + .attr('x', -x.rangeBand() / (staggerLabels ? 1 : 2 )); + + // Setup Axes + if (showXAxis) { + xAxis + .scale(x) + .ticks( nv.utils.calcTicksX(availableWidth/100, data) ) + .tickSize(-availableHeight, 0); + + g.select('.nv-x.nv-axis').attr('transform', 'translate(0,' + y.range()[0] + ')'); + g.select('.nv-x.nv-axis').call(xAxis); + + var xTicks = g.select('.nv-x.nv-axis').selectAll('g'); + if (staggerLabels) { + xTicks + .selectAll('text') + .attr('transform', function(d,i,j) { return 'translate(0,' + (j % 2 == 0 ? '5' : '17') + ')' }) + } + } + + if (showYAxis) { + yAxis + .scale(y) + .ticks( Math.floor(availableHeight/36) ) // can't use nv.utils.calcTicksY with Object data + .tickSize( -availableWidth, 0); + + g.select('.nv-y.nv-axis').call(yAxis); + } + + // Zero line + g.select(".nv-zeroLine line") + .attr("x1",0) + .attr("x2",availableWidth) + .attr("y1", y(0)) + .attr("y2", y(0)) + ; + + //============================================================ + // Event Handling/Dispatching (in chart's scope) + //------------------------------------------------------------ + }); + + renderWatch.renderEnd('nv-boxplot chart immediate'); + return chart; + } + + //============================================================ + // Event Handling/Dispatching (out of chart's scope) + //------------------------------------------------------------ + + boxplot.dispatch.on('elementMouseover.tooltip', function(evt) { + tooltip.data(evt).hidden(false); + }); + + boxplot.dispatch.on('elementMouseout.tooltip', function(evt) { + tooltip.data(evt).hidden(true); + }); + + boxplot.dispatch.on('elementMousemove.tooltip', function(evt) { + tooltip.position({top: d3.event.pageY, left: d3.event.pageX})(); + }); + + //============================================================ + // Expose Public Variables + //------------------------------------------------------------ + + chart.dispatch = dispatch; + chart.boxplot = boxplot; + chart.xAxis = xAxis; + chart.yAxis = yAxis; + chart.tooltip = tooltip; + + chart.options = nv.utils.optionsFunc.bind(chart); + + chart._options = Object.create({}, { + // simple options, just get/set the necessary values + width: {get: function(){return width;}, set: function(_){width=_;}}, + height: {get: function(){return height;}, set: function(_){height=_;}}, + staggerLabels: {get: function(){return staggerLabels;}, set: function(_){staggerLabels=_;}}, + showXAxis: {get: function(){return showXAxis;}, set: function(_){showXAxis=_;}}, + showYAxis: {get: function(){return showYAxis;}, set: function(_){showYAxis=_;}}, + tooltips: {get: function(){return tooltips;}, set: function(_){tooltips=_;}}, + tooltipContent: {get: function(){return tooltip;}, set: function(_){tooltip=_;}}, + noData: {get: function(){return noData;}, set: function(_){noData=_;}}, + + // options that require extra logic in the setter + margin: {get: function(){return margin;}, set: function(_){ + margin.top = _.top !== undefined ? _.top : margin.top; + margin.right = _.right !== undefined ? _.right : margin.right; + margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom; + margin.left = _.left !== undefined ? _.left : margin.left; + }}, + duration: {get: function(){return duration;}, set: function(_){ + duration = _; + renderWatch.reset(duration); + boxplot.duration(duration); + xAxis.duration(duration); + yAxis.duration(duration); + }}, + color: {get: function(){return color;}, set: function(_){ + color = nv.utils.getColor(_); + boxplot.color(color); + }}, + rightAlignYAxis: {get: function(){return rightAlignYAxis;}, set: function(_){ + rightAlignYAxis = _; + yAxis.orient( (_) ? 'right' : 'left'); + }} + }); + + nv.utils.inheritOptions(chart, boxplot); + nv.utils.initOptions(chart); + + return chart; +} +// Chart design based on the recommendations of Stephen Few. Implementation +// based on the work of Clint Ivy, Jamie Love, and Jason Davies. +// http://projects.instantcognition.com/protovis/bulletchart/ + +nv.models.bullet = function() { + "use strict"; + + //============================================================ + // Public Variables with Default Settings + //------------------------------------------------------------ + + var margin = {top: 0, right: 0, bottom: 0, left: 0} + , orient = 'left' // TODO top & bottom + , reverse = false + , ranges = function(d) { return d.ranges } + , markers = function(d) { return d.markers ? d.markers : [0] } + , measures = function(d) { return d.measures } + , rangeLabels = function(d) { return d.rangeLabels ? d.rangeLabels : [] } + , markerLabels = function(d) { return d.markerLabels ? d.markerLabels : [] } + , measureLabels = function(d) { return d.measureLabels ? d.measureLabels : [] } + , forceX = [0] // List of numbers to Force into the X scale (ie. 0, or a max / min, etc.) + , width = 380 + , height = 30 + , container = null + , tickFormat = null + , color = nv.utils.getColor(['#1f77b4']) + , dispatch = d3.dispatch('elementMouseover', 'elementMouseout', 'elementMousemove') + ; + + function chart(selection) { + selection.each(function(d, i) { + var availableWidth = width - margin.left - margin.right, + availableHeight = height - margin.top - margin.bottom; + + container = d3.select(this); + nv.utils.initSVG(container); + + var rangez = ranges.call(this, d, i).slice().sort(d3.descending), + markerz = markers.call(this, d, i).slice().sort(d3.descending), + measurez = measures.call(this, d, i).slice().sort(d3.descending), + rangeLabelz = rangeLabels.call(this, d, i).slice(), + markerLabelz = markerLabels.call(this, d, i).slice(), + measureLabelz = measureLabels.call(this, d, i).slice(); + + // Setup Scales + // Compute the new x-scale. + var x1 = d3.scale.linear() + .domain( d3.extent(d3.merge([forceX, rangez])) ) + .range(reverse ? [availableWidth, 0] : [0, availableWidth]); + + // Retrieve the old x-scale, if this is an update. + var x0 = this.__chart__ || d3.scale.linear() + .domain([0, Infinity]) + .range(x1.range()); + + // Stash the new scale. + this.__chart__ = x1; + + var rangeMin = d3.min(rangez), //rangez[2] + rangeMax = d3.max(rangez), //rangez[0] + rangeAvg = rangez[1]; + + // Setup containers and skeleton of chart + var wrap = container.selectAll('g.nv-wrap.nv-bullet').data([d]); + var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-bullet'); + var gEnter = wrapEnter.append('g'); + var g = wrap.select('g'); + + gEnter.append('rect').attr('class', 'nv-range nv-rangeMax'); + gEnter.append('rect').attr('class', 'nv-range nv-rangeAvg'); + gEnter.append('rect').attr('class', 'nv-range nv-rangeMin'); + gEnter.append('rect').attr('class', 'nv-measure'); + + wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); + + var w0 = function(d) { return Math.abs(x0(d) - x0(0)) }, // TODO: could optimize by precalculating x0(0) and x1(0) + w1 = function(d) { return Math.abs(x1(d) - x1(0)) }; + var xp0 = function(d) { return d < 0 ? x0(d) : x0(0) }, + xp1 = function(d) { return d < 0 ? x1(d) : x1(0) }; + + g.select('rect.nv-rangeMax') + .attr('height', availableHeight) + .attr('width', w1(rangeMax > 0 ? rangeMax : rangeMin)) + .attr('x', xp1(rangeMax > 0 ? rangeMax : rangeMin)) + .datum(rangeMax > 0 ? rangeMax : rangeMin) + + g.select('rect.nv-rangeAvg') + .attr('height', availableHeight) + .attr('width', w1(rangeAvg)) + .attr('x', xp1(rangeAvg)) + .datum(rangeAvg) + + g.select('rect.nv-rangeMin') + .attr('height', availableHeight) + .attr('width', w1(rangeMax)) + .attr('x', xp1(rangeMax)) + .attr('width', w1(rangeMax > 0 ? rangeMin : rangeMax)) + .attr('x', xp1(rangeMax > 0 ? rangeMin : rangeMax)) + .datum(rangeMax > 0 ? rangeMin : rangeMax) + + g.select('rect.nv-measure') + .style('fill', color) + .attr('height', availableHeight / 3) + .attr('y', availableHeight / 3) + .attr('width', measurez < 0 ? + x1(0) - x1(measurez[0]) + : x1(measurez[0]) - x1(0)) + .attr('x', xp1(measurez)) + .on('mouseover', function() { + dispatch.elementMouseover({ + value: measurez[0], + label: measureLabelz[0] || 'Current', + color: d3.select(this).style("fill") + }) + }) + .on('mousemove', function() { + dispatch.elementMousemove({ + value: measurez[0], + label: measureLabelz[0] || 'Current', + color: d3.select(this).style("fill") + }) + }) + .on('mouseout', function() { + dispatch.elementMouseout({ + value: measurez[0], + label: measureLabelz[0] || 'Current', + color: d3.select(this).style("fill") + }) + }); + + var h3 = availableHeight / 6; + + var markerData = markerz.map( function(marker, index) { + return {value: marker, label: markerLabelz[index]} + }); + gEnter + .selectAll("path.nv-markerTriangle") + .data(markerData) + .enter() + .append('path') + .attr('class', 'nv-markerTriangle') + .attr('transform', function(d) { return 'translate(' + x1(d.value) + ',' + (availableHeight / 2) + ')' }) + .attr('d', 'M0,' + h3 + 'L' + h3 + ',' + (-h3) + ' ' + (-h3) + ',' + (-h3) + 'Z') + .on('mouseover', function(d) { + dispatch.elementMouseover({ + value: d.value, + label: d.label || 'Previous', + color: d3.select(this).style("fill"), + pos: [x1(d.value), availableHeight/2] + }) + + }) + .on('mousemove', function(d) { + dispatch.elementMousemove({ + value: d.value, + label: d.label || 'Previous', + color: d3.select(this).style("fill") + }) + }) + .on('mouseout', function(d, i) { + dispatch.elementMouseout({ + value: d.value, + label: d.label || 'Previous', + color: d3.select(this).style("fill") + }) + }); + + wrap.selectAll('.nv-range') + .on('mouseover', function(d,i) { + var label = rangeLabelz[i] || (!i ? "Maximum" : i == 1 ? "Mean" : "Minimum"); + dispatch.elementMouseover({ + value: d, + label: label, + color: d3.select(this).style("fill") + }) + }) + .on('mousemove', function() { + dispatch.elementMousemove({ + value: measurez[0], + label: measureLabelz[0] || 'Previous', + color: d3.select(this).style("fill") + }) + }) + .on('mouseout', function(d,i) { + var label = rangeLabelz[i] || (!i ? "Maximum" : i == 1 ? "Mean" : "Minimum"); + dispatch.elementMouseout({ + value: d, + label: label, + color: d3.select(this).style("fill") + }) + }); + }); + + return chart; + } + + //============================================================ + // Expose Public Variables + //------------------------------------------------------------ + + chart.dispatch = dispatch; + chart.options = nv.utils.optionsFunc.bind(chart); + + chart._options = Object.create({}, { + // simple options, just get/set the necessary values + ranges: {get: function(){return ranges;}, set: function(_){ranges=_;}}, // ranges (bad, satisfactory, good) + markers: {get: function(){return markers;}, set: function(_){markers=_;}}, // markers (previous, goal) + measures: {get: function(){return measures;}, set: function(_){measures=_;}}, // measures (actual, forecast) + forceX: {get: function(){return forceX;}, set: function(_){forceX=_;}}, + width: {get: function(){return width;}, set: function(_){width=_;}}, + height: {get: function(){return height;}, set: function(_){height=_;}}, + tickFormat: {get: function(){return tickFormat;}, set: function(_){tickFormat=_;}}, + + // options that require extra logic in the setter + margin: {get: function(){return margin;}, set: function(_){ + margin.top = _.top !== undefined ? _.top : margin.top; + margin.right = _.right !== undefined ? _.right : margin.right; + margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom; + margin.left = _.left !== undefined ? _.left : margin.left; + }}, + orient: {get: function(){return orient;}, set: function(_){ // left, right, top, bottom + orient = _; + reverse = orient == 'right' || orient == 'bottom'; + }}, + color: {get: function(){return color;}, set: function(_){ + color = nv.utils.getColor(_); + }} + }); + + nv.utils.initOptions(chart); + return chart; +}; + + + +// Chart design based on the recommendations of Stephen Few. Implementation +// based on the work of Clint Ivy, Jamie Love, and Jason Davies. +// http://projects.instantcognition.com/protovis/bulletchart/ +nv.models.bulletChart = function() { + "use strict"; + + //============================================================ + // Public Variables with Default Settings + //------------------------------------------------------------ + + var bullet = nv.models.bullet(); + var tooltip = nv.models.tooltip(); + + var orient = 'left' // TODO top & bottom + , reverse = false + , margin = {top: 5, right: 40, bottom: 20, left: 120} + , ranges = function(d) { return d.ranges } + , markers = function(d) { return d.markers ? d.markers : [0] } + , measures = function(d) { return d.measures } + , width = null + , height = 55 + , tickFormat = null + , ticks = null + , noData = null + , dispatch = d3.dispatch('tooltipShow', 'tooltipHide') + ; + + tooltip.duration(0).headerEnabled(false); + + function chart(selection) { + selection.each(function(d, i) { + var container = d3.select(this); + nv.utils.initSVG(container); + + var availableWidth = nv.utils.availableWidth(width, container, margin), + availableHeight = height - margin.top - margin.bottom, + that = this; + + chart.update = function() { chart(selection) }; + chart.container = this; + + // Display No Data message if there's nothing to show. + if (!d || !ranges.call(this, d, i)) { + nv.utils.noData(chart, container) + return chart; + } else { + container.selectAll('.nv-noData').remove(); + } + + var rangez = ranges.call(this, d, i).slice().sort(d3.descending), + markerz = markers.call(this, d, i).slice().sort(d3.descending), + measurez = measures.call(this, d, i).slice().sort(d3.descending); + + // Setup containers and skeleton of chart + var wrap = container.selectAll('g.nv-wrap.nv-bulletChart').data([d]); + var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-bulletChart'); + var gEnter = wrapEnter.append('g'); + var g = wrap.select('g'); + + gEnter.append('g').attr('class', 'nv-bulletWrap'); + gEnter.append('g').attr('class', 'nv-titles'); + + wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); + + // Compute the new x-scale. + var x1 = d3.scale.linear() + .domain([0, Math.max(rangez[0], markerz[0], measurez[0])]) // TODO: need to allow forceX and forceY, and xDomain, yDomain + .range(reverse ? [availableWidth, 0] : [0, availableWidth]); + + // Retrieve the old x-scale, if this is an update. + var x0 = this.__chart__ || d3.scale.linear() + .domain([0, Infinity]) + .range(x1.range()); + + // Stash the new scale. + this.__chart__ = x1; + + var w0 = function(d) { return Math.abs(x0(d) - x0(0)) }, // TODO: could optimize by precalculating x0(0) and x1(0) + w1 = function(d) { return Math.abs(x1(d) - x1(0)) }; + + var title = gEnter.select('.nv-titles').append('g') + .attr('text-anchor', 'end') + .attr('transform', 'translate(-6,' + (height - margin.top - margin.bottom) / 2 + ')'); + title.append('text') + .attr('class', 'nv-title') + .text(function(d) { return d.title; }); + + title.append('text') + .attr('class', 'nv-subtitle') + .attr('dy', '1em') + .text(function(d) { return d.subtitle; }); + + bullet + .width(availableWidth) + .height(availableHeight) + + var bulletWrap = g.select('.nv-bulletWrap'); + d3.transition(bulletWrap).call(bullet); + + // Compute the tick format. + var format = tickFormat || x1.tickFormat( availableWidth / 100 ); + + // Update the tick groups. + var tick = g.selectAll('g.nv-tick') + .data(x1.ticks( ticks ? ticks : (availableWidth / 50) ), function(d) { + return this.textContent || format(d); + }); + + // Initialize the ticks with the old scale, x0. + var tickEnter = tick.enter().append('g') + .attr('class', 'nv-tick') + .attr('transform', function(d) { return 'translate(' + x0(d) + ',0)' }) + .style('opacity', 1e-6); + + tickEnter.append('line') + .attr('y1', availableHeight) + .attr('y2', availableHeight * 7 / 6); + + tickEnter.append('text') + .attr('text-anchor', 'middle') + .attr('dy', '1em') + .attr('y', availableHeight * 7 / 6) + .text(format); + + // Transition the updating ticks to the new scale, x1. + var tickUpdate = d3.transition(tick) + .attr('transform', function(d) { return 'translate(' + x1(d) + ',0)' }) + .style('opacity', 1); + + tickUpdate.select('line') + .attr('y1', availableHeight) + .attr('y2', availableHeight * 7 / 6); + + tickUpdate.select('text') + .attr('y', availableHeight * 7 / 6); + + // Transition the exiting ticks to the new scale, x1. + d3.transition(tick.exit()) + .attr('transform', function(d) { return 'translate(' + x1(d) + ',0)' }) + .style('opacity', 1e-6) + .remove(); + }); + + d3.timer.flush(); + return chart; + } + + //============================================================ + // Event Handling/Dispatching (out of chart's scope) + //------------------------------------------------------------ + + bullet.dispatch.on('elementMouseover.tooltip', function(evt) { + evt['series'] = { + key: evt.label, + value: evt.value, + color: evt.color + }; + tooltip.data(evt).hidden(false); + }); + + bullet.dispatch.on('elementMouseout.tooltip', function(evt) { + tooltip.hidden(true); + }); + + bullet.dispatch.on('elementMousemove.tooltip', function(evt) { + tooltip.position({top: d3.event.pageY, left: d3.event.pageX})(); + }); + + //============================================================ + // Expose Public Variables + //------------------------------------------------------------ + + chart.bullet = bullet; + chart.dispatch = dispatch; + chart.tooltip = tooltip; + + chart.options = nv.utils.optionsFunc.bind(chart); + + chart._options = Object.create({}, { + // simple options, just get/set the necessary values + ranges: {get: function(){return ranges;}, set: function(_){ranges=_;}}, // ranges (bad, satisfactory, good) + markers: {get: function(){return markers;}, set: function(_){markers=_;}}, // markers (previous, goal) + measures: {get: function(){return measures;}, set: function(_){measures=_;}}, // measures (actual, forecast) + width: {get: function(){return width;}, set: function(_){width=_;}}, + height: {get: function(){return height;}, set: function(_){height=_;}}, + tickFormat: {get: function(){return tickFormat;}, set: function(_){tickFormat=_;}}, + ticks: {get: function(){return ticks;}, set: function(_){ticks=_;}}, + noData: {get: function(){return noData;}, set: function(_){noData=_;}}, + + // deprecated options + tooltips: {get: function(){return tooltip.enabled();}, set: function(_){ + // deprecated after 1.7.1 + nv.deprecated('tooltips', 'use chart.tooltip.enabled() instead'); + tooltip.enabled(!!_); + }}, + tooltipContent: {get: function(){return tooltip.contentGenerator();}, set: function(_){ + // deprecated after 1.7.1 + nv.deprecated('tooltipContent', 'use chart.tooltip.contentGenerator() instead'); + tooltip.contentGenerator(_); + }}, + + // options that require extra logic in the setter + margin: {get: function(){return margin;}, set: function(_){ + margin.top = _.top !== undefined ? _.top : margin.top; + margin.right = _.right !== undefined ? _.right : margin.right; + margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom; + margin.left = _.left !== undefined ? _.left : margin.left; + }}, + orient: {get: function(){return orient;}, set: function(_){ // left, right, top, bottom + orient = _; + reverse = orient == 'right' || orient == 'bottom'; + }} + }); + + nv.utils.inheritOptions(chart, bullet); + nv.utils.initOptions(chart); + + return chart; +}; + + + +nv.models.candlestickBar = function() { + "use strict"; + + //============================================================ + // Public Variables with Default Settings + //------------------------------------------------------------ + + var margin = {top: 0, right: 0, bottom: 0, left: 0} + , width = null + , height = null + , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one + , container + , x = d3.scale.linear() + , y = d3.scale.linear() + , getX = function(d) { return d.x } + , getY = function(d) { return d.y } + , getOpen = function(d) { return d.open } + , getClose = function(d) { return d.close } + , getHigh = function(d) { return d.high } + , getLow = function(d) { return d.low } + , forceX = [] + , forceY = [] + , padData = false // If true, adds half a data points width to front and back, for lining up a line chart with a bar chart + , clipEdge = true + , color = nv.utils.defaultColor() + , interactive = false + , xDomain + , yDomain + , xRange + , yRange + , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState', 'renderEnd', 'chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout', 'elementMousemove') + ; + + //============================================================ + // Private Variables + //------------------------------------------------------------ + + function chart(selection) { + selection.each(function(data) { + container = d3.select(this); + var availableWidth = nv.utils.availableWidth(width, container, margin), + availableHeight = nv.utils.availableHeight(height, container, margin); + + nv.utils.initSVG(container); + + // Width of the candlestick bars. + var barWidth = (availableWidth / data[0].values.length) * .45; + + // Setup Scales + x.domain(xDomain || d3.extent(data[0].values.map(getX).concat(forceX) )); + + if (padData) + x.range(xRange || [availableWidth * .5 / data[0].values.length, availableWidth * (data[0].values.length - .5) / data[0].values.length ]); + else + x.range(xRange || [5 + barWidth / 2, availableWidth - barWidth / 2 - 5]); + + y.domain(yDomain || [ + d3.min(data[0].values.map(getLow).concat(forceY)), + d3.max(data[0].values.map(getHigh).concat(forceY)) + ] + ).range(yRange || [availableHeight, 0]); + + // If scale's domain don't have a range, slightly adjust to make one... so a chart can show a single data point + if (x.domain()[0] === x.domain()[1]) + x.domain()[0] ? + x.domain([x.domain()[0] - x.domain()[0] * 0.01, x.domain()[1] + x.domain()[1] * 0.01]) + : x.domain([-1,1]); + + if (y.domain()[0] === y.domain()[1]) + y.domain()[0] ? + y.domain([y.domain()[0] + y.domain()[0] * 0.01, y.domain()[1] - y.domain()[1] * 0.01]) + : y.domain([-1,1]); + + // Setup containers and skeleton of chart + var wrap = d3.select(this).selectAll('g.nv-wrap.nv-candlestickBar').data([data[0].values]); + var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-candlestickBar'); + var defsEnter = wrapEnter.append('defs'); + var gEnter = wrapEnter.append('g'); + var g = wrap.select('g'); + + gEnter.append('g').attr('class', 'nv-ticks'); + + wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); + + container + .on('click', function(d,i) { + dispatch.chartClick({ + data: d, + index: i, + pos: d3.event, + id: id + }); + }); + + defsEnter.append('clipPath') + .attr('id', 'nv-chart-clip-path-' + id) + .append('rect'); + + wrap.select('#nv-chart-clip-path-' + id + ' rect') + .attr('width', availableWidth) + .attr('height', availableHeight); + + g .attr('clip-path', clipEdge ? 'url(#nv-chart-clip-path-' + id + ')' : ''); + + var ticks = wrap.select('.nv-ticks').selectAll('.nv-tick') + .data(function(d) { return d }); + ticks.exit().remove(); + + // The colors are currently controlled by CSS. + var tickGroups = ticks.enter().append('g') + .attr('class', function(d, i, j) { return (getOpen(d, i) > getClose(d, i) ? 'nv-tick negative' : 'nv-tick positive') + ' nv-tick-' + j + '-' + i}); + + var lines = tickGroups.append('line') + .attr('class', 'nv-candlestick-lines') + .attr('transform', function(d, i) { return 'translate(' + x(getX(d, i)) + ',0)'; }) + .attr('x1', 0) + .attr('y1', function(d, i) { return y(getHigh(d, i)); }) + .attr('x2', 0) + .attr('y2', function(d, i) { return y(getLow(d, i)); }); + + var rects = tickGroups.append('rect') + .attr('class', 'nv-candlestick-rects nv-bars') + .attr('transform', function(d, i) { + return 'translate(' + (x(getX(d, i)) - barWidth/2) + ',' + + (y(getY(d, i)) - (getOpen(d, i) > getClose(d, i) ? (y(getClose(d, i)) - y(getOpen(d, i))) : 0)) + + ')'; + }) + .attr('x', 0) + .attr('y', 0) + .attr('width', barWidth) + .attr('height', function(d, i) { + var open = getOpen(d, i); + var close = getClose(d, i); + return open > close ? y(close) - y(open) : y(open) - y(close); + }); + + container.selectAll('.nv-candlestick-lines').transition() + .attr('transform', function(d, i) { return 'translate(' + x(getX(d, i)) + ',0)'; }) + .attr('x1', 0) + .attr('y1', function(d, i) { return y(getHigh(d, i)); }) + .attr('x2', 0) + .attr('y2', function(d, i) { return y(getLow(d, i)); }); + + container.selectAll('.nv-candlestick-rects').transition() + .attr('transform', function(d, i) { + return 'translate(' + (x(getX(d, i)) - barWidth/2) + ',' + + (y(getY(d, i)) - (getOpen(d, i) > getClose(d, i) ? (y(getClose(d, i)) - y(getOpen(d, i))) : 0)) + + ')'; + }) + .attr('x', 0) + .attr('y', 0) + .attr('width', barWidth) + .attr('height', function(d, i) { + var open = getOpen(d, i); + var close = getClose(d, i); + return open > close ? y(close) - y(open) : y(open) - y(close); + }); + }); + + return chart; + } + + + //Create methods to allow outside functions to highlight a specific bar. + chart.highlightPoint = function(pointIndex, isHoverOver) { + chart.clearHighlights(); + container.select(".nv-candlestickBar .nv-tick-0-" + pointIndex) + .classed("hover", isHoverOver) + ; + }; + + chart.clearHighlights = function() { + container.select(".nv-candlestickBar .nv-tick.hover") + .classed("hover", false) + ; + }; + + //============================================================ + // Expose Public Variables + //------------------------------------------------------------ + + chart.dispatch = dispatch; + chart.options = nv.utils.optionsFunc.bind(chart); + + chart._options = Object.create({}, { + // simple options, just get/set the necessary values + width: {get: function(){return width;}, set: function(_){width=_;}}, + height: {get: function(){return height;}, set: function(_){height=_;}}, + xScale: {get: function(){return x;}, set: function(_){x=_;}}, + yScale: {get: function(){return y;}, set: function(_){y=_;}}, + xDomain: {get: function(){return xDomain;}, set: function(_){xDomain=_;}}, + yDomain: {get: function(){return yDomain;}, set: function(_){yDomain=_;}}, + xRange: {get: function(){return xRange;}, set: function(_){xRange=_;}}, + yRange: {get: function(){return yRange;}, set: function(_){yRange=_;}}, + forceX: {get: function(){return forceX;}, set: function(_){forceX=_;}}, + forceY: {get: function(){return forceY;}, set: function(_){forceY=_;}}, + padData: {get: function(){return padData;}, set: function(_){padData=_;}}, + clipEdge: {get: function(){return clipEdge;}, set: function(_){clipEdge=_;}}, + id: {get: function(){return id;}, set: function(_){id=_;}}, + interactive: {get: function(){return interactive;}, set: function(_){interactive=_;}}, + + x: {get: function(){return getX;}, set: function(_){getX=_;}}, + y: {get: function(){return getY;}, set: function(_){getY=_;}}, + open: {get: function(){return getOpen();}, set: function(_){getOpen=_;}}, + close: {get: function(){return getClose();}, set: function(_){getClose=_;}}, + high: {get: function(){return getHigh;}, set: function(_){getHigh=_;}}, + low: {get: function(){return getLow;}, set: function(_){getLow=_;}}, + + // options that require extra logic in the setter + margin: {get: function(){return margin;}, set: function(_){ + margin.top = _.top != undefined ? _.top : margin.top; + margin.right = _.right != undefined ? _.right : margin.right; + margin.bottom = _.bottom != undefined ? _.bottom : margin.bottom; + margin.left = _.left != undefined ? _.left : margin.left; + }}, + color: {get: function(){return color;}, set: function(_){ + color = nv.utils.getColor(_); + }} + }); + + nv.utils.initOptions(chart); + return chart; +}; + +nv.models.cumulativeLineChart = function() { + "use strict"; + + //============================================================ + // Public Variables with Default Settings + //------------------------------------------------------------ + + var lines = nv.models.line() + , xAxis = nv.models.axis() + , yAxis = nv.models.axis() + , legend = nv.models.legend() + , controls = nv.models.legend() + , interactiveLayer = nv.interactiveGuideline() + , tooltip = nv.models.tooltip() + ; + + var margin = {top: 30, right: 30, bottom: 50, left: 60} + , color = nv.utils.defaultColor() + , width = null + , height = null + , showLegend = true + , showXAxis = true + , showYAxis = true + , rightAlignYAxis = false + , showControls = true + , useInteractiveGuideline = false + , rescaleY = true + , x //can be accessed via chart.xScale() + , y //can be accessed via chart.yScale() + , id = lines.id() + , state = nv.utils.state() + , defaultState = null + , noData = null + , average = function(d) { return d.average } + , dispatch = d3.dispatch('stateChange', 'changeState', 'renderEnd') + , transitionDuration = 250 + , duration = 250 + , noErrorCheck = false //if set to TRUE, will bypass an error check in the indexify function. + ; + + state.index = 0; + state.rescaleY = rescaleY; + + xAxis.orient('bottom').tickPadding(7); + yAxis.orient((rightAlignYAxis) ? 'right' : 'left'); + + tooltip.valueFormatter(function(d, i) { + return yAxis.tickFormat()(d, i); + }).headerFormatter(function(d, i) { + return xAxis.tickFormat()(d, i); + }); + + controls.updateState(false); + + //============================================================ + // Private Variables + //------------------------------------------------------------ + + var dx = d3.scale.linear() + , index = {i: 0, x: 0} + , renderWatch = nv.utils.renderWatch(dispatch, duration) + ; + + var stateGetter = function(data) { + return function(){ + return { + active: data.map(function(d) { return !d.disabled }), + index: index.i, + rescaleY: rescaleY + }; + } + }; + + var stateSetter = function(data) { + return function(state) { + if (state.index !== undefined) + index.i = state.index; + if (state.rescaleY !== undefined) + rescaleY = state.rescaleY; + if (state.active !== undefined) + data.forEach(function(series,i) { + series.disabled = !state.active[i]; + }); + } + }; + + function chart(selection) { + renderWatch.reset(); + renderWatch.models(lines); + if (showXAxis) renderWatch.models(xAxis); + if (showYAxis) renderWatch.models(yAxis); + selection.each(function(data) { + var container = d3.select(this); + nv.utils.initSVG(container); + container.classed('nv-chart-' + id, true); + var that = this; + + var availableWidth = nv.utils.availableWidth(width, container, margin), + availableHeight = nv.utils.availableHeight(height, container, margin); + + chart.update = function() { + if (duration === 0) + container.call(chart); + else + container.transition().duration(duration).call(chart) + }; + chart.container = this; + + state + .setter(stateSetter(data), chart.update) + .getter(stateGetter(data)) + .update(); + + // DEPRECATED set state.disableddisabled + state.disabled = data.map(function(d) { return !!d.disabled }); + + if (!defaultState) { + var key; + defaultState = {}; + for (key in state) { + if (state[key] instanceof Array) + defaultState[key] = state[key].slice(0); + else + defaultState[key] = state[key]; + } + } + + var indexDrag = d3.behavior.drag() + .on('dragstart', dragStart) + .on('drag', dragMove) + .on('dragend', dragEnd); + + + function dragStart(d,i) { + d3.select(chart.container) + .style('cursor', 'ew-resize'); + } + + function dragMove(d,i) { + index.x = d3.event.x; + index.i = Math.round(dx.invert(index.x)); + updateZero(); + } + + function dragEnd(d,i) { + d3.select(chart.container) + .style('cursor', 'auto'); + + // update state and send stateChange with new index + state.index = index.i; + dispatch.stateChange(state); + } + + // Display No Data message if there's nothing to show. + if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) { + nv.utils.noData(chart, container) + return chart; + } else { + container.selectAll('.nv-noData').remove(); + } + + // Setup Scales + x = lines.xScale(); + y = lines.yScale(); + + if (!rescaleY) { + var seriesDomains = data + .filter(function(series) { return !series.disabled }) + .map(function(series,i) { + var initialDomain = d3.extent(series.values, lines.y()); + + //account for series being disabled when losing 95% or more + if (initialDomain[0] < -.95) initialDomain[0] = -.95; + + return [ + (initialDomain[0] - initialDomain[1]) / (1 + initialDomain[1]), + (initialDomain[1] - initialDomain[0]) / (1 + initialDomain[0]) + ]; + }); + + var completeDomain = [ + d3.min(seriesDomains, function(d) { return d[0] }), + d3.max(seriesDomains, function(d) { return d[1] }) + ]; + + lines.yDomain(completeDomain); + } else { + lines.yDomain(null); + } + + dx.domain([0, data[0].values.length - 1]) //Assumes all series have same length + .range([0, availableWidth]) + .clamp(true); + + var data = indexify(index.i, data); + + // Setup containers and skeleton of chart + var interactivePointerEvents = (useInteractiveGuideline) ? "none" : "all"; + var wrap = container.selectAll('g.nv-wrap.nv-cumulativeLine').data([data]); + var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-cumulativeLine').append('g'); + var g = wrap.select('g'); + + gEnter.append('g').attr('class', 'nv-interactive'); + gEnter.append('g').attr('class', 'nv-x nv-axis').style("pointer-events","none"); + gEnter.append('g').attr('class', 'nv-y nv-axis'); + gEnter.append('g').attr('class', 'nv-background'); + gEnter.append('g').attr('class', 'nv-linesWrap').style("pointer-events",interactivePointerEvents); + gEnter.append('g').attr('class', 'nv-avgLinesWrap').style("pointer-events","none"); + gEnter.append('g').attr('class', 'nv-legendWrap'); + gEnter.append('g').attr('class', 'nv-controlsWrap'); + + // Legend + if (showLegend) { + legend.width(availableWidth); + + g.select('.nv-legendWrap') + .datum(data) + .call(legend); + + if ( margin.top != legend.height()) { + margin.top = legend.height(); + availableHeight = nv.utils.availableHeight(height, container, margin); + } + + g.select('.nv-legendWrap') + .attr('transform', 'translate(0,' + (-margin.top) +')') + } + + // Controls + if (showControls) { + var controlsData = [ + { key: 'Re-scale y-axis', disabled: !rescaleY } + ]; + + controls + .width(140) + .color(['#444', '#444', '#444']) + .rightAlign(false) + .margin({top: 5, right: 0, bottom: 5, left: 20}) + ; + + g.select('.nv-controlsWrap') + .datum(controlsData) + .attr('transform', 'translate(0,' + (-margin.top) +')') + .call(controls); + } + + wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); + + if (rightAlignYAxis) { + g.select(".nv-y.nv-axis") + .attr("transform", "translate(" + availableWidth + ",0)"); + } + + // Show error if series goes below 100% + var tempDisabled = data.filter(function(d) { return d.tempDisabled }); + + wrap.select('.tempDisabled').remove(); //clean-up and prevent duplicates + if (tempDisabled.length) { + wrap.append('text').attr('class', 'tempDisabled') + .attr('x', availableWidth / 2) + .attr('y', '-.71em') + .style('text-anchor', 'end') + .text(tempDisabled.map(function(d) { return d.key }).join(', ') + ' values cannot be calculated for this time period.'); + } + + //Set up interactive layer + if (useInteractiveGuideline) { + interactiveLayer + .width(availableWidth) + .height(availableHeight) + .margin({left:margin.left,top:margin.top}) + .svgContainer(container) + .xScale(x); + wrap.select(".nv-interactive").call(interactiveLayer); + } + + gEnter.select('.nv-background') + .append('rect'); + + g.select('.nv-background rect') + .attr('width', availableWidth) + .attr('height', availableHeight); + + lines + //.x(function(d) { return d.x }) + .y(function(d) { return d.display.y }) + .width(availableWidth) + .height(availableHeight) + .color(data.map(function(d,i) { + return d.color || color(d, i); + }).filter(function(d,i) { return !data[i].disabled && !data[i].tempDisabled; })); + + var linesWrap = g.select('.nv-linesWrap') + .datum(data.filter(function(d) { return !d.disabled && !d.tempDisabled })); + + linesWrap.call(lines); + + //Store a series index number in the data array. + data.forEach(function(d,i) { + d.seriesIndex = i; + }); + + var avgLineData = data.filter(function(d) { + return !d.disabled && !!average(d); + }); + + var avgLines = g.select(".nv-avgLinesWrap").selectAll("line") + .data(avgLineData, function(d) { return d.key; }); + + var getAvgLineY = function(d) { + //If average lines go off the svg element, clamp them to the svg bounds. + var yVal = y(average(d)); + if (yVal < 0) return 0; + if (yVal > availableHeight) return availableHeight; + return yVal; + }; + + avgLines.enter() + .append('line') + .style('stroke-width',2) + .style('stroke-dasharray','10,10') + .style('stroke',function (d,i) { + return lines.color()(d,d.seriesIndex); + }) + .attr('x1',0) + .attr('x2',availableWidth) + .attr('y1', getAvgLineY) + .attr('y2', getAvgLineY); + + avgLines + .style('stroke-opacity',function(d){ + //If average lines go offscreen, make them transparent + var yVal = y(average(d)); + if (yVal < 0 || yVal > availableHeight) return 0; + return 1; + }) + .attr('x1',0) + .attr('x2',availableWidth) + .attr('y1', getAvgLineY) + .attr('y2', getAvgLineY); + + avgLines.exit().remove(); + + //Create index line + var indexLine = linesWrap.selectAll('.nv-indexLine') + .data([index]); + indexLine.enter().append('rect').attr('class', 'nv-indexLine') + .attr('width', 3) + .attr('x', -2) + .attr('fill', 'red') + .attr('fill-opacity', .5) + .style("pointer-events","all") + .call(indexDrag); + + indexLine + .attr('transform', function(d) { return 'translate(' + dx(d.i) + ',0)' }) + .attr('height', availableHeight); + + // Setup Axes + if (showXAxis) { + xAxis + .scale(x) + ._ticks( nv.utils.calcTicksX(availableWidth/70, data) ) + .tickSize(-availableHeight, 0); + + g.select('.nv-x.nv-axis') + .attr('transform', 'translate(0,' + y.range()[0] + ')'); + g.select('.nv-x.nv-axis') + .call(xAxis); + } + + if (showYAxis) { + yAxis + .scale(y) + ._ticks( nv.utils.calcTicksY(availableHeight/36, data) ) + .tickSize( -availableWidth, 0); + + g.select('.nv-y.nv-axis') + .call(yAxis); + } + + //============================================================ + // Event Handling/Dispatching (in chart's scope) + //------------------------------------------------------------ + + function updateZero() { + indexLine + .data([index]); + + //When dragging the index line, turn off line transitions. + // Then turn them back on when done dragging. + var oldDuration = chart.duration(); + chart.duration(0); + chart.update(); + chart.duration(oldDuration); + } + + g.select('.nv-background rect') + .on('click', function() { + index.x = d3.mouse(this)[0]; + index.i = Math.round(dx.invert(index.x)); + + // update state and send stateChange with new index + state.index = index.i; + dispatch.stateChange(state); + + updateZero(); + }); + + lines.dispatch.on('elementClick', function(e) { + index.i = e.pointIndex; + index.x = dx(index.i); + + // update state and send stateChange with new index + state.index = index.i; + dispatch.stateChange(state); + + updateZero(); + }); + + controls.dispatch.on('legendClick', function(d,i) { + d.disabled = !d.disabled; + rescaleY = !d.disabled; + + state.rescaleY = rescaleY; + dispatch.stateChange(state); + chart.update(); + }); + + legend.dispatch.on('stateChange', function(newState) { + for (var key in newState) + state[key] = newState[key]; + dispatch.stateChange(state); + chart.update(); + }); + + interactiveLayer.dispatch.on('elementMousemove', function(e) { + lines.clearHighlights(); + var singlePoint, pointIndex, pointXLocation, allData = []; + + data + .filter(function(series, i) { + series.seriesIndex = i; + return !series.disabled; + }) + .forEach(function(series,i) { + pointIndex = nv.interactiveBisect(series.values, e.pointXValue, chart.x()); + lines.highlightPoint(i, pointIndex, true); + var point = series.values[pointIndex]; + if (typeof point === 'undefined') return; + if (typeof singlePoint === 'undefined') singlePoint = point; + if (typeof pointXLocation === 'undefined') pointXLocation = chart.xScale()(chart.x()(point,pointIndex)); + allData.push({ + key: series.key, + value: chart.y()(point, pointIndex), + color: color(series,series.seriesIndex) + }); + }); + + //Highlight the tooltip entry based on which point the mouse is closest to. + if (allData.length > 2) { + var yValue = chart.yScale().invert(e.mouseY); + var domainExtent = Math.abs(chart.yScale().domain()[0] - chart.yScale().domain()[1]); + var threshold = 0.03 * domainExtent; + var indexToHighlight = nv.nearestValueIndex(allData.map(function(d){return d.value}),yValue,threshold); + if (indexToHighlight !== null) + allData[indexToHighlight].highlight = true; + } + + var xValue = xAxis.tickFormat()(chart.x()(singlePoint,pointIndex), pointIndex); + interactiveLayer.tooltip + .position({left: pointXLocation + margin.left, top: e.mouseY + margin.top}) + .chartContainer(that.parentNode) + .valueFormatter(function(d,i) { + return yAxis.tickFormat()(d); + }) + .data( + { + value: xValue, + series: allData + } + )(); + + interactiveLayer.renderGuideLine(pointXLocation); + }); + + interactiveLayer.dispatch.on("elementMouseout",function(e) { + lines.clearHighlights(); + }); + + // Update chart from a state object passed to event handler + dispatch.on('changeState', function(e) { + if (typeof e.disabled !== 'undefined') { + data.forEach(function(series,i) { + series.disabled = e.disabled[i]; + }); + + state.disabled = e.disabled; + } + + if (typeof e.index !== 'undefined') { + index.i = e.index; + index.x = dx(index.i); + + state.index = e.index; + + indexLine + .data([index]); + } + + if (typeof e.rescaleY !== 'undefined') { + rescaleY = e.rescaleY; + } + + chart.update(); + }); + + }); + + renderWatch.renderEnd('cumulativeLineChart immediate'); + + return chart; + } + + //============================================================ + // Event Handling/Dispatching (out of chart's scope) + //------------------------------------------------------------ + + lines.dispatch.on('elementMouseover.tooltip', function(evt) { + var point = { + x: chart.x()(evt.point), + y: chart.y()(evt.point), + color: evt.point.color + }; + evt.point = point; + tooltip.data(evt).position(evt.pos).hidden(false); + }); + + lines.dispatch.on('elementMouseout.tooltip', function(evt) { + tooltip.hidden(true) + }); + + //============================================================ + // Functions + //------------------------------------------------------------ + + var indexifyYGetter = null; + /* Normalize the data according to an index point. */ + function indexify(idx, data) { + if (!indexifyYGetter) indexifyYGetter = lines.y(); + return data.map(function(line, i) { + if (!line.values) { + return line; + } + var indexValue = line.values[idx]; + if (indexValue == null) { + return line; + } + var v = indexifyYGetter(indexValue, idx); + + //TODO: implement check below, and disable series if series loses 100% or more cause divide by 0 issue + if (v < -.95 && !noErrorCheck) { + //if a series loses more than 100%, calculations fail.. anything close can cause major distortion (but is mathematically correct till it hits 100) + + line.tempDisabled = true; + return line; + } + + line.tempDisabled = false; + + line.values = line.values.map(function(point, pointIndex) { + point.display = {'y': (indexifyYGetter(point, pointIndex) - v) / (1 + v) }; + return point; + }); + + return line; + }) + } + + //============================================================ + // Expose Public Variables + //------------------------------------------------------------ + + // expose chart's sub-components + chart.dispatch = dispatch; + chart.lines = lines; + chart.legend = legend; + chart.controls = controls; + chart.xAxis = xAxis; + chart.yAxis = yAxis; + chart.interactiveLayer = interactiveLayer; + chart.state = state; + chart.tooltip = tooltip; + + chart.options = nv.utils.optionsFunc.bind(chart); + + chart._options = Object.create({}, { + // simple options, just get/set the necessary values + width: {get: function(){return width;}, set: function(_){width=_;}}, + height: {get: function(){return height;}, set: function(_){height=_;}}, + rescaleY: {get: function(){return rescaleY;}, set: function(_){rescaleY=_;}}, + showControls: {get: function(){return showControls;}, set: function(_){showControls=_;}}, + showLegend: {get: function(){return showLegend;}, set: function(_){showLegend=_;}}, + average: {get: function(){return average;}, set: function(_){average=_;}}, + defaultState: {get: function(){return defaultState;}, set: function(_){defaultState=_;}}, + noData: {get: function(){return noData;}, set: function(_){noData=_;}}, + showXAxis: {get: function(){return showXAxis;}, set: function(_){showXAxis=_;}}, + showYAxis: {get: function(){return showYAxis;}, set: function(_){showYAxis=_;}}, + noErrorCheck: {get: function(){return noErrorCheck;}, set: function(_){noErrorCheck=_;}}, + + // deprecated options + tooltips: {get: function(){return tooltip.enabled();}, set: function(_){ + // deprecated after 1.7.1 + nv.deprecated('tooltips', 'use chart.tooltip.enabled() instead'); + tooltip.enabled(!!_); + }}, + tooltipContent: {get: function(){return tooltip.contentGenerator();}, set: function(_){ + // deprecated after 1.7.1 + nv.deprecated('tooltipContent', 'use chart.tooltip.contentGenerator() instead'); + tooltip.contentGenerator(_); + }}, + + // options that require extra logic in the setter + margin: {get: function(){return margin;}, set: function(_){ + margin.top = _.top !== undefined ? _.top : margin.top; + margin.right = _.right !== undefined ? _.right : margin.right; + margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom; + margin.left = _.left !== undefined ? _.left : margin.left; + }}, + color: {get: function(){return color;}, set: function(_){ + color = nv.utils.getColor(_); + legend.color(color); + }}, + useInteractiveGuideline: {get: function(){return useInteractiveGuideline;}, set: function(_){ + useInteractiveGuideline = _; + if (_ === true) { + chart.interactive(false); + chart.useVoronoi(false); + } + }}, + rightAlignYAxis: {get: function(){return rightAlignYAxis;}, set: function(_){ + rightAlignYAxis = _; + yAxis.orient( (_) ? 'right' : 'left'); + }}, + duration: {get: function(){return duration;}, set: function(_){ + duration = _; + lines.duration(duration); + xAxis.duration(duration); + yAxis.duration(duration); + renderWatch.reset(duration); + }} + }); + + nv.utils.inheritOptions(chart, lines); + nv.utils.initOptions(chart); + + return chart; +}; +//TODO: consider deprecating by adding necessary features to multiBar model +nv.models.discreteBar = function() { + "use strict"; + + //============================================================ + // Public Variables with Default Settings + //------------------------------------------------------------ + + var margin = {top: 0, right: 0, bottom: 0, left: 0} + , width = 960 + , height = 500 + , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one + , container + , x = d3.scale.ordinal() + , y = d3.scale.linear() + , getX = function(d) { return d.x } + , getY = function(d) { return d.y } + , forceY = [0] // 0 is forced by default.. this makes sense for the majority of bar graphs... user can always do chart.forceY([]) to remove + , color = nv.utils.defaultColor() + , showValues = false + , valueFormat = d3.format(',.2f') + , xDomain + , yDomain + , xRange + , yRange + , dispatch = d3.dispatch('chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout', 'elementMousemove', 'renderEnd') + , rectClass = 'discreteBar' + , duration = 250 + ; + + //============================================================ + // Private Variables + //------------------------------------------------------------ + + var x0, y0; + var renderWatch = nv.utils.renderWatch(dispatch, duration); + + function chart(selection) { + renderWatch.reset(); + selection.each(function(data) { + var availableWidth = width - margin.left - margin.right, + availableHeight = height - margin.top - margin.bottom; + + container = d3.select(this); + nv.utils.initSVG(container); + + //add series index to each data point for reference + data.forEach(function(series, i) { + series.values.forEach(function(point) { + point.series = i; + }); + }); + + // Setup Scales + // remap and flatten the data for use in calculating the scales' domains + var seriesData = (xDomain && yDomain) ? [] : // if we know xDomain and yDomain, no need to calculate + data.map(function(d) { + return d.values.map(function(d,i) { + return { x: getX(d,i), y: getY(d,i), y0: d.y0 } + }) + }); + + x .domain(xDomain || d3.merge(seriesData).map(function(d) { return d.x })) + .rangeBands(xRange || [0, availableWidth], .1); + y .domain(yDomain || d3.extent(d3.merge(seriesData).map(function(d) { return d.y }).concat(forceY))); + + // If showValues, pad the Y axis range to account for label height + if (showValues) y.range(yRange || [availableHeight - (y.domain()[0] < 0 ? 12 : 0), y.domain()[1] > 0 ? 12 : 0]); + else y.range(yRange || [availableHeight, 0]); + + //store old scales if they exist + x0 = x0 || x; + y0 = y0 || y.copy().range([y(0),y(0)]); + + // Setup containers and skeleton of chart + var wrap = container.selectAll('g.nv-wrap.nv-discretebar').data([data]); + var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-discretebar'); + var gEnter = wrapEnter.append('g'); + var g = wrap.select('g'); + + gEnter.append('g').attr('class', 'nv-groups'); + wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); + + //TODO: by definition, the discrete bar should not have multiple groups, will modify/remove later + var groups = wrap.select('.nv-groups').selectAll('.nv-group') + .data(function(d) { return d }, function(d) { return d.key }); + groups.enter().append('g') + .style('stroke-opacity', 1e-6) + .style('fill-opacity', 1e-6); + groups.exit() + .watchTransition(renderWatch, 'discreteBar: exit groups') + .style('stroke-opacity', 1e-6) + .style('fill-opacity', 1e-6) + .remove(); + groups + .attr('class', function(d,i) { return 'nv-group nv-series-' + i }) + .classed('hover', function(d) { return d.hover }); + groups + .watchTransition(renderWatch, 'discreteBar: groups') + .style('stroke-opacity', 1) + .style('fill-opacity', .75); + + var bars = groups.selectAll('g.nv-bar') + .data(function(d) { return d.values }); + bars.exit().remove(); + + var barsEnter = bars.enter().append('g') + .attr('transform', function(d,i,j) { + return 'translate(' + (x(getX(d,i)) + x.rangeBand() * .05 ) + ', ' + y(0) + ')' + }) + .on('mouseover', function(d,i) { //TODO: figure out why j works above, but not here + d3.select(this).classed('hover', true); + dispatch.elementMouseover({ + data: d, + index: i, + color: d3.select(this).style("fill") + }); + }) + .on('mouseout', function(d,i) { + d3.select(this).classed('hover', false); + dispatch.elementMouseout({ + data: d, + index: i, + color: d3.select(this).style("fill") + }); + }) + .on('mousemove', function(d,i) { + dispatch.elementMousemove({ + data: d, + index: i, + color: d3.select(this).style("fill") + }); + }) + .on('click', function(d,i) { + dispatch.elementClick({ + data: d, + index: i, + color: d3.select(this).style("fill") + }); + d3.event.stopPropagation(); + }) + .on('dblclick', function(d,i) { + dispatch.elementDblClick({ + data: d, + index: i, + color: d3.select(this).style("fill") + }); + d3.event.stopPropagation(); + }); + + barsEnter.append('rect') + .attr('height', 0) + .attr('width', x.rangeBand() * .9 / data.length ) + + if (showValues) { + barsEnter.append('text') + .attr('text-anchor', 'middle') + ; + + bars.select('text') + .text(function(d,i) { return valueFormat(getY(d,i)) }) + .watchTransition(renderWatch, 'discreteBar: bars text') + .attr('x', x.rangeBand() * .9 / 2) + .attr('y', function(d,i) { return getY(d,i) < 0 ? y(getY(d,i)) - y(0) + 12 : -4 }) + + ; + } else { + bars.selectAll('text').remove(); + } + + bars + .attr('class', function(d,i) { return getY(d,i) < 0 ? 'nv-bar negative' : 'nv-bar positive' }) + .style('fill', function(d,i) { return d.color || color(d,i) }) + .style('stroke', function(d,i) { return d.color || color(d,i) }) + .select('rect') + .attr('class', rectClass) + .watchTransition(renderWatch, 'discreteBar: bars rect') + .attr('width', x.rangeBand() * .9 / data.length); + bars.watchTransition(renderWatch, 'discreteBar: bars') + //.delay(function(d,i) { return i * 1200 / data[0].values.length }) + .attr('transform', function(d,i) { + var left = x(getX(d,i)) + x.rangeBand() * .05, + top = getY(d,i) < 0 ? + y(0) : + y(0) - y(getY(d,i)) < 1 ? + y(0) - 1 : //make 1 px positive bars show up above y=0 + y(getY(d,i)); + + return 'translate(' + left + ', ' + top + ')' + }) + .select('rect') + .attr('height', function(d,i) { + return Math.max(Math.abs(y(getY(d,i)) - y((yDomain && yDomain[0]) || 0)) || 1) + }); + + + //store old scales for use in transitions on update + x0 = x.copy(); + y0 = y.copy(); + + }); + + renderWatch.renderEnd('discreteBar immediate'); + return chart; + } + + //============================================================ + // Expose Public Variables + //------------------------------------------------------------ + + chart.dispatch = dispatch; + chart.options = nv.utils.optionsFunc.bind(chart); + + chart._options = Object.create({}, { + // simple options, just get/set the necessary values + width: {get: function(){return width;}, set: function(_){width=_;}}, + height: {get: function(){return height;}, set: function(_){height=_;}}, + forceY: {get: function(){return forceY;}, set: function(_){forceY=_;}}, + showValues: {get: function(){return showValues;}, set: function(_){showValues=_;}}, + x: {get: function(){return getX;}, set: function(_){getX=_;}}, + y: {get: function(){return getY;}, set: function(_){getY=_;}}, + xScale: {get: function(){return x;}, set: function(_){x=_;}}, + yScale: {get: function(){return y;}, set: function(_){y=_;}}, + xDomain: {get: function(){return xDomain;}, set: function(_){xDomain=_;}}, + yDomain: {get: function(){return yDomain;}, set: function(_){yDomain=_;}}, + xRange: {get: function(){return xRange;}, set: function(_){xRange=_;}}, + yRange: {get: function(){return yRange;}, set: function(_){yRange=_;}}, + valueFormat: {get: function(){return valueFormat;}, set: function(_){valueFormat=_;}}, + id: {get: function(){return id;}, set: function(_){id=_;}}, + rectClass: {get: function(){return rectClass;}, set: function(_){rectClass=_;}}, + + // options that require extra logic in the setter + margin: {get: function(){return margin;}, set: function(_){ + margin.top = _.top !== undefined ? _.top : margin.top; + margin.right = _.right !== undefined ? _.right : margin.right; + margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom; + margin.left = _.left !== undefined ? _.left : margin.left; + }}, + color: {get: function(){return color;}, set: function(_){ + color = nv.utils.getColor(_); + }}, + duration: {get: function(){return duration;}, set: function(_){ + duration = _; + renderWatch.reset(duration); + }} + }); + + nv.utils.initOptions(chart); + + return chart; +}; + +nv.models.discreteBarChart = function() { + "use strict"; + + //============================================================ + // Public Variables with Default Settings + //------------------------------------------------------------ + + var discretebar = nv.models.discreteBar() + , xAxis = nv.models.axis() + , yAxis = nv.models.axis() + , tooltip = nv.models.tooltip() + ; + + var margin = {top: 15, right: 10, bottom: 50, left: 60} + , width = null + , height = null + , color = nv.utils.getColor() + , showXAxis = true + , showYAxis = true + , rightAlignYAxis = false + , staggerLabels = false + , x + , y + , noData = null + , dispatch = d3.dispatch('beforeUpdate','renderEnd') + , duration = 250 + ; + + xAxis + .orient('bottom') + .showMaxMin(false) + .tickFormat(function(d) { return d }) + ; + yAxis + .orient((rightAlignYAxis) ? 'right' : 'left') + .tickFormat(d3.format(',.1f')) + ; + + tooltip + .duration(0) + .headerEnabled(false) + .valueFormatter(function(d, i) { + return yAxis.tickFormat()(d, i); + }) + .keyFormatter(function(d, i) { + return xAxis.tickFormat()(d, i); + }); + + //============================================================ + // Private Variables + //------------------------------------------------------------ + + var renderWatch = nv.utils.renderWatch(dispatch, duration); + + function chart(selection) { + renderWatch.reset(); + renderWatch.models(discretebar); + if (showXAxis) renderWatch.models(xAxis); + if (showYAxis) renderWatch.models(yAxis); + + selection.each(function(data) { + var container = d3.select(this), + that = this; + nv.utils.initSVG(container); + var availableWidth = nv.utils.availableWidth(width, container, margin), + availableHeight = nv.utils.availableHeight(height, container, margin); + + chart.update = function() { + dispatch.beforeUpdate(); + container.transition().duration(duration).call(chart); + }; + chart.container = this; + + // Display No Data message if there's nothing to show. + if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) { + nv.utils.noData(chart, container); + return chart; + } else { + container.selectAll('.nv-noData').remove(); + } + + // Setup Scales + x = discretebar.xScale(); + y = discretebar.yScale().clamp(true); + + // Setup containers and skeleton of chart + var wrap = container.selectAll('g.nv-wrap.nv-discreteBarWithAxes').data([data]); + var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-discreteBarWithAxes').append('g'); + var defsEnter = gEnter.append('defs'); + var g = wrap.select('g'); + + gEnter.append('g').attr('class', 'nv-x nv-axis'); + gEnter.append('g').attr('class', 'nv-y nv-axis') + .append('g').attr('class', 'nv-zeroLine') + .append('line'); + + gEnter.append('g').attr('class', 'nv-barsWrap'); + + g.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); + + if (rightAlignYAxis) { + g.select(".nv-y.nv-axis") + .attr("transform", "translate(" + availableWidth + ",0)"); + } + + // Main Chart Component(s) + discretebar + .width(availableWidth) + .height(availableHeight); + + var barsWrap = g.select('.nv-barsWrap') + .datum(data.filter(function(d) { return !d.disabled })); + + barsWrap.transition().call(discretebar); + + + defsEnter.append('clipPath') + .attr('id', 'nv-x-label-clip-' + discretebar.id()) + .append('rect'); + + g.select('#nv-x-label-clip-' + discretebar.id() + ' rect') + .attr('width', x.rangeBand() * (staggerLabels ? 2 : 1)) + .attr('height', 16) + .attr('x', -x.rangeBand() / (staggerLabels ? 1 : 2 )); + + // Setup Axes + if (showXAxis) { + xAxis + .scale(x) + ._ticks( nv.utils.calcTicksX(availableWidth/100, data) ) + .tickSize(-availableHeight, 0); + + g.select('.nv-x.nv-axis') + .attr('transform', 'translate(0,' + (y.range()[0] + ((discretebar.showValues() && y.domain()[0] < 0) ? 16 : 0)) + ')'); + g.select('.nv-x.nv-axis').call(xAxis); + + var xTicks = g.select('.nv-x.nv-axis').selectAll('g'); + if (staggerLabels) { + xTicks + .selectAll('text') + .attr('transform', function(d,i,j) { return 'translate(0,' + (j % 2 == 0 ? '5' : '17') + ')' }) + } + } + + if (showYAxis) { + yAxis + .scale(y) + ._ticks( nv.utils.calcTicksY(availableHeight/36, data) ) + .tickSize( -availableWidth, 0); + + g.select('.nv-y.nv-axis').call(yAxis); + } + + // Zero line + g.select(".nv-zeroLine line") + .attr("x1",0) + .attr("x2",availableWidth) + .attr("y1", y(0)) + .attr("y2", y(0)) + ; + }); + + renderWatch.renderEnd('discreteBar chart immediate'); + return chart; + } + + //============================================================ + // Event Handling/Dispatching (out of chart's scope) + //------------------------------------------------------------ + + discretebar.dispatch.on('elementMouseover.tooltip', function(evt) { + evt['series'] = { + key: chart.x()(evt.data), + value: chart.y()(evt.data), + color: evt.color + }; + tooltip.data(evt).hidden(false); + }); + + discretebar.dispatch.on('elementMouseout.tooltip', function(evt) { + tooltip.hidden(true); + }); + + discretebar.dispatch.on('elementMousemove.tooltip', function(evt) { + tooltip.position({top: d3.event.pageY, left: d3.event.pageX})(); + }); + + //============================================================ + // Expose Public Variables + //------------------------------------------------------------ + + chart.dispatch = dispatch; + chart.discretebar = discretebar; + chart.xAxis = xAxis; + chart.yAxis = yAxis; + chart.tooltip = tooltip; + + chart.options = nv.utils.optionsFunc.bind(chart); + + chart._options = Object.create({}, { + // simple options, just get/set the necessary values + width: {get: function(){return width;}, set: function(_){width=_;}}, + height: {get: function(){return height;}, set: function(_){height=_;}}, + staggerLabels: {get: function(){return staggerLabels;}, set: function(_){staggerLabels=_;}}, + showXAxis: {get: function(){return showXAxis;}, set: function(_){showXAxis=_;}}, + showYAxis: {get: function(){return showYAxis;}, set: function(_){showYAxis=_;}}, + noData: {get: function(){return noData;}, set: function(_){noData=_;}}, + + // deprecated options + tooltips: {get: function(){return tooltip.enabled();}, set: function(_){ + // deprecated after 1.7.1 + nv.deprecated('tooltips', 'use chart.tooltip.enabled() instead'); + tooltip.enabled(!!_); + }}, + tooltipContent: {get: function(){return tooltip.contentGenerator();}, set: function(_){ + // deprecated after 1.7.1 + nv.deprecated('tooltipContent', 'use chart.tooltip.contentGenerator() instead'); + tooltip.contentGenerator(_); + }}, + + // options that require extra logic in the setter + margin: {get: function(){return margin;}, set: function(_){ + margin.top = _.top !== undefined ? _.top : margin.top; + margin.right = _.right !== undefined ? _.right : margin.right; + margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom; + margin.left = _.left !== undefined ? _.left : margin.left; + }}, + duration: {get: function(){return duration;}, set: function(_){ + duration = _; + renderWatch.reset(duration); + discretebar.duration(duration); + xAxis.duration(duration); + yAxis.duration(duration); + }}, + color: {get: function(){return color;}, set: function(_){ + color = nv.utils.getColor(_); + discretebar.color(color); + }}, + rightAlignYAxis: {get: function(){return rightAlignYAxis;}, set: function(_){ + rightAlignYAxis = _; + yAxis.orient( (_) ? 'right' : 'left'); + }} + }); + + nv.utils.inheritOptions(chart, discretebar); + nv.utils.initOptions(chart); + + return chart; +} + +nv.models.distribution = function() { + "use strict"; + //============================================================ + // Public Variables with Default Settings + //------------------------------------------------------------ + + var margin = {top: 0, right: 0, bottom: 0, left: 0} + , width = 400 //technically width or height depending on x or y.... + , size = 8 + , axis = 'x' // 'x' or 'y'... horizontal or vertical + , getData = function(d) { return d[axis] } // defaults d.x or d.y + , color = nv.utils.defaultColor() + , scale = d3.scale.linear() + , domain + , duration = 250 + , dispatch = d3.dispatch('renderEnd') + ; + + //============================================================ + + + //============================================================ + // Private Variables + //------------------------------------------------------------ + + var scale0; + var renderWatch = nv.utils.renderWatch(dispatch, duration); + + //============================================================ + + + function chart(selection) { + renderWatch.reset(); + selection.each(function(data) { + var availableLength = width - (axis === 'x' ? margin.left + margin.right : margin.top + margin.bottom), + naxis = axis == 'x' ? 'y' : 'x', + container = d3.select(this); + nv.utils.initSVG(container); + + //------------------------------------------------------------ + // Setup Scales + + scale0 = scale0 || scale; + + //------------------------------------------------------------ + + + //------------------------------------------------------------ + // Setup containers and skeleton of chart + + var wrap = container.selectAll('g.nv-distribution').data([data]); + var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-distribution'); + var gEnter = wrapEnter.append('g'); + var g = wrap.select('g'); + + wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')') + + //------------------------------------------------------------ + + + var distWrap = g.selectAll('g.nv-dist') + .data(function(d) { return d }, function(d) { return d.key }); + + distWrap.enter().append('g'); + distWrap + .attr('class', function(d,i) { return 'nv-dist nv-series-' + i }) + .style('stroke', function(d,i) { return color(d, i) }); + + var dist = distWrap.selectAll('line.nv-dist' + axis) + .data(function(d) { return d.values }) + dist.enter().append('line') + .attr(axis + '1', function(d,i) { return scale0(getData(d,i)) }) + .attr(axis + '2', function(d,i) { return scale0(getData(d,i)) }) + renderWatch.transition(distWrap.exit().selectAll('line.nv-dist' + axis), 'dist exit') + // .transition() + .attr(axis + '1', function(d,i) { return scale(getData(d,i)) }) + .attr(axis + '2', function(d,i) { return scale(getData(d,i)) }) + .style('stroke-opacity', 0) + .remove(); + dist + .attr('class', function(d,i) { return 'nv-dist' + axis + ' nv-dist' + axis + '-' + i }) + .attr(naxis + '1', 0) + .attr(naxis + '2', size); + renderWatch.transition(dist, 'dist') + // .transition() + .attr(axis + '1', function(d,i) { return scale(getData(d,i)) }) + .attr(axis + '2', function(d,i) { return scale(getData(d,i)) }) + + + scale0 = scale.copy(); + + }); + renderWatch.renderEnd('distribution immediate'); + return chart; + } + + + //============================================================ + // Expose Public Variables + //------------------------------------------------------------ + chart.options = nv.utils.optionsFunc.bind(chart); + chart.dispatch = dispatch; + + chart.margin = function(_) { + if (!arguments.length) return margin; + margin.top = typeof _.top != 'undefined' ? _.top : margin.top; + margin.right = typeof _.right != 'undefined' ? _.right : margin.right; + margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom; + margin.left = typeof _.left != 'undefined' ? _.left : margin.left; + return chart; + }; + + chart.width = function(_) { + if (!arguments.length) return width; + width = _; + return chart; + }; + + chart.axis = function(_) { + if (!arguments.length) return axis; + axis = _; + return chart; + }; + + chart.size = function(_) { + if (!arguments.length) return size; + size = _; + return chart; + }; + + chart.getData = function(_) { + if (!arguments.length) return getData; + getData = d3.functor(_); + return chart; + }; + + chart.scale = function(_) { + if (!arguments.length) return scale; + scale = _; + return chart; + }; + + chart.color = function(_) { + if (!arguments.length) return color; + color = nv.utils.getColor(_); + return chart; + }; + + chart.duration = function(_) { + if (!arguments.length) return duration; + duration = _; + renderWatch.reset(duration); + return chart; + }; + //============================================================ + + + return chart; +} +nv.models.furiousLegend = function() { + "use strict"; + + //============================================================ + // Public Variables with Default Settings + //------------------------------------------------------------ + + var margin = {top: 5, right: 0, bottom: 5, left: 0} + , width = 400 + , height = 20 + , getKey = function(d) { return d.key } + , color = nv.utils.getColor() + , align = true + , padding = 28 //define how much space between legend items. - recommend 32 for furious version + , rightAlign = true + , updateState = true //If true, legend will update data.disabled and trigger a 'stateChange' dispatch. + , radioButtonMode = false //If true, clicking legend items will cause it to behave like a radio button. (only one can be selected at a time) + , expanded = false + , dispatch = d3.dispatch('legendClick', 'legendDblclick', 'legendMouseover', 'legendMouseout', 'stateChange') + , vers = 'classic' //Options are "classic" and "furious" + ; + + function chart(selection) { + selection.each(function(data) { + var availableWidth = width - margin.left - margin.right, + container = d3.select(this); + nv.utils.initSVG(container); + + // Setup containers and skeleton of chart + var wrap = container.selectAll('g.nv-legend').data([data]); + var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-legend').append('g'); + var g = wrap.select('g'); + + wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); + + var series = g.selectAll('.nv-series') + .data(function(d) { + if(vers != 'furious') return d; + + return d.filter(function(n) { + return expanded ? true : !n.disengaged; + }); + }); + var seriesEnter = series.enter().append('g').attr('class', 'nv-series') + + var seriesShape; + + if(vers == 'classic') { + seriesEnter.append('circle') + .style('stroke-width', 2) + .attr('class','nv-legend-symbol') + .attr('r', 5); + + seriesShape = series.select('circle'); + } else if (vers == 'furious') { + seriesEnter.append('rect') + .style('stroke-width', 2) + .attr('class','nv-legend-symbol') + .attr('rx', 3) + .attr('ry', 3); + + seriesShape = series.select('rect'); + + seriesEnter.append('g') + .attr('class', 'nv-check-box') + .property('innerHTML','<path d="M0.5,5 L22.5,5 L22.5,26.5 L0.5,26.5 L0.5,5 Z" class="nv-box"></path><path d="M5.5,12.8618467 L11.9185089,19.2803556 L31,0.198864511" class="nv-check"></path>') + .attr('transform', 'translate(-10,-8)scale(0.5)'); + + var seriesCheckbox = series.select('.nv-check-box'); + + seriesCheckbox.each(function(d,i) { + d3.select(this).selectAll('path') + .attr('stroke', setTextColor(d,i)); + }); + } + + seriesEnter.append('text') + .attr('text-anchor', 'start') + .attr('class','nv-legend-text') + .attr('dy', '.32em') + .attr('dx', '8'); + + var seriesText = series.select('text.nv-legend-text'); + + series + .on('mouseover', function(d,i) { + dispatch.legendMouseover(d,i); //TODO: Make consistent with other event objects + }) + .on('mouseout', function(d,i) { + dispatch.legendMouseout(d,i); + }) + .on('click', function(d,i) { + dispatch.legendClick(d,i); + // make sure we re-get data in case it was modified + var data = series.data(); + if (updateState) { + if(vers =='classic') { + if (radioButtonMode) { + //Radio button mode: set every series to disabled, + // and enable the clicked series. + data.forEach(function(series) { series.disabled = true}); + d.disabled = false; + } + else { + d.disabled = !d.disabled; + if (data.every(function(series) { return series.disabled})) { + //the default behavior of NVD3 legends is, if every single series + // is disabled, turn all series' back on. + data.forEach(function(series) { series.disabled = false}); + } + } + } else if(vers == 'furious') { + if(expanded) { + d.disengaged = !d.disengaged; + d.userDisabled = d.userDisabled == undefined ? !!d.disabled : d.userDisabled; + d.disabled = d.disengaged || d.userDisabled; + } else if (!expanded) { + d.disabled = !d.disabled; + d.userDisabled = d.disabled; + var engaged = data.filter(function(d) { return !d.disengaged; }); + if (engaged.every(function(series) { return series.userDisabled })) { + //the default behavior of NVD3 legends is, if every single series + // is disabled, turn all series' back on. + data.forEach(function(series) { + series.disabled = series.userDisabled = false; + }); + } + } + } + dispatch.stateChange({ + disabled: data.map(function(d) { return !!d.disabled }), + disengaged: data.map(function(d) { return !!d.disengaged }) + }); + + } + }) + .on('dblclick', function(d,i) { + if(vers == 'furious' && expanded) return; + dispatch.legendDblclick(d,i); + if (updateState) { + // make sure we re-get data in case it was modified + var data = series.data(); + //the default behavior of NVD3 legends, when double clicking one, + // is to set all other series' to false, and make the double clicked series enabled. + data.forEach(function(series) { + series.disabled = true; + if(vers == 'furious') series.userDisabled = series.disabled; + }); + d.disabled = false; + if(vers == 'furious') d.userDisabled = d.disabled; + dispatch.stateChange({ + disabled: data.map(function(d) { return !!d.disabled }) + }); + } + }); + + series.classed('nv-disabled', function(d) { return d.userDisabled }); + series.exit().remove(); + + seriesText + .attr('fill', setTextColor) + .text(getKey); + + //TODO: implement fixed-width and max-width options (max-width is especially useful with the align option) + // NEW ALIGNING CODE, TODO: clean up + + var versPadding; + switch(vers) { + case 'furious' : + versPadding = 23; + break; + case 'classic' : + versPadding = 20; + } + + if (align) { + + var seriesWidths = []; + series.each(function(d,i) { + var legendText = d3.select(this).select('text'); + var nodeTextLength; + try { + nodeTextLength = legendText.node().getComputedTextLength(); + // If the legendText is display:none'd (nodeTextLength == 0), simulate an error so we approximate, instead + if(nodeTextLength <= 0) throw Error(); + } + catch(e) { + nodeTextLength = nv.utils.calcApproxTextWidth(legendText); + } + + seriesWidths.push(nodeTextLength + padding); + }); + + var seriesPerRow = 0; + var legendWidth = 0; + var columnWidths = []; + + while ( legendWidth < availableWidth && seriesPerRow < seriesWidths.length) { + columnWidths[seriesPerRow] = seriesWidths[seriesPerRow]; + legendWidth += seriesWidths[seriesPerRow++]; + } + if (seriesPerRow === 0) seriesPerRow = 1; //minimum of one series per row + + while ( legendWidth > availableWidth && seriesPerRow > 1 ) { + columnWidths = []; + seriesPerRow--; + + for (var k = 0; k < seriesWidths.length; k++) { + if (seriesWidths[k] > (columnWidths[k % seriesPerRow] || 0) ) + columnWidths[k % seriesPerRow] = seriesWidths[k]; + } + + legendWidth = columnWidths.reduce(function(prev, cur, index, array) { + return prev + cur; + }); + } + + var xPositions = []; + for (var i = 0, curX = 0; i < seriesPerRow; i++) { + xPositions[i] = curX; + curX += columnWidths[i]; + } + + series + .attr('transform', function(d, i) { + return 'translate(' + xPositions[i % seriesPerRow] + ',' + (5 + Math.floor(i / seriesPerRow) * versPadding) + ')'; + }); + + //position legend as far right as possible within the total width + if (rightAlign) { + g.attr('transform', 'translate(' + (width - margin.right - legendWidth) + ',' + margin.top + ')'); + } + else { + g.attr('transform', 'translate(0' + ',' + margin.top + ')'); + } + + height = margin.top + margin.bottom + (Math.ceil(seriesWidths.length / seriesPerRow) * versPadding); + + } else { + + var ypos = 5, + newxpos = 5, + maxwidth = 0, + xpos; + series + .attr('transform', function(d, i) { + var length = d3.select(this).select('text').node().getComputedTextLength() + padding; + xpos = newxpos; + + if (width < margin.left + margin.right + xpos + length) { + newxpos = xpos = 5; + ypos += versPadding; + } + + newxpos += length; + if (newxpos > maxwidth) maxwidth = newxpos; + + return 'translate(' + xpos + ',' + ypos + ')'; + }); + + //position legend as far right as possible within the total width + g.attr('transform', 'translate(' + (width - margin.right - maxwidth) + ',' + margin.top + ')'); + + height = margin.top + margin.bottom + ypos + 15; + } + + if(vers == 'furious') { + // Size rectangles after text is placed + seriesShape + .attr('width', function(d,i) { + return seriesText[0][i].getComputedTextLength() + 27; + }) + .attr('height', 18) + .attr('y', -9) + .attr('x', -15) + } + + seriesShape + .style('fill', setBGColor) + .style('stroke', function(d,i) { return d.color || color(d, i) }); + }); + + function setTextColor(d,i) { + if(vers != 'furious') return '#000'; + if(expanded) { + return d.disengaged ? color(d,i) : '#fff'; + } else if (!expanded) { + return !!d.disabled ? color(d,i) : '#fff'; + } + } + + function setBGColor(d,i) { + if(expanded && vers == 'furious') { + return d.disengaged ? '#fff' : color(d,i); + } else { + return !!d.disabled ? '#fff' : color(d,i); + } + } + + return chart; + } + + //============================================================ + // Expose Public Variables + //------------------------------------------------------------ + + chart.dispatch = dispatch; + chart.options = nv.utils.optionsFunc.bind(chart); + + chart._options = Object.create({}, { + // simple options, just get/set the necessary values + width: {get: function(){return width;}, set: function(_){width=_;}}, + height: {get: function(){return height;}, set: function(_){height=_;}}, + key: {get: function(){return getKey;}, set: function(_){getKey=_;}}, + align: {get: function(){return align;}, set: function(_){align=_;}}, + rightAlign: {get: function(){return rightAlign;}, set: function(_){rightAlign=_;}}, + padding: {get: function(){return padding;}, set: function(_){padding=_;}}, + updateState: {get: function(){return updateState;}, set: function(_){updateState=_;}}, + radioButtonMode: {get: function(){return radioButtonMode;}, set: function(_){radioButtonMode=_;}}, + expanded: {get: function(){return expanded;}, set: function(_){expanded=_;}}, + vers: {get: function(){return vers;}, set: function(_){vers=_;}}, + + // options that require extra logic in the setter + margin: {get: function(){return margin;}, set: function(_){ + margin.top = _.top !== undefined ? _.top : margin.top; + margin.right = _.right !== undefined ? _.right : margin.right; + margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom; + margin.left = _.left !== undefined ? _.left : margin.left; + }}, + color: {get: function(){return color;}, set: function(_){ + color = nv.utils.getColor(_); + }} + }); + + nv.utils.initOptions(chart); + + return chart; +}; +//TODO: consider deprecating and using multibar with single series for this +nv.models.historicalBar = function() { + "use strict"; + + //============================================================ + // Public Variables with Default Settings + //------------------------------------------------------------ + + var margin = {top: 0, right: 0, bottom: 0, left: 0} + , width = null + , height = null + , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one + , container = null + , x = d3.scale.linear() + , y = d3.scale.linear() + , getX = function(d) { return d.x } + , getY = function(d) { return d.y } + , forceX = [] + , forceY = [0] + , padData = false + , clipEdge = true + , color = nv.utils.defaultColor() + , xDomain + , yDomain + , xRange + , yRange + , dispatch = d3.dispatch('chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout', 'elementMousemove', 'renderEnd') + , interactive = true + ; + + var renderWatch = nv.utils.renderWatch(dispatch, 0); + + function chart(selection) { + selection.each(function(data) { + renderWatch.reset(); + + container = d3.select(this); + var availableWidth = nv.utils.availableWidth(width, container, margin), + availableHeight = nv.utils.availableHeight(height, container, margin); + + nv.utils.initSVG(container); + + // Setup Scales + x.domain(xDomain || d3.extent(data[0].values.map(getX).concat(forceX) )); + + if (padData) + x.range(xRange || [availableWidth * .5 / data[0].values.length, availableWidth * (data[0].values.length - .5) / data[0].values.length ]); + else + x.range(xRange || [0, availableWidth]); + + y.domain(yDomain || d3.extent(data[0].values.map(getY).concat(forceY) )) + .range(yRange || [availableHeight, 0]); + + // If scale's domain don't have a range, slightly adjust to make one... so a chart can show a single data point + if (x.domain()[0] === x.domain()[1]) + x.domain()[0] ? + x.domain([x.domain()[0] - x.domain()[0] * 0.01, x.domain()[1] + x.domain()[1] * 0.01]) + : x.domain([-1,1]); + + if (y.domain()[0] === y.domain()[1]) + y.domain()[0] ? + y.domain([y.domain()[0] + y.domain()[0] * 0.01, y.domain()[1] - y.domain()[1] * 0.01]) + : y.domain([-1,1]); + + // Setup containers and skeleton of chart + var wrap = container.selectAll('g.nv-wrap.nv-historicalBar-' + id).data([data[0].values]); + var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-historicalBar-' + id); + var defsEnter = wrapEnter.append('defs'); + var gEnter = wrapEnter.append('g'); + var g = wrap.select('g'); + + gEnter.append('g').attr('class', 'nv-bars'); + wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); + + container + .on('click', function(d,i) { + dispatch.chartClick({ + data: d, + index: i, + pos: d3.event, + id: id + }); + }); + + defsEnter.append('clipPath') + .attr('id', 'nv-chart-clip-path-' + id) + .append('rect'); + + wrap.select('#nv-chart-clip-path-' + id + ' rect') + .attr('width', availableWidth) + .attr('height', availableHeight); + + g.attr('clip-path', clipEdge ? 'url(#nv-chart-clip-path-' + id + ')' : ''); + + var bars = wrap.select('.nv-bars').selectAll('.nv-bar') + .data(function(d) { return d }, function(d,i) {return getX(d,i)}); + bars.exit().remove(); + + bars.enter().append('rect') + .attr('x', 0 ) + .attr('y', function(d,i) { return nv.utils.NaNtoZero(y(Math.max(0, getY(d,i)))) }) + .attr('height', function(d,i) { return nv.utils.NaNtoZero(Math.abs(y(getY(d,i)) - y(0))) }) + .attr('transform', function(d,i) { return 'translate(' + (x(getX(d,i)) - availableWidth / data[0].values.length * .45) + ',0)'; }) + .on('mouseover', function(d,i) { + if (!interactive) return; + d3.select(this).classed('hover', true); + dispatch.elementMouseover({ + data: d, + index: i, + color: d3.select(this).style("fill") + }); + + }) + .on('mouseout', function(d,i) { + if (!interactive) return; + d3.select(this).classed('hover', false); + dispatch.elementMouseout({ + data: d, + index: i, + color: d3.select(this).style("fill") + }); + }) + .on('mousemove', function(d,i) { + if (!interactive) return; + dispatch.elementMousemove({ + data: d, + index: i, + color: d3.select(this).style("fill") + }); + }) + .on('click', function(d,i) { + if (!interactive) return; + dispatch.elementClick({ + data: d, + index: i, + color: d3.select(this).style("fill") + }); + d3.event.stopPropagation(); + }) + .on('dblclick', function(d,i) { + if (!interactive) return; + dispatch.elementDblClick({ + data: d, + index: i, + color: d3.select(this).style("fill") + }); + d3.event.stopPropagation(); + }); + + bars + .attr('fill', function(d,i) { return color(d, i); }) + .attr('class', function(d,i,j) { return (getY(d,i) < 0 ? 'nv-bar negative' : 'nv-bar positive') + ' nv-bar-' + j + '-' + i }) + .watchTransition(renderWatch, 'bars') + .attr('transform', function(d,i) { return 'translate(' + (x(getX(d,i)) - availableWidth / data[0].values.length * .45) + ',0)'; }) + //TODO: better width calculations that don't assume always uniform data spacing;w + .attr('width', (availableWidth / data[0].values.length) * .9 ); + + bars.watchTransition(renderWatch, 'bars') + .attr('y', function(d,i) { + var rval = getY(d,i) < 0 ? + y(0) : + y(0) - y(getY(d,i)) < 1 ? + y(0) - 1 : + y(getY(d,i)); + return nv.utils.NaNtoZero(rval); + }) + .attr('height', function(d,i) { return nv.utils.NaNtoZero(Math.max(Math.abs(y(getY(d,i)) - y(0)),1)) }); + + }); + + renderWatch.renderEnd('historicalBar immediate'); + return chart; + } + + //Create methods to allow outside functions to highlight a specific bar. + chart.highlightPoint = function(pointIndex, isHoverOver) { + container + .select(".nv-bars .nv-bar-0-" + pointIndex) + .classed("hover", isHoverOver) + ; + }; + + chart.clearHighlights = function() { + container + .select(".nv-bars .nv-bar.hover") + .classed("hover", false) + ; + }; + + //============================================================ + // Expose Public Variables + //------------------------------------------------------------ + + chart.dispatch = dispatch; + chart.options = nv.utils.optionsFunc.bind(chart); + + chart._options = Object.create({}, { + // simple options, just get/set the necessary values + width: {get: function(){return width;}, set: function(_){width=_;}}, + height: {get: function(){return height;}, set: function(_){height=_;}}, + forceX: {get: function(){return forceX;}, set: function(_){forceX=_;}}, + forceY: {get: function(){return forceY;}, set: function(_){forceY=_;}}, + padData: {get: function(){return padData;}, set: function(_){padData=_;}}, + x: {get: function(){return getX;}, set: function(_){getX=_;}}, + y: {get: function(){return getY;}, set: function(_){getY=_;}}, + xScale: {get: function(){return x;}, set: function(_){x=_;}}, + yScale: {get: function(){return y;}, set: function(_){y=_;}}, + xDomain: {get: function(){return xDomain;}, set: function(_){xDomain=_;}}, + yDomain: {get: function(){return yDomain;}, set: function(_){yDomain=_;}}, + xRange: {get: function(){return xRange;}, set: function(_){xRange=_;}}, + yRange: {get: function(){return yRange;}, set: function(_){yRange=_;}}, + clipEdge: {get: function(){return clipEdge;}, set: function(_){clipEdge=_;}}, + id: {get: function(){return id;}, set: function(_){id=_;}}, + interactive: {get: function(){return interactive;}, set: function(_){interactive=_;}}, + + // options that require extra logic in the setter + margin: {get: function(){return margin;}, set: function(_){ + margin.top = _.top !== undefined ? _.top : margin.top; + margin.right = _.right !== undefined ? _.right : margin.right; + margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom; + margin.left = _.left !== undefined ? _.left : margin.left; + }}, + color: {get: function(){return color;}, set: function(_){ + color = nv.utils.getColor(_); + }} + }); + + nv.utils.initOptions(chart); + + return chart; +}; + +nv.models.historicalBarChart = function(bar_model) { + "use strict"; + + //============================================================ + // Public Variables with Default Settings + //------------------------------------------------------------ + + var bars = bar_model || nv.models.historicalBar() + , xAxis = nv.models.axis() + , yAxis = nv.models.axis() + , legend = nv.models.legend() + , interactiveLayer = nv.interactiveGuideline() + , tooltip = nv.models.tooltip() + ; + + + var margin = {top: 30, right: 90, bottom: 50, left: 90} + , color = nv.utils.defaultColor() + , width = null + , height = null + , showLegend = false + , showXAxis = true + , showYAxis = true + , rightAlignYAxis = false + , useInteractiveGuideline = false + , x + , y + , state = {} + , defaultState = null + , noData = null + , dispatch = d3.dispatch('tooltipHide', 'stateChange', 'changeState', 'renderEnd') + , transitionDuration = 250 + ; + + xAxis.orient('bottom').tickPadding(7); + yAxis.orient( (rightAlignYAxis) ? 'right' : 'left'); + tooltip + .duration(0) + .headerEnabled(false) + .valueFormatter(function(d, i) { + return yAxis.tickFormat()(d, i); + }) + .headerFormatter(function(d, i) { + return xAxis.tickFormat()(d, i); + }); + + + //============================================================ + // Private Variables + //------------------------------------------------------------ + + var renderWatch = nv.utils.renderWatch(dispatch, 0); + + function chart(selection) { + selection.each(function(data) { + renderWatch.reset(); + renderWatch.models(bars); + if (showXAxis) renderWatch.models(xAxis); + if (showYAxis) renderWatch.models(yAxis); + + var container = d3.select(this), + that = this; + nv.utils.initSVG(container); + var availableWidth = nv.utils.availableWidth(width, container, margin), + availableHeight = nv.utils.availableHeight(height, container, margin); + + chart.update = function() { container.transition().duration(transitionDuration).call(chart) }; + chart.container = this; + + //set state.disabled + state.disabled = data.map(function(d) { return !!d.disabled }); + + if (!defaultState) { + var key; + defaultState = {}; + for (key in state) { + if (state[key] instanceof Array) + defaultState[key] = state[key].slice(0); + else + defaultState[key] = state[key]; + } + } + + // Display noData message if there's nothing to show. + if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) { + nv.utils.noData(chart, container) + return chart; + } else { + container.selectAll('.nv-noData').remove(); + } + + // Setup Scales + x = bars.xScale(); + y = bars.yScale(); + + // Setup containers and skeleton of chart + var wrap = container.selectAll('g.nv-wrap.nv-historicalBarChart').data([data]); + var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-historicalBarChart').append('g'); + var g = wrap.select('g'); + + gEnter.append('g').attr('class', 'nv-x nv-axis'); + gEnter.append('g').attr('class', 'nv-y nv-axis'); + gEnter.append('g').attr('class', 'nv-barsWrap'); + gEnter.append('g').attr('class', 'nv-legendWrap'); + gEnter.append('g').attr('class', 'nv-interactive'); + + // Legend + if (showLegend) { + legend.width(availableWidth); + + g.select('.nv-legendWrap') + .datum(data) + .call(legend); + + if ( margin.top != legend.height()) { + margin.top = legend.height(); + availableHeight = nv.utils.availableHeight(height, container, margin); + } + + wrap.select('.nv-legendWrap') + .attr('transform', 'translate(0,' + (-margin.top) +')') + } + wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); + + if (rightAlignYAxis) { + g.select(".nv-y.nv-axis") + .attr("transform", "translate(" + availableWidth + ",0)"); + } + + //Set up interactive layer + if (useInteractiveGuideline) { + interactiveLayer + .width(availableWidth) + .height(availableHeight) + .margin({left:margin.left, top:margin.top}) + .svgContainer(container) + .xScale(x); + wrap.select(".nv-interactive").call(interactiveLayer); + } + bars + .width(availableWidth) + .height(availableHeight) + .color(data.map(function(d,i) { + return d.color || color(d, i); + }).filter(function(d,i) { return !data[i].disabled })); + + var barsWrap = g.select('.nv-barsWrap') + .datum(data.filter(function(d) { return !d.disabled })); + barsWrap.transition().call(bars); + + // Setup Axes + if (showXAxis) { + xAxis + .scale(x) + ._ticks( nv.utils.calcTicksX(availableWidth/100, data) ) + .tickSize(-availableHeight, 0); + + g.select('.nv-x.nv-axis') + .attr('transform', 'translate(0,' + y.range()[0] + ')'); + g.select('.nv-x.nv-axis') + .transition() + .call(xAxis); + } + + if (showYAxis) { + yAxis + .scale(y) + ._ticks( nv.utils.calcTicksY(availableHeight/36, data) ) + .tickSize( -availableWidth, 0); + + g.select('.nv-y.nv-axis') + .transition() + .call(yAxis); + } + + //============================================================ + // Event Handling/Dispatching (in chart's scope) + //------------------------------------------------------------ + + interactiveLayer.dispatch.on('elementMousemove', function(e) { + bars.clearHighlights(); + + var singlePoint, pointIndex, pointXLocation, allData = []; + data + .filter(function(series, i) { + series.seriesIndex = i; + return !series.disabled; + }) + .forEach(function(series,i) { + pointIndex = nv.interactiveBisect(series.values, e.pointXValue, chart.x()); + bars.highlightPoint(pointIndex,true); + var point = series.values[pointIndex]; + if (point === undefined) return; + if (singlePoint === undefined) singlePoint = point; + if (pointXLocation === undefined) pointXLocation = chart.xScale()(chart.x()(point,pointIndex)); + allData.push({ + key: series.key, + value: chart.y()(point, pointIndex), + color: color(series,series.seriesIndex), + data: series.values[pointIndex] + }); + }); + + var xValue = xAxis.tickFormat()(chart.x()(singlePoint,pointIndex)); + interactiveLayer.tooltip + .position({left: pointXLocation + margin.left, top: e.mouseY + margin.top}) + .chartContainer(that.parentNode) + .valueFormatter(function(d,i) { + return yAxis.tickFormat()(d); + }) + .data({ + value: xValue, + index: pointIndex, + series: allData + })(); + + interactiveLayer.renderGuideLine(pointXLocation); + + }); + + interactiveLayer.dispatch.on("elementMouseout",function(e) { + dispatch.tooltipHide(); + bars.clearHighlights(); + }); + + legend.dispatch.on('legendClick', function(d,i) { + d.disabled = !d.disabled; + + if (!data.filter(function(d) { return !d.disabled }).length) { + data.map(function(d) { + d.disabled = false; + wrap.selectAll('.nv-series').classed('disabled', false); + return d; + }); + } + + state.disabled = data.map(function(d) { return !!d.disabled }); + dispatch.stateChange(state); + + selection.transition().call(chart); + }); + + legend.dispatch.on('legendDblclick', function(d) { + //Double clicking should always enable current series, and disabled all others. + data.forEach(function(d) { + d.disabled = true; + }); + d.disabled = false; + + state.disabled = data.map(function(d) { return !!d.disabled }); + dispatch.stateChange(state); + chart.update(); + }); + + dispatch.on('changeState', function(e) { + if (typeof e.disabled !== 'undefined') { + data.forEach(function(series,i) { + series.disabled = e.disabled[i]; + }); + + state.disabled = e.disabled; + } + + chart.update(); + }); + }); + + renderWatch.renderEnd('historicalBarChart immediate'); + return chart; + } + + //============================================================ + // Event Handling/Dispatching (out of chart's scope) + //------------------------------------------------------------ + + bars.dispatch.on('elementMouseover.tooltip', function(evt) { + evt['series'] = { + key: chart.x()(evt.data), + value: chart.y()(evt.data), + color: evt.color + }; + tooltip.data(evt).hidden(false); + }); + + bars.dispatch.on('elementMouseout.tooltip', function(evt) { + tooltip.hidden(true); + }); + + bars.dispatch.on('elementMousemove.tooltip', function(evt) { + tooltip.position({top: d3.event.pageY, left: d3.event.pageX})(); + }); + + //============================================================ + // Expose Public Variables + //------------------------------------------------------------ + + // expose chart's sub-components + chart.dispatch = dispatch; + chart.bars = bars; + chart.legend = legend; + chart.xAxis = xAxis; + chart.yAxis = yAxis; + chart.interactiveLayer = interactiveLayer; + chart.tooltip = tooltip; + + chart.options = nv.utils.optionsFunc.bind(chart); + + chart._options = Object.create({}, { + // simple options, just get/set the necessary values + width: {get: function(){return width;}, set: function(_){width=_;}}, + height: {get: function(){return height;}, set: function(_){height=_;}}, + showLegend: {get: function(){return showLegend;}, set: function(_){showLegend=_;}}, + showXAxis: {get: function(){return showXAxis;}, set: function(_){showXAxis=_;}}, + showYAxis: {get: function(){return showYAxis;}, set: function(_){showYAxis=_;}}, + defaultState: {get: function(){return defaultState;}, set: function(_){defaultState=_;}}, + noData: {get: function(){return noData;}, set: function(_){noData=_;}}, + + // deprecated options + tooltips: {get: function(){return tooltip.enabled();}, set: function(_){ + // deprecated after 1.7.1 + nv.deprecated('tooltips', 'use chart.tooltip.enabled() instead'); + tooltip.enabled(!!_); + }}, + tooltipContent: {get: function(){return tooltip.contentGenerator();}, set: function(_){ + // deprecated after 1.7.1 + nv.deprecated('tooltipContent', 'use chart.tooltip.contentGenerator() instead'); + tooltip.contentGenerator(_); + }}, + + // options that require extra logic in the setter + margin: {get: function(){return margin;}, set: function(_){ + margin.top = _.top !== undefined ? _.top : margin.top; + margin.right = _.right !== undefined ? _.right : margin.right; + margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom; + margin.left = _.left !== undefined ? _.left : margin.left; + }}, + color: {get: function(){return color;}, set: function(_){ + color = nv.utils.getColor(_); + legend.color(color); + bars.color(color); + }}, + duration: {get: function(){return transitionDuration;}, set: function(_){ + transitionDuration=_; + renderWatch.reset(transitionDuration); + yAxis.duration(transitionDuration); + xAxis.duration(transitionDuration); + }}, + rightAlignYAxis: {get: function(){return rightAlignYAxis;}, set: function(_){ + rightAlignYAxis = _; + yAxis.orient( (_) ? 'right' : 'left'); + }}, + useInteractiveGuideline: {get: function(){return useInteractiveGuideline;}, set: function(_){ + useInteractiveGuideline = _; + if (_ === true) { + chart.interactive(false); + } + }} + }); + + nv.utils.inheritOptions(chart, bars); + nv.utils.initOptions(chart); + + return chart; +}; + + +// ohlcChart is just a historical chart with ohlc bars and some tweaks +nv.models.ohlcBarChart = function() { + var chart = nv.models.historicalBarChart(nv.models.ohlcBar()); + + // special default tooltip since we show multiple values per x + chart.useInteractiveGuideline(true); + chart.interactiveLayer.tooltip.contentGenerator(function(data) { + // we assume only one series exists for this chart + var d = data.series[0].data; + // match line colors as defined in nv.d3.css + var color = d.open < d.close ? "2ca02c" : "d62728"; + return '' + + '<h3 style="color: #' + color + '">' + data.value + '</h3>' + + '<table>' + + '<tr><td>open:</td><td>' + chart.yAxis.tickFormat()(d.open) + '</td></tr>' + + '<tr><td>close:</td><td>' + chart.yAxis.tickFormat()(d.close) + '</td></tr>' + + '<tr><td>high</td><td>' + chart.yAxis.tickFormat()(d.high) + '</td></tr>' + + '<tr><td>low:</td><td>' + chart.yAxis.tickFormat()(d.low) + '</td></tr>' + + '</table>'; + }); + return chart; +}; + +// candlestickChart is just a historical chart with candlestick bars and some tweaks +nv.models.candlestickBarChart = function() { + var chart = nv.models.historicalBarChart(nv.models.candlestickBar()); + + // special default tooltip since we show multiple values per x + chart.useInteractiveGuideline(true); + chart.interactiveLayer.tooltip.contentGenerator(function(data) { + // we assume only one series exists for this chart + var d = data.series[0].data; + // match line colors as defined in nv.d3.css + var color = d.open < d.close ? "2ca02c" : "d62728"; + return '' + + '<h3 style="color: #' + color + '">' + data.value + '</h3>' + + '<table>' + + '<tr><td>open:</td><td>' + chart.yAxis.tickFormat()(d.open) + '</td></tr>' + + '<tr><td>close:</td><td>' + chart.yAxis.tickFormat()(d.close) + '</td></tr>' + + '<tr><td>high</td><td>' + chart.yAxis.tickFormat()(d.high) + '</td></tr>' + + '<tr><td>low:</td><td>' + chart.yAxis.tickFormat()(d.low) + '</td></tr>' + + '</table>'; + }); + return chart; +}; +nv.models.legend = function() { + "use strict"; + + //============================================================ + // Public Variables with Default Settings + //------------------------------------------------------------ + + var margin = {top: 5, right: 0, bottom: 5, left: 0} + , width = 400 + , height = 20 + , getKey = function(d) { return d.key } + , color = nv.utils.getColor() + , align = true + , padding = 32 //define how much space between legend items. - recommend 32 for furious version + , rightAlign = true + , updateState = true //If true, legend will update data.disabled and trigger a 'stateChange' dispatch. + , radioButtonMode = false //If true, clicking legend items will cause it to behave like a radio button. (only one can be selected at a time) + , expanded = false + , dispatch = d3.dispatch('legendClick', 'legendDblclick', 'legendMouseover', 'legendMouseout', 'stateChange') + , vers = 'classic' //Options are "classic" and "furious" + ; + + function chart(selection) { + selection.each(function(data) { + var availableWidth = width - margin.left - margin.right, + container = d3.select(this); + nv.utils.initSVG(container); + + // Setup containers and skeleton of chart + var wrap = container.selectAll('g.nv-legend').data([data]); + var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-legend').append('g'); + var g = wrap.select('g'); + + wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); + + var series = g.selectAll('.nv-series') + .data(function(d) { + if(vers != 'furious') return d; + + return d.filter(function(n) { + return expanded ? true : !n.disengaged; + }); + }); + + var seriesEnter = series.enter().append('g').attr('class', 'nv-series'); + var seriesShape; + + var versPadding; + switch(vers) { + case 'furious' : + versPadding = 23; + break; + case 'classic' : + versPadding = 20; + } + + if(vers == 'classic') { + seriesEnter.append('circle') + .style('stroke-width', 2) + .attr('class','nv-legend-symbol') + .attr('r', 5); + + seriesShape = series.select('circle'); + } else if (vers == 'furious') { + seriesEnter.append('rect') + .style('stroke-width', 2) + .attr('class','nv-legend-symbol') + .attr('rx', 3) + .attr('ry', 3); + + seriesShape = series.select('.nv-legend-symbol'); + + seriesEnter.append('g') + .attr('class', 'nv-check-box') + .property('innerHTML','<path d="M0.5,5 L22.5,5 L22.5,26.5 L0.5,26.5 L0.5,5 Z" class="nv-box"></path><path d="M5.5,12.8618467 L11.9185089,19.2803556 L31,0.198864511" class="nv-check"></path>') + .attr('transform', 'translate(-10,-8)scale(0.5)'); + + var seriesCheckbox = series.select('.nv-check-box'); + + seriesCheckbox.each(function(d,i) { + d3.select(this).selectAll('path') + .attr('stroke', setTextColor(d,i)); + }); + } + + seriesEnter.append('text') + .attr('text-anchor', 'start') + .attr('class','nv-legend-text') + .attr('dy', '.32em') + .attr('dx', '8'); + + var seriesText = series.select('text.nv-legend-text'); + + series + .on('mouseover', function(d,i) { + dispatch.legendMouseover(d,i); //TODO: Make consistent with other event objects + }) + .on('mouseout', function(d,i) { + dispatch.legendMouseout(d,i); + }) + .on('click', function(d,i) { + dispatch.legendClick(d,i); + // make sure we re-get data in case it was modified + var data = series.data(); + if (updateState) { + if(vers =='classic') { + if (radioButtonMode) { + //Radio button mode: set every series to disabled, + // and enable the clicked series. + data.forEach(function(series) { series.disabled = true}); + d.disabled = false; + } + else { + d.disabled = !d.disabled; + if (data.every(function(series) { return series.disabled})) { + //the default behavior of NVD3 legends is, if every single series + // is disabled, turn all series' back on. + data.forEach(function(series) { series.disabled = false}); + } + } + } else if(vers == 'furious') { + if(expanded) { + d.disengaged = !d.disengaged; + d.userDisabled = d.userDisabled == undefined ? !!d.disabled : d.userDisabled; + d.disabled = d.disengaged || d.userDisabled; + } else if (!expanded) { + d.disabled = !d.disabled; + d.userDisabled = d.disabled; + var engaged = data.filter(function(d) { return !d.disengaged; }); + if (engaged.every(function(series) { return series.userDisabled })) { + //the default behavior of NVD3 legends is, if every single series + // is disabled, turn all series' back on. + data.forEach(function(series) { + series.disabled = series.userDisabled = false; + }); + } + } + } + dispatch.stateChange({ + disabled: data.map(function(d) { return !!d.disabled }), + disengaged: data.map(function(d) { return !!d.disengaged }) + }); + + } + }) + .on('dblclick', function(d,i) { + if(vers == 'furious' && expanded) return; + dispatch.legendDblclick(d,i); + if (updateState) { + // make sure we re-get data in case it was modified + var data = series.data(); + //the default behavior of NVD3 legends, when double clicking one, + // is to set all other series' to false, and make the double clicked series enabled. + data.forEach(function(series) { + series.disabled = true; + if(vers == 'furious') series.userDisabled = series.disabled; + }); + d.disabled = false; + if(vers == 'furious') d.userDisabled = d.disabled; + dispatch.stateChange({ + disabled: data.map(function(d) { return !!d.disabled }) + }); + } + }); + + series.classed('nv-disabled', function(d) { return d.userDisabled }); + series.exit().remove(); + + seriesText + .attr('fill', setTextColor) + .text(getKey); + + //TODO: implement fixed-width and max-width options (max-width is especially useful with the align option) + // NEW ALIGNING CODE, TODO: clean up + var legendWidth = 0; + if (align) { + + var seriesWidths = []; + series.each(function(d,i) { + var legendText = d3.select(this).select('text'); + var nodeTextLength; + try { + nodeTextLength = legendText.node().getComputedTextLength(); + // If the legendText is display:none'd (nodeTextLength == 0), simulate an error so we approximate, instead + if(nodeTextLength <= 0) throw Error(); + } + catch(e) { + nodeTextLength = nv.utils.calcApproxTextWidth(legendText); + } + + seriesWidths.push(nodeTextLength + padding); + }); + + var seriesPerRow = 0; + var columnWidths = []; + legendWidth = 0; + + while ( legendWidth < availableWidth && seriesPerRow < seriesWidths.length) { + columnWidths[seriesPerRow] = seriesWidths[seriesPerRow]; + legendWidth += seriesWidths[seriesPerRow++]; + } + if (seriesPerRow === 0) seriesPerRow = 1; //minimum of one series per row + + while ( legendWidth > availableWidth && seriesPerRow > 1 ) { + columnWidths = []; + seriesPerRow--; + + for (var k = 0; k < seriesWidths.length; k++) { + if (seriesWidths[k] > (columnWidths[k % seriesPerRow] || 0) ) + columnWidths[k % seriesPerRow] = seriesWidths[k]; + } + + legendWidth = columnWidths.reduce(function(prev, cur, index, array) { + return prev + cur; + }); + } + + var xPositions = []; + for (var i = 0, curX = 0; i < seriesPerRow; i++) { + xPositions[i] = curX; + curX += columnWidths[i]; + } + + series + .attr('transform', function(d, i) { + return 'translate(' + xPositions[i % seriesPerRow] + ',' + (5 + Math.floor(i / seriesPerRow) * versPadding) + ')'; + }); + + //position legend as far right as possible within the total width + if (rightAlign) { + g.attr('transform', 'translate(' + (width - margin.right - legendWidth) + ',' + margin.top + ')'); + } + else { + g.attr('transform', 'translate(0' + ',' + margin.top + ')'); + } + + height = margin.top + margin.bottom + (Math.ceil(seriesWidths.length / seriesPerRow) * versPadding); + + } else { + + var ypos = 5, + newxpos = 5, + maxwidth = 0, + xpos; + series + .attr('transform', function(d, i) { + var length = d3.select(this).select('text').node().getComputedTextLength() + padding; + xpos = newxpos; + + if (width < margin.left + margin.right + xpos + length) { + newxpos = xpos = 5; + ypos += versPadding; + } + + newxpos += length; + if (newxpos > maxwidth) maxwidth = newxpos; + + if(legendWidth < xpos + maxwidth) { + legendWidth = xpos + maxwidth; + } + return 'translate(' + xpos + ',' + ypos + ')'; + }); + + //position legend as far right as possible within the total width + g.attr('transform', 'translate(' + (width - margin.right - maxwidth) + ',' + margin.top + ')'); + + height = margin.top + margin.bottom + ypos + 15; + } + + if(vers == 'furious') { + // Size rectangles after text is placed + seriesShape + .attr('width', function(d,i) { + return seriesText[0][i].getComputedTextLength() + 27; + }) + .attr('height', 18) + .attr('y', -9) + .attr('x', -15); + + // The background for the expanded legend (UI) + gEnter.insert('rect',':first-child') + .attr('class', 'nv-legend-bg') + .attr('fill', '#eee') + // .attr('stroke', '#444') + .attr('opacity',0); + + var seriesBG = g.select('.nv-legend-bg'); + + seriesBG + .transition().duration(300) + .attr('x', -versPadding ) + .attr('width', legendWidth + versPadding - 12) + .attr('height', height + 10) + .attr('y', -margin.top - 10) + .attr('opacity', expanded ? 1 : 0); + + + } + + seriesShape + .style('fill', setBGColor) + .style('fill-opacity', setBGOpacity) + .style('stroke', setBGColor); + }); + + function setTextColor(d,i) { + if(vers != 'furious') return '#000'; + if(expanded) { + return d.disengaged ? '#000' : '#fff'; + } else if (!expanded) { + if(!d.color) d.color = color(d,i); + return !!d.disabled ? d.color : '#fff'; + } + } + + function setBGColor(d,i) { + if(expanded && vers == 'furious') { + return d.disengaged ? '#eee' : d.color || color(d,i); + } else { + return d.color || color(d,i); + } + } + + + function setBGOpacity(d,i) { + if(expanded && vers == 'furious') { + return 1; + } else { + return !!d.disabled ? 0 : 1; + } + } + + return chart; + } + + //============================================================ + // Expose Public Variables + //------------------------------------------------------------ + + chart.dispatch = dispatch; + chart.options = nv.utils.optionsFunc.bind(chart); + + chart._options = Object.create({}, { + // simple options, just get/set the necessary values + width: {get: function(){return width;}, set: function(_){width=_;}}, + height: {get: function(){return height;}, set: function(_){height=_;}}, + key: {get: function(){return getKey;}, set: function(_){getKey=_;}}, + align: {get: function(){return align;}, set: function(_){align=_;}}, + rightAlign: {get: function(){return rightAlign;}, set: function(_){rightAlign=_;}}, + padding: {get: function(){return padding;}, set: function(_){padding=_;}}, + updateState: {get: function(){return updateState;}, set: function(_){updateState=_;}}, + radioButtonMode: {get: function(){return radioButtonMode;}, set: function(_){radioButtonMode=_;}}, + expanded: {get: function(){return expanded;}, set: function(_){expanded=_;}}, + vers: {get: function(){return vers;}, set: function(_){vers=_;}}, + + // options that require extra logic in the setter + margin: {get: function(){return margin;}, set: function(_){ + margin.top = _.top !== undefined ? _.top : margin.top; + margin.right = _.right !== undefined ? _.right : margin.right; + margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom; + margin.left = _.left !== undefined ? _.left : margin.left; + }}, + color: {get: function(){return color;}, set: function(_){ + color = nv.utils.getColor(_); + }} + }); + + nv.utils.initOptions(chart); + + return chart; +}; + +nv.models.line = function() { + "use strict"; + //============================================================ + // Public Variables with Default Settings + //------------------------------------------------------------ + + var scatter = nv.models.scatter() + ; + + var margin = {top: 0, right: 0, bottom: 0, left: 0} + , width = 960 + , height = 500 + , container = null + , strokeWidth = 1.5 + , color = nv.utils.defaultColor() // a function that returns a color + , getX = function(d) { return d.x } // accessor to get the x value from a data point + , getY = function(d) { return d.y } // accessor to get the y value from a data point + , defined = function(d,i) { return !isNaN(getY(d,i)) && getY(d,i) !== null } // allows a line to be not continuous when it is not defined + , isArea = function(d) { return d.area } // decides if a line is an area or just a line + , clipEdge = false // if true, masks lines within x and y scale + , x //can be accessed via chart.xScale() + , y //can be accessed via chart.yScale() + , interpolate = "linear" // controls the line interpolation + , duration = 250 + , dispatch = d3.dispatch('elementClick', 'elementMouseover', 'elementMouseout', 'renderEnd') + ; + + scatter + .pointSize(16) // default size + .pointDomain([16,256]) //set to speed up calculation, needs to be unset if there is a custom size accessor + ; + + //============================================================ + + + //============================================================ + // Private Variables + //------------------------------------------------------------ + + var x0, y0 //used to store previous scales + , renderWatch = nv.utils.renderWatch(dispatch, duration) + ; + + //============================================================ + + + function chart(selection) { + renderWatch.reset(); + renderWatch.models(scatter); + selection.each(function(data) { + container = d3.select(this); + var availableWidth = nv.utils.availableWidth(width, container, margin), + availableHeight = nv.utils.availableHeight(height, container, margin); + nv.utils.initSVG(container); + + // Setup Scales + x = scatter.xScale(); + y = scatter.yScale(); + + x0 = x0 || x; + y0 = y0 || y; + + // Setup containers and skeleton of chart + var wrap = container.selectAll('g.nv-wrap.nv-line').data([data]); + var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-line'); + var defsEnter = wrapEnter.append('defs'); + var gEnter = wrapEnter.append('g'); + var g = wrap.select('g'); + + gEnter.append('g').attr('class', 'nv-groups'); + gEnter.append('g').attr('class', 'nv-scatterWrap'); + + wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); + + scatter + .width(availableWidth) + .height(availableHeight); + + var scatterWrap = wrap.select('.nv-scatterWrap'); + scatterWrap.call(scatter); + + defsEnter.append('clipPath') + .attr('id', 'nv-edge-clip-' + scatter.id()) + .append('rect'); + + wrap.select('#nv-edge-clip-' + scatter.id() + ' rect') + .attr('width', availableWidth) + .attr('height', (availableHeight > 0) ? availableHeight : 0); + + g .attr('clip-path', clipEdge ? 'url(#nv-edge-clip-' + scatter.id() + ')' : ''); + scatterWrap + .attr('clip-path', clipEdge ? 'url(#nv-edge-clip-' + scatter.id() + ')' : ''); + + var groups = wrap.select('.nv-groups').selectAll('.nv-group') + .data(function(d) { return d }, function(d) { return d.key }); + groups.enter().append('g') + .style('stroke-opacity', 1e-6) + .style('stroke-width', function(d) { return d.strokeWidth || strokeWidth }) + .style('fill-opacity', 1e-6); + + groups.exit().remove(); + + groups + .attr('class', function(d,i) { + return (d.classed || '') + ' nv-group nv-series-' + i; + }) + .classed('hover', function(d) { return d.hover }) + .style('fill', function(d,i){ return color(d, i) }) + .style('stroke', function(d,i){ return color(d, i)}); + groups.watchTransition(renderWatch, 'line: groups') + .style('stroke-opacity', 1) + .style('fill-opacity', function(d) { return d.fillOpacity || .5}); + + var areaPaths = groups.selectAll('path.nv-area') + .data(function(d) { return isArea(d) ? [d] : [] }); // this is done differently than lines because I need to check if series is an area + areaPaths.enter().append('path') + .attr('class', 'nv-area') + .attr('d', function(d) { + return d3.svg.area() + .interpolate(interpolate) + .defined(defined) + .x(function(d,i) { return nv.utils.NaNtoZero(x0(getX(d,i))) }) + .y0(function(d,i) { return nv.utils.NaNtoZero(y0(getY(d,i))) }) + .y1(function(d,i) { return y0( y.domain()[0] <= 0 ? y.domain()[1] >= 0 ? 0 : y.domain()[1] : y.domain()[0] ) }) + //.y1(function(d,i) { return y0(0) }) //assuming 0 is within y domain.. may need to tweak this + .apply(this, [d.values]) + }); + groups.exit().selectAll('path.nv-area') + .remove(); + + areaPaths.watchTransition(renderWatch, 'line: areaPaths') + .attr('d', function(d) { + return d3.svg.area() + .interpolate(interpolate) + .defined(defined) + .x(function(d,i) { return nv.utils.NaNtoZero(x(getX(d,i))) }) + .y0(function(d,i) { return nv.utils.NaNtoZero(y(getY(d,i))) }) + .y1(function(d,i) { return y( y.domain()[0] <= 0 ? y.domain()[1] >= 0 ? 0 : y.domain()[1] : y.domain()[0] ) }) + //.y1(function(d,i) { return y0(0) }) //assuming 0 is within y domain.. may need to tweak this + .apply(this, [d.values]) + }); + + var linePaths = groups.selectAll('path.nv-line') + .data(function(d) { return [d.values] }); + + linePaths.enter().append('path') + .attr('class', 'nv-line') + .attr('d', + d3.svg.line() + .interpolate(interpolate) + .defined(defined) + .x(function(d,i) { return nv.utils.NaNtoZero(x0(getX(d,i))) }) + .y(function(d,i) { return nv.utils.NaNtoZero(y0(getY(d,i))) }) + ); + + linePaths.watchTransition(renderWatch, 'line: linePaths') + .attr('d', + d3.svg.line() + .interpolate(interpolate) + .defined(defined) + .x(function(d,i) { return nv.utils.NaNtoZero(x(getX(d,i))) }) + .y(function(d,i) { return nv.utils.NaNtoZero(y(getY(d,i))) }) + ); + + //store old scales for use in transitions on update + x0 = x.copy(); + y0 = y.copy(); + }); + renderWatch.renderEnd('line immediate'); + return chart; + } + + + //============================================================ + // Expose Public Variables + //------------------------------------------------------------ + + chart.dispatch = dispatch; + chart.scatter = scatter; + // Pass through events + scatter.dispatch.on('elementClick', function(){ dispatch.elementClick.apply(this, arguments); }); + scatter.dispatch.on('elementMouseover', function(){ dispatch.elementMouseover.apply(this, arguments); }); + scatter.dispatch.on('elementMouseout', function(){ dispatch.elementMouseout.apply(this, arguments); }); + + chart.options = nv.utils.optionsFunc.bind(chart); + + chart._options = Object.create({}, { + // simple options, just get/set the necessary values + width: {get: function(){return width;}, set: function(_){width=_;}}, + height: {get: function(){return height;}, set: function(_){height=_;}}, + defined: {get: function(){return defined;}, set: function(_){defined=_;}}, + interpolate: {get: function(){return interpolate;}, set: function(_){interpolate=_;}}, + clipEdge: {get: function(){return clipEdge;}, set: function(_){clipEdge=_;}}, + + // options that require extra logic in the setter + margin: {get: function(){return margin;}, set: function(_){ + margin.top = _.top !== undefined ? _.top : margin.top; + margin.right = _.right !== undefined ? _.right : margin.right; + margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom; + margin.left = _.left !== undefined ? _.left : margin.left; + }}, + duration: {get: function(){return duration;}, set: function(_){ + duration = _; + renderWatch.reset(duration); + scatter.duration(duration); + }}, + isArea: {get: function(){return isArea;}, set: function(_){ + isArea = d3.functor(_); + }}, + x: {get: function(){return getX;}, set: function(_){ + getX = _; + scatter.x(_); + }}, + y: {get: function(){return getY;}, set: function(_){ + getY = _; + scatter.y(_); + }}, + color: {get: function(){return color;}, set: function(_){ + color = nv.utils.getColor(_); + scatter.color(color); + }} + }); + + nv.utils.inheritOptions(chart, scatter); + nv.utils.initOptions(chart); + + return chart; +}; +nv.models.lineChart = function() { + "use strict"; + + //============================================================ + // Public Variables with Default Settings + //------------------------------------------------------------ + + var lines = nv.models.line() + , xAxis = nv.models.axis() + , yAxis = nv.models.axis() + , legend = nv.models.legend() + , interactiveLayer = nv.interactiveGuideline() + , tooltip = nv.models.tooltip() + ; + + var margin = {top: 30, right: 20, bottom: 50, left: 60} + , color = nv.utils.defaultColor() + , width = null + , height = null + , showLegend = true + , showXAxis = true + , showYAxis = true + , rightAlignYAxis = false + , useInteractiveGuideline = false + , x + , y + , state = nv.utils.state() + , defaultState = null + , noData = null + , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState', 'renderEnd') + , duration = 250 + ; + + // set options on sub-objects for this chart + xAxis.orient('bottom').tickPadding(7); + yAxis.orient(rightAlignYAxis ? 'right' : 'left'); + tooltip.valueFormatter(function(d, i) { + return yAxis.tickFormat()(d, i); + }).headerFormatter(function(d, i) { + return xAxis.tickFormat()(d, i); + }); + + + //============================================================ + // Private Variables + //------------------------------------------------------------ + + var renderWatch = nv.utils.renderWatch(dispatch, duration); + + var stateGetter = function(data) { + return function(){ + return { + active: data.map(function(d) { return !d.disabled }) + }; + } + }; + + var stateSetter = function(data) { + return function(state) { + if (state.active !== undefined) + data.forEach(function(series,i) { + series.disabled = !state.active[i]; + }); + } + }; + + function chart(selection) { + renderWatch.reset(); + renderWatch.models(lines); + if (showXAxis) renderWatch.models(xAxis); + if (showYAxis) renderWatch.models(yAxis); + + selection.each(function(data) { + var container = d3.select(this), + that = this; + nv.utils.initSVG(container); + var availableWidth = nv.utils.availableWidth(width, container, margin), + availableHeight = nv.utils.availableHeight(height, container, margin); + + chart.update = function() { + if (duration === 0) + container.call(chart); + else + container.transition().duration(duration).call(chart) + }; + chart.container = this; + + state + .setter(stateSetter(data), chart.update) + .getter(stateGetter(data)) + .update(); + + // DEPRECATED set state.disableddisabled + state.disabled = data.map(function(d) { return !!d.disabled }); + + if (!defaultState) { + var key; + defaultState = {}; + for (key in state) { + if (state[key] instanceof Array) + defaultState[key] = state[key].slice(0); + else + defaultState[key] = state[key]; + } + } + + // Display noData message if there's nothing to show. + if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) { + nv.utils.noData(chart, container) + return chart; + } else { + container.selectAll('.nv-noData').remove(); + } + + + // Setup Scales + x = lines.xScale(); + y = lines.yScale(); + + // Setup containers and skeleton of chart + var wrap = container.selectAll('g.nv-wrap.nv-lineChart').data([data]); + var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-lineChart').append('g'); + var g = wrap.select('g'); + + gEnter.append("rect").style("opacity",0); + gEnter.append('g').attr('class', 'nv-x nv-axis'); + gEnter.append('g').attr('class', 'nv-y nv-axis'); + gEnter.append('g').attr('class', 'nv-linesWrap'); + gEnter.append('g').attr('class', 'nv-legendWrap'); + gEnter.append('g').attr('class', 'nv-interactive'); + + g.select("rect") + .attr("width",availableWidth) + .attr("height",(availableHeight > 0) ? availableHeight : 0); + + // Legend + if (showLegend) { + legend.width(availableWidth); + + g.select('.nv-legendWrap') + .datum(data) + .call(legend); + + if ( margin.top != legend.height()) { + margin.top = legend.height(); + availableHeight = nv.utils.availableHeight(height, container, margin); + } + + wrap.select('.nv-legendWrap') + .attr('transform', 'translate(0,' + (-margin.top) +')') + } + + wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); + + if (rightAlignYAxis) { + g.select(".nv-y.nv-axis") + .attr("transform", "translate(" + availableWidth + ",0)"); + } + + //Set up interactive layer + if (useInteractiveGuideline) { + interactiveLayer + .width(availableWidth) + .height(availableHeight) + .margin({left:margin.left, top:margin.top}) + .svgContainer(container) + .xScale(x); + wrap.select(".nv-interactive").call(interactiveLayer); + } + + lines + .width(availableWidth) + .height(availableHeight) + .color(data.map(function(d,i) { + return d.color || color(d, i); + }).filter(function(d,i) { return !data[i].disabled })); + + + var linesWrap = g.select('.nv-linesWrap') + .datum(data.filter(function(d) { return !d.disabled })); + + linesWrap.call(lines); + + // Setup Axes + if (showXAxis) { + xAxis + .scale(x) + ._ticks(nv.utils.calcTicksX(availableWidth/100, data) ) + .tickSize(-availableHeight, 0); + + g.select('.nv-x.nv-axis') + .attr('transform', 'translate(0,' + y.range()[0] + ')'); + g.select('.nv-x.nv-axis') + .call(xAxis); + } + + if (showYAxis) { + yAxis + .scale(y) + ._ticks(nv.utils.calcTicksY(availableHeight/36, data) ) + .tickSize( -availableWidth, 0); + + g.select('.nv-y.nv-axis') + .call(yAxis); + } + + //============================================================ + // Event Handling/Dispatching (in chart's scope) + //------------------------------------------------------------ + + legend.dispatch.on('stateChange', function(newState) { + for (var key in newState) + state[key] = newState[key]; + dispatch.stateChange(state); + chart.update(); + }); + + interactiveLayer.dispatch.on('elementMousemove', function(e) { + lines.clearHighlights(); + var singlePoint, pointIndex, pointXLocation, allData = []; + data + .filter(function(series, i) { + series.seriesIndex = i; + return !series.disabled; + }) + .forEach(function(series,i) { + pointIndex = nv.interactiveBisect(series.values, e.pointXValue, chart.x()); + var point = series.values[pointIndex]; + var pointYValue = chart.y()(point, pointIndex); + if (pointYValue != null) { + lines.highlightPoint(i, pointIndex, true); + } + if (point === undefined) return; + if (singlePoint === undefined) singlePoint = point; + if (pointXLocation === undefined) pointXLocation = chart.xScale()(chart.x()(point,pointIndex)); + allData.push({ + key: series.key, + value: pointYValue, + color: color(series,series.seriesIndex) + }); + }); + //Highlight the tooltip entry based on which point the mouse is closest to. + if (allData.length > 2) { + var yValue = chart.yScale().invert(e.mouseY); + var domainExtent = Math.abs(chart.yScale().domain()[0] - chart.yScale().domain()[1]); + var threshold = 0.03 * domainExtent; + var indexToHighlight = nv.nearestValueIndex(allData.map(function(d){return d.value}),yValue,threshold); + if (indexToHighlight !== null) + allData[indexToHighlight].highlight = true; + } + + var xValue = xAxis.tickFormat()(chart.x()(singlePoint,pointIndex)); + interactiveLayer.tooltip + .position({left: e.mouseX + margin.left, top: e.mouseY + margin.top}) + .chartContainer(that.parentNode) + .valueFormatter(function(d,i) { + return d == null ? "N/A" : yAxis.tickFormat()(d); + }) + .data({ + value: xValue, + index: pointIndex, + series: allData + })(); + + interactiveLayer.renderGuideLine(pointXLocation); + + }); + + interactiveLayer.dispatch.on('elementClick', function(e) { + var pointXLocation, allData = []; + + data.filter(function(series, i) { + series.seriesIndex = i; + return !series.disabled; + }).forEach(function(series) { + var pointIndex = nv.interactiveBisect(series.values, e.pointXValue, chart.x()); + var point = series.values[pointIndex]; + if (typeof point === 'undefined') return; + if (typeof pointXLocation === 'undefined') pointXLocation = chart.xScale()(chart.x()(point,pointIndex)); + var yPos = chart.yScale()(chart.y()(point,pointIndex)); + allData.push({ + point: point, + pointIndex: pointIndex, + pos: [pointXLocation, yPos], + seriesIndex: series.seriesIndex, + series: series + }); + }); + + lines.dispatch.elementClick(allData); + }); + + interactiveLayer.dispatch.on("elementMouseout",function(e) { + lines.clearHighlights(); + }); + + dispatch.on('changeState', function(e) { + if (typeof e.disabled !== 'undefined' && data.length === e.disabled.length) { + data.forEach(function(series,i) { + series.disabled = e.disabled[i]; + }); + + state.disabled = e.disabled; + } + + chart.update(); + }); + + }); + + renderWatch.renderEnd('lineChart immediate'); + return chart; + } + + //============================================================ + // Event Handling/Dispatching (out of chart's scope) + //------------------------------------------------------------ + + lines.dispatch.on('elementMouseover.tooltip', function(evt) { + tooltip.data(evt).position(evt.pos).hidden(false); + }); + + lines.dispatch.on('elementMouseout.tooltip', function(evt) { + tooltip.hidden(true) + }); + + //============================================================ + // Expose Public Variables + //------------------------------------------------------------ + + // expose chart's sub-components + chart.dispatch = dispatch; + chart.lines = lines; + chart.legend = legend; + chart.xAxis = xAxis; + chart.yAxis = yAxis; + chart.interactiveLayer = interactiveLayer; + chart.tooltip = tooltip; + + chart.dispatch = dispatch; + chart.options = nv.utils.optionsFunc.bind(chart); + + chart._options = Object.create({}, { + // simple options, just get/set the necessary values + width: {get: function(){return width;}, set: function(_){width=_;}}, + height: {get: function(){return height;}, set: function(_){height=_;}}, + showLegend: {get: function(){return showLegend;}, set: function(_){showLegend=_;}}, + showXAxis: {get: function(){return showXAxis;}, set: function(_){showXAxis=_;}}, + showYAxis: {get: function(){return showYAxis;}, set: function(_){showYAxis=_;}}, + defaultState: {get: function(){return defaultState;}, set: function(_){defaultState=_;}}, + noData: {get: function(){return noData;}, set: function(_){noData=_;}}, + + // deprecated options + tooltips: {get: function(){return tooltip.enabled();}, set: function(_){ + // deprecated after 1.7.1 + nv.deprecated('tooltips', 'use chart.tooltip.enabled() instead'); + tooltip.enabled(!!_); + }}, + tooltipContent: {get: function(){return tooltip.contentGenerator();}, set: function(_){ + // deprecated after 1.7.1 + nv.deprecated('tooltipContent', 'use chart.tooltip.contentGenerator() instead'); + tooltip.contentGenerator(_); + }}, + + // options that require extra logic in the setter + margin: {get: function(){return margin;}, set: function(_){ + margin.top = _.top !== undefined ? _.top : margin.top; + margin.right = _.right !== undefined ? _.right : margin.right; + margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom; + margin.left = _.left !== undefined ? _.left : margin.left; + }}, + duration: {get: function(){return duration;}, set: function(_){ + duration = _; + renderWatch.reset(duration); + lines.duration(duration); + xAxis.duration(duration); + yAxis.duration(duration); + }}, + color: {get: function(){return color;}, set: function(_){ + color = nv.utils.getColor(_); + legend.color(color); + lines.color(color); + }}, + rightAlignYAxis: {get: function(){return rightAlignYAxis;}, set: function(_){ + rightAlignYAxis = _; + yAxis.orient( rightAlignYAxis ? 'right' : 'left'); + }}, + useInteractiveGuideline: {get: function(){return useInteractiveGuideline;}, set: function(_){ + useInteractiveGuideline = _; + if (useInteractiveGuideline) { + lines.interactive(false); + lines.useVoronoi(false); + } + }} + }); + + nv.utils.inheritOptions(chart, lines); + nv.utils.initOptions(chart); + + return chart; +}; +nv.models.linePlusBarChart = function() { + "use strict"; + + //============================================================ + // Public Variables with Default Settings + //------------------------------------------------------------ + + var lines = nv.models.line() + , lines2 = nv.models.line() + , bars = nv.models.historicalBar() + , bars2 = nv.models.historicalBar() + , xAxis = nv.models.axis() + , x2Axis = nv.models.axis() + , y1Axis = nv.models.axis() + , y2Axis = nv.models.axis() + , y3Axis = nv.models.axis() + , y4Axis = nv.models.axis() + , legend = nv.models.legend() + , brush = d3.svg.brush() + , tooltip = nv.models.tooltip() + ; + + var margin = {top: 30, right: 30, bottom: 30, left: 60} + , margin2 = {top: 0, right: 30, bottom: 20, left: 60} + , width = null + , height = null + , getX = function(d) { return d.x } + , getY = function(d) { return d.y } + , color = nv.utils.defaultColor() + , showLegend = true + , focusEnable = true + , focusShowAxisY = false + , focusShowAxisX = true + , focusHeight = 50 + , extent + , brushExtent = null + , x + , x2 + , y1 + , y2 + , y3 + , y4 + , noData = null + , dispatch = d3.dispatch('brush', 'stateChange', 'changeState') + , transitionDuration = 0 + , state = nv.utils.state() + , defaultState = null + , legendLeftAxisHint = ' (left axis)' + , legendRightAxisHint = ' (right axis)' + ; + + lines.clipEdge(true); + lines2.interactive(false); + xAxis.orient('bottom').tickPadding(5); + y1Axis.orient('left'); + y2Axis.orient('right'); + x2Axis.orient('bottom').tickPadding(5); + y3Axis.orient('left'); + y4Axis.orient('right'); + + tooltip.headerEnabled(true).headerFormatter(function(d, i) { + return xAxis.tickFormat()(d, i); + }); + + //============================================================ + // Private Variables + //------------------------------------------------------------ + + var stateGetter = function(data) { + return function(){ + return { + active: data.map(function(d) { return !d.disabled }) + }; + } + }; + + var stateSetter = function(data) { + return function(state) { + if (state.active !== undefined) + data.forEach(function(series,i) { + series.disabled = !state.active[i]; + }); + } + }; + + function chart(selection) { + selection.each(function(data) { + var container = d3.select(this), + that = this; + nv.utils.initSVG(container); + var availableWidth = nv.utils.availableWidth(width, container, margin), + availableHeight1 = nv.utils.availableHeight(height, container, margin) + - (focusEnable ? focusHeight : 0), + availableHeight2 = focusHeight - margin2.top - margin2.bottom; + + chart.update = function() { container.transition().duration(transitionDuration).call(chart); }; + chart.container = this; + + state + .setter(stateSetter(data), chart.update) + .getter(stateGetter(data)) + .update(); + + // DEPRECATED set state.disableddisabled + state.disabled = data.map(function(d) { return !!d.disabled }); + + if (!defaultState) { + var key; + defaultState = {}; + for (key in state) { + if (state[key] instanceof Array) + defaultState[key] = state[key].slice(0); + else + defaultState[key] = state[key]; + } + } + + // Display No Data message if there's nothing to show. + if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) { + nv.utils.noData(chart, container) + return chart; + } else { + container.selectAll('.nv-noData').remove(); + } + + // Setup Scales + var dataBars = data.filter(function(d) { return !d.disabled && d.bar }); + var dataLines = data.filter(function(d) { return !d.bar }); // removed the !d.disabled clause here to fix Issue #240 + + x = bars.xScale(); + x2 = x2Axis.scale(); + y1 = bars.yScale(); + y2 = lines.yScale(); + y3 = bars2.yScale(); + y4 = lines2.yScale(); + + var series1 = data + .filter(function(d) { return !d.disabled && d.bar }) + .map(function(d) { + return d.values.map(function(d,i) { + return { x: getX(d,i), y: getY(d,i) } + }) + }); + + var series2 = data + .filter(function(d) { return !d.disabled && !d.bar }) + .map(function(d) { + return d.values.map(function(d,i) { + return { x: getX(d,i), y: getY(d,i) } + }) + }); + + x.range([0, availableWidth]); + + x2 .domain(d3.extent(d3.merge(series1.concat(series2)), function(d) { return d.x } )) + .range([0, availableWidth]); + + // Setup containers and skeleton of chart + var wrap = container.selectAll('g.nv-wrap.nv-linePlusBar').data([data]); + var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-linePlusBar').append('g'); + var g = wrap.select('g'); + + gEnter.append('g').attr('class', 'nv-legendWrap'); + + // this is the main chart + var focusEnter = gEnter.append('g').attr('class', 'nv-focus'); + focusEnter.append('g').attr('class', 'nv-x nv-axis'); + focusEnter.append('g').attr('class', 'nv-y1 nv-axis'); + focusEnter.append('g').attr('class', 'nv-y2 nv-axis'); + focusEnter.append('g').attr('class', 'nv-barsWrap'); + focusEnter.append('g').attr('class', 'nv-linesWrap'); + + // context chart is where you can focus in + var contextEnter = gEnter.append('g').attr('class', 'nv-context'); + contextEnter.append('g').attr('class', 'nv-x nv-axis'); + contextEnter.append('g').attr('class', 'nv-y1 nv-axis'); + contextEnter.append('g').attr('class', 'nv-y2 nv-axis'); + contextEnter.append('g').attr('class', 'nv-barsWrap'); + contextEnter.append('g').attr('class', 'nv-linesWrap'); + contextEnter.append('g').attr('class', 'nv-brushBackground'); + contextEnter.append('g').attr('class', 'nv-x nv-brush'); + + //============================================================ + // Legend + //------------------------------------------------------------ + + if (showLegend) { + var legendWidth = legend.align() ? availableWidth / 2 : availableWidth; + var legendXPosition = legend.align() ? legendWidth : 0; + + legend.width(legendWidth); + + g.select('.nv-legendWrap') + .datum(data.map(function(series) { + series.originalKey = series.originalKey === undefined ? series.key : series.originalKey; + series.key = series.originalKey + (series.bar ? legendLeftAxisHint : legendRightAxisHint); + return series; + })) + .call(legend); + + if ( margin.top != legend.height()) { + margin.top = legend.height(); + // FIXME: shouldn't this be "- (focusEnabled ? focusHeight : 0)"? + availableHeight1 = nv.utils.availableHeight(height, container, margin) - focusHeight; + } + + g.select('.nv-legendWrap') + .attr('transform', 'translate(' + legendXPosition + ',' + (-margin.top) +')'); + } + + wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); + + //============================================================ + // Context chart (focus chart) components + //------------------------------------------------------------ + + // hide or show the focus context chart + g.select('.nv-context').style('display', focusEnable ? 'initial' : 'none'); + + bars2 + .width(availableWidth) + .height(availableHeight2) + .color(data.map(function (d, i) { + return d.color || color(d, i); + }).filter(function (d, i) { + return !data[i].disabled && data[i].bar + })); + lines2 + .width(availableWidth) + .height(availableHeight2) + .color(data.map(function (d, i) { + return d.color || color(d, i); + }).filter(function (d, i) { + return !data[i].disabled && !data[i].bar + })); + + var bars2Wrap = g.select('.nv-context .nv-barsWrap') + .datum(dataBars.length ? dataBars : [ + {values: []} + ]); + var lines2Wrap = g.select('.nv-context .nv-linesWrap') + .datum(!dataLines[0].disabled ? dataLines : [ + {values: []} + ]); + + g.select('.nv-context') + .attr('transform', 'translate(0,' + ( availableHeight1 + margin.bottom + margin2.top) + ')'); + + bars2Wrap.transition().call(bars2); + lines2Wrap.transition().call(lines2); + + // context (focus chart) axis controls + if (focusShowAxisX) { + x2Axis + ._ticks( nv.utils.calcTicksX(availableWidth / 100, data)) + .tickSize(-availableHeight2, 0); + g.select('.nv-context .nv-x.nv-axis') + .attr('transform', 'translate(0,' + y3.range()[0] + ')'); + g.select('.nv-context .nv-x.nv-axis').transition() + .call(x2Axis); + } + + if (focusShowAxisY) { + y3Axis + .scale(y3) + ._ticks( availableHeight2 / 36 ) + .tickSize( -availableWidth, 0); + y4Axis + .scale(y4) + ._ticks( availableHeight2 / 36 ) + .tickSize(dataBars.length ? 0 : -availableWidth, 0); // Show the y2 rules only if y1 has none + + g.select('.nv-context .nv-y3.nv-axis') + .style('opacity', dataBars.length ? 1 : 0) + .attr('transform', 'translate(0,' + x2.range()[0] + ')'); + g.select('.nv-context .nv-y2.nv-axis') + .style('opacity', dataLines.length ? 1 : 0) + .attr('transform', 'translate(' + x2.range()[1] + ',0)'); + + g.select('.nv-context .nv-y1.nv-axis').transition() + .call(y3Axis); + g.select('.nv-context .nv-y2.nv-axis').transition() + .call(y4Axis); + } + + // Setup Brush + brush.x(x2).on('brush', onBrush); + + if (brushExtent) brush.extent(brushExtent); + + var brushBG = g.select('.nv-brushBackground').selectAll('g') + .data([brushExtent || brush.extent()]); + + var brushBGenter = brushBG.enter() + .append('g'); + + brushBGenter.append('rect') + .attr('class', 'left') + .attr('x', 0) + .attr('y', 0) + .attr('height', availableHeight2); + + brushBGenter.append('rect') + .attr('class', 'right') + .attr('x', 0) + .attr('y', 0) + .attr('height', availableHeight2); + + var gBrush = g.select('.nv-x.nv-brush') + .call(brush); + gBrush.selectAll('rect') + //.attr('y', -5) + .attr('height', availableHeight2); + gBrush.selectAll('.resize').append('path').attr('d', resizePath); + + //============================================================ + // Event Handling/Dispatching (in chart's scope) + //------------------------------------------------------------ + + legend.dispatch.on('stateChange', function(newState) { + for (var key in newState) + state[key] = newState[key]; + dispatch.stateChange(state); + chart.update(); + }); + + // Update chart from a state object passed to event handler + dispatch.on('changeState', function(e) { + if (typeof e.disabled !== 'undefined') { + data.forEach(function(series,i) { + series.disabled = e.disabled[i]; + }); + state.disabled = e.disabled; + } + chart.update(); + }); + + //============================================================ + // Functions + //------------------------------------------------------------ + + // Taken from crossfilter (http://square.github.com/crossfilter/) + function resizePath(d) { + var e = +(d == 'e'), + x = e ? 1 : -1, + y = availableHeight2 / 3; + return 'M' + (.5 * x) + ',' + y + + 'A6,6 0 0 ' + e + ' ' + (6.5 * x) + ',' + (y + 6) + + 'V' + (2 * y - 6) + + 'A6,6 0 0 ' + e + ' ' + (.5 * x) + ',' + (2 * y) + + 'Z' + + 'M' + (2.5 * x) + ',' + (y + 8) + + 'V' + (2 * y - 8) + + 'M' + (4.5 * x) + ',' + (y + 8) + + 'V' + (2 * y - 8); + } + + + function updateBrushBG() { + if (!brush.empty()) brush.extent(brushExtent); + brushBG + .data([brush.empty() ? x2.domain() : brushExtent]) + .each(function(d,i) { + var leftWidth = x2(d[0]) - x2.range()[0], + rightWidth = x2.range()[1] - x2(d[1]); + d3.select(this).select('.left') + .attr('width', leftWidth < 0 ? 0 : leftWidth); + + d3.select(this).select('.right') + .attr('x', x2(d[1])) + .attr('width', rightWidth < 0 ? 0 : rightWidth); + }); + } + + function onBrush() { + brushExtent = brush.empty() ? null : brush.extent(); + extent = brush.empty() ? x2.domain() : brush.extent(); + dispatch.brush({extent: extent, brush: brush}); + updateBrushBG(); + + // Prepare Main (Focus) Bars and Lines + bars + .width(availableWidth) + .height(availableHeight1) + .color(data.map(function(d,i) { + return d.color || color(d, i); + }).filter(function(d,i) { return !data[i].disabled && data[i].bar })); + + lines + .width(availableWidth) + .height(availableHeight1) + .color(data.map(function(d,i) { + return d.color || color(d, i); + }).filter(function(d,i) { return !data[i].disabled && !data[i].bar })); + + var focusBarsWrap = g.select('.nv-focus .nv-barsWrap') + .datum(!dataBars.length ? [{values:[]}] : + dataBars + .map(function(d,i) { + return { + key: d.key, + values: d.values.filter(function(d,i) { + return bars.x()(d,i) >= extent[0] && bars.x()(d,i) <= extent[1]; + }) + } + }) + ); + + var focusLinesWrap = g.select('.nv-focus .nv-linesWrap') + .datum(dataLines[0].disabled ? [{values:[]}] : + dataLines + .map(function(d,i) { + return { + area: d.area, + fillOpacity: d.fillOpacity, + key: d.key, + values: d.values.filter(function(d,i) { + return lines.x()(d,i) >= extent[0] && lines.x()(d,i) <= extent[1]; + }) + } + }) + ); + + // Update Main (Focus) X Axis + if (dataBars.length) { + x = bars.xScale(); + } else { + x = lines.xScale(); + } + + xAxis + .scale(x) + ._ticks( nv.utils.calcTicksX(availableWidth/100, data) ) + .tickSize(-availableHeight1, 0); + + xAxis.domain([Math.ceil(extent[0]), Math.floor(extent[1])]); + + g.select('.nv-x.nv-axis').transition().duration(transitionDuration) + .call(xAxis); + + // Update Main (Focus) Bars and Lines + focusBarsWrap.transition().duration(transitionDuration).call(bars); + focusLinesWrap.transition().duration(transitionDuration).call(lines); + + // Setup and Update Main (Focus) Y Axes + g.select('.nv-focus .nv-x.nv-axis') + .attr('transform', 'translate(0,' + y1.range()[0] + ')'); + + y1Axis + .scale(y1) + ._ticks( nv.utils.calcTicksY(availableHeight1/36, data) ) + .tickSize(-availableWidth, 0); + y2Axis + .scale(y2) + ._ticks( nv.utils.calcTicksY(availableHeight1/36, data) ) + .tickSize(dataBars.length ? 0 : -availableWidth, 0); // Show the y2 rules only if y1 has none + + g.select('.nv-focus .nv-y1.nv-axis') + .style('opacity', dataBars.length ? 1 : 0); + g.select('.nv-focus .nv-y2.nv-axis') + .style('opacity', dataLines.length && !dataLines[0].disabled ? 1 : 0) + .attr('transform', 'translate(' + x.range()[1] + ',0)'); + + g.select('.nv-focus .nv-y1.nv-axis').transition().duration(transitionDuration) + .call(y1Axis); + g.select('.nv-focus .nv-y2.nv-axis').transition().duration(transitionDuration) + .call(y2Axis); + } + + onBrush(); + + }); + + return chart; + } + + //============================================================ + // Event Handling/Dispatching (out of chart's scope) + //------------------------------------------------------------ + + lines.dispatch.on('elementMouseover.tooltip', function(evt) { + tooltip + .duration(100) + .valueFormatter(function(d, i) { + return y2Axis.tickFormat()(d, i); + }) + .data(evt) + .position(evt.pos) + .hidden(false); + }); + + lines.dispatch.on('elementMouseout.tooltip', function(evt) { + tooltip.hidden(true) + }); + + bars.dispatch.on('elementMouseover.tooltip', function(evt) { + evt.value = chart.x()(evt.data); + evt['series'] = { + value: chart.y()(evt.data), + color: evt.color + }; + tooltip + .duration(0) + .valueFormatter(function(d, i) { + return y1Axis.tickFormat()(d, i); + }) + .data(evt) + .hidden(false); + }); + + bars.dispatch.on('elementMouseout.tooltip', function(evt) { + tooltip.hidden(true); + }); + + bars.dispatch.on('elementMousemove.tooltip', function(evt) { + tooltip.position({top: d3.event.pageY, left: d3.event.pageX})(); + }); + + //============================================================ + + + //============================================================ + // Expose Public Variables + //------------------------------------------------------------ + + // expose chart's sub-components + chart.dispatch = dispatch; + chart.legend = legend; + chart.lines = lines; + chart.lines2 = lines2; + chart.bars = bars; + chart.bars2 = bars2; + chart.xAxis = xAxis; + chart.x2Axis = x2Axis; + chart.y1Axis = y1Axis; + chart.y2Axis = y2Axis; + chart.y3Axis = y3Axis; + chart.y4Axis = y4Axis; + chart.tooltip = tooltip; + + chart.options = nv.utils.optionsFunc.bind(chart); + + chart._options = Object.create({}, { + // simple options, just get/set the necessary values + width: {get: function(){return width;}, set: function(_){width=_;}}, + height: {get: function(){return height;}, set: function(_){height=_;}}, + showLegend: {get: function(){return showLegend;}, set: function(_){showLegend=_;}}, + brushExtent: {get: function(){return brushExtent;}, set: function(_){brushExtent=_;}}, + noData: {get: function(){return noData;}, set: function(_){noData=_;}}, + focusEnable: {get: function(){return focusEnable;}, set: function(_){focusEnable=_;}}, + focusHeight: {get: function(){return focusHeight;}, set: function(_){focusHeight=_;}}, + focusShowAxisX: {get: function(){return focusShowAxisX;}, set: function(_){focusShowAxisX=_;}}, + focusShowAxisY: {get: function(){return focusShowAxisY;}, set: function(_){focusShowAxisY=_;}}, + legendLeftAxisHint: {get: function(){return legendLeftAxisHint;}, set: function(_){legendLeftAxisHint=_;}}, + legendRightAxisHint: {get: function(){return legendRightAxisHint;}, set: function(_){legendRightAxisHint=_;}}, + + // deprecated options + tooltips: {get: function(){return tooltip.enabled();}, set: function(_){ + // deprecated after 1.7.1 + nv.deprecated('tooltips', 'use chart.tooltip.enabled() instead'); + tooltip.enabled(!!_); + }}, + tooltipContent: {get: function(){return tooltip.contentGenerator();}, set: function(_){ + // deprecated after 1.7.1 + nv.deprecated('tooltipContent', 'use chart.tooltip.contentGenerator() instead'); + tooltip.contentGenerator(_); + }}, + + // options that require extra logic in the setter + margin: {get: function(){return margin;}, set: function(_){ + margin.top = _.top !== undefined ? _.top : margin.top; + margin.right = _.right !== undefined ? _.right : margin.right; + margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom; + margin.left = _.left !== undefined ? _.left : margin.left; + }}, + duration: {get: function(){return transitionDuration;}, set: function(_){ + transitionDuration = _; + }}, + color: {get: function(){return color;}, set: function(_){ + color = nv.utils.getColor(_); + legend.color(color); + }}, + x: {get: function(){return getX;}, set: function(_){ + getX = _; + lines.x(_); + lines2.x(_); + bars.x(_); + bars2.x(_); + }}, + y: {get: function(){return getY;}, set: function(_){ + getY = _; + lines.y(_); + lines2.y(_); + bars.y(_); + bars2.y(_); + }} + }); + + nv.utils.inheritOptions(chart, lines); + nv.utils.initOptions(chart); + + return chart; +}; +nv.models.lineWithFocusChart = function() { + "use strict"; + + //============================================================ + // Public Variables with Default Settings + //------------------------------------------------------------ + + var lines = nv.models.line() + , lines2 = nv.models.line() + , xAxis = nv.models.axis() + , yAxis = nv.models.axis() + , x2Axis = nv.models.axis() + , y2Axis = nv.models.axis() + , legend = nv.models.legend() + , brush = d3.svg.brush() + , tooltip = nv.models.tooltip() + , interactiveLayer = nv.interactiveGuideline() + ; + + var margin = {top: 30, right: 30, bottom: 30, left: 60} + , margin2 = {top: 0, right: 30, bottom: 20, left: 60} + , color = nv.utils.defaultColor() + , width = null + , height = null + , height2 = 50 + , useInteractiveGuideline = false + , x + , y + , x2 + , y2 + , showLegend = true + , brushExtent = null + , noData = null + , dispatch = d3.dispatch('brush', 'stateChange', 'changeState') + , transitionDuration = 250 + , state = nv.utils.state() + , defaultState = null + ; + + lines.clipEdge(true).duration(0); + lines2.interactive(false); + xAxis.orient('bottom').tickPadding(5); + yAxis.orient('left'); + x2Axis.orient('bottom').tickPadding(5); + y2Axis.orient('left'); + + tooltip.valueFormatter(function(d, i) { + return yAxis.tickFormat()(d, i); + }).headerFormatter(function(d, i) { + return xAxis.tickFormat()(d, i); + }); + + //============================================================ + // Private Variables + //------------------------------------------------------------ + + var stateGetter = function(data) { + return function(){ + return { + active: data.map(function(d) { return !d.disabled }) + }; + } + }; + + var stateSetter = function(data) { + return function(state) { + if (state.active !== undefined) + data.forEach(function(series,i) { + series.disabled = !state.active[i]; + }); + } + }; + + function chart(selection) { + selection.each(function(data) { + var container = d3.select(this), + that = this; + nv.utils.initSVG(container); + var availableWidth = nv.utils.availableWidth(width, container, margin), + availableHeight1 = nv.utils.availableHeight(height, container, margin) - height2, + availableHeight2 = height2 - margin2.top - margin2.bottom; + + chart.update = function() { container.transition().duration(transitionDuration).call(chart) }; + chart.container = this; + + state + .setter(stateSetter(data), chart.update) + .getter(stateGetter(data)) + .update(); + + // DEPRECATED set state.disableddisabled + state.disabled = data.map(function(d) { return !!d.disabled }); + + if (!defaultState) { + var key; + defaultState = {}; + for (key in state) { + if (state[key] instanceof Array) + defaultState[key] = state[key].slice(0); + else + defaultState[key] = state[key]; + } + } + + // Display No Data message if there's nothing to show. + if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) { + nv.utils.noData(chart, container) + return chart; + } else { + container.selectAll('.nv-noData').remove(); + } + + // Setup Scales + x = lines.xScale(); + y = lines.yScale(); + x2 = lines2.xScale(); + y2 = lines2.yScale(); + + // Setup containers and skeleton of chart + var wrap = container.selectAll('g.nv-wrap.nv-lineWithFocusChart').data([data]); + var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-lineWithFocusChart').append('g'); + var g = wrap.select('g'); + + gEnter.append('g').attr('class', 'nv-legendWrap'); + + var focusEnter = gEnter.append('g').attr('class', 'nv-focus'); + focusEnter.append('g').attr('class', 'nv-x nv-axis'); + focusEnter.append('g').attr('class', 'nv-y nv-axis'); + focusEnter.append('g').attr('class', 'nv-linesWrap'); + focusEnter.append('g').attr('class', 'nv-interactive'); + + var contextEnter = gEnter.append('g').attr('class', 'nv-context'); + contextEnter.append('g').attr('class', 'nv-x nv-axis'); + contextEnter.append('g').attr('class', 'nv-y nv-axis'); + contextEnter.append('g').attr('class', 'nv-linesWrap'); + contextEnter.append('g').attr('class', 'nv-brushBackground'); + contextEnter.append('g').attr('class', 'nv-x nv-brush'); + + // Legend + if (showLegend) { + legend.width(availableWidth); + + g.select('.nv-legendWrap') + .datum(data) + .call(legend); + + if ( margin.top != legend.height()) { + margin.top = legend.height(); + availableHeight1 = nv.utils.availableHeight(height, container, margin) - height2; + } + + g.select('.nv-legendWrap') + .attr('transform', 'translate(0,' + (-margin.top) +')') + } + + wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); + + + //Set up interactive layer + if (useInteractiveGuideline) { + interactiveLayer + .width(availableWidth) + .height(availableHeight1) + .margin({left:margin.left, top:margin.top}) + .svgContainer(container) + .xScale(x); + wrap.select(".nv-interactive").call(interactiveLayer); + } + + // Main Chart Component(s) + lines + .width(availableWidth) + .height(availableHeight1) + .color( + data + .map(function(d,i) { + return d.color || color(d, i); + }) + .filter(function(d,i) { + return !data[i].disabled; + }) + ); + + lines2 + .defined(lines.defined()) + .width(availableWidth) + .height(availableHeight2) + .color( + data + .map(function(d,i) { + return d.color || color(d, i); + }) + .filter(function(d,i) { + return !data[i].disabled; + }) + ); + + g.select('.nv-context') + .attr('transform', 'translate(0,' + ( availableHeight1 + margin.bottom + margin2.top) + ')') + + var contextLinesWrap = g.select('.nv-context .nv-linesWrap') + .datum(data.filter(function(d) { return !d.disabled })) + + d3.transition(contextLinesWrap).call(lines2); + + // Setup Main (Focus) Axes + xAxis + .scale(x) + ._ticks( nv.utils.calcTicksX(availableWidth/100, data) ) + .tickSize(-availableHeight1, 0); + + yAxis + .scale(y) + ._ticks( nv.utils.calcTicksY(availableHeight1/36, data) ) + .tickSize( -availableWidth, 0); + + g.select('.nv-focus .nv-x.nv-axis') + .attr('transform', 'translate(0,' + availableHeight1 + ')'); + + // Setup Brush + brush + .x(x2) + .on('brush', function() { + onBrush(); + }); + + if (brushExtent) brush.extent(brushExtent); + + var brushBG = g.select('.nv-brushBackground').selectAll('g') + .data([brushExtent || brush.extent()]) + + var brushBGenter = brushBG.enter() + .append('g'); + + brushBGenter.append('rect') + .attr('class', 'left') + .attr('x', 0) + .attr('y', 0) + .attr('height', availableHeight2); + + brushBGenter.append('rect') + .attr('class', 'right') + .attr('x', 0) + .attr('y', 0) + .attr('height', availableHeight2); + + var gBrush = g.select('.nv-x.nv-brush') + .call(brush); + gBrush.selectAll('rect') + .attr('height', availableHeight2); + gBrush.selectAll('.resize').append('path').attr('d', resizePath); + + onBrush(); + + // Setup Secondary (Context) Axes + x2Axis + .scale(x2) + ._ticks( nv.utils.calcTicksX(availableWidth/100, data) ) + .tickSize(-availableHeight2, 0); + + g.select('.nv-context .nv-x.nv-axis') + .attr('transform', 'translate(0,' + y2.range()[0] + ')'); + d3.transition(g.select('.nv-context .nv-x.nv-axis')) + .call(x2Axis); + + y2Axis + .scale(y2) + ._ticks( nv.utils.calcTicksY(availableHeight2/36, data) ) + .tickSize( -availableWidth, 0); + + d3.transition(g.select('.nv-context .nv-y.nv-axis')) + .call(y2Axis); + + g.select('.nv-context .nv-x.nv-axis') + .attr('transform', 'translate(0,' + y2.range()[0] + ')'); + + //============================================================ + // Event Handling/Dispatching (in chart's scope) + //------------------------------------------------------------ + + legend.dispatch.on('stateChange', function(newState) { + for (var key in newState) + state[key] = newState[key]; + dispatch.stateChange(state); + chart.update(); + }); + + interactiveLayer.dispatch.on('elementMousemove', function(e) { + lines.clearHighlights(); + var singlePoint, pointIndex, pointXLocation, allData = []; + data + .filter(function(series, i) { + series.seriesIndex = i; + return !series.disabled; + }) + .forEach(function(series,i) { + var extent = brush.empty() ? x2.domain() : brush.extent(); + var currentValues = series.values.filter(function(d,i) { + return lines.x()(d,i) >= extent[0] && lines.x()(d,i) <= extent[1]; + }); + + pointIndex = nv.interactiveBisect(currentValues, e.pointXValue, lines.x()); + var point = currentValues[pointIndex]; + var pointYValue = chart.y()(point, pointIndex); + if (pointYValue != null) { + lines.highlightPoint(i, pointIndex, true); + } + if (point === undefined) return; + if (singlePoint === undefined) singlePoint = point; + if (pointXLocation === undefined) pointXLocation = chart.xScale()(chart.x()(point,pointIndex)); + allData.push({ + key: series.key, + value: chart.y()(point, pointIndex), + color: color(series,series.seriesIndex) + }); + }); + //Highlight the tooltip entry based on which point the mouse is closest to. + if (allData.length > 2) { + var yValue = chart.yScale().invert(e.mouseY); + var domainExtent = Math.abs(chart.yScale().domain()[0] - chart.yScale().domain()[1]); + var threshold = 0.03 * domainExtent; + var indexToHighlight = nv.nearestValueIndex(allData.map(function(d){return d.value}),yValue,threshold); + if (indexToHighlight !== null) + allData[indexToHighlight].highlight = true; + } + + var xValue = xAxis.tickFormat()(chart.x()(singlePoint,pointIndex)); + interactiveLayer.tooltip + .position({left: e.mouseX + margin.left, top: e.mouseY + margin.top}) + .chartContainer(that.parentNode) + .valueFormatter(function(d,i) { + return d == null ? "N/A" : yAxis.tickFormat()(d); + }) + .data({ + value: xValue, + index: pointIndex, + series: allData + })(); + + interactiveLayer.renderGuideLine(pointXLocation); + + }); + + interactiveLayer.dispatch.on("elementMouseout",function(e) { + lines.clearHighlights(); + }); + + dispatch.on('changeState', function(e) { + if (typeof e.disabled !== 'undefined') { + data.forEach(function(series,i) { + series.disabled = e.disabled[i]; + }); + } + chart.update(); + }); + + //============================================================ + // Functions + //------------------------------------------------------------ + + // Taken from crossfilter (http://square.github.com/crossfilter/) + function resizePath(d) { + var e = +(d == 'e'), + x = e ? 1 : -1, + y = availableHeight2 / 3; + return 'M' + (.5 * x) + ',' + y + + 'A6,6 0 0 ' + e + ' ' + (6.5 * x) + ',' + (y + 6) + + 'V' + (2 * y - 6) + + 'A6,6 0 0 ' + e + ' ' + (.5 * x) + ',' + (2 * y) + + 'Z' + + 'M' + (2.5 * x) + ',' + (y + 8) + + 'V' + (2 * y - 8) + + 'M' + (4.5 * x) + ',' + (y + 8) + + 'V' + (2 * y - 8); + } + + + function updateBrushBG() { + if (!brush.empty()) brush.extent(brushExtent); + brushBG + .data([brush.empty() ? x2.domain() : brushExtent]) + .each(function(d,i) { + var leftWidth = x2(d[0]) - x.range()[0], + rightWidth = availableWidth - x2(d[1]); + d3.select(this).select('.left') + .attr('width', leftWidth < 0 ? 0 : leftWidth); + + d3.select(this).select('.right') + .attr('x', x2(d[1])) + .attr('width', rightWidth < 0 ? 0 : rightWidth); + }); + } + + + function onBrush() { + brushExtent = brush.empty() ? null : brush.extent(); + var extent = brush.empty() ? x2.domain() : brush.extent(); + + //The brush extent cannot be less than one. If it is, don't update the line chart. + if (Math.abs(extent[0] - extent[1]) <= 1) { + return; + } + + dispatch.brush({extent: extent, brush: brush}); + + + updateBrushBG(); + + // Update Main (Focus) + var focusLinesWrap = g.select('.nv-focus .nv-linesWrap') + .datum( + data + .filter(function(d) { return !d.disabled }) + .map(function(d,i) { + return { + key: d.key, + area: d.area, + values: d.values.filter(function(d,i) { + return lines.x()(d,i) >= extent[0] && lines.x()(d,i) <= extent[1]; + }) + } + }) + ); + focusLinesWrap.transition().duration(transitionDuration).call(lines); + + + // Update Main (Focus) Axes + g.select('.nv-focus .nv-x.nv-axis').transition().duration(transitionDuration) + .call(xAxis); + g.select('.nv-focus .nv-y.nv-axis').transition().duration(transitionDuration) + .call(yAxis); + } + }); + + return chart; + } + + //============================================================ + // Event Handling/Dispatching (out of chart's scope) + //------------------------------------------------------------ + + lines.dispatch.on('elementMouseover.tooltip', function(evt) { + tooltip.data(evt).position(evt.pos).hidden(false); + }); + + lines.dispatch.on('elementMouseout.tooltip', function(evt) { + tooltip.hidden(true) + }); + + //============================================================ + // Expose Public Variables + //------------------------------------------------------------ + + // expose chart's sub-components + chart.dispatch = dispatch; + chart.legend = legend; + chart.lines = lines; + chart.lines2 = lines2; + chart.xAxis = xAxis; + chart.yAxis = yAxis; + chart.x2Axis = x2Axis; + chart.y2Axis = y2Axis; + chart.interactiveLayer = interactiveLayer; + chart.tooltip = tooltip; + + chart.options = nv.utils.optionsFunc.bind(chart); + + chart._options = Object.create({}, { + // simple options, just get/set the necessary values + width: {get: function(){return width;}, set: function(_){width=_;}}, + height: {get: function(){return height;}, set: function(_){height=_;}}, + focusHeight: {get: function(){return height2;}, set: function(_){height2=_;}}, + showLegend: {get: function(){return showLegend;}, set: function(_){showLegend=_;}}, + brushExtent: {get: function(){return brushExtent;}, set: function(_){brushExtent=_;}}, + defaultState: {get: function(){return defaultState;}, set: function(_){defaultState=_;}}, + noData: {get: function(){return noData;}, set: function(_){noData=_;}}, + + // deprecated options + tooltips: {get: function(){return tooltip.enabled();}, set: function(_){ + // deprecated after 1.7.1 + nv.deprecated('tooltips', 'use chart.tooltip.enabled() instead'); + tooltip.enabled(!!_); + }}, + tooltipContent: {get: function(){return tooltip.contentGenerator();}, set: function(_){ + // deprecated after 1.7.1 + nv.deprecated('tooltipContent', 'use chart.tooltip.contentGenerator() instead'); + tooltip.contentGenerator(_); + }}, + + // options that require extra logic in the setter + margin: {get: function(){return margin;}, set: function(_){ + margin.top = _.top !== undefined ? _.top : margin.top; + margin.right = _.right !== undefined ? _.right : margin.right; + margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom; + margin.left = _.left !== undefined ? _.left : margin.left; + }}, + color: {get: function(){return color;}, set: function(_){ + color = nv.utils.getColor(_); + legend.color(color); + // line color is handled above? + }}, + interpolate: {get: function(){return lines.interpolate();}, set: function(_){ + lines.interpolate(_); + lines2.interpolate(_); + }}, + xTickFormat: {get: function(){return xAxis.tickFormat();}, set: function(_){ + xAxis.tickFormat(_); + x2Axis.tickFormat(_); + }}, + yTickFormat: {get: function(){return yAxis.tickFormat();}, set: function(_){ + yAxis.tickFormat(_); + y2Axis.tickFormat(_); + }}, + duration: {get: function(){return transitionDuration;}, set: function(_){ + transitionDuration=_; + yAxis.duration(transitionDuration); + y2Axis.duration(transitionDuration); + xAxis.duration(transitionDuration); + x2Axis.duration(transitionDuration); + }}, + x: {get: function(){return lines.x();}, set: function(_){ + lines.x(_); + lines2.x(_); + }}, + y: {get: function(){return lines.y();}, set: function(_){ + lines.y(_); + lines2.y(_); + }}, + useInteractiveGuideline: {get: function(){return useInteractiveGuideline;}, set: function(_){ + useInteractiveGuideline = _; + if (useInteractiveGuideline) { + lines.interactive(false); + lines.useVoronoi(false); + } + }} + }); + + nv.utils.inheritOptions(chart, lines); + nv.utils.initOptions(chart); + + return chart; +}; +nv.models.multiBar = function() { + "use strict"; + + //============================================================ + // Public Variables with Default Settings + //------------------------------------------------------------ + + var margin = {top: 0, right: 0, bottom: 0, left: 0} + , width = 960 + , height = 500 + , x = d3.scale.ordinal() + , y = d3.scale.linear() + , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one + , container = null + , getX = function(d) { return d.x } + , getY = function(d) { return d.y } + , getYerr = function(d) { return d.yErr } + , forceY = [0] // 0 is forced by default.. this makes sense for the majority of bar graphs... user can always do chart.forceY([]) to remove + , clipEdge = true + , stacked = false + , stackOffset = 'zero' // options include 'silhouette', 'wiggle', 'expand', 'zero', or a custom function + , color = nv.utils.defaultColor() + , errorBarColor = nv.utils.defaultColor() + , hideable = false + , barColor = null // adding the ability to set the color for each rather than the whole group + , disabled // used in conjunction with barColor to communicate from multiBarHorizontalChart what series are disabled + , duration = 500 + , xDomain + , yDomain + , xRange + , yRange + , groupSpacing = 0.1 + , dispatch = d3.dispatch('chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout', 'elementMousemove', 'renderEnd') + ; + + //============================================================ + // Private Variables + //------------------------------------------------------------ + + var x0, y0 //used to store previous scales + , renderWatch = nv.utils.renderWatch(dispatch, duration) + ; + + var last_datalength = 0; + + function chart(selection) { + renderWatch.reset(); + selection.each(function(data) { + var availableWidth = width - margin.left - margin.right, + availableHeight = height - margin.top - margin.bottom; + + container = d3.select(this); + nv.utils.initSVG(container); + var nonStackableCount = 0; + // This function defines the requirements for render complete + var endFn = function(d, i) { + if (d.series === data.length - 1 && i === data[0].values.length - 1) + return true; + return false; + }; + + if(hideable && data.length) hideable = [{ + values: data[0].values.map(function(d) { + return { + x: d.x, + y: 0, + series: d.series, + size: 0.01 + };} + )}]; + + if (stacked) { + var parsed = d3.layout.stack() + .offset(stackOffset) + .values(function(d){ return d.values }) + .y(getY) + (!data.length && hideable ? hideable : data); + + parsed.forEach(function(series, i){ + // if series is non-stackable, use un-parsed data + if (series.nonStackable) { + data[i].nonStackableSeries = nonStackableCount++; + parsed[i] = data[i]; + } else { + // don't stack this seires on top of the nonStackable seriees + if (i > 0 && parsed[i - 1].nonStackable){ + parsed[i].values.map(function(d,j){ + d.y0 -= parsed[i - 1].values[j].y; + d.y1 = d.y0 + d.y; + }); + } + } + }); + data = parsed; + } + //add series index and key to each data point for reference + data.forEach(function(series, i) { + series.values.forEach(function(point) { + point.series = i; + point.key = series.key; + }); + }); + + // HACK for negative value stacking + if (stacked) { + data[0].values.map(function(d,i) { + var posBase = 0, negBase = 0; + data.map(function(d, idx) { + if (!data[idx].nonStackable) { + var f = d.values[i] + f.size = Math.abs(f.y); + if (f.y<0) { + f.y1 = negBase; + negBase = negBase - f.size; + } else + { + f.y1 = f.size + posBase; + posBase = posBase + f.size; + } + } + + }); + }); + } + // Setup Scales + // remap and flatten the data for use in calculating the scales' domains + var seriesData = (xDomain && yDomain) ? [] : // if we know xDomain and yDomain, no need to calculate + data.map(function(d, idx) { + return d.values.map(function(d,i) { + return { x: getX(d,i), y: getY(d,i), y0: d.y0, y1: d.y1, idx:idx, yErr: getYerr(d,i)} + }) + }); + + x.domain(xDomain || d3.merge(seriesData).map(function(d) { return d.x })) + .rangeBands(xRange || [0, availableWidth], groupSpacing); + + y.domain(yDomain || d3.extent(d3.merge( + d3.merge(seriesData).map(function(d) { + var domain = d.y; + // increase the domain range if this series is stackable + if (stacked && !data[d.idx].nonStackable) { + if (d.y > 0){ + domain = d.y1 + } else { + domain = d.y1 + d.y + } + } + var yerr = d.yErr; + if (yerr) { + if (yerr.length) { + return [domain + yerr[0], domain + yerr[1]]; + } else { + yerr = Math.abs(yerr) + return [domain - yerr, domain + yerr]; + } + } else { + return [domain]; + } + })).concat(forceY))) + .range(yRange || [availableHeight, 0]); + + // If scale's domain don't have a range, slightly adjust to make one... so a chart can show a single data point + if (x.domain()[0] === x.domain()[1]) + x.domain()[0] ? + x.domain([x.domain()[0] - x.domain()[0] * 0.01, x.domain()[1] + x.domain()[1] * 0.01]) + : x.domain([-1,1]); + + if (y.domain()[0] === y.domain()[1]) + y.domain()[0] ? + y.domain([y.domain()[0] + y.domain()[0] * 0.01, y.domain()[1] - y.domain()[1] * 0.01]) + : y.domain([-1,1]); + + x0 = x0 || x; + y0 = y0 || y; + + // Setup containers and skeleton of chart + var wrap = container.selectAll('g.nv-wrap.nv-multibar').data([data]); + var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-multibar'); + var defsEnter = wrapEnter.append('defs'); + var gEnter = wrapEnter.append('g'); + var g = wrap.select('g'); + + gEnter.append('g').attr('class', 'nv-groups'); + wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); + + defsEnter.append('clipPath') + .attr('id', 'nv-edge-clip-' + id) + .append('rect'); + wrap.select('#nv-edge-clip-' + id + ' rect') + .attr('width', availableWidth) + .attr('height', availableHeight); + + g.attr('clip-path', clipEdge ? 'url(#nv-edge-clip-' + id + ')' : ''); + + var groups = wrap.select('.nv-groups').selectAll('.nv-group') + .data(function(d) { return d }, function(d,i) { return i }); + groups.enter().append('g') + .style('stroke-opacity', 1e-6) + .style('fill-opacity', 1e-6); + + var exitTransition = renderWatch + .transition(groups.exit().selectAll('g.nv-bar'), 'multibarExit', Math.min(100, duration)) + .attr('y', function(d, i, j) { + var yVal = y0(0) || 0; + if (stacked) { + if (data[d.series] && !data[d.series].nonStackable) { + yVal = y0(d.y0); + } + } + return yVal; + }) + .attr('height', 0) + .remove(); + if (exitTransition.delay) + exitTransition.delay(function(d,i) { + var delay = i * (duration / (last_datalength + 1)) - i; + return delay; + }); + groups + .attr('class', function(d,i) { return 'nv-group nv-series-' + i }) + .classed('hover', function(d) { return d.hover }) + .style('fill', function(d,i){ return color(d, i) }) + .style('stroke', function(d,i){ return color(d, i) }); + groups + .style('stroke-opacity', 1) + .style('fill-opacity', 0.75); + + var bars = groups.selectAll('g.nv-bar') + .data(function(d) { return (hideable && !data.length) ? hideable.values : d.values }); + bars.exit().remove(); + + var barsEnter = bars.enter().append('g') + .attr('class', function(d,i) { return getY(d,i) < 0 ? 'nv-bar negative' : 'nv-bar positive'}) + .attr('transform', function(d,i,j) { + var _x = stacked && !data[j].nonStackable ? 0 : (j * x.rangeBand() / data.length ); + var _y = y0(stacked && !data[j].nonStackable ? d.y0 : 0) || 0; + return 'translate(' + _x + ',' + _y + ')'; + }) + ; + + barsEnter.append('rect') + .attr('height', 0) + .attr('width', function(d,i,j) { return x.rangeBand() / (stacked && !data[j].nonStackable ? 1 : data.length) }) + .style('fill', function(d,i,j){ return color(d, j, i); }) + .style('stroke', function(d,i,j){ return color(d, j, i); }) + ; + bars + .on('mouseover', function(d,i) { //TODO: figure out why j works above, but not here + d3.select(this).classed('hover', true); + dispatch.elementMouseover({ + data: d, + index: i, + color: d3.select(this).style("fill") + }); + }) + .on('mouseout', function(d,i) { + d3.select(this).classed('hover', false); + dispatch.elementMouseout({ + data: d, + index: i, + color: d3.select(this).style("fill") + }); + }) + .on('mousemove', function(d,i) { + dispatch.elementMousemove({ + data: d, + index: i, + color: d3.select(this).style("fill") + }); + }) + .on('click', function(d,i) { + dispatch.elementClick({ + data: d, + index: i, + color: d3.select(this).style("fill") + }); + d3.event.stopPropagation(); + }) + .on('dblclick', function(d,i) { + dispatch.elementDblClick({ + data: d, + index: i, + color: d3.select(this).style("fill") + }); + d3.event.stopPropagation(); + }); + + if (getYerr(data[0].values[0], 0)) { + barsEnter.append('polyline'); + + bars.select('polyline') + .attr('fill', 'none') + .attr('stroke', function(d, i, j) { return errorBarColor(d, j, i); }) + .attr('points', function(d,i) { + var yerr = getYerr(d,i) + , mid = 0.8 * x.rangeBand() / ((stacked ? 1 : data.length) * 2); + yerr = yerr.length ? yerr : [-Math.abs(yerr), Math.abs(yerr)]; + yerr = yerr.map(function(e) { return y(e) - y(0); }); + var a = [[-mid, yerr[0]], [mid, yerr[0]], [0, yerr[0]], [0, yerr[1]], [-mid, yerr[1]], [mid, yerr[1]]]; + return a.map(function (path) { return path.join(',') }).join(' '); + }) + .attr('transform', function(d, i) { + var xOffset = x.rangeBand() / ((stacked ? 1 : data.length) * 2); + var yOffset = getY(d,i) < 0 ? y(getY(d, i)) - y(0) : 0; + return 'translate(' + xOffset + ', ' + yOffset + ')'; + }) + } + + bars + .attr('class', function(d,i) { return getY(d,i) < 0 ? 'nv-bar negative' : 'nv-bar positive'}) + + if (barColor) { + if (!disabled) disabled = data.map(function() { return true }); + bars.select('rect') + .style('fill', function(d,i,j) { return d3.rgb(barColor(d,i)).darker( disabled.map(function(d,i) { return i }).filter(function(d,i){ return !disabled[i] })[j] ).toString(); }) + .style('stroke', function(d,i,j) { return d3.rgb(barColor(d,i)).darker( disabled.map(function(d,i) { return i }).filter(function(d,i){ return !disabled[i] })[j] ).toString(); }); + } + + var barSelection = + bars.watchTransition(renderWatch, 'multibar', Math.min(250, duration)) + .delay(function(d,i) { + return i * duration / data[0].values.length; + }); + if (stacked){ + barSelection + .attr('transform', function(d,i,j) { + var yVal = 0; + // if stackable, stack it on top of the previous series + if (!data[j].nonStackable) { + yVal = y(d.y1); + } else { + if (getY(d,i) < 0){ + yVal = y(0); + } else { + if (y(0) - y(getY(d,i)) < -1){ + yVal = y(0) - 1; + } else { + yVal = y(getY(d, i)) || 0; + } + } + } + var width = 0; + if (data[j].nonStackable) { + width = d.series * x.rangeBand() / data.length; + if (data.length !== nonStackableCount){ + width = data[j].nonStackableSeries * x.rangeBand()/(nonStackableCount*2); + } + } + var xVal = width + x(getX(d, i)); + return 'translate(' + xVal + ',' + yVal + ')'; + }) + .select('rect') + .attr('height', function(d,i,j) { + if (!data[j].nonStackable) { + return Math.max(Math.abs(y(d.y+d.y0) - y(d.y0)), 1); + } else { + return Math.max(Math.abs(y(getY(d,i)) - y(0)),1) || 0; + } + }) + .attr('width', function(d,i,j){ + if (!data[j].nonStackable) { + return x.rangeBand(); + } else { + // if all series are nonStacable, take the full width + var width = (x.rangeBand() / nonStackableCount); + // otherwise, nonStackable graph will be only taking the half-width + // of the x rangeBand + if (data.length !== nonStackableCount) { + width = x.rangeBand()/(nonStackableCount*2); + } + return width; + } + }); + } + else { + barSelection.attr('transform', function(d,i) { + var xVal = d.series * x.rangeBand() / data.length + x(getX(d, i)); + var yVal = getY(d,i) < 0 ? + y(0) : + y(0) - y(getY(d,i)) < 1 ? + y(0) - 1 : + y(getY(d,i)) || 0; + return 'translate(' + xVal + ',' + yVal + ')'; + }) + .select('rect') + .attr('width', x.rangeBand() / data.length) + .attr('height', function(d,i) { + return Math.max(Math.abs(y(getY(d,i)) - y(0)),1) || 0; + }); + } + + //store old scales for use in transitions on update + x0 = x.copy(); + y0 = y.copy(); + + // keep track of the last data value length for transition calculations + if (data[0] && data[0].values) { + last_datalength = data[0].values.length; + } + + }); + + renderWatch.renderEnd('multibar immediate'); + + return chart; + } + + //============================================================ + // Expose Public Variables + //------------------------------------------------------------ + + chart.dispatch = dispatch; + + chart.options = nv.utils.optionsFunc.bind(chart); + + chart._options = Object.create({}, { + // simple options, just get/set the necessary values + width: {get: function(){return width;}, set: function(_){width=_;}}, + height: {get: function(){return height;}, set: function(_){height=_;}}, + x: {get: function(){return getX;}, set: function(_){getX=_;}}, + y: {get: function(){return getY;}, set: function(_){getY=_;}}, + yErr: {get: function(){return getYerr;}, set: function(_){getYerr=_;}}, + xScale: {get: function(){return x;}, set: function(_){x=_;}}, + yScale: {get: function(){return y;}, set: function(_){y=_;}}, + xDomain: {get: function(){return xDomain;}, set: function(_){xDomain=_;}}, + yDomain: {get: function(){return yDomain;}, set: function(_){yDomain=_;}}, + xRange: {get: function(){return xRange;}, set: function(_){xRange=_;}}, + yRange: {get: function(){return yRange;}, set: function(_){yRange=_;}}, + forceY: {get: function(){return forceY;}, set: function(_){forceY=_;}}, + stacked: {get: function(){return stacked;}, set: function(_){stacked=_;}}, + stackOffset: {get: function(){return stackOffset;}, set: function(_){stackOffset=_;}}, + clipEdge: {get: function(){return clipEdge;}, set: function(_){clipEdge=_;}}, + disabled: {get: function(){return disabled;}, set: function(_){disabled=_;}}, + id: {get: function(){return id;}, set: function(_){id=_;}}, + hideable: {get: function(){return hideable;}, set: function(_){hideable=_;}}, + groupSpacing:{get: function(){return groupSpacing;}, set: function(_){groupSpacing=_;}}, + + // options that require extra logic in the setter + margin: {get: function(){return margin;}, set: function(_){ + margin.top = _.top !== undefined ? _.top : margin.top; + margin.right = _.right !== undefined ? _.right : margin.right; + margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom; + margin.left = _.left !== undefined ? _.left : margin.left; + }}, + duration: {get: function(){return duration;}, set: function(_){ + duration = _; + renderWatch.reset(duration); + }}, + color: {get: function(){return color;}, set: function(_){ + color = nv.utils.getColor(_); + }}, + barColor: {get: function(){return barColor;}, set: function(_){ + barColor = _ ? nv.utils.getColor(_) : null; + }}, + errorBarColor: {get: function(){return errorBarColor;}, set: function(_){ + errorBarColor = _ ? nv.utils.getColor(_) : null; + }} + }); + + nv.utils.initOptions(chart); + + return chart; +}; + +nv.models.multiBarChart = function() { + "use strict"; + + //============================================================ + // Public Variables with Default Settings + //------------------------------------------------------------ + + var multibar = nv.models.multiBar() + , xAxis = nv.models.axis() + , yAxis = nv.models.axis() + , legend = nv.models.legend() + , controls = nv.models.legend() + , tooltip = nv.models.tooltip() + ; + + var margin = {top: 30, right: 20, bottom: 50, left: 60} + , width = null + , height = null + , color = nv.utils.defaultColor() + , showControls = true + , controlLabels = {} + , showLegend = true + , showXAxis = true + , showYAxis = true + , rightAlignYAxis = false + , reduceXTicks = true // if false a tick will show for every data point + , staggerLabels = false + , rotateLabels = 0 + , x //can be accessed via chart.xScale() + , y //can be accessed via chart.yScale() + , state = nv.utils.state() + , defaultState = null + , noData = null + , dispatch = d3.dispatch('stateChange', 'changeState', 'renderEnd') + , controlWidth = function() { return showControls ? 180 : 0 } + , duration = 250 + ; + + state.stacked = false // DEPRECATED Maintained for backward compatibility + + multibar.stacked(false); + xAxis + .orient('bottom') + .tickPadding(7) + .showMaxMin(false) + .tickFormat(function(d) { return d }) + ; + yAxis + .orient((rightAlignYAxis) ? 'right' : 'left') + .tickFormat(d3.format(',.1f')) + ; + + tooltip + .duration(0) + .valueFormatter(function(d, i) { + return yAxis.tickFormat()(d, i); + }) + .headerFormatter(function(d, i) { + return xAxis.tickFormat()(d, i); + }); + + controls.updateState(false); + + //============================================================ + // Private Variables + //------------------------------------------------------------ + + var renderWatch = nv.utils.renderWatch(dispatch); + var stacked = false; + + var stateGetter = function(data) { + return function(){ + return { + active: data.map(function(d) { return !d.disabled }), + stacked: stacked + }; + } + }; + + var stateSetter = function(data) { + return function(state) { + if (state.stacked !== undefined) + stacked = state.stacked; + if (state.active !== undefined) + data.forEach(function(series,i) { + series.disabled = !state.active[i]; + }); + } + }; + + function chart(selection) { + renderWatch.reset(); + renderWatch.models(multibar); + if (showXAxis) renderWatch.models(xAxis); + if (showYAxis) renderWatch.models(yAxis); + + selection.each(function(data) { + var container = d3.select(this), + that = this; + nv.utils.initSVG(container); + var availableWidth = nv.utils.availableWidth(width, container, margin), + availableHeight = nv.utils.availableHeight(height, container, margin); + + chart.update = function() { + if (duration === 0) + container.call(chart); + else + container.transition() + .duration(duration) + .call(chart); + }; + chart.container = this; + + state + .setter(stateSetter(data), chart.update) + .getter(stateGetter(data)) + .update(); + + // DEPRECATED set state.disableddisabled + state.disabled = data.map(function(d) { return !!d.disabled }); + + if (!defaultState) { + var key; + defaultState = {}; + for (key in state) { + if (state[key] instanceof Array) + defaultState[key] = state[key].slice(0); + else + defaultState[key] = state[key]; + } + } + + // Display noData message if there's nothing to show. + if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) { + nv.utils.noData(chart, container) + return chart; + } else { + container.selectAll('.nv-noData').remove(); + } + + // Setup Scales + x = multibar.xScale(); + y = multibar.yScale(); + + // Setup containers and skeleton of chart + var wrap = container.selectAll('g.nv-wrap.nv-multiBarWithLegend').data([data]); + var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-multiBarWithLegend').append('g'); + var g = wrap.select('g'); + + gEnter.append('g').attr('class', 'nv-x nv-axis'); + gEnter.append('g').attr('class', 'nv-y nv-axis'); + gEnter.append('g').attr('class', 'nv-barsWrap'); + gEnter.append('g').attr('class', 'nv-legendWrap'); + gEnter.append('g').attr('class', 'nv-controlsWrap'); + + // Legend + if (showLegend) { + legend.width(availableWidth - controlWidth()); + + g.select('.nv-legendWrap') + .datum(data) + .call(legend); + + if ( margin.top != legend.height()) { + margin.top = legend.height(); + availableHeight = nv.utils.availableHeight(height, container, margin); + } + + g.select('.nv-legendWrap') + .attr('transform', 'translate(' + controlWidth() + ',' + (-margin.top) +')'); + } + + // Controls + if (showControls) { + var controlsData = [ + { key: controlLabels.grouped || 'Grouped', disabled: multibar.stacked() }, + { key: controlLabels.stacked || 'Stacked', disabled: !multibar.stacked() } + ]; + + controls.width(controlWidth()).color(['#444', '#444', '#444']); + g.select('.nv-controlsWrap') + .datum(controlsData) + .attr('transform', 'translate(0,' + (-margin.top) +')') + .call(controls); + } + + wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); + if (rightAlignYAxis) { + g.select(".nv-y.nv-axis") + .attr("transform", "translate(" + availableWidth + ",0)"); + } + + // Main Chart Component(s) + multibar + .disabled(data.map(function(series) { return series.disabled })) + .width(availableWidth) + .height(availableHeight) + .color(data.map(function(d,i) { + return d.color || color(d, i); + }).filter(function(d,i) { return !data[i].disabled })); + + + var barsWrap = g.select('.nv-barsWrap') + .datum(data.filter(function(d) { return !d.disabled })); + + barsWrap.call(multibar); + + // Setup Axes + if (showXAxis) { + xAxis + .scale(x) + ._ticks( nv.utils.calcTicksX(availableWidth/100, data) ) + .tickSize(-availableHeight, 0); + + g.select('.nv-x.nv-axis') + .attr('transform', 'translate(0,' + y.range()[0] + ')'); + g.select('.nv-x.nv-axis') + .call(xAxis); + + var xTicks = g.select('.nv-x.nv-axis > g').selectAll('g'); + + xTicks + .selectAll('line, text') + .style('opacity', 1) + + if (staggerLabels) { + var getTranslate = function(x,y) { + return "translate(" + x + "," + y + ")"; + }; + + var staggerUp = 5, staggerDown = 17; //pixels to stagger by + // Issue #140 + xTicks + .selectAll("text") + .attr('transform', function(d,i,j) { + return getTranslate(0, (j % 2 == 0 ? staggerUp : staggerDown)); + }); + + var totalInBetweenTicks = d3.selectAll(".nv-x.nv-axis .nv-wrap g g text")[0].length; + g.selectAll(".nv-x.nv-axis .nv-axisMaxMin text") + .attr("transform", function(d,i) { + return getTranslate(0, (i === 0 || totalInBetweenTicks % 2 !== 0) ? staggerDown : staggerUp); + }); + } + + if (reduceXTicks) + xTicks + .filter(function(d,i) { + return i % Math.ceil(data[0].values.length / (availableWidth / 100)) !== 0; + }) + .selectAll('text, line') + .style('opacity', 0); + + if(rotateLabels) + xTicks + .selectAll('.tick text') + .attr('transform', 'rotate(' + rotateLabels + ' 0,0)') + .style('text-anchor', rotateLabels > 0 ? 'start' : 'end'); + + g.select('.nv-x.nv-axis').selectAll('g.nv-axisMaxMin text') + .style('opacity', 1); + } + + if (showYAxis) { + yAxis + .scale(y) + ._ticks( nv.utils.calcTicksY(availableHeight/36, data) ) + .tickSize( -availableWidth, 0); + + g.select('.nv-y.nv-axis') + .call(yAxis); + } + + //============================================================ + // Event Handling/Dispatching (in chart's scope) + //------------------------------------------------------------ + + legend.dispatch.on('stateChange', function(newState) { + for (var key in newState) + state[key] = newState[key]; + dispatch.stateChange(state); + chart.update(); + }); + + controls.dispatch.on('legendClick', function(d,i) { + if (!d.disabled) return; + controlsData = controlsData.map(function(s) { + s.disabled = true; + return s; + }); + d.disabled = false; + + switch (d.key) { + case 'Grouped': + case controlLabels.grouped: + multibar.stacked(false); + break; + case 'Stacked': + case controlLabels.stacked: + multibar.stacked(true); + break; + } + + state.stacked = multibar.stacked(); + dispatch.stateChange(state); + chart.update(); + }); + + // Update chart from a state object passed to event handler + dispatch.on('changeState', function(e) { + if (typeof e.disabled !== 'undefined') { + data.forEach(function(series,i) { + series.disabled = e.disabled[i]; + }); + state.disabled = e.disabled; + } + if (typeof e.stacked !== 'undefined') { + multibar.stacked(e.stacked); + state.stacked = e.stacked; + stacked = e.stacked; + } + chart.update(); + }); + }); + + renderWatch.renderEnd('multibarchart immediate'); + return chart; + } + + //============================================================ + // Event Handling/Dispatching (out of chart's scope) + //------------------------------------------------------------ + + multibar.dispatch.on('elementMouseover.tooltip', function(evt) { + evt.value = chart.x()(evt.data); + evt['series'] = { + key: evt.data.key, + value: chart.y()(evt.data), + color: evt.color + }; + tooltip.data(evt).hidden(false); + }); + + multibar.dispatch.on('elementMouseout.tooltip', function(evt) { + tooltip.hidden(true); + }); + + multibar.dispatch.on('elementMousemove.tooltip', function(evt) { + tooltip.position({top: d3.event.pageY, left: d3.event.pageX})(); + }); + + //============================================================ + // Expose Public Variables + //------------------------------------------------------------ + + // expose chart's sub-components + chart.dispatch = dispatch; + chart.multibar = multibar; + chart.legend = legend; + chart.controls = controls; + chart.xAxis = xAxis; + chart.yAxis = yAxis; + chart.state = state; + chart.tooltip = tooltip; + + chart.options = nv.utils.optionsFunc.bind(chart); + + chart._options = Object.create({}, { + // simple options, just get/set the necessary values + width: {get: function(){return width;}, set: function(_){width=_;}}, + height: {get: function(){return height;}, set: function(_){height=_;}}, + showLegend: {get: function(){return showLegend;}, set: function(_){showLegend=_;}}, + showControls: {get: function(){return showControls;}, set: function(_){showControls=_;}}, + controlLabels: {get: function(){return controlLabels;}, set: function(_){controlLabels=_;}}, + showXAxis: {get: function(){return showXAxis;}, set: function(_){showXAxis=_;}}, + showYAxis: {get: function(){return showYAxis;}, set: function(_){showYAxis=_;}}, + defaultState: {get: function(){return defaultState;}, set: function(_){defaultState=_;}}, + noData: {get: function(){return noData;}, set: function(_){noData=_;}}, + reduceXTicks: {get: function(){return reduceXTicks;}, set: function(_){reduceXTicks=_;}}, + rotateLabels: {get: function(){return rotateLabels;}, set: function(_){rotateLabels=_;}}, + staggerLabels: {get: function(){return staggerLabels;}, set: function(_){staggerLabels=_;}}, + + // deprecated options + tooltips: {get: function(){return tooltip.enabled();}, set: function(_){ + // deprecated after 1.7.1 + nv.deprecated('tooltips', 'use chart.tooltip.enabled() instead'); + tooltip.enabled(!!_); + }}, + tooltipContent: {get: function(){return tooltip.contentGenerator();}, set: function(_){ + // deprecated after 1.7.1 + nv.deprecated('tooltipContent', 'use chart.tooltip.contentGenerator() instead'); + tooltip.contentGenerator(_); + }}, + + // options that require extra logic in the setter + margin: {get: function(){return margin;}, set: function(_){ + margin.top = _.top !== undefined ? _.top : margin.top; + margin.right = _.right !== undefined ? _.right : margin.right; + margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom; + margin.left = _.left !== undefined ? _.left : margin.left; + }}, + duration: {get: function(){return duration;}, set: function(_){ + duration = _; + multibar.duration(duration); + xAxis.duration(duration); + yAxis.duration(duration); + renderWatch.reset(duration); + }}, + color: {get: function(){return color;}, set: function(_){ + color = nv.utils.getColor(_); + legend.color(color); + }}, + rightAlignYAxis: {get: function(){return rightAlignYAxis;}, set: function(_){ + rightAlignYAxis = _; + yAxis.orient( rightAlignYAxis ? 'right' : 'left'); + }}, + barColor: {get: function(){return multibar.barColor;}, set: function(_){ + multibar.barColor(_); + legend.color(function(d,i) {return d3.rgb('#ccc').darker(i * 1.5).toString();}) + }} + }); + + nv.utils.inheritOptions(chart, multibar); + nv.utils.initOptions(chart); + + return chart; +}; + +nv.models.multiBarHorizontal = function() { + "use strict"; + + //============================================================ + // Public Variables with Default Settings + //------------------------------------------------------------ + + var margin = {top: 0, right: 0, bottom: 0, left: 0} + , width = 960 + , height = 500 + , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one + , container = null + , x = d3.scale.ordinal() + , y = d3.scale.linear() + , getX = function(d) { return d.x } + , getY = function(d) { return d.y } + , getYerr = function(d) { return d.yErr } + , forceY = [0] // 0 is forced by default.. this makes sense for the majority of bar graphs... user can always do chart.forceY([]) to remove + , color = nv.utils.defaultColor() + , barColor = null // adding the ability to set the color for each rather than the whole group + , errorBarColor = nv.utils.defaultColor() + , disabled // used in conjunction with barColor to communicate from multiBarHorizontalChart what series are disabled + , stacked = false + , showValues = false + , showBarLabels = false + , valuePadding = 60 + , groupSpacing = 0.1 + , valueFormat = d3.format(',.2f') + , delay = 1200 + , xDomain + , yDomain + , xRange + , yRange + , duration = 250 + , dispatch = d3.dispatch('chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout', 'elementMousemove', 'renderEnd') + ; + + //============================================================ + // Private Variables + //------------------------------------------------------------ + + var x0, y0; //used to store previous scales + var renderWatch = nv.utils.renderWatch(dispatch, duration); + + function chart(selection) { + renderWatch.reset(); + selection.each(function(data) { + var availableWidth = width - margin.left - margin.right, + availableHeight = height - margin.top - margin.bottom; + + container = d3.select(this); + nv.utils.initSVG(container); + + if (stacked) + data = d3.layout.stack() + .offset('zero') + .values(function(d){ return d.values }) + .y(getY) + (data); + + //add series index and key to each data point for reference + data.forEach(function(series, i) { + series.values.forEach(function(point) { + point.series = i; + point.key = series.key; + }); + }); + + // HACK for negative value stacking + if (stacked) + data[0].values.map(function(d,i) { + var posBase = 0, negBase = 0; + data.map(function(d) { + var f = d.values[i] + f.size = Math.abs(f.y); + if (f.y<0) { + f.y1 = negBase - f.size; + negBase = negBase - f.size; + } else + { + f.y1 = posBase; + posBase = posBase + f.size; + } + }); + }); + + // Setup Scales + // remap and flatten the data for use in calculating the scales' domains + var seriesData = (xDomain && yDomain) ? [] : // if we know xDomain and yDomain, no need to calculate + data.map(function(d) { + return d.values.map(function(d,i) { + return { x: getX(d,i), y: getY(d,i), y0: d.y0, y1: d.y1, yErr: getYerr(d,i) } + }) + }); + + x.domain(xDomain || d3.merge(seriesData).map(function(d) { return d.x })) + .rangeBands(xRange || [0, availableHeight], groupSpacing); + + y.domain(yDomain || d3.extent(d3.merge( + d3.merge(seriesData).map(function(d) { + var domain = d.y; + if (stacked) { + if (d.y > 0){ + domain = d.y1 + d.y + } else { + domain = d.y1 + } + } + var yerr = d.yErr; + if (yerr) { + if (yerr.length) { + return [domain + yerr[0], domain + yerr[1]]; + } else { + yerr = Math.abs(yerr) + return [domain - yerr, domain + yerr]; + } + } else { + return [domain]; + } + })).concat(forceY))) + + if (showValues && !stacked) + y.range(yRange || [(y.domain()[0] < 0 ? valuePadding : 0), availableWidth - (y.domain()[1] > 0 ? valuePadding : 0) ]); + else + y.range(yRange || [0, availableWidth]); + + x0 = x0 || x; + y0 = y0 || d3.scale.linear().domain(y.domain()).range([y(0),y(0)]); + + // Setup containers and skeleton of chart + var wrap = d3.select(this).selectAll('g.nv-wrap.nv-multibarHorizontal').data([data]); + var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-multibarHorizontal'); + var defsEnter = wrapEnter.append('defs'); + var gEnter = wrapEnter.append('g'); + var g = wrap.select('g'); + + gEnter.append('g').attr('class', 'nv-groups'); + wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); + + var groups = wrap.select('.nv-groups').selectAll('.nv-group') + .data(function(d) { return d }, function(d,i) { return i }); + groups.enter().append('g') + .style('stroke-opacity', 1e-6) + .style('fill-opacity', 1e-6); + groups.exit().watchTransition(renderWatch, 'multibarhorizontal: exit groups') + .style('stroke-opacity', 1e-6) + .style('fill-opacity', 1e-6) + .remove(); + groups + .attr('class', function(d,i) { return 'nv-group nv-series-' + i }) + .classed('hover', function(d) { return d.hover }) + .style('fill', function(d,i){ return color(d, i) }) + .style('stroke', function(d,i){ return color(d, i) }); + groups.watchTransition(renderWatch, 'multibarhorizontal: groups') + .style('stroke-opacity', 1) + .style('fill-opacity', .75); + + var bars = groups.selectAll('g.nv-bar') + .data(function(d) { return d.values }); + bars.exit().remove(); + + var barsEnter = bars.enter().append('g') + .attr('transform', function(d,i,j) { + return 'translate(' + y0(stacked ? d.y0 : 0) + ',' + (stacked ? 0 : (j * x.rangeBand() / data.length ) + x(getX(d,i))) + ')' + }); + + barsEnter.append('rect') + .attr('width', 0) + .attr('height', x.rangeBand() / (stacked ? 1 : data.length) ) + + bars + .on('mouseover', function(d,i) { //TODO: figure out why j works above, but not here + d3.select(this).classed('hover', true); + dispatch.elementMouseover({ + data: d, + index: i, + color: d3.select(this).style("fill") + }); + }) + .on('mouseout', function(d,i) { + d3.select(this).classed('hover', false); + dispatch.elementMouseout({ + data: d, + index: i, + color: d3.select(this).style("fill") + }); + }) + .on('mouseout', function(d,i) { + dispatch.elementMouseout({ + data: d, + index: i, + color: d3.select(this).style("fill") + }); + }) + .on('mousemove', function(d,i) { + dispatch.elementMousemove({ + data: d, + index: i, + color: d3.select(this).style("fill") + }); + }) + .on('click', function(d,i) { + dispatch.elementClick({ + data: d, + index: i, + color: d3.select(this).style("fill") + }); + d3.event.stopPropagation(); + }) + .on('dblclick', function(d,i) { + dispatch.elementDblClick({ + data: d, + index: i, + color: d3.select(this).style("fill") + }); + d3.event.stopPropagation(); + }); + + if (getYerr(data[0].values[0], 0)) { + barsEnter.append('polyline'); + + bars.select('polyline') + .attr('fill', 'none') + .attr('stroke', function(d,i,j) { return errorBarColor(d, j, i); }) + .attr('points', function(d,i) { + var xerr = getYerr(d,i) + , mid = 0.8 * x.rangeBand() / ((stacked ? 1 : data.length) * 2); + xerr = xerr.length ? xerr : [-Math.abs(xerr), Math.abs(xerr)]; + xerr = xerr.map(function(e) { return y(e) - y(0); }); + var a = [[xerr[0],-mid], [xerr[0],mid], [xerr[0],0], [xerr[1],0], [xerr[1],-mid], [xerr[1],mid]]; + return a.map(function (path) { return path.join(',') }).join(' '); + }) + .attr('transform', function(d,i) { + var mid = x.rangeBand() / ((stacked ? 1 : data.length) * 2); + return 'translate(' + (getY(d,i) < 0 ? 0 : y(getY(d,i)) - y(0)) + ', ' + mid + ')' + }); + } + + barsEnter.append('text'); + + if (showValues && !stacked) { + bars.select('text') + .attr('text-anchor', function(d,i) { return getY(d,i) < 0 ? 'end' : 'start' }) + .attr('y', x.rangeBand() / (data.length * 2)) + .attr('dy', '.32em') + .text(function(d,i) { + var t = valueFormat(getY(d,i)) + , yerr = getYerr(d,i); + if (yerr === undefined) + return t; + if (!yerr.length) + return t + '±' + valueFormat(Math.abs(yerr)); + return t + '+' + valueFormat(Math.abs(yerr[1])) + '-' + valueFormat(Math.abs(yerr[0])); + }); + bars.watchTransition(renderWatch, 'multibarhorizontal: bars') + .select('text') + .attr('x', function(d,i) { return getY(d,i) < 0 ? -4 : y(getY(d,i)) - y(0) + 4 }) + } else { + bars.selectAll('text').text(''); + } + + if (showBarLabels && !stacked) { + barsEnter.append('text').classed('nv-bar-label',true); + bars.select('text.nv-bar-label') + .attr('text-anchor', function(d,i) { return getY(d,i) < 0 ? 'start' : 'end' }) + .attr('y', x.rangeBand() / (data.length * 2)) + .attr('dy', '.32em') + .text(function(d,i) { return getX(d,i) }); + bars.watchTransition(renderWatch, 'multibarhorizontal: bars') + .select('text.nv-bar-label') + .attr('x', function(d,i) { return getY(d,i) < 0 ? y(0) - y(getY(d,i)) + 4 : -4 }); + } + else { + bars.selectAll('text.nv-bar-label').text(''); + } + + bars + .attr('class', function(d,i) { return getY(d,i) < 0 ? 'nv-bar negative' : 'nv-bar positive'}) + + if (barColor) { + if (!disabled) disabled = data.map(function() { return true }); + bars + .style('fill', function(d,i,j) { return d3.rgb(barColor(d,i)).darker( disabled.map(function(d,i) { return i }).filter(function(d,i){ return !disabled[i] })[j] ).toString(); }) + .style('stroke', function(d,i,j) { return d3.rgb(barColor(d,i)).darker( disabled.map(function(d,i) { return i }).filter(function(d,i){ return !disabled[i] })[j] ).toString(); }); + } + + if (stacked) + bars.watchTransition(renderWatch, 'multibarhorizontal: bars') + .attr('transform', function(d,i) { + return 'translate(' + y(d.y1) + ',' + x(getX(d,i)) + ')' + }) + .select('rect') + .attr('width', function(d,i) { + return Math.abs(y(getY(d,i) + d.y0) - y(d.y0)) + }) + .attr('height', x.rangeBand() ); + else + bars.watchTransition(renderWatch, 'multibarhorizontal: bars') + .attr('transform', function(d,i) { + //TODO: stacked must be all positive or all negative, not both? + return 'translate(' + + (getY(d,i) < 0 ? y(getY(d,i)) : y(0)) + + ',' + + (d.series * x.rangeBand() / data.length + + + x(getX(d,i)) ) + + ')' + }) + .select('rect') + .attr('height', x.rangeBand() / data.length ) + .attr('width', function(d,i) { + return Math.max(Math.abs(y(getY(d,i)) - y(0)),1) + }); + + //store old scales for use in transitions on update + x0 = x.copy(); + y0 = y.copy(); + + }); + + renderWatch.renderEnd('multibarHorizontal immediate'); + return chart; + } + + //============================================================ + // Expose Public Variables + //------------------------------------------------------------ + + chart.dispatch = dispatch; + + chart.options = nv.utils.optionsFunc.bind(chart); + + chart._options = Object.create({}, { + // simple options, just get/set the necessary values + width: {get: function(){return width;}, set: function(_){width=_;}}, + height: {get: function(){return height;}, set: function(_){height=_;}}, + x: {get: function(){return getX;}, set: function(_){getX=_;}}, + y: {get: function(){return getY;}, set: function(_){getY=_;}}, + yErr: {get: function(){return getYerr;}, set: function(_){getYerr=_;}}, + xScale: {get: function(){return x;}, set: function(_){x=_;}}, + yScale: {get: function(){return y;}, set: function(_){y=_;}}, + xDomain: {get: function(){return xDomain;}, set: function(_){xDomain=_;}}, + yDomain: {get: function(){return yDomain;}, set: function(_){yDomain=_;}}, + xRange: {get: function(){return xRange;}, set: function(_){xRange=_;}}, + yRange: {get: function(){return yRange;}, set: function(_){yRange=_;}}, + forceY: {get: function(){return forceY;}, set: function(_){forceY=_;}}, + stacked: {get: function(){return stacked;}, set: function(_){stacked=_;}}, + showValues: {get: function(){return showValues;}, set: function(_){showValues=_;}}, + // this shows the group name, seems pointless? + //showBarLabels: {get: function(){return showBarLabels;}, set: function(_){showBarLabels=_;}}, + disabled: {get: function(){return disabled;}, set: function(_){disabled=_;}}, + id: {get: function(){return id;}, set: function(_){id=_;}}, + valueFormat: {get: function(){return valueFormat;}, set: function(_){valueFormat=_;}}, + valuePadding: {get: function(){return valuePadding;}, set: function(_){valuePadding=_;}}, + groupSpacing:{get: function(){return groupSpacing;}, set: function(_){groupSpacing=_;}}, + + // options that require extra logic in the setter + margin: {get: function(){return margin;}, set: function(_){ + margin.top = _.top !== undefined ? _.top : margin.top; + margin.right = _.right !== undefined ? _.right : margin.right; + margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom; + margin.left = _.left !== undefined ? _.left : margin.left; + }}, + duration: {get: function(){return duration;}, set: function(_){ + duration = _; + renderWatch.reset(duration); + }}, + color: {get: function(){return color;}, set: function(_){ + color = nv.utils.getColor(_); + }}, + barColor: {get: function(){return barColor;}, set: function(_){ + barColor = _ ? nv.utils.getColor(_) : null; + }}, + errorBarColor: {get: function(){return errorBarColor;}, set: function(_){ + errorBarColor = _ ? nv.utils.getColor(_) : null; + }} + }); + + nv.utils.initOptions(chart); + + return chart; +}; + +nv.models.multiBarHorizontalChart = function() { + "use strict"; + + //============================================================ + // Public Variables with Default Settings + //------------------------------------------------------------ + + var multibar = nv.models.multiBarHorizontal() + , xAxis = nv.models.axis() + , yAxis = nv.models.axis() + , legend = nv.models.legend().height(30) + , controls = nv.models.legend().height(30) + , tooltip = nv.models.tooltip() + ; + + var margin = {top: 30, right: 20, bottom: 50, left: 60} + , width = null + , height = null + , color = nv.utils.defaultColor() + , showControls = true + , controlLabels = {} + , showLegend = true + , showXAxis = true + , showYAxis = true + , stacked = false + , x //can be accessed via chart.xScale() + , y //can be accessed via chart.yScale() + , state = nv.utils.state() + , defaultState = null + , noData = null + , dispatch = d3.dispatch('stateChange', 'changeState','renderEnd') + , controlWidth = function() { return showControls ? 180 : 0 } + , duration = 250 + ; + + state.stacked = false; // DEPRECATED Maintained for backward compatibility + + multibar.stacked(stacked); + + xAxis + .orient('left') + .tickPadding(5) + .showMaxMin(false) + .tickFormat(function(d) { return d }) + ; + yAxis + .orient('bottom') + .tickFormat(d3.format(',.1f')) + ; + + tooltip + .duration(0) + .valueFormatter(function(d, i) { + return yAxis.tickFormat()(d, i); + }) + .headerFormatter(function(d, i) { + return xAxis.tickFormat()(d, i); + }); + + controls.updateState(false); + + //============================================================ + // Private Variables + //------------------------------------------------------------ + + var stateGetter = function(data) { + return function(){ + return { + active: data.map(function(d) { return !d.disabled }), + stacked: stacked + }; + } + }; + + var stateSetter = function(data) { + return function(state) { + if (state.stacked !== undefined) + stacked = state.stacked; + if (state.active !== undefined) + data.forEach(function(series,i) { + series.disabled = !state.active[i]; + }); + } + }; + + var renderWatch = nv.utils.renderWatch(dispatch, duration); + + function chart(selection) { + renderWatch.reset(); + renderWatch.models(multibar); + if (showXAxis) renderWatch.models(xAxis); + if (showYAxis) renderWatch.models(yAxis); + + selection.each(function(data) { + var container = d3.select(this), + that = this; + nv.utils.initSVG(container); + var availableWidth = nv.utils.availableWidth(width, container, margin), + availableHeight = nv.utils.availableHeight(height, container, margin); + + chart.update = function() { container.transition().duration(duration).call(chart) }; + chart.container = this; + + stacked = multibar.stacked(); + + state + .setter(stateSetter(data), chart.update) + .getter(stateGetter(data)) + .update(); + + // DEPRECATED set state.disableddisabled + state.disabled = data.map(function(d) { return !!d.disabled }); + + if (!defaultState) { + var key; + defaultState = {}; + for (key in state) { + if (state[key] instanceof Array) + defaultState[key] = state[key].slice(0); + else + defaultState[key] = state[key]; + } + } + + // Display No Data message if there's nothing to show. + if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) { + nv.utils.noData(chart, container) + return chart; + } else { + container.selectAll('.nv-noData').remove(); + } + + // Setup Scales + x = multibar.xScale(); + y = multibar.yScale(); + + // Setup containers and skeleton of chart + var wrap = container.selectAll('g.nv-wrap.nv-multiBarHorizontalChart').data([data]); + var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-multiBarHorizontalChart').append('g'); + var g = wrap.select('g'); + + gEnter.append('g').attr('class', 'nv-x nv-axis'); + gEnter.append('g').attr('class', 'nv-y nv-axis') + .append('g').attr('class', 'nv-zeroLine') + .append('line'); + gEnter.append('g').attr('class', 'nv-barsWrap'); + gEnter.append('g').attr('class', 'nv-legendWrap'); + gEnter.append('g').attr('class', 'nv-controlsWrap'); + + // Legend + if (showLegend) { + legend.width(availableWidth - controlWidth()); + + g.select('.nv-legendWrap') + .datum(data) + .call(legend); + + if ( margin.top != legend.height()) { + margin.top = legend.height(); + availableHeight = nv.utils.availableHeight(height, container, margin); + } + + g.select('.nv-legendWrap') + .attr('transform', 'translate(' + controlWidth() + ',' + (-margin.top) +')'); + } + + // Controls + if (showControls) { + var controlsData = [ + { key: controlLabels.grouped || 'Grouped', disabled: multibar.stacked() }, + { key: controlLabels.stacked || 'Stacked', disabled: !multibar.stacked() } + ]; + + controls.width(controlWidth()).color(['#444', '#444', '#444']); + g.select('.nv-controlsWrap') + .datum(controlsData) + .attr('transform', 'translate(0,' + (-margin.top) +')') + .call(controls); + } + + wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); + + // Main Chart Component(s) + multibar + .disabled(data.map(function(series) { return series.disabled })) + .width(availableWidth) + .height(availableHeight) + .color(data.map(function(d,i) { + return d.color || color(d, i); + }).filter(function(d,i) { return !data[i].disabled })); + + var barsWrap = g.select('.nv-barsWrap') + .datum(data.filter(function(d) { return !d.disabled })); + + barsWrap.transition().call(multibar); + + // Setup Axes + if (showXAxis) { + xAxis + .scale(x) + ._ticks( nv.utils.calcTicksY(availableHeight/24, data) ) + .tickSize(-availableWidth, 0); + + g.select('.nv-x.nv-axis').call(xAxis); + + var xTicks = g.select('.nv-x.nv-axis').selectAll('g'); + + xTicks + .selectAll('line, text'); + } + + if (showYAxis) { + yAxis + .scale(y) + ._ticks( nv.utils.calcTicksX(availableWidth/100, data) ) + .tickSize( -availableHeight, 0); + + g.select('.nv-y.nv-axis') + .attr('transform', 'translate(0,' + availableHeight + ')'); + g.select('.nv-y.nv-axis').call(yAxis); + } + + // Zero line + g.select(".nv-zeroLine line") + .attr("x1", y(0)) + .attr("x2", y(0)) + .attr("y1", 0) + .attr("y2", -availableHeight) + ; + + //============================================================ + // Event Handling/Dispatching (in chart's scope) + //------------------------------------------------------------ + + legend.dispatch.on('stateChange', function(newState) { + for (var key in newState) + state[key] = newState[key]; + dispatch.stateChange(state); + chart.update(); + }); + + controls.dispatch.on('legendClick', function(d,i) { + if (!d.disabled) return; + controlsData = controlsData.map(function(s) { + s.disabled = true; + return s; + }); + d.disabled = false; + + switch (d.key) { + case 'Grouped': + multibar.stacked(false); + break; + case 'Stacked': + multibar.stacked(true); + break; + } + + state.stacked = multibar.stacked(); + dispatch.stateChange(state); + stacked = multibar.stacked(); + + chart.update(); + }); + + // Update chart from a state object passed to event handler + dispatch.on('changeState', function(e) { + + if (typeof e.disabled !== 'undefined') { + data.forEach(function(series,i) { + series.disabled = e.disabled[i]; + }); + + state.disabled = e.disabled; + } + + if (typeof e.stacked !== 'undefined') { + multibar.stacked(e.stacked); + state.stacked = e.stacked; + stacked = e.stacked; + } + + chart.update(); + }); + }); + renderWatch.renderEnd('multibar horizontal chart immediate'); + return chart; + } + + //============================================================ + // Event Handling/Dispatching (out of chart's scope) + //------------------------------------------------------------ + + multibar.dispatch.on('elementMouseover.tooltip', function(evt) { + evt.value = chart.x()(evt.data); + evt['series'] = { + key: evt.data.key, + value: chart.y()(evt.data), + color: evt.color + }; + tooltip.data(evt).hidden(false); + }); + + multibar.dispatch.on('elementMouseout.tooltip', function(evt) { + tooltip.hidden(true); + }); + + multibar.dispatch.on('elementMousemove.tooltip', function(evt) { + tooltip.position({top: d3.event.pageY, left: d3.event.pageX})(); + }); + + //============================================================ + // Expose Public Variables + //------------------------------------------------------------ + + // expose chart's sub-components + chart.dispatch = dispatch; + chart.multibar = multibar; + chart.legend = legend; + chart.controls = controls; + chart.xAxis = xAxis; + chart.yAxis = yAxis; + chart.state = state; + chart.tooltip = tooltip; + + chart.options = nv.utils.optionsFunc.bind(chart); + + chart._options = Object.create({}, { + // simple options, just get/set the necessary values + width: {get: function(){return width;}, set: function(_){width=_;}}, + height: {get: function(){return height;}, set: function(_){height=_;}}, + showLegend: {get: function(){return showLegend;}, set: function(_){showLegend=_;}}, + showControls: {get: function(){return showControls;}, set: function(_){showControls=_;}}, + controlLabels: {get: function(){return controlLabels;}, set: function(_){controlLabels=_;}}, + showXAxis: {get: function(){return showXAxis;}, set: function(_){showXAxis=_;}}, + showYAxis: {get: function(){return showYAxis;}, set: function(_){showYAxis=_;}}, + defaultState: {get: function(){return defaultState;}, set: function(_){defaultState=_;}}, + noData: {get: function(){return noData;}, set: function(_){noData=_;}}, + + // deprecated options + tooltips: {get: function(){return tooltip.enabled();}, set: function(_){ + // deprecated after 1.7.1 + nv.deprecated('tooltips', 'use chart.tooltip.enabled() instead'); + tooltip.enabled(!!_); + }}, + tooltipContent: {get: function(){return tooltip.contentGenerator();}, set: function(_){ + // deprecated after 1.7.1 + nv.deprecated('tooltipContent', 'use chart.tooltip.contentGenerator() instead'); + tooltip.contentGenerator(_); + }}, + + // options that require extra logic in the setter + margin: {get: function(){return margin;}, set: function(_){ + margin.top = _.top !== undefined ? _.top : margin.top; + margin.right = _.right !== undefined ? _.right : margin.right; + margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom; + margin.left = _.left !== undefined ? _.left : margin.left; + }}, + duration: {get: function(){return duration;}, set: function(_){ + duration = _; + renderWatch.reset(duration); + multibar.duration(duration); + xAxis.duration(duration); + yAxis.duration(duration); + }}, + color: {get: function(){return color;}, set: function(_){ + color = nv.utils.getColor(_); + legend.color(color); + }}, + barColor: {get: function(){return multibar.barColor;}, set: function(_){ + multibar.barColor(_); + legend.color(function(d,i) {return d3.rgb('#ccc').darker(i * 1.5).toString();}) + }} + }); + + nv.utils.inheritOptions(chart, multibar); + nv.utils.initOptions(chart); + + return chart; +}; +nv.models.multiChart = function() { + "use strict"; + + //============================================================ + // Public Variables with Default Settings + //------------------------------------------------------------ + + var margin = {top: 30, right: 20, bottom: 50, left: 60}, + color = nv.utils.defaultColor(), + width = null, + height = null, + showLegend = true, + noData = null, + yDomain1, + yDomain2, + getX = function(d) { return d.x }, + getY = function(d) { return d.y}, + interpolate = 'monotone', + useVoronoi = true + ; + + //============================================================ + // Private Variables + //------------------------------------------------------------ + + var x = d3.scale.linear(), + yScale1 = d3.scale.linear(), + yScale2 = d3.scale.linear(), + + lines1 = nv.models.line().yScale(yScale1), + lines2 = nv.models.line().yScale(yScale2), + + bars1 = nv.models.multiBar().stacked(false).yScale(yScale1), + bars2 = nv.models.multiBar().stacked(false).yScale(yScale2), + + stack1 = nv.models.stackedArea().yScale(yScale1), + stack2 = nv.models.stackedArea().yScale(yScale2), + + xAxis = nv.models.axis().scale(x).orient('bottom').tickPadding(5), + yAxis1 = nv.models.axis().scale(yScale1).orient('left'), + yAxis2 = nv.models.axis().scale(yScale2).orient('right'), + + legend = nv.models.legend().height(30), + tooltip = nv.models.tooltip(), + dispatch = d3.dispatch(); + + function chart(selection) { + selection.each(function(data) { + var container = d3.select(this), + that = this; + nv.utils.initSVG(container); + + chart.update = function() { container.transition().call(chart); }; + chart.container = this; + + var availableWidth = nv.utils.availableWidth(width, container, margin), + availableHeight = nv.utils.availableHeight(height, container, margin); + + var dataLines1 = data.filter(function(d) {return d.type == 'line' && d.yAxis == 1}); + var dataLines2 = data.filter(function(d) {return d.type == 'line' && d.yAxis == 2}); + var dataBars1 = data.filter(function(d) {return d.type == 'bar' && d.yAxis == 1}); + var dataBars2 = data.filter(function(d) {return d.type == 'bar' && d.yAxis == 2}); + var dataStack1 = data.filter(function(d) {return d.type == 'area' && d.yAxis == 1}); + var dataStack2 = data.filter(function(d) {return d.type == 'area' && d.yAxis == 2}); + + // Display noData message if there's nothing to show. + if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) { + nv.utils.noData(chart, container); + return chart; + } else { + container.selectAll('.nv-noData').remove(); + } + + var series1 = data.filter(function(d) {return !d.disabled && d.yAxis == 1}) + .map(function(d) { + return d.values.map(function(d,i) { + return { x: d.x, y: d.y } + }) + }); + + var series2 = data.filter(function(d) {return !d.disabled && d.yAxis == 2}) + .map(function(d) { + return d.values.map(function(d,i) { + return { x: d.x, y: d.y } + }) + }); + + x .domain(d3.extent(d3.merge(series1.concat(series2)), function(d) { return d.x } )) + .range([0, availableWidth]); + + var wrap = container.selectAll('g.wrap.multiChart').data([data]); + var gEnter = wrap.enter().append('g').attr('class', 'wrap nvd3 multiChart').append('g'); + + gEnter.append('g').attr('class', 'nv-x nv-axis'); + gEnter.append('g').attr('class', 'nv-y1 nv-axis'); + gEnter.append('g').attr('class', 'nv-y2 nv-axis'); + gEnter.append('g').attr('class', 'lines1Wrap'); + gEnter.append('g').attr('class', 'lines2Wrap'); + gEnter.append('g').attr('class', 'bars1Wrap'); + gEnter.append('g').attr('class', 'bars2Wrap'); + gEnter.append('g').attr('class', 'stack1Wrap'); + gEnter.append('g').attr('class', 'stack2Wrap'); + gEnter.append('g').attr('class', 'legendWrap'); + + var g = wrap.select('g'); + + var color_array = data.map(function(d,i) { + return data[i].color || color(d, i); + }); + + if (showLegend) { + var legendWidth = legend.align() ? availableWidth / 2 : availableWidth; + var legendXPosition = legend.align() ? legendWidth : 0; + + legend.width(legendWidth); + legend.color(color_array); + + g.select('.legendWrap') + .datum(data.map(function(series) { + series.originalKey = series.originalKey === undefined ? series.key : series.originalKey; + series.key = series.originalKey + (series.yAxis == 1 ? '' : ' (right axis)'); + return series; + })) + .call(legend); + + if ( margin.top != legend.height()) { + margin.top = legend.height(); + availableHeight = nv.utils.availableHeight(height, container, margin); + } + + g.select('.legendWrap') + .attr('transform', 'translate(' + legendXPosition + ',' + (-margin.top) +')'); + } + + lines1 + .width(availableWidth) + .height(availableHeight) + .interpolate(interpolate) + .color(color_array.filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 1 && data[i].type == 'line'})); + lines2 + .width(availableWidth) + .height(availableHeight) + .interpolate(interpolate) + .color(color_array.filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 2 && data[i].type == 'line'})); + bars1 + .width(availableWidth) + .height(availableHeight) + .color(color_array.filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 1 && data[i].type == 'bar'})); + bars2 + .width(availableWidth) + .height(availableHeight) + .color(color_array.filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 2 && data[i].type == 'bar'})); + stack1 + .width(availableWidth) + .height(availableHeight) + .color(color_array.filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 1 && data[i].type == 'area'})); + stack2 + .width(availableWidth) + .height(availableHeight) + .color(color_array.filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 2 && data[i].type == 'area'})); + + g.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); + + var lines1Wrap = g.select('.lines1Wrap') + .datum(dataLines1.filter(function(d){return !d.disabled})); + var bars1Wrap = g.select('.bars1Wrap') + .datum(dataBars1.filter(function(d){return !d.disabled})); + var stack1Wrap = g.select('.stack1Wrap') + .datum(dataStack1.filter(function(d){return !d.disabled})); + var lines2Wrap = g.select('.lines2Wrap') + .datum(dataLines2.filter(function(d){return !d.disabled})); + var bars2Wrap = g.select('.bars2Wrap') + .datum(dataBars2.filter(function(d){return !d.disabled})); + var stack2Wrap = g.select('.stack2Wrap') + .datum(dataStack2.filter(function(d){return !d.disabled})); + + var extraValue1 = dataStack1.length ? dataStack1.map(function(a){return a.values}).reduce(function(a,b){ + return a.map(function(aVal,i){return {x: aVal.x, y: aVal.y + b[i].y}}) + }).concat([{x:0, y:0}]) : []; + var extraValue2 = dataStack2.length ? dataStack2.map(function(a){return a.values}).reduce(function(a,b){ + return a.map(function(aVal,i){return {x: aVal.x, y: aVal.y + b[i].y}}) + }).concat([{x:0, y:0}]) : []; + + yScale1 .domain(yDomain1 || d3.extent(d3.merge(series1).concat(extraValue1), function(d) { return d.y } )) + .range([0, availableHeight]); + + yScale2 .domain(yDomain2 || d3.extent(d3.merge(series2).concat(extraValue2), function(d) { return d.y } )) + .range([0, availableHeight]); + + lines1.yDomain(yScale1.domain()); + bars1.yDomain(yScale1.domain()); + stack1.yDomain(yScale1.domain()); + + lines2.yDomain(yScale2.domain()); + bars2.yDomain(yScale2.domain()); + stack2.yDomain(yScale2.domain()); + + if(dataStack1.length){d3.transition(stack1Wrap).call(stack1);} + if(dataStack2.length){d3.transition(stack2Wrap).call(stack2);} + + if(dataBars1.length){d3.transition(bars1Wrap).call(bars1);} + if(dataBars2.length){d3.transition(bars2Wrap).call(bars2);} + + if(dataLines1.length){d3.transition(lines1Wrap).call(lines1);} + if(dataLines2.length){d3.transition(lines2Wrap).call(lines2);} + + xAxis + ._ticks( nv.utils.calcTicksX(availableWidth/100, data) ) + .tickSize(-availableHeight, 0); + + g.select('.nv-x.nv-axis') + .attr('transform', 'translate(0,' + availableHeight + ')'); + d3.transition(g.select('.nv-x.nv-axis')) + .call(xAxis); + + yAxis1 + ._ticks( nv.utils.calcTicksY(availableHeight/36, data) ) + .tickSize( -availableWidth, 0); + + + d3.transition(g.select('.nv-y1.nv-axis')) + .call(yAxis1); + + yAxis2 + ._ticks( nv.utils.calcTicksY(availableHeight/36, data) ) + .tickSize( -availableWidth, 0); + + d3.transition(g.select('.nv-y2.nv-axis')) + .call(yAxis2); + + g.select('.nv-y1.nv-axis') + .classed('nv-disabled', series1.length ? false : true) + .attr('transform', 'translate(' + x.range()[0] + ',0)'); + + g.select('.nv-y2.nv-axis') + .classed('nv-disabled', series2.length ? false : true) + .attr('transform', 'translate(' + x.range()[1] + ',0)'); + + legend.dispatch.on('stateChange', function(newState) { + chart.update(); + }); + + //============================================================ + // Event Handling/Dispatching + //------------------------------------------------------------ + + function mouseover_line(evt) { + var yaxis = data[evt.seriesIndex].yAxis === 2 ? yAxis2 : yAxis1; + evt.value = evt.point.x; + evt.series = { + value: evt.point.y, + color: evt.point.color + }; + tooltip + .duration(100) + .valueFormatter(function(d, i) { + return yaxis.tickFormat()(d, i); + }) + .data(evt) + .position(evt.pos) + .hidden(false); + } + + function mouseover_stack(evt) { + var yaxis = data[evt.seriesIndex].yAxis === 2 ? yAxis2 : yAxis1; + evt.point['x'] = stack1.x()(evt.point); + evt.point['y'] = stack1.y()(evt.point); + tooltip + .duration(100) + .valueFormatter(function(d, i) { + return yaxis.tickFormat()(d, i); + }) + .data(evt) + .position(evt.pos) + .hidden(false); + } + + function mouseover_bar(evt) { + var yaxis = data[evt.data.series].yAxis === 2 ? yAxis2 : yAxis1; + + evt.value = bars1.x()(evt.data); + evt['series'] = { + value: bars1.y()(evt.data), + color: evt.color + }; + tooltip + .duration(0) + .valueFormatter(function(d, i) { + return yaxis.tickFormat()(d, i); + }) + .data(evt) + .hidden(false); + } + + lines1.dispatch.on('elementMouseover.tooltip', mouseover_line); + lines2.dispatch.on('elementMouseover.tooltip', mouseover_line); + lines1.dispatch.on('elementMouseout.tooltip', function(evt) { + tooltip.hidden(true) + }); + lines2.dispatch.on('elementMouseout.tooltip', function(evt) { + tooltip.hidden(true) + }); + + stack1.dispatch.on('elementMouseover.tooltip', mouseover_stack); + stack2.dispatch.on('elementMouseover.tooltip', mouseover_stack); + stack1.dispatch.on('elementMouseout.tooltip', function(evt) { + tooltip.hidden(true) + }); + stack2.dispatch.on('elementMouseout.tooltip', function(evt) { + tooltip.hidden(true) + }); + + bars1.dispatch.on('elementMouseover.tooltip', mouseover_bar); + bars2.dispatch.on('elementMouseover.tooltip', mouseover_bar); + + bars1.dispatch.on('elementMouseout.tooltip', function(evt) { + tooltip.hidden(true); + }); + bars2.dispatch.on('elementMouseout.tooltip', function(evt) { + tooltip.hidden(true); + }); + bars1.dispatch.on('elementMousemove.tooltip', function(evt) { + tooltip.position({top: d3.event.pageY, left: d3.event.pageX})(); + }); + bars2.dispatch.on('elementMousemove.tooltip', function(evt) { + tooltip.position({top: d3.event.pageY, left: d3.event.pageX})(); + }); + + }); + + return chart; + } + + //============================================================ + // Global getters and setters + //------------------------------------------------------------ + + chart.dispatch = dispatch; + chart.lines1 = lines1; + chart.lines2 = lines2; + chart.bars1 = bars1; + chart.bars2 = bars2; + chart.stack1 = stack1; + chart.stack2 = stack2; + chart.xAxis = xAxis; + chart.yAxis1 = yAxis1; + chart.yAxis2 = yAxis2; + chart.tooltip = tooltip; + + chart.options = nv.utils.optionsFunc.bind(chart); + + chart._options = Object.create({}, { + // simple options, just get/set the necessary values + width: {get: function(){return width;}, set: function(_){width=_;}}, + height: {get: function(){return height;}, set: function(_){height=_;}}, + showLegend: {get: function(){return showLegend;}, set: function(_){showLegend=_;}}, + yDomain1: {get: function(){return yDomain1;}, set: function(_){yDomain1=_;}}, + yDomain2: {get: function(){return yDomain2;}, set: function(_){yDomain2=_;}}, + noData: {get: function(){return noData;}, set: function(_){noData=_;}}, + interpolate: {get: function(){return interpolate;}, set: function(_){interpolate=_;}}, + + // deprecated options + tooltips: {get: function(){return tooltip.enabled();}, set: function(_){ + // deprecated after 1.7.1 + nv.deprecated('tooltips', 'use chart.tooltip.enabled() instead'); + tooltip.enabled(!!_); + }}, + tooltipContent: {get: function(){return tooltip.contentGenerator();}, set: function(_){ + // deprecated after 1.7.1 + nv.deprecated('tooltipContent', 'use chart.tooltip.contentGenerator() instead'); + tooltip.contentGenerator(_); + }}, + + // options that require extra logic in the setter + margin: {get: function(){return margin;}, set: function(_){ + margin.top = _.top !== undefined ? _.top : margin.top; + margin.right = _.right !== undefined ? _.right : margin.right; + margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom; + margin.left = _.left !== undefined ? _.left : margin.left; + }}, + color: {get: function(){return color;}, set: function(_){ + color = nv.utils.getColor(_); + }}, + x: {get: function(){return getX;}, set: function(_){ + getX = _; + lines1.x(_); + lines2.x(_); + bars1.x(_); + bars2.x(_); + stack1.x(_); + stack2.x(_); + }}, + y: {get: function(){return getY;}, set: function(_){ + getY = _; + lines1.y(_); + lines2.y(_); + stack1.y(_); + stack2.y(_); + bars1.y(_); + bars2.y(_); + }}, + useVoronoi: {get: function(){return useVoronoi;}, set: function(_){ + useVoronoi=_; + lines1.useVoronoi(_); + lines2.useVoronoi(_); + stack1.useVoronoi(_); + stack2.useVoronoi(_); + }} + }); + + nv.utils.initOptions(chart); + + return chart; +}; + + +nv.models.ohlcBar = function() { + "use strict"; + + //============================================================ + // Public Variables with Default Settings + //------------------------------------------------------------ + + var margin = {top: 0, right: 0, bottom: 0, left: 0} + , width = null + , height = null + , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one + , container = null + , x = d3.scale.linear() + , y = d3.scale.linear() + , getX = function(d) { return d.x } + , getY = function(d) { return d.y } + , getOpen = function(d) { return d.open } + , getClose = function(d) { return d.close } + , getHigh = function(d) { return d.high } + , getLow = function(d) { return d.low } + , forceX = [] + , forceY = [] + , padData = false // If true, adds half a data points width to front and back, for lining up a line chart with a bar chart + , clipEdge = true + , color = nv.utils.defaultColor() + , interactive = false + , xDomain + , yDomain + , xRange + , yRange + , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState', 'renderEnd', 'chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout', 'elementMousemove') + ; + + //============================================================ + // Private Variables + //------------------------------------------------------------ + + function chart(selection) { + selection.each(function(data) { + container = d3.select(this); + var availableWidth = nv.utils.availableWidth(width, container, margin), + availableHeight = nv.utils.availableHeight(height, container, margin); + + nv.utils.initSVG(container); + + // ohlc bar width. + var w = (availableWidth / data[0].values.length) * .9; + + // Setup Scales + x.domain(xDomain || d3.extent(data[0].values.map(getX).concat(forceX) )); + + if (padData) + x.range(xRange || [availableWidth * .5 / data[0].values.length, availableWidth * (data[0].values.length - .5) / data[0].values.length ]); + else + x.range(xRange || [5 + w/2, availableWidth - w/2 - 5]); + + y.domain(yDomain || [ + d3.min(data[0].values.map(getLow).concat(forceY)), + d3.max(data[0].values.map(getHigh).concat(forceY)) + ] + ).range(yRange || [availableHeight, 0]); + + // If scale's domain don't have a range, slightly adjust to make one... so a chart can show a single data point + if (x.domain()[0] === x.domain()[1]) + x.domain()[0] ? + x.domain([x.domain()[0] - x.domain()[0] * 0.01, x.domain()[1] + x.domain()[1] * 0.01]) + : x.domain([-1,1]); + + if (y.domain()[0] === y.domain()[1]) + y.domain()[0] ? + y.domain([y.domain()[0] + y.domain()[0] * 0.01, y.domain()[1] - y.domain()[1] * 0.01]) + : y.domain([-1,1]); + + // Setup containers and skeleton of chart + var wrap = d3.select(this).selectAll('g.nv-wrap.nv-ohlcBar').data([data[0].values]); + var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-ohlcBar'); + var defsEnter = wrapEnter.append('defs'); + var gEnter = wrapEnter.append('g'); + var g = wrap.select('g'); + + gEnter.append('g').attr('class', 'nv-ticks'); + + wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); + + container + .on('click', function(d,i) { + dispatch.chartClick({ + data: d, + index: i, + pos: d3.event, + id: id + }); + }); + + defsEnter.append('clipPath') + .attr('id', 'nv-chart-clip-path-' + id) + .append('rect'); + + wrap.select('#nv-chart-clip-path-' + id + ' rect') + .attr('width', availableWidth) + .attr('height', availableHeight); + + g .attr('clip-path', clipEdge ? 'url(#nv-chart-clip-path-' + id + ')' : ''); + + var ticks = wrap.select('.nv-ticks').selectAll('.nv-tick') + .data(function(d) { return d }); + ticks.exit().remove(); + + ticks.enter().append('path') + .attr('class', function(d,i,j) { return (getOpen(d,i) > getClose(d,i) ? 'nv-tick negative' : 'nv-tick positive') + ' nv-tick-' + j + '-' + i }) + .attr('d', function(d,i) { + return 'm0,0l0,' + + (y(getOpen(d,i)) + - y(getHigh(d,i))) + + 'l' + + (-w/2) + + ',0l' + + (w/2) + + ',0l0,' + + (y(getLow(d,i)) - y(getOpen(d,i))) + + 'l0,' + + (y(getClose(d,i)) + - y(getLow(d,i))) + + 'l' + + (w/2) + + ',0l' + + (-w/2) + + ',0z'; + }) + .attr('transform', function(d,i) { return 'translate(' + x(getX(d,i)) + ',' + y(getHigh(d,i)) + ')'; }) + .attr('fill', function(d,i) { return color[0]; }) + .attr('stroke', function(d,i) { return color[0]; }) + .attr('x', 0 ) + .attr('y', function(d,i) { return y(Math.max(0, getY(d,i))) }) + .attr('height', function(d,i) { return Math.abs(y(getY(d,i)) - y(0)) }); + + // the bar colors are controlled by CSS currently + ticks.attr('class', function(d,i,j) { + return (getOpen(d,i) > getClose(d,i) ? 'nv-tick negative' : 'nv-tick positive') + ' nv-tick-' + j + '-' + i; + }); + + d3.transition(ticks) + .attr('transform', function(d,i) { return 'translate(' + x(getX(d,i)) + ',' + y(getHigh(d,i)) + ')'; }) + .attr('d', function(d,i) { + var w = (availableWidth / data[0].values.length) * .9; + return 'm0,0l0,' + + (y(getOpen(d,i)) + - y(getHigh(d,i))) + + 'l' + + (-w/2) + + ',0l' + + (w/2) + + ',0l0,' + + (y(getLow(d,i)) + - y(getOpen(d,i))) + + 'l0,' + + (y(getClose(d,i)) + - y(getLow(d,i))) + + 'l' + + (w/2) + + ',0l' + + (-w/2) + + ',0z'; + }); + }); + + return chart; + } + + + //Create methods to allow outside functions to highlight a specific bar. + chart.highlightPoint = function(pointIndex, isHoverOver) { + chart.clearHighlights(); + container.select(".nv-ohlcBar .nv-tick-0-" + pointIndex) + .classed("hover", isHoverOver) + ; + }; + + chart.clearHighlights = function() { + container.select(".nv-ohlcBar .nv-tick.hover") + .classed("hover", false) + ; + }; + + //============================================================ + // Expose Public Variables + //------------------------------------------------------------ + + chart.dispatch = dispatch; + chart.options = nv.utils.optionsFunc.bind(chart); + + chart._options = Object.create({}, { + // simple options, just get/set the necessary values + width: {get: function(){return width;}, set: function(_){width=_;}}, + height: {get: function(){return height;}, set: function(_){height=_;}}, + xScale: {get: function(){return x;}, set: function(_){x=_;}}, + yScale: {get: function(){return y;}, set: function(_){y=_;}}, + xDomain: {get: function(){return xDomain;}, set: function(_){xDomain=_;}}, + yDomain: {get: function(){return yDomain;}, set: function(_){yDomain=_;}}, + xRange: {get: function(){return xRange;}, set: function(_){xRange=_;}}, + yRange: {get: function(){return yRange;}, set: function(_){yRange=_;}}, + forceX: {get: function(){return forceX;}, set: function(_){forceX=_;}}, + forceY: {get: function(){return forceY;}, set: function(_){forceY=_;}}, + padData: {get: function(){return padData;}, set: function(_){padData=_;}}, + clipEdge: {get: function(){return clipEdge;}, set: function(_){clipEdge=_;}}, + id: {get: function(){return id;}, set: function(_){id=_;}}, + interactive: {get: function(){return interactive;}, set: function(_){interactive=_;}}, + + x: {get: function(){return getX;}, set: function(_){getX=_;}}, + y: {get: function(){return getY;}, set: function(_){getY=_;}}, + open: {get: function(){return getOpen();}, set: function(_){getOpen=_;}}, + close: {get: function(){return getClose();}, set: function(_){getClose=_;}}, + high: {get: function(){return getHigh;}, set: function(_){getHigh=_;}}, + low: {get: function(){return getLow;}, set: function(_){getLow=_;}}, + + // options that require extra logic in the setter + margin: {get: function(){return margin;}, set: function(_){ + margin.top = _.top != undefined ? _.top : margin.top; + margin.right = _.right != undefined ? _.right : margin.right; + margin.bottom = _.bottom != undefined ? _.bottom : margin.bottom; + margin.left = _.left != undefined ? _.left : margin.left; + }}, + color: {get: function(){return color;}, set: function(_){ + color = nv.utils.getColor(_); + }} + }); + + nv.utils.initOptions(chart); + return chart; +}; +// Code adapted from Jason Davies' "Parallel Coordinates" +// http://bl.ocks.org/jasondavies/1341281 +nv.models.parallelCoordinates = function() { + "use strict"; + + //============================================================ + // Public Variables with Default Settings + //------------------------------------------------------------ + + var margin = {top: 30, right: 0, bottom: 10, left: 0} + , width = null + , height = null + , x = d3.scale.ordinal() + , y = {} + , dimensionNames = [] + , dimensionFormats = [] + , color = nv.utils.defaultColor() + , filters = [] + , active = [] + , dragging = [] + , lineTension = 1 + , dispatch = d3.dispatch('brush', 'elementMouseover', 'elementMouseout') + ; + + //============================================================ + // Private Variables + //------------------------------------------------------------ + + function chart(selection) { + selection.each(function(data) { + var container = d3.select(this); + var availableWidth = nv.utils.availableWidth(width, container, margin), + availableHeight = nv.utils.availableHeight(height, container, margin); + + nv.utils.initSVG(container); + + active = data; //set all active before first brush call + + // Setup Scales + x.rangePoints([0, availableWidth], 1).domain(dimensionNames); + + //Set as true if all values on an axis are missing. + var onlyNanValues = {}; + // Extract the list of dimensions and create a scale for each. + dimensionNames.forEach(function(d) { + var extent = d3.extent(data, function(p) { return +p[d]; }); + onlyNanValues[d] = false; + //If there is no values to display on an axis, set the extent to 0 + if (extent[0] === undefined) { + onlyNanValues[d] = true; + extent[0] = 0; + extent[1] = 0; + } + //Scale axis if there is only one value + if (extent[0] === extent[1]) { + extent[0] = extent[0] - 1; + extent[1] = extent[1] + 1; + } + //Use 90% of (availableHeight - 12) for the axis range, 12 reprensenting the space necessary to display "undefined values" text. + //The remaining 10% are used to display the missingValue line. + y[d] = d3.scale.linear() + .domain(extent) + .range([(availableHeight - 12) * 0.9, 0]); + + y[d].brush = d3.svg.brush().y(y[d]).on('brush', brush); + + return d != 'name'; + }); + + // Setup containers and skeleton of chart + var wrap = container.selectAll('g.nv-wrap.nv-parallelCoordinates').data([data]); + var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-parallelCoordinates'); + var gEnter = wrapEnter.append('g'); + var g = wrap.select('g'); + + gEnter.append('g').attr('class', 'nv-parallelCoordinates background'); + gEnter.append('g').attr('class', 'nv-parallelCoordinates foreground'); + gEnter.append('g').attr('class', 'nv-parallelCoordinates missingValuesline'); + + wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); + + var line = d3.svg.line().interpolate('cardinal').tension(lineTension), + axis = d3.svg.axis().orient('left'), + axisDrag = d3.behavior.drag() + .on('dragstart', dragStart) + .on('drag', dragMove) + .on('dragend', dragEnd); + + //Add missing value line at the bottom of the chart + var missingValuesline, missingValueslineText; + var step = x.range()[1] - x.range()[0]; + var axisWithMissingValues = []; + var lineData = [0 + step / 2, availableHeight - 12, availableWidth - step / 2, availableHeight - 12]; + missingValuesline = wrap.select('.missingValuesline').selectAll('line').data([lineData]); + missingValuesline.enter().append('line'); + missingValuesline.exit().remove(); + missingValuesline.attr("x1", function(d) { return d[0]; }) + .attr("y1", function(d) { return d[1]; }) + .attr("x2", function(d) { return d[2]; }) + .attr("y2", function(d) { return d[3]; }); + + //Add the text "undefined values" under the missing value line + missingValueslineText = wrap.select('.missingValuesline').selectAll('text').data(["undefined values"]); + missingValueslineText.append('text').data(["undefined values"]); + missingValueslineText.enter().append('text'); + missingValueslineText.exit().remove(); + missingValueslineText.attr("y", availableHeight) + //To have the text right align with the missingValues line, substract 92 representing the text size. + .attr("x", availableWidth - 92 - step / 2) + .text(function(d) { return d; }); + + // Add grey background lines for context. + var background = wrap.select('.background').selectAll('path').data(data); + background.enter().append('path'); + background.exit().remove(); + background.attr('d', path); + + // Add blue foreground lines for focus. + var foreground = wrap.select('.foreground').selectAll('path').data(data); + foreground.enter().append('path') + foreground.exit().remove(); + foreground.attr('d', path).attr('stroke', color); + foreground.on("mouseover", function (d, i) { + d3.select(this).classed('hover', true); + dispatch.elementMouseover({ + label: d.name, + data: d.data, + index: i, + pos: [d3.mouse(this.parentNode)[0], d3.mouse(this.parentNode)[1]] + }); + + }); + foreground.on("mouseout", function (d, i) { + d3.select(this).classed('hover', false); + dispatch.elementMouseout({ + label: d.name, + data: d.data, + index: i + }); + }); + + // Add a group element for each dimension. + var dimensions = g.selectAll('.dimension').data(dimensionNames); + var dimensionsEnter = dimensions.enter().append('g').attr('class', 'nv-parallelCoordinates dimension'); + dimensionsEnter.append('g').attr('class', 'nv-parallelCoordinates nv-axis'); + dimensionsEnter.append('g').attr('class', 'nv-parallelCoordinates-brush'); + dimensionsEnter.append('text').attr('class', 'nv-parallelCoordinates nv-label'); + + dimensions.attr('transform', function(d) { return 'translate(' + x(d) + ',0)'; }); + dimensions.exit().remove(); + + // Add an axis and title. + dimensions.select('.nv-label') + .style("cursor", "move") + .attr('dy', '-1em') + .attr('text-anchor', 'middle') + .text(String) + .on("mouseover", function(d, i) { + dispatch.elementMouseover({ + dim: d, + pos: [d3.mouse(this.parentNode.parentNode)[0], d3.mouse(this.parentNode.parentNode)[1]] + }); + }) + .on("mouseout", function(d, i) { + dispatch.elementMouseout({ + dim: d + }); + }) + .call(axisDrag); + + dimensions.select('.nv-axis') + .each(function (d, i) { + d3.select(this).call(axis.scale(y[d]).tickFormat(d3.format(dimensionFormats[i]))); + }); + + dimensions.select('.nv-parallelCoordinates-brush') + .each(function (d) { + d3.select(this).call(y[d].brush); + }) + .selectAll('rect') + .attr('x', -8) + .attr('width', 16); + + // Returns the path for a given data point. + function path(d) { + return line(dimensionNames.map(function (p) { + //If value if missing, put the value on the missing value line + if(isNaN(d[p]) || isNaN(parseFloat(d[p]))) { + var domain = y[p].domain(); + var range = y[p].range(); + var min = domain[0] - (domain[1] - domain[0]) / 9; + + //If it's not already the case, allow brush to select undefined values + if(axisWithMissingValues.indexOf(p) < 0) { + + var newscale = d3.scale.linear().domain([min, domain[1]]).range([availableHeight - 12, range[1]]); + y[p].brush.y(newscale); + axisWithMissingValues.push(p); + } + + return [x(p), y[p](min)]; + } + + //If parallelCoordinate contain missing values show the missing values line otherwise, hide it. + if(axisWithMissingValues.length > 0) { + missingValuesline.style("display", "inline"); + missingValueslineText.style("display", "inline"); + } else { + missingValuesline.style("display", "none"); + missingValueslineText.style("display", "none"); + } + + return [x(p), y[p](d[p])]; + })); + } + + // Handles a brush event, toggling the display of foreground lines. + function brush() { + var actives = dimensionNames.filter(function(p) { return !y[p].brush.empty(); }), + extents = actives.map(function(p) { return y[p].brush.extent(); }); + + filters = []; //erase current filters + actives.forEach(function(d,i) { + filters[i] = { + dimension: d, + extent: extents[i] + } + }); + + active = []; //erase current active list + foreground.style('display', function(d) { + var isActive = actives.every(function(p, i) { + if(isNaN(d[p]) && extents[i][0] == y[p].brush.y().domain()[0]) return true; + return extents[i][0] <= d[p] && d[p] <= extents[i][1]; + }); + if (isActive) active.push(d); + return isActive ? null : 'none'; + }); + + dispatch.brush({ + filters: filters, + active: active + }); + } + + function dragStart(d, i) { + dragging[d] = this.parentNode.__origin__ = x(d); + background.attr("visibility", "hidden"); + + } + + function dragMove(d, i) { + dragging[d] = Math.min(availableWidth, Math.max(0, this.parentNode.__origin__ += d3.event.x)); + foreground.attr("d", path); + dimensionNames.sort(function (a, b) { return position(a) - position(b); }); + x.domain(dimensionNames); + dimensions.attr("transform", function(d) { return "translate(" + position(d) + ")"; }); + } + + function dragEnd(d, i) { + delete this.parentNode.__origin__; + delete dragging[d]; + d3.select(this.parentNode).attr("transform", "translate(" + x(d) + ")"); + foreground + .attr("d", path); + background + .attr("d", path) + .attr("visibility", null); + + } + + function position(d) { + var v = dragging[d]; + return v == null ? x(d) : v; + } + }); + + return chart; + } + + //============================================================ + // Expose Public Variables + //------------------------------------------------------------ + + chart.dispatch = dispatch; + chart.options = nv.utils.optionsFunc.bind(chart); + + chart._options = Object.create({}, { + // simple options, just get/set the necessary values + width: {get: function(){return width;}, set: function(_){width= _;}}, + height: {get: function(){return height;}, set: function(_){height= _;}}, + dimensionNames: {get: function() { return dimensionNames;}, set: function(_){dimensionNames= _;}}, + dimensionFormats : {get: function(){return dimensionFormats;}, set: function (_){dimensionFormats=_;}}, + lineTension: {get: function(){return lineTension;}, set: function(_){lineTension = _;}}, + + // deprecated options + dimensions: {get: function (){return dimensionNames;}, set: function(_){ + // deprecated after 1.8.1 + nv.deprecated('dimensions', 'use dimensionNames instead'); + dimensionNames = _; + }}, + + // options that require extra logic in the setter + margin: {get: function(){return margin;}, set: function(_){ + margin.top = _.top !== undefined ? _.top : margin.top; + margin.right = _.right !== undefined ? _.right : margin.right; + margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom; + margin.left = _.left !== undefined ? _.left : margin.left; + }}, + color: {get: function(){return color;}, set: function(_){ + color = nv.utils.getColor(_); + }} + }); + + nv.utils.initOptions(chart); + return chart; +}; +nv.models.pie = function() { + "use strict"; + + //============================================================ + // Public Variables with Default Settings + //------------------------------------------------------------ + + var margin = {top: 0, right: 0, bottom: 0, left: 0} + , width = 500 + , height = 500 + , getX = function(d) { return d.x } + , getY = function(d) { return d.y } + , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one + , container = null + , color = nv.utils.defaultColor() + , valueFormat = d3.format(',.2f') + , showLabels = true + , labelsOutside = false + , labelType = "key" + , labelThreshold = .02 //if slice percentage is under this, don't show label + , donut = false + , title = false + , growOnHover = true + , titleOffset = 0 + , labelSunbeamLayout = false + , startAngle = false + , padAngle = false + , endAngle = false + , cornerRadius = 0 + , donutRatio = 0.5 + , arcsRadius = [] + , dispatch = d3.dispatch('chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout', 'elementMousemove', 'renderEnd') + ; + + var arcs = []; + var arcsOver = []; + + //============================================================ + // chart function + //------------------------------------------------------------ + + var renderWatch = nv.utils.renderWatch(dispatch); + + function chart(selection) { + renderWatch.reset(); + selection.each(function(data) { + var availableWidth = width - margin.left - margin.right + , availableHeight = height - margin.top - margin.bottom + , radius = Math.min(availableWidth, availableHeight) / 2 + , arcsRadiusOuter = [] + , arcsRadiusInner = [] + ; + + container = d3.select(this) + if (arcsRadius.length === 0) { + var outer = radius - radius / 5; + var inner = donutRatio * radius; + for (var i = 0; i < data[0].length; i++) { + arcsRadiusOuter.push(outer); + arcsRadiusInner.push(inner); + } + } else { + arcsRadiusOuter = arcsRadius.map(function (d) { return (d.outer - d.outer / 5) * radius; }); + arcsRadiusInner = arcsRadius.map(function (d) { return (d.inner - d.inner / 5) * radius; }); + donutRatio = d3.min(arcsRadius.map(function (d) { return (d.inner - d.inner / 5); })); + } + nv.utils.initSVG(container); + + // Setup containers and skeleton of chart + var wrap = container.selectAll('.nv-wrap.nv-pie').data(data); + var wrapEnter = wrap.enter().append('g').attr('class','nvd3 nv-wrap nv-pie nv-chart-' + id); + var gEnter = wrapEnter.append('g'); + var g = wrap.select('g'); + var g_pie = gEnter.append('g').attr('class', 'nv-pie'); + gEnter.append('g').attr('class', 'nv-pieLabels'); + + wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); + g.select('.nv-pie').attr('transform', 'translate(' + availableWidth / 2 + ',' + availableHeight / 2 + ')'); + g.select('.nv-pieLabels').attr('transform', 'translate(' + availableWidth / 2 + ',' + availableHeight / 2 + ')'); + + // + container.on('click', function(d,i) { + dispatch.chartClick({ + data: d, + index: i, + pos: d3.event, + id: id + }); + }); + + arcs = []; + arcsOver = []; + for (var i = 0; i < data[0].length; i++) { + + var arc = d3.svg.arc().outerRadius(arcsRadiusOuter[i]); + var arcOver = d3.svg.arc().outerRadius(arcsRadiusOuter[i] + 5); + + if (startAngle !== false) { + arc.startAngle(startAngle); + arcOver.startAngle(startAngle); + } + if (endAngle !== false) { + arc.endAngle(endAngle); + arcOver.endAngle(endAngle); + } + if (donut) { + arc.innerRadius(arcsRadiusInner[i]); + arcOver.innerRadius(arcsRadiusInner[i]); + } + + if (arc.cornerRadius && cornerRadius) { + arc.cornerRadius(cornerRadius); + arcOver.cornerRadius(cornerRadius); + } + + arcs.push(arc); + arcsOver.push(arcOver); + } + + // Setup the Pie chart and choose the data element + var pie = d3.layout.pie() + .sort(null) + .value(function(d) { return d.disabled ? 0 : getY(d) }); + + // padAngle added in d3 3.5 + if (pie.padAngle && padAngle) { + pie.padAngle(padAngle); + } + + // if title is specified and donut, put it in the middle + if (donut && title) { + g_pie.append("text").attr('class', 'nv-pie-title'); + + wrap.select('.nv-pie-title') + .style("text-anchor", "middle") + .text(function (d) { + return title; + }) + .style("font-size", (Math.min(availableWidth, availableHeight)) * donutRatio * 2 / (title.length + 2) + "px") + .attr("dy", "0.35em") // trick to vertically center text + .attr('transform', function(d, i) { + return 'translate(0, '+ titleOffset + ')'; + }); + } + + var slices = wrap.select('.nv-pie').selectAll('.nv-slice').data(pie); + var pieLabels = wrap.select('.nv-pieLabels').selectAll('.nv-label').data(pie); + + slices.exit().remove(); + pieLabels.exit().remove(); + + var ae = slices.enter().append('g'); + ae.attr('class', 'nv-slice'); + ae.on('mouseover', function(d, i) { + d3.select(this).classed('hover', true); + if (growOnHover) { + d3.select(this).select("path").transition() + .duration(70) + .attr("d", arcsOver[i]); + } + dispatch.elementMouseover({ + data: d.data, + index: i, + color: d3.select(this).style("fill") + }); + }); + ae.on('mouseout', function(d, i) { + d3.select(this).classed('hover', false); + if (growOnHover) { + d3.select(this).select("path").transition() + .duration(50) + .attr("d", arcs[i]); + } + dispatch.elementMouseout({data: d.data, index: i}); + }); + ae.on('mousemove', function(d, i) { + dispatch.elementMousemove({data: d.data, index: i}); + }); + ae.on('click', function(d, i) { + dispatch.elementClick({ + data: d.data, + index: i, + color: d3.select(this).style("fill") + }); + }); + ae.on('dblclick', function(d, i) { + dispatch.elementDblClick({ + data: d.data, + index: i, + color: d3.select(this).style("fill") + }); + }); + + slices.attr('fill', function(d,i) { return color(d.data, i); }); + slices.attr('stroke', function(d,i) { return color(d.data, i); }); + + var paths = ae.append('path').each(function(d) { + this._current = d; + }); + + slices.select('path') + .transition() + .attr('d', function (d, i) { return arcs[i](d); }) + .attrTween('d', arcTween); + + if (showLabels) { + // This does the normal label + var labelsArc = []; + for (var i = 0; i < data[0].length; i++) { + labelsArc.push(arcs[i]); + + if (labelsOutside) { + if (donut) { + labelsArc[i] = d3.svg.arc().outerRadius(arcs[i].outerRadius()); + if (startAngle !== false) labelsArc[i].startAngle(startAngle); + if (endAngle !== false) labelsArc[i].endAngle(endAngle); + } + } else if (!donut) { + labelsArc[i].innerRadius(0); + } + } + + pieLabels.enter().append("g").classed("nv-label",true).each(function(d,i) { + var group = d3.select(this); + + group.attr('transform', function (d, i) { + if (labelSunbeamLayout) { + d.outerRadius = arcsRadiusOuter[i] + 10; // Set Outer Coordinate + d.innerRadius = arcsRadiusOuter[i] + 15; // Set Inner Coordinate + var rotateAngle = (d.startAngle + d.endAngle) / 2 * (180 / Math.PI); + if ((d.startAngle + d.endAngle) / 2 < Math.PI) { + rotateAngle -= 90; + } else { + rotateAngle += 90; + } + return 'translate(' + labelsArc[i].centroid(d) + ') rotate(' + rotateAngle + ')'; + } else { + d.outerRadius = radius + 10; // Set Outer Coordinate + d.innerRadius = radius + 15; // Set Inner Coordinate + return 'translate(' + labelsArc[i].centroid(d) + ')' + } + }); + + group.append('rect') + .style('stroke', '#fff') + .style('fill', '#fff') + .attr("rx", 3) + .attr("ry", 3); + + group.append('text') + .style('text-anchor', labelSunbeamLayout ? ((d.startAngle + d.endAngle) / 2 < Math.PI ? 'start' : 'end') : 'middle') //center the text on it's origin or begin/end if orthogonal aligned + .style('fill', '#000') + }); + + var labelLocationHash = {}; + var avgHeight = 14; + var avgWidth = 140; + var createHashKey = function(coordinates) { + return Math.floor(coordinates[0]/avgWidth) * avgWidth + ',' + Math.floor(coordinates[1]/avgHeight) * avgHeight; + }; + + pieLabels.watchTransition(renderWatch, 'pie labels').attr('transform', function (d, i) { + if (labelSunbeamLayout) { + d.outerRadius = arcsRadiusOuter[i] + 10; // Set Outer Coordinate + d.innerRadius = arcsRadiusOuter[i] + 15; // Set Inner Coordinate + var rotateAngle = (d.startAngle + d.endAngle) / 2 * (180 / Math.PI); + if ((d.startAngle + d.endAngle) / 2 < Math.PI) { + rotateAngle -= 90; + } else { + rotateAngle += 90; + } + return 'translate(' + labelsArc[i].centroid(d) + ') rotate(' + rotateAngle + ')'; + } else { + d.outerRadius = radius + 10; // Set Outer Coordinate + d.innerRadius = radius + 15; // Set Inner Coordinate + + /* + Overlapping pie labels are not good. What this attempts to do is, prevent overlapping. + Each label location is hashed, and if a hash collision occurs, we assume an overlap. + Adjust the label's y-position to remove the overlap. + */ + var center = labelsArc[i].centroid(d); + if (d.value) { + var hashKey = createHashKey(center); + if (labelLocationHash[hashKey]) { + center[1] -= avgHeight; + } + labelLocationHash[createHashKey(center)] = true; + } + return 'translate(' + center + ')' + } + }); + + pieLabels.select(".nv-label text") + .style('text-anchor', function(d,i) { + //center the text on it's origin or begin/end if orthogonal aligned + return labelSunbeamLayout ? ((d.startAngle + d.endAngle) / 2 < Math.PI ? 'start' : 'end') : 'middle'; + }) + .text(function(d, i) { + var percent = (d.endAngle - d.startAngle) / (2 * Math.PI); + var label = ''; + if (!d.value || percent < labelThreshold) return ''; + + if(typeof labelType === 'function') { + label = labelType(d, i, { + 'key': getX(d.data), + 'value': getY(d.data), + 'percent': valueFormat(percent) + }); + } else { + switch (labelType) { + case 'key': + label = getX(d.data); + break; + case 'value': + label = valueFormat(getY(d.data)); + break; + case 'percent': + label = d3.format('%')(percent); + break; + } + } + return label; + }) + ; + } + + + // Computes the angle of an arc, converting from radians to degrees. + function angle(d) { + var a = (d.startAngle + d.endAngle) * 90 / Math.PI - 90; + return a > 90 ? a - 180 : a; + } + + function arcTween(a, idx) { + a.endAngle = isNaN(a.endAngle) ? 0 : a.endAngle; + a.startAngle = isNaN(a.startAngle) ? 0 : a.startAngle; + if (!donut) a.innerRadius = 0; + var i = d3.interpolate(this._current, a); + this._current = i(0); + return function (t) { + return arcs[idx](i(t)); + }; + } + }); + + renderWatch.renderEnd('pie immediate'); + return chart; + } + + //============================================================ + // Expose Public Variables + //------------------------------------------------------------ + + chart.dispatch = dispatch; + chart.options = nv.utils.optionsFunc.bind(chart); + + chart._options = Object.create({}, { + // simple options, just get/set the necessary values + arcsRadius: { get: function () { return arcsRadius; }, set: function (_) { arcsRadius = _; } }, + width: {get: function(){return width;}, set: function(_){width=_;}}, + height: {get: function(){return height;}, set: function(_){height=_;}}, + showLabels: {get: function(){return showLabels;}, set: function(_){showLabels=_;}}, + title: {get: function(){return title;}, set: function(_){title=_;}}, + titleOffset: {get: function(){return titleOffset;}, set: function(_){titleOffset=_;}}, + labelThreshold: {get: function(){return labelThreshold;}, set: function(_){labelThreshold=_;}}, + valueFormat: {get: function(){return valueFormat;}, set: function(_){valueFormat=_;}}, + x: {get: function(){return getX;}, set: function(_){getX=_;}}, + id: {get: function(){return id;}, set: function(_){id=_;}}, + endAngle: {get: function(){return endAngle;}, set: function(_){endAngle=_;}}, + startAngle: {get: function(){return startAngle;}, set: function(_){startAngle=_;}}, + padAngle: {get: function(){return padAngle;}, set: function(_){padAngle=_;}}, + cornerRadius: {get: function(){return cornerRadius;}, set: function(_){cornerRadius=_;}}, + donutRatio: {get: function(){return donutRatio;}, set: function(_){donutRatio=_;}}, + labelsOutside: {get: function(){return labelsOutside;}, set: function(_){labelsOutside=_;}}, + labelSunbeamLayout: {get: function(){return labelSunbeamLayout;}, set: function(_){labelSunbeamLayout=_;}}, + donut: {get: function(){return donut;}, set: function(_){donut=_;}}, + growOnHover: {get: function(){return growOnHover;}, set: function(_){growOnHover=_;}}, + + // depreciated after 1.7.1 + pieLabelsOutside: {get: function(){return labelsOutside;}, set: function(_){ + labelsOutside=_; + nv.deprecated('pieLabelsOutside', 'use labelsOutside instead'); + }}, + // depreciated after 1.7.1 + donutLabelsOutside: {get: function(){return labelsOutside;}, set: function(_){ + labelsOutside=_; + nv.deprecated('donutLabelsOutside', 'use labelsOutside instead'); + }}, + // deprecated after 1.7.1 + labelFormat: {get: function(){ return valueFormat;}, set: function(_) { + valueFormat=_; + nv.deprecated('labelFormat','use valueFormat instead'); + }}, + + // options that require extra logic in the setter + margin: {get: function(){return margin;}, set: function(_){ + margin.top = typeof _.top != 'undefined' ? _.top : margin.top; + margin.right = typeof _.right != 'undefined' ? _.right : margin.right; + margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom; + margin.left = typeof _.left != 'undefined' ? _.left : margin.left; + }}, + y: {get: function(){return getY;}, set: function(_){ + getY=d3.functor(_); + }}, + color: {get: function(){return color;}, set: function(_){ + color=nv.utils.getColor(_); + }}, + labelType: {get: function(){return labelType;}, set: function(_){ + labelType= _ || 'key'; + }} + }); + + nv.utils.initOptions(chart); + return chart; +}; +nv.models.pieChart = function() { + "use strict"; + + //============================================================ + // Public Variables with Default Settings + //------------------------------------------------------------ + + var pie = nv.models.pie(); + var legend = nv.models.legend(); + var tooltip = nv.models.tooltip(); + + var margin = {top: 30, right: 20, bottom: 20, left: 20} + , width = null + , height = null + , showLegend = true + , legendPosition = "top" + , color = nv.utils.defaultColor() + , state = nv.utils.state() + , defaultState = null + , noData = null + , duration = 250 + , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState','renderEnd') + ; + + tooltip + .headerEnabled(false) + .duration(0) + .valueFormatter(function(d, i) { + return pie.valueFormat()(d, i); + }); + + //============================================================ + // Private Variables + //------------------------------------------------------------ + + var renderWatch = nv.utils.renderWatch(dispatch); + + var stateGetter = function(data) { + return function(){ + return { + active: data.map(function(d) { return !d.disabled }) + }; + } + }; + + var stateSetter = function(data) { + return function(state) { + if (state.active !== undefined) { + data.forEach(function (series, i) { + series.disabled = !state.active[i]; + }); + } + } + }; + + //============================================================ + // Chart function + //------------------------------------------------------------ + + function chart(selection) { + renderWatch.reset(); + renderWatch.models(pie); + + selection.each(function(data) { + var container = d3.select(this); + nv.utils.initSVG(container); + + var that = this; + var availableWidth = nv.utils.availableWidth(width, container, margin), + availableHeight = nv.utils.availableHeight(height, container, margin); + + chart.update = function() { container.transition().call(chart); }; + chart.container = this; + + state.setter(stateSetter(data), chart.update) + .getter(stateGetter(data)) + .update(); + + //set state.disabled + state.disabled = data.map(function(d) { return !!d.disabled }); + + if (!defaultState) { + var key; + defaultState = {}; + for (key in state) { + if (state[key] instanceof Array) + defaultState[key] = state[key].slice(0); + else + defaultState[key] = state[key]; + } + } + + // Display No Data message if there's nothing to show. + if (!data || !data.length) { + nv.utils.noData(chart, container); + return chart; + } else { + container.selectAll('.nv-noData').remove(); + } + + // Setup containers and skeleton of chart + var wrap = container.selectAll('g.nv-wrap.nv-pieChart').data([data]); + var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-pieChart').append('g'); + var g = wrap.select('g'); + + gEnter.append('g').attr('class', 'nv-pieWrap'); + gEnter.append('g').attr('class', 'nv-legendWrap'); + + // Legend + if (showLegend) { + if (legendPosition === "top") { + legend.width( availableWidth ).key(pie.x()); + + wrap.select('.nv-legendWrap') + .datum(data) + .call(legend); + + if ( margin.top != legend.height()) { + margin.top = legend.height(); + availableHeight = nv.utils.availableHeight(height, container, margin); + } + + wrap.select('.nv-legendWrap') + .attr('transform', 'translate(0,' + (-margin.top) +')'); + } else if (legendPosition === "right") { + var legendWidth = nv.models.legend().width(); + if (availableWidth / 2 < legendWidth) { + legendWidth = (availableWidth / 2) + } + legend.height(availableHeight).key(pie.x()); + legend.width(legendWidth); + availableWidth -= legend.width(); + + wrap.select('.nv-legendWrap') + .datum(data) + .call(legend) + .attr('transform', 'translate(' + (availableWidth) +',0)'); + } + } + wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); + + // Main Chart Component(s) + pie.width(availableWidth).height(availableHeight); + var pieWrap = g.select('.nv-pieWrap').datum([data]); + d3.transition(pieWrap).call(pie); + + //============================================================ + // Event Handling/Dispatching (in chart's scope) + //------------------------------------------------------------ + + legend.dispatch.on('stateChange', function(newState) { + for (var key in newState) { + state[key] = newState[key]; + } + dispatch.stateChange(state); + chart.update(); + }); + + // Update chart from a state object passed to event handler + dispatch.on('changeState', function(e) { + if (typeof e.disabled !== 'undefined') { + data.forEach(function(series,i) { + series.disabled = e.disabled[i]; + }); + state.disabled = e.disabled; + } + chart.update(); + }); + }); + + renderWatch.renderEnd('pieChart immediate'); + return chart; + } + + //============================================================ + // Event Handling/Dispatching (out of chart's scope) + //------------------------------------------------------------ + + pie.dispatch.on('elementMouseover.tooltip', function(evt) { + evt['series'] = { + key: chart.x()(evt.data), + value: chart.y()(evt.data), + color: evt.color + }; + tooltip.data(evt).hidden(false); + }); + + pie.dispatch.on('elementMouseout.tooltip', function(evt) { + tooltip.hidden(true); + }); + + pie.dispatch.on('elementMousemove.tooltip', function(evt) { + tooltip.position({top: d3.event.pageY, left: d3.event.pageX})(); + }); + + //============================================================ + // Expose Public Variables + //------------------------------------------------------------ + + // expose chart's sub-components + chart.legend = legend; + chart.dispatch = dispatch; + chart.pie = pie; + chart.tooltip = tooltip; + chart.options = nv.utils.optionsFunc.bind(chart); + + // use Object get/set functionality to map between vars and chart functions + chart._options = Object.create({}, { + // simple options, just get/set the necessary values + noData: {get: function(){return noData;}, set: function(_){noData=_;}}, + showLegend: {get: function(){return showLegend;}, set: function(_){showLegend=_;}}, + legendPosition: {get: function(){return legendPosition;}, set: function(_){legendPosition=_;}}, + defaultState: {get: function(){return defaultState;}, set: function(_){defaultState=_;}}, + + // deprecated options + tooltips: {get: function(){return tooltip.enabled();}, set: function(_){ + // deprecated after 1.7.1 + nv.deprecated('tooltips', 'use chart.tooltip.enabled() instead'); + tooltip.enabled(!!_); + }}, + tooltipContent: {get: function(){return tooltip.contentGenerator();}, set: function(_){ + // deprecated after 1.7.1 + nv.deprecated('tooltipContent', 'use chart.tooltip.contentGenerator() instead'); + tooltip.contentGenerator(_); + }}, + + // options that require extra logic in the setter + color: {get: function(){return color;}, set: function(_){ + color = _; + legend.color(color); + pie.color(color); + }}, + duration: {get: function(){return duration;}, set: function(_){ + duration = _; + renderWatch.reset(duration); + }}, + margin: {get: function(){return margin;}, set: function(_){ + margin.top = _.top !== undefined ? _.top : margin.top; + margin.right = _.right !== undefined ? _.right : margin.right; + margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom; + margin.left = _.left !== undefined ? _.left : margin.left; + }} + }); + nv.utils.inheritOptions(chart, pie); + nv.utils.initOptions(chart); + return chart; +}; + +nv.models.scatter = function() { + "use strict"; + + //============================================================ + // Public Variables with Default Settings + //------------------------------------------------------------ + + var margin = {top: 0, right: 0, bottom: 0, left: 0} + , width = null + , height = null + , color = nv.utils.defaultColor() // chooses color + , id = Math.floor(Math.random() * 100000) //Create semi-unique ID incase user doesn't select one + , container = null + , x = d3.scale.linear() + , y = d3.scale.linear() + , z = d3.scale.linear() //linear because d3.svg.shape.size is treated as area + , getX = function(d) { return d.x } // accessor to get the x value + , getY = function(d) { return d.y } // accessor to get the y value + , getSize = function(d) { return d.size || 1} // accessor to get the point size + , getShape = function(d) { return d.shape || 'circle' } // accessor to get point shape + , forceX = [] // List of numbers to Force into the X scale (ie. 0, or a max / min, etc.) + , forceY = [] // List of numbers to Force into the Y scale + , forceSize = [] // List of numbers to Force into the Size scale + , interactive = true // If true, plots a voronoi overlay for advanced point intersection + , pointActive = function(d) { return !d.notActive } // any points that return false will be filtered out + , padData = false // If true, adds half a data points width to front and back, for lining up a line chart with a bar chart + , padDataOuter = .1 //outerPadding to imitate ordinal scale outer padding + , clipEdge = false // if true, masks points within x and y scale + , clipVoronoi = true // if true, masks each point with a circle... can turn off to slightly increase performance + , showVoronoi = false // display the voronoi areas + , clipRadius = function() { return 25 } // function to get the radius for voronoi point clips + , xDomain = null // Override x domain (skips the calculation from data) + , yDomain = null // Override y domain + , xRange = null // Override x range + , yRange = null // Override y range + , sizeDomain = null // Override point size domain + , sizeRange = null + , singlePoint = false + , dispatch = d3.dispatch('elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout', 'renderEnd') + , useVoronoi = true + , duration = 250 + ; + + + //============================================================ + // Private Variables + //------------------------------------------------------------ + + var x0, y0, z0 // used to store previous scales + , timeoutID + , needsUpdate = false // Flag for when the points are visually updating, but the interactive layer is behind, to disable tooltips + , renderWatch = nv.utils.renderWatch(dispatch, duration) + , _sizeRange_def = [16, 256] + ; + + function chart(selection) { + renderWatch.reset(); + selection.each(function(data) { + container = d3.select(this); + var availableWidth = nv.utils.availableWidth(width, container, margin), + availableHeight = nv.utils.availableHeight(height, container, margin); + + nv.utils.initSVG(container); + + //add series index to each data point for reference + data.forEach(function(series, i) { + series.values.forEach(function(point) { + point.series = i; + }); + }); + + // Setup Scales + // remap and flatten the data for use in calculating the scales' domains + var seriesData = (xDomain && yDomain && sizeDomain) ? [] : // if we know xDomain and yDomain and sizeDomain, no need to calculate.... if Size is constant remember to set sizeDomain to speed up performance + d3.merge( + data.map(function(d) { + return d.values.map(function(d,i) { + return { x: getX(d,i), y: getY(d,i), size: getSize(d,i) } + }) + }) + ); + + x .domain(xDomain || d3.extent(seriesData.map(function(d) { return d.x; }).concat(forceX))) + + if (padData && data[0]) + x.range(xRange || [(availableWidth * padDataOuter + availableWidth) / (2 *data[0].values.length), availableWidth - availableWidth * (1 + padDataOuter) / (2 * data[0].values.length) ]); + //x.range([availableWidth * .5 / data[0].values.length, availableWidth * (data[0].values.length - .5) / data[0].values.length ]); + else + x.range(xRange || [0, availableWidth]); + + y .domain(yDomain || d3.extent(seriesData.map(function(d) { return d.y }).concat(forceY))) + .range(yRange || [availableHeight, 0]); + + z .domain(sizeDomain || d3.extent(seriesData.map(function(d) { return d.size }).concat(forceSize))) + .range(sizeRange || _sizeRange_def); + + // If scale's domain don't have a range, slightly adjust to make one... so a chart can show a single data point + singlePoint = x.domain()[0] === x.domain()[1] || y.domain()[0] === y.domain()[1]; + + if (x.domain()[0] === x.domain()[1]) + x.domain()[0] ? + x.domain([x.domain()[0] - x.domain()[0] * 0.01, x.domain()[1] + x.domain()[1] * 0.01]) + : x.domain([-1,1]); + + if (y.domain()[0] === y.domain()[1]) + y.domain()[0] ? + y.domain([y.domain()[0] - y.domain()[0] * 0.01, y.domain()[1] + y.domain()[1] * 0.01]) + : y.domain([-1,1]); + + if ( isNaN(x.domain()[0])) { + x.domain([-1,1]); + } + + if ( isNaN(y.domain()[0])) { + y.domain([-1,1]); + } + + x0 = x0 || x; + y0 = y0 || y; + z0 = z0 || z; + + // Setup containers and skeleton of chart + var wrap = container.selectAll('g.nv-wrap.nv-scatter').data([data]); + var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-scatter nv-chart-' + id); + var defsEnter = wrapEnter.append('defs'); + var gEnter = wrapEnter.append('g'); + var g = wrap.select('g'); + + wrap.classed('nv-single-point', singlePoint); + gEnter.append('g').attr('class', 'nv-groups'); + gEnter.append('g').attr('class', 'nv-point-paths'); + wrapEnter.append('g').attr('class', 'nv-point-clips'); + + wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); + + defsEnter.append('clipPath') + .attr('id', 'nv-edge-clip-' + id) + .append('rect'); + + wrap.select('#nv-edge-clip-' + id + ' rect') + .attr('width', availableWidth) + .attr('height', (availableHeight > 0) ? availableHeight : 0); + + g.attr('clip-path', clipEdge ? 'url(#nv-edge-clip-' + id + ')' : ''); + + function updateInteractiveLayer() { + // Always clear needs-update flag regardless of whether or not + // we will actually do anything (avoids needless invocations). + needsUpdate = false; + + if (!interactive) return false; + + // inject series and point index for reference into voronoi + if (useVoronoi === true) { + var vertices = d3.merge(data.map(function(group, groupIndex) { + return group.values + .map(function(point, pointIndex) { + // *Adding noise to make duplicates very unlikely + // *Injecting series and point index for reference + /* *Adding a 'jitter' to the points, because there's an issue in d3.geom.voronoi. + */ + var pX = getX(point,pointIndex); + var pY = getY(point,pointIndex); + + return [x(pX)+ Math.random() * 1e-4, + y(pY)+ Math.random() * 1e-4, + groupIndex, + pointIndex, point]; //temp hack to add noise until I think of a better way so there are no duplicates + }) + .filter(function(pointArray, pointIndex) { + return pointActive(pointArray[4], pointIndex); // Issue #237.. move filter to after map, so pointIndex is correct! + }) + }) + ); + + if (vertices.length == 0) return false; // No active points, we're done + if (vertices.length < 3) { + // Issue #283 - Adding 2 dummy points to the voronoi b/c voronoi requires min 3 points to work + vertices.push([x.range()[0] - 20, y.range()[0] - 20, null, null]); + vertices.push([x.range()[1] + 20, y.range()[1] + 20, null, null]); + vertices.push([x.range()[0] - 20, y.range()[0] + 20, null, null]); + vertices.push([x.range()[1] + 20, y.range()[1] - 20, null, null]); + } + + // keep voronoi sections from going more than 10 outside of graph + // to avoid overlap with other things like legend etc + var bounds = d3.geom.polygon([ + [-10,-10], + [-10,height + 10], + [width + 10,height + 10], + [width + 10,-10] + ]); + + var voronoi = d3.geom.voronoi(vertices).map(function(d, i) { + return { + 'data': bounds.clip(d), + 'series': vertices[i][2], + 'point': vertices[i][3] + } + }); + + // nuke all voronoi paths on reload and recreate them + wrap.select('.nv-point-paths').selectAll('path').remove(); + var pointPaths = wrap.select('.nv-point-paths').selectAll('path').data(voronoi); + var vPointPaths = pointPaths + .enter().append("svg:path") + .attr("d", function(d) { + if (!d || !d.data || d.data.length === 0) + return 'M 0 0'; + else + return "M" + d.data.join(",") + "Z"; + }) + .attr("id", function(d,i) { + return "nv-path-"+i; }) + .attr("clip-path", function(d,i) { return "url(#nv-clip-"+i+")"; }) + ; + + // good for debugging point hover issues + if (showVoronoi) { + vPointPaths.style("fill", d3.rgb(230, 230, 230)) + .style('fill-opacity', 0.4) + .style('stroke-opacity', 1) + .style("stroke", d3.rgb(200,200,200)); + } + + if (clipVoronoi) { + // voronoi sections are already set to clip, + // just create the circles with the IDs they expect + wrap.select('.nv-point-clips').selectAll('clipPath').remove(); + wrap.select('.nv-point-clips').selectAll("clipPath") + .data(vertices) + .enter().append("svg:clipPath") + .attr("id", function(d, i) { return "nv-clip-"+i;}) + .append("svg:circle") + .attr('cx', function(d) { return d[0]; }) + .attr('cy', function(d) { return d[1]; }) + .attr('r', clipRadius); + } + + var mouseEventCallback = function(d, mDispatch) { + if (needsUpdate) return 0; + var series = data[d.series]; + if (series === undefined) return; + var point = series.values[d.point]; + point['color'] = color(series, d.series); + + // standardize attributes for tooltip. + point['x'] = getX(point); + point['y'] = getY(point); + + // can't just get box of event node since it's actually a voronoi polygon + var box = container.node().getBoundingClientRect(); + var scrollTop = window.pageYOffset || document.documentElement.scrollTop; + var scrollLeft = window.pageXOffset || document.documentElement.scrollLeft; + + var pos = { + left: x(getX(point, d.point)) + box.left + scrollLeft + margin.left + 10, + top: y(getY(point, d.point)) + box.top + scrollTop + margin.top + 10 + }; + + mDispatch({ + point: point, + series: series, + pos: pos, + seriesIndex: d.series, + pointIndex: d.point + }); + }; + + pointPaths + .on('click', function(d) { + mouseEventCallback(d, dispatch.elementClick); + }) + .on('dblclick', function(d) { + mouseEventCallback(d, dispatch.elementDblClick); + }) + .on('mouseover', function(d) { + mouseEventCallback(d, dispatch.elementMouseover); + }) + .on('mouseout', function(d, i) { + mouseEventCallback(d, dispatch.elementMouseout); + }); + + } else { + // add event handlers to points instead voronoi paths + wrap.select('.nv-groups').selectAll('.nv-group') + .selectAll('.nv-point') + //.data(dataWithPoints) + //.style('pointer-events', 'auto') // recativate events, disabled by css + .on('click', function(d,i) { + //nv.log('test', d, i); + if (needsUpdate || !data[d.series]) return 0; //check if this is a dummy point + var series = data[d.series], + point = series.values[i]; + + dispatch.elementClick({ + point: point, + series: series, + pos: [x(getX(point, i)) + margin.left, y(getY(point, i)) + margin.top], + seriesIndex: d.series, + pointIndex: i + }); + }) + .on('dblclick', function(d,i) { + if (needsUpdate || !data[d.series]) return 0; //check if this is a dummy point + var series = data[d.series], + point = series.values[i]; + + dispatch.elementDblClick({ + point: point, + series: series, + pos: [x(getX(point, i)) + margin.left, y(getY(point, i)) + margin.top], + seriesIndex: d.series, + pointIndex: i + }); + }) + .on('mouseover', function(d,i) { + if (needsUpdate || !data[d.series]) return 0; //check if this is a dummy point + var series = data[d.series], + point = series.values[i]; + + dispatch.elementMouseover({ + point: point, + series: series, + pos: [x(getX(point, i)) + margin.left, y(getY(point, i)) + margin.top], + seriesIndex: d.series, + pointIndex: i, + color: color(d, i) + }); + }) + .on('mouseout', function(d,i) { + if (needsUpdate || !data[d.series]) return 0; //check if this is a dummy point + var series = data[d.series], + point = series.values[i]; + + dispatch.elementMouseout({ + point: point, + series: series, + seriesIndex: d.series, + pointIndex: i, + color: color(d, i) + }); + }); + } + } + + needsUpdate = true; + var groups = wrap.select('.nv-groups').selectAll('.nv-group') + .data(function(d) { return d }, function(d) { return d.key }); + groups.enter().append('g') + .style('stroke-opacity', 1e-6) + .style('fill-opacity', 1e-6); + groups.exit() + .remove(); + groups + .attr('class', function(d,i) { return 'nv-group nv-series-' + i }) + .classed('hover', function(d) { return d.hover }); + groups.watchTransition(renderWatch, 'scatter: groups') + .style('fill', function(d,i) { return color(d, i) }) + .style('stroke', function(d,i) { return color(d, i) }) + .style('stroke-opacity', 1) + .style('fill-opacity', .5); + + // create the points, maintaining their IDs from the original data set + var points = groups.selectAll('path.nv-point') + .data(function(d) { + return d.values.map( + function (point, pointIndex) { + return [point, pointIndex] + }).filter( + function(pointArray, pointIndex) { + return pointActive(pointArray[0], pointIndex) + }) + }); + points.enter().append('path') + .style('fill', function (d) { return d.color }) + .style('stroke', function (d) { return d.color }) + .attr('transform', function(d) { + return 'translate(' + x0(getX(d[0],d[1])) + ',' + y0(getY(d[0],d[1])) + ')' + }) + .attr('d', + nv.utils.symbol() + .type(function(d) { return getShape(d[0]); }) + .size(function(d) { return z(getSize(d[0],d[1])) }) + ); + points.exit().remove(); + groups.exit().selectAll('path.nv-point') + .watchTransition(renderWatch, 'scatter exit') + .attr('transform', function(d) { + return 'translate(' + x(getX(d[0],d[1])) + ',' + y(getY(d[0],d[1])) + ')' + }) + .remove(); + points.each(function(d) { + d3.select(this) + .classed('nv-point', true) + .classed('nv-point-' + d[1], true) + .classed('nv-noninteractive', !interactive) + .classed('hover',false) + ; + }); + points + .watchTransition(renderWatch, 'scatter points') + .attr('transform', function(d) { + //nv.log(d, getX(d[0],d[1]), x(getX(d[0],d[1]))); + return 'translate(' + x(getX(d[0],d[1])) + ',' + y(getY(d[0],d[1])) + ')' + }) + .attr('d', + nv.utils.symbol() + .type(function(d) { return getShape(d[0]); }) + .size(function(d) { return z(getSize(d[0],d[1])) }) + ); + + // Delay updating the invisible interactive layer for smoother animation + clearTimeout(timeoutID); // stop repeat calls to updateInteractiveLayer + timeoutID = setTimeout(updateInteractiveLayer, 300); + //updateInteractiveLayer(); + + //store old scales for use in transitions on update + x0 = x.copy(); + y0 = y.copy(); + z0 = z.copy(); + + }); + renderWatch.renderEnd('scatter immediate'); + return chart; + } + + //============================================================ + // Expose Public Variables + //------------------------------------------------------------ + + chart.dispatch = dispatch; + chart.options = nv.utils.optionsFunc.bind(chart); + + // utility function calls provided by this chart + chart._calls = new function() { + this.clearHighlights = function () { + nv.dom.write(function() { + container.selectAll(".nv-point.hover").classed("hover", false); + }); + return null; + }; + this.highlightPoint = function (seriesIndex, pointIndex, isHoverOver) { + nv.dom.write(function() { + container.select(" .nv-series-" + seriesIndex + " .nv-point-" + pointIndex) + .classed("hover", isHoverOver); + }); + }; + }; + + // trigger calls from events too + dispatch.on('elementMouseover.point', function(d) { + if (interactive) chart._calls.highlightPoint(d.seriesIndex,d.pointIndex,true); + }); + + dispatch.on('elementMouseout.point', function(d) { + if (interactive) chart._calls.highlightPoint(d.seriesIndex,d.pointIndex,false); + }); + + chart._options = Object.create({}, { + // simple options, just get/set the necessary values + width: {get: function(){return width;}, set: function(_){width=_;}}, + height: {get: function(){return height;}, set: function(_){height=_;}}, + xScale: {get: function(){return x;}, set: function(_){x=_;}}, + yScale: {get: function(){return y;}, set: function(_){y=_;}}, + pointScale: {get: function(){return z;}, set: function(_){z=_;}}, + xDomain: {get: function(){return xDomain;}, set: function(_){xDomain=_;}}, + yDomain: {get: function(){return yDomain;}, set: function(_){yDomain=_;}}, + pointDomain: {get: function(){return sizeDomain;}, set: function(_){sizeDomain=_;}}, + xRange: {get: function(){return xRange;}, set: function(_){xRange=_;}}, + yRange: {get: function(){return yRange;}, set: function(_){yRange=_;}}, + pointRange: {get: function(){return sizeRange;}, set: function(_){sizeRange=_;}}, + forceX: {get: function(){return forceX;}, set: function(_){forceX=_;}}, + forceY: {get: function(){return forceY;}, set: function(_){forceY=_;}}, + forcePoint: {get: function(){return forceSize;}, set: function(_){forceSize=_;}}, + interactive: {get: function(){return interactive;}, set: function(_){interactive=_;}}, + pointActive: {get: function(){return pointActive;}, set: function(_){pointActive=_;}}, + padDataOuter: {get: function(){return padDataOuter;}, set: function(_){padDataOuter=_;}}, + padData: {get: function(){return padData;}, set: function(_){padData=_;}}, + clipEdge: {get: function(){return clipEdge;}, set: function(_){clipEdge=_;}}, + clipVoronoi: {get: function(){return clipVoronoi;}, set: function(_){clipVoronoi=_;}}, + clipRadius: {get: function(){return clipRadius;}, set: function(_){clipRadius=_;}}, + showVoronoi: {get: function(){return showVoronoi;}, set: function(_){showVoronoi=_;}}, + id: {get: function(){return id;}, set: function(_){id=_;}}, + + + // simple functor options + x: {get: function(){return getX;}, set: function(_){getX = d3.functor(_);}}, + y: {get: function(){return getY;}, set: function(_){getY = d3.functor(_);}}, + pointSize: {get: function(){return getSize;}, set: function(_){getSize = d3.functor(_);}}, + pointShape: {get: function(){return getShape;}, set: function(_){getShape = d3.functor(_);}}, + + // options that require extra logic in the setter + margin: {get: function(){return margin;}, set: function(_){ + margin.top = _.top !== undefined ? _.top : margin.top; + margin.right = _.right !== undefined ? _.right : margin.right; + margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom; + margin.left = _.left !== undefined ? _.left : margin.left; + }}, + duration: {get: function(){return duration;}, set: function(_){ + duration = _; + renderWatch.reset(duration); + }}, + color: {get: function(){return color;}, set: function(_){ + color = nv.utils.getColor(_); + }}, + useVoronoi: {get: function(){return useVoronoi;}, set: function(_){ + useVoronoi = _; + if (useVoronoi === false) { + clipVoronoi = false; + } + }} + }); + + nv.utils.initOptions(chart); + return chart; +}; + +nv.models.scatterChart = function() { + "use strict"; + + //============================================================ + // Public Variables with Default Settings + //------------------------------------------------------------ + + var scatter = nv.models.scatter() + , xAxis = nv.models.axis() + , yAxis = nv.models.axis() + , legend = nv.models.legend() + , distX = nv.models.distribution() + , distY = nv.models.distribution() + , tooltip = nv.models.tooltip() + ; + + var margin = {top: 30, right: 20, bottom: 50, left: 75} + , width = null + , height = null + , container = null + , color = nv.utils.defaultColor() + , x = scatter.xScale() + , y = scatter.yScale() + , showDistX = false + , showDistY = false + , showLegend = true + , showXAxis = true + , showYAxis = true + , rightAlignYAxis = false + , state = nv.utils.state() + , defaultState = null + , dispatch = d3.dispatch('stateChange', 'changeState', 'renderEnd') + , noData = null + , duration = 250 + ; + + scatter.xScale(x).yScale(y); + xAxis.orient('bottom').tickPadding(10); + yAxis + .orient((rightAlignYAxis) ? 'right' : 'left') + .tickPadding(10) + ; + distX.axis('x'); + distY.axis('y'); + tooltip + .headerFormatter(function(d, i) { + return xAxis.tickFormat()(d, i); + }) + .valueFormatter(function(d, i) { + return yAxis.tickFormat()(d, i); + }); + + //============================================================ + // Private Variables + //------------------------------------------------------------ + + var x0, y0 + , renderWatch = nv.utils.renderWatch(dispatch, duration); + + var stateGetter = function(data) { + return function(){ + return { + active: data.map(function(d) { return !d.disabled }) + }; + } + }; + + var stateSetter = function(data) { + return function(state) { + if (state.active !== undefined) + data.forEach(function(series,i) { + series.disabled = !state.active[i]; + }); + } + }; + + function chart(selection) { + renderWatch.reset(); + renderWatch.models(scatter); + if (showXAxis) renderWatch.models(xAxis); + if (showYAxis) renderWatch.models(yAxis); + if (showDistX) renderWatch.models(distX); + if (showDistY) renderWatch.models(distY); + + selection.each(function(data) { + var that = this; + + container = d3.select(this); + nv.utils.initSVG(container); + + var availableWidth = nv.utils.availableWidth(width, container, margin), + availableHeight = nv.utils.availableHeight(height, container, margin); + + chart.update = function() { + if (duration === 0) + container.call(chart); + else + container.transition().duration(duration).call(chart); + }; + chart.container = this; + + state + .setter(stateSetter(data), chart.update) + .getter(stateGetter(data)) + .update(); + + // DEPRECATED set state.disableddisabled + state.disabled = data.map(function(d) { return !!d.disabled }); + + if (!defaultState) { + var key; + defaultState = {}; + for (key in state) { + if (state[key] instanceof Array) + defaultState[key] = state[key].slice(0); + else + defaultState[key] = state[key]; + } + } + + // Display noData message if there's nothing to show. + if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) { + nv.utils.noData(chart, container); + renderWatch.renderEnd('scatter immediate'); + return chart; + } else { + container.selectAll('.nv-noData').remove(); + } + + // Setup Scales + x = scatter.xScale(); + y = scatter.yScale(); + + // Setup containers and skeleton of chart + var wrap = container.selectAll('g.nv-wrap.nv-scatterChart').data([data]); + var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-scatterChart nv-chart-' + scatter.id()); + var gEnter = wrapEnter.append('g'); + var g = wrap.select('g'); + + // background for pointer events + gEnter.append('rect').attr('class', 'nvd3 nv-background').style("pointer-events","none"); + + gEnter.append('g').attr('class', 'nv-x nv-axis'); + gEnter.append('g').attr('class', 'nv-y nv-axis'); + gEnter.append('g').attr('class', 'nv-scatterWrap'); + gEnter.append('g').attr('class', 'nv-regressionLinesWrap'); + gEnter.append('g').attr('class', 'nv-distWrap'); + gEnter.append('g').attr('class', 'nv-legendWrap'); + + if (rightAlignYAxis) { + g.select(".nv-y.nv-axis") + .attr("transform", "translate(" + availableWidth + ",0)"); + } + + // Legend + if (showLegend) { + var legendWidth = availableWidth; + legend.width(legendWidth); + + wrap.select('.nv-legendWrap') + .datum(data) + .call(legend); + + if ( margin.top != legend.height()) { + margin.top = legend.height(); + availableHeight = nv.utils.availableHeight(height, container, margin); + } + + wrap.select('.nv-legendWrap') + .attr('transform', 'translate(0' + ',' + (-margin.top) +')'); + } + + wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); + + // Main Chart Component(s) + scatter + .width(availableWidth) + .height(availableHeight) + .color(data.map(function(d,i) { + return d.color || color(d, i); + }).filter(function(d,i) { return !data[i].disabled })); + + wrap.select('.nv-scatterWrap') + .datum(data.filter(function(d) { return !d.disabled })) + .call(scatter); + + + wrap.select('.nv-regressionLinesWrap') + .attr('clip-path', 'url(#nv-edge-clip-' + scatter.id() + ')'); + + var regWrap = wrap.select('.nv-regressionLinesWrap').selectAll('.nv-regLines') + .data(function (d) { + return d; + }); + + regWrap.enter().append('g').attr('class', 'nv-regLines'); + + var regLine = regWrap.selectAll('.nv-regLine') + .data(function (d) { + return [d] + }); + + regLine.enter() + .append('line').attr('class', 'nv-regLine') + .style('stroke-opacity', 0); + + // don't add lines unless we have slope and intercept to use + regLine.filter(function(d) { + return d.intercept && d.slope; + }) + .watchTransition(renderWatch, 'scatterPlusLineChart: regline') + .attr('x1', x.range()[0]) + .attr('x2', x.range()[1]) + .attr('y1', function (d, i) { + return y(x.domain()[0] * d.slope + d.intercept) + }) + .attr('y2', function (d, i) { + return y(x.domain()[1] * d.slope + d.intercept) + }) + .style('stroke', function (d, i, j) { + return color(d, j) + }) + .style('stroke-opacity', function (d, i) { + return (d.disabled || typeof d.slope === 'undefined' || typeof d.intercept === 'undefined') ? 0 : 1 + }); + + // Setup Axes + if (showXAxis) { + xAxis + .scale(x) + ._ticks( nv.utils.calcTicksX(availableWidth/100, data) ) + .tickSize( -availableHeight , 0); + + g.select('.nv-x.nv-axis') + .attr('transform', 'translate(0,' + y.range()[0] + ')') + .call(xAxis); + } + + if (showYAxis) { + yAxis + .scale(y) + ._ticks( nv.utils.calcTicksY(availableHeight/36, data) ) + .tickSize( -availableWidth, 0); + + g.select('.nv-y.nv-axis') + .call(yAxis); + } + + + if (showDistX) { + distX + .getData(scatter.x()) + .scale(x) + .width(availableWidth) + .color(data.map(function(d,i) { + return d.color || color(d, i); + }).filter(function(d,i) { return !data[i].disabled })); + gEnter.select('.nv-distWrap').append('g') + .attr('class', 'nv-distributionX'); + g.select('.nv-distributionX') + .attr('transform', 'translate(0,' + y.range()[0] + ')') + .datum(data.filter(function(d) { return !d.disabled })) + .call(distX); + } + + if (showDistY) { + distY + .getData(scatter.y()) + .scale(y) + .width(availableHeight) + .color(data.map(function(d,i) { + return d.color || color(d, i); + }).filter(function(d,i) { return !data[i].disabled })); + gEnter.select('.nv-distWrap').append('g') + .attr('class', 'nv-distributionY'); + g.select('.nv-distributionY') + .attr('transform', 'translate(' + (rightAlignYAxis ? availableWidth : -distY.size() ) + ',0)') + .datum(data.filter(function(d) { return !d.disabled })) + .call(distY); + } + + //============================================================ + // Event Handling/Dispatching (in chart's scope) + //------------------------------------------------------------ + + legend.dispatch.on('stateChange', function(newState) { + for (var key in newState) + state[key] = newState[key]; + dispatch.stateChange(state); + chart.update(); + }); + + // Update chart from a state object passed to event handler + dispatch.on('changeState', function(e) { + if (typeof e.disabled !== 'undefined') { + data.forEach(function(series,i) { + series.disabled = e.disabled[i]; + }); + state.disabled = e.disabled; + } + chart.update(); + }); + + // mouseover needs availableHeight so we just keep scatter mouse events inside the chart block + scatter.dispatch.on('elementMouseout.tooltip', function(evt) { + tooltip.hidden(true); + container.select('.nv-chart-' + scatter.id() + ' .nv-series-' + evt.seriesIndex + ' .nv-distx-' + evt.pointIndex) + .attr('y1', 0); + container.select('.nv-chart-' + scatter.id() + ' .nv-series-' + evt.seriesIndex + ' .nv-disty-' + evt.pointIndex) + .attr('x2', distY.size()); + }); + + scatter.dispatch.on('elementMouseover.tooltip', function(evt) { + container.select('.nv-series-' + evt.seriesIndex + ' .nv-distx-' + evt.pointIndex) + .attr('y1', evt.pos.top - availableHeight - margin.top); + container.select('.nv-series-' + evt.seriesIndex + ' .nv-disty-' + evt.pointIndex) + .attr('x2', evt.pos.left + distX.size() - margin.left); + tooltip.position(evt.pos).data(evt).hidden(false); + }); + + //store old scales for use in transitions on update + x0 = x.copy(); + y0 = y.copy(); + + }); + + renderWatch.renderEnd('scatter with line immediate'); + return chart; + } + + //============================================================ + // Expose Public Variables + //------------------------------------------------------------ + + // expose chart's sub-components + chart.dispatch = dispatch; + chart.scatter = scatter; + chart.legend = legend; + chart.xAxis = xAxis; + chart.yAxis = yAxis; + chart.distX = distX; + chart.distY = distY; + chart.tooltip = tooltip; + + chart.options = nv.utils.optionsFunc.bind(chart); + chart._options = Object.create({}, { + // simple options, just get/set the necessary values + width: {get: function(){return width;}, set: function(_){width=_;}}, + height: {get: function(){return height;}, set: function(_){height=_;}}, + container: {get: function(){return container;}, set: function(_){container=_;}}, + showDistX: {get: function(){return showDistX;}, set: function(_){showDistX=_;}}, + showDistY: {get: function(){return showDistY;}, set: function(_){showDistY=_;}}, + showLegend: {get: function(){return showLegend;}, set: function(_){showLegend=_;}}, + showXAxis: {get: function(){return showXAxis;}, set: function(_){showXAxis=_;}}, + showYAxis: {get: function(){return showYAxis;}, set: function(_){showYAxis=_;}}, + defaultState: {get: function(){return defaultState;}, set: function(_){defaultState=_;}}, + noData: {get: function(){return noData;}, set: function(_){noData=_;}}, + duration: {get: function(){return duration;}, set: function(_){duration=_;}}, + + // deprecated options + tooltips: {get: function(){return tooltip.enabled();}, set: function(_){ + // deprecated after 1.7.1 + nv.deprecated('tooltips', 'use chart.tooltip.enabled() instead'); + tooltip.enabled(!!_); + }}, + tooltipContent: {get: function(){return tooltip.contentGenerator();}, set: function(_){ + // deprecated after 1.7.1 + nv.deprecated('tooltipContent', 'use chart.tooltip.contentGenerator() instead'); + tooltip.contentGenerator(_); + }}, + tooltipXContent: {get: function(){return tooltip.contentGenerator();}, set: function(_){ + // deprecated after 1.7.1 + nv.deprecated('tooltipContent', 'This option is removed, put values into main tooltip.'); + }}, + tooltipYContent: {get: function(){return tooltip.contentGenerator();}, set: function(_){ + // deprecated after 1.7.1 + nv.deprecated('tooltipContent', 'This option is removed, put values into main tooltip.'); + }}, + + // options that require extra logic in the setter + margin: {get: function(){return margin;}, set: function(_){ + margin.top = _.top !== undefined ? _.top : margin.top; + margin.right = _.right !== undefined ? _.right : margin.right; + margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom; + margin.left = _.left !== undefined ? _.left : margin.left; + }}, + rightAlignYAxis: {get: function(){return rightAlignYAxis;}, set: function(_){ + rightAlignYAxis = _; + yAxis.orient( (_) ? 'right' : 'left'); + }}, + color: {get: function(){return color;}, set: function(_){ + color = nv.utils.getColor(_); + legend.color(color); + distX.color(color); + distY.color(color); + }} + }); + + nv.utils.inheritOptions(chart, scatter); + nv.utils.initOptions(chart); + return chart; +}; + +nv.models.sparkline = function() { + "use strict"; + + //============================================================ + // Public Variables with Default Settings + //------------------------------------------------------------ + + var margin = {top: 2, right: 0, bottom: 2, left: 0} + , width = 400 + , height = 32 + , container = null + , animate = true + , x = d3.scale.linear() + , y = d3.scale.linear() + , getX = function(d) { return d.x } + , getY = function(d) { return d.y } + , color = nv.utils.getColor(['#000']) + , xDomain + , yDomain + , xRange + , yRange + ; + + function chart(selection) { + selection.each(function(data) { + var availableWidth = width - margin.left - margin.right, + availableHeight = height - margin.top - margin.bottom; + + container = d3.select(this); + nv.utils.initSVG(container); + + // Setup Scales + x .domain(xDomain || d3.extent(data, getX )) + .range(xRange || [0, availableWidth]); + + y .domain(yDomain || d3.extent(data, getY )) + .range(yRange || [availableHeight, 0]); + + // Setup containers and skeleton of chart + var wrap = container.selectAll('g.nv-wrap.nv-sparkline').data([data]); + var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-sparkline'); + var gEnter = wrapEnter.append('g'); + var g = wrap.select('g'); + + wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')') + + var paths = wrap.selectAll('path') + .data(function(d) { return [d] }); + paths.enter().append('path'); + paths.exit().remove(); + paths + .style('stroke', function(d,i) { return d.color || color(d, i) }) + .attr('d', d3.svg.line() + .x(function(d,i) { return x(getX(d,i)) }) + .y(function(d,i) { return y(getY(d,i)) }) + ); + + // TODO: Add CURRENT data point (Need Min, Mac, Current / Most recent) + var points = wrap.selectAll('circle.nv-point') + .data(function(data) { + var yValues = data.map(function(d, i) { return getY(d,i); }); + function pointIndex(index) { + if (index != -1) { + var result = data[index]; + result.pointIndex = index; + return result; + } else { + return null; + } + } + var maxPoint = pointIndex(yValues.lastIndexOf(y.domain()[1])), + minPoint = pointIndex(yValues.indexOf(y.domain()[0])), + currentPoint = pointIndex(yValues.length - 1); + return [minPoint, maxPoint, currentPoint].filter(function (d) {return d != null;}); + }); + points.enter().append('circle'); + points.exit().remove(); + points + .attr('cx', function(d,i) { return x(getX(d,d.pointIndex)) }) + .attr('cy', function(d,i) { return y(getY(d,d.pointIndex)) }) + .attr('r', 2) + .attr('class', function(d,i) { + return getX(d, d.pointIndex) == x.domain()[1] ? 'nv-point nv-currentValue' : + getY(d, d.pointIndex) == y.domain()[0] ? 'nv-point nv-minValue' : 'nv-point nv-maxValue' + }); + }); + + return chart; + } + + //============================================================ + // Expose Public Variables + //------------------------------------------------------------ + + chart.options = nv.utils.optionsFunc.bind(chart); + + chart._options = Object.create({}, { + // simple options, just get/set the necessary values + width: {get: function(){return width;}, set: function(_){width=_;}}, + height: {get: function(){return height;}, set: function(_){height=_;}}, + xDomain: {get: function(){return xDomain;}, set: function(_){xDomain=_;}}, + yDomain: {get: function(){return yDomain;}, set: function(_){yDomain=_;}}, + xRange: {get: function(){return xRange;}, set: function(_){xRange=_;}}, + yRange: {get: function(){return yRange;}, set: function(_){yRange=_;}}, + xScale: {get: function(){return x;}, set: function(_){x=_;}}, + yScale: {get: function(){return y;}, set: function(_){y=_;}}, + animate: {get: function(){return animate;}, set: function(_){animate=_;}}, + + //functor options + x: {get: function(){return getX;}, set: function(_){getX=d3.functor(_);}}, + y: {get: function(){return getY;}, set: function(_){getY=d3.functor(_);}}, + + // options that require extra logic in the setter + margin: {get: function(){return margin;}, set: function(_){ + margin.top = _.top !== undefined ? _.top : margin.top; + margin.right = _.right !== undefined ? _.right : margin.right; + margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom; + margin.left = _.left !== undefined ? _.left : margin.left; + }}, + color: {get: function(){return color;}, set: function(_){ + color = nv.utils.getColor(_); + }} + }); + + nv.utils.initOptions(chart); + return chart; +}; + +nv.models.sparklinePlus = function() { + "use strict"; + + //============================================================ + // Public Variables with Default Settings + //------------------------------------------------------------ + + var sparkline = nv.models.sparkline(); + + var margin = {top: 15, right: 100, bottom: 10, left: 50} + , width = null + , height = null + , x + , y + , index = [] + , paused = false + , xTickFormat = d3.format(',r') + , yTickFormat = d3.format(',.2f') + , showLastValue = true + , alignValue = true + , rightAlignValue = false + , noData = null + ; + + function chart(selection) { + selection.each(function(data) { + var container = d3.select(this); + nv.utils.initSVG(container); + + var availableWidth = nv.utils.availableWidth(width, container, margin), + availableHeight = nv.utils.availableHeight(height, container, margin); + + chart.update = function() { chart(selection) }; + chart.container = this; + + // Display No Data message if there's nothing to show. + if (!data || !data.length) { + nv.utils.noData(chart, container) + return chart; + } else { + container.selectAll('.nv-noData').remove(); + } + + var currentValue = sparkline.y()(data[data.length-1], data.length-1); + + // Setup Scales + x = sparkline.xScale(); + y = sparkline.yScale(); + + // Setup containers and skeleton of chart + var wrap = container.selectAll('g.nv-wrap.nv-sparklineplus').data([data]); + var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-sparklineplus'); + var gEnter = wrapEnter.append('g'); + var g = wrap.select('g'); + + gEnter.append('g').attr('class', 'nv-sparklineWrap'); + gEnter.append('g').attr('class', 'nv-valueWrap'); + gEnter.append('g').attr('class', 'nv-hoverArea'); + + wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); + + // Main Chart Component(s) + var sparklineWrap = g.select('.nv-sparklineWrap'); + + sparkline.width(availableWidth).height(availableHeight); + sparklineWrap.call(sparkline); + + if (showLastValue) { + var valueWrap = g.select('.nv-valueWrap'); + var value = valueWrap.selectAll('.nv-currentValue') + .data([currentValue]); + + value.enter().append('text').attr('class', 'nv-currentValue') + .attr('dx', rightAlignValue ? -8 : 8) + .attr('dy', '.9em') + .style('text-anchor', rightAlignValue ? 'end' : 'start'); + + value + .attr('x', availableWidth + (rightAlignValue ? margin.right : 0)) + .attr('y', alignValue ? function (d) { + return y(d) + } : 0) + .style('fill', sparkline.color()(data[data.length - 1], data.length - 1)) + .text(yTickFormat(currentValue)); + } + + gEnter.select('.nv-hoverArea').append('rect') + .on('mousemove', sparklineHover) + .on('click', function() { paused = !paused }) + .on('mouseout', function() { index = []; updateValueLine(); }); + + g.select('.nv-hoverArea rect') + .attr('transform', function(d) { return 'translate(' + -margin.left + ',' + -margin.top + ')' }) + .attr('width', availableWidth + margin.left + margin.right) + .attr('height', availableHeight + margin.top); + + //index is currently global (within the chart), may or may not keep it that way + function updateValueLine() { + if (paused) return; + + var hoverValue = g.selectAll('.nv-hoverValue').data(index); + + var hoverEnter = hoverValue.enter() + .append('g').attr('class', 'nv-hoverValue') + .style('stroke-opacity', 0) + .style('fill-opacity', 0); + + hoverValue.exit() + .transition().duration(250) + .style('stroke-opacity', 0) + .style('fill-opacity', 0) + .remove(); + + hoverValue + .attr('transform', function(d) { return 'translate(' + x(sparkline.x()(data[d],d)) + ',0)' }) + .transition().duration(250) + .style('stroke-opacity', 1) + .style('fill-opacity', 1); + + if (!index.length) return; + + hoverEnter.append('line') + .attr('x1', 0) + .attr('y1', -margin.top) + .attr('x2', 0) + .attr('y2', availableHeight); + + hoverEnter.append('text').attr('class', 'nv-xValue') + .attr('x', -6) + .attr('y', -margin.top) + .attr('text-anchor', 'end') + .attr('dy', '.9em'); + + g.select('.nv-hoverValue .nv-xValue') + .text(xTickFormat(sparkline.x()(data[index[0]], index[0]))); + + hoverEnter.append('text').attr('class', 'nv-yValue') + .attr('x', 6) + .attr('y', -margin.top) + .attr('text-anchor', 'start') + .attr('dy', '.9em'); + + g.select('.nv-hoverValue .nv-yValue') + .text(yTickFormat(sparkline.y()(data[index[0]], index[0]))); + } + + function sparklineHover() { + if (paused) return; + + var pos = d3.mouse(this)[0] - margin.left; + + function getClosestIndex(data, x) { + var distance = Math.abs(sparkline.x()(data[0], 0) - x); + var closestIndex = 0; + for (var i = 0; i < data.length; i++){ + if (Math.abs(sparkline.x()(data[i], i) - x) < distance) { + distance = Math.abs(sparkline.x()(data[i], i) - x); + closestIndex = i; + } + } + return closestIndex; + } + + index = [getClosestIndex(data, Math.round(x.invert(pos)))]; + updateValueLine(); + } + + }); + + return chart; + } + + //============================================================ + // Expose Public Variables + //------------------------------------------------------------ + + // expose chart's sub-components + chart.sparkline = sparkline; + + chart.options = nv.utils.optionsFunc.bind(chart); + + chart._options = Object.create({}, { + // simple options, just get/set the necessary values + width: {get: function(){return width;}, set: function(_){width=_;}}, + height: {get: function(){return height;}, set: function(_){height=_;}}, + xTickFormat: {get: function(){return xTickFormat;}, set: function(_){xTickFormat=_;}}, + yTickFormat: {get: function(){return yTickFormat;}, set: function(_){yTickFormat=_;}}, + showLastValue: {get: function(){return showLastValue;}, set: function(_){showLastValue=_;}}, + alignValue: {get: function(){return alignValue;}, set: function(_){alignValue=_;}}, + rightAlignValue: {get: function(){return rightAlignValue;}, set: function(_){rightAlignValue=_;}}, + noData: {get: function(){return noData;}, set: function(_){noData=_;}}, + + // options that require extra logic in the setter + margin: {get: function(){return margin;}, set: function(_){ + margin.top = _.top !== undefined ? _.top : margin.top; + margin.right = _.right !== undefined ? _.right : margin.right; + margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom; + margin.left = _.left !== undefined ? _.left : margin.left; + }} + }); + + nv.utils.inheritOptions(chart, sparkline); + nv.utils.initOptions(chart); + + return chart; +}; + +nv.models.stackedArea = function() { + "use strict"; + + //============================================================ + // Public Variables with Default Settings + //------------------------------------------------------------ + + var margin = {top: 0, right: 0, bottom: 0, left: 0} + , width = 960 + , height = 500 + , color = nv.utils.defaultColor() // a function that computes the color + , id = Math.floor(Math.random() * 100000) //Create semi-unique ID incase user doesn't selet one + , container = null + , getX = function(d) { return d.x } // accessor to get the x value from a data point + , getY = function(d) { return d.y } // accessor to get the y value from a data point + , style = 'stack' + , offset = 'zero' + , order = 'default' + , interpolate = 'linear' // controls the line interpolation + , clipEdge = false // if true, masks lines within x and y scale + , x //can be accessed via chart.xScale() + , y //can be accessed via chart.yScale() + , scatter = nv.models.scatter() + , duration = 250 + , dispatch = d3.dispatch('areaClick', 'areaMouseover', 'areaMouseout','renderEnd', 'elementClick', 'elementMouseover', 'elementMouseout') + ; + + scatter + .pointSize(2.2) // default size + .pointDomain([2.2, 2.2]) // all the same size by default + ; + + /************************************ + * offset: + * 'wiggle' (stream) + * 'zero' (stacked) + * 'expand' (normalize to 100%) + * 'silhouette' (simple centered) + * + * order: + * 'inside-out' (stream) + * 'default' (input order) + ************************************/ + + var renderWatch = nv.utils.renderWatch(dispatch, duration); + + function chart(selection) { + renderWatch.reset(); + renderWatch.models(scatter); + selection.each(function(data) { + var availableWidth = width - margin.left - margin.right, + availableHeight = height - margin.top - margin.bottom; + + container = d3.select(this); + nv.utils.initSVG(container); + + // Setup Scales + x = scatter.xScale(); + y = scatter.yScale(); + + var dataRaw = data; + // Injecting point index into each point because d3.layout.stack().out does not give index + data.forEach(function(aseries, i) { + aseries.seriesIndex = i; + aseries.values = aseries.values.map(function(d, j) { + d.index = j; + d.seriesIndex = i; + return d; + }); + }); + + var dataFiltered = data.filter(function(series) { + return !series.disabled; + }); + + data = d3.layout.stack() + .order(order) + .offset(offset) + .values(function(d) { return d.values }) //TODO: make values customizeable in EVERY model in this fashion + .x(getX) + .y(getY) + .out(function(d, y0, y) { + d.display = { + y: y, + y0: y0 + }; + }) + (dataFiltered); + + // Setup containers and skeleton of chart + var wrap = container.selectAll('g.nv-wrap.nv-stackedarea').data([data]); + var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-stackedarea'); + var defsEnter = wrapEnter.append('defs'); + var gEnter = wrapEnter.append('g'); + var g = wrap.select('g'); + + gEnter.append('g').attr('class', 'nv-areaWrap'); + gEnter.append('g').attr('class', 'nv-scatterWrap'); + + wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); + + // If the user has not specified forceY, make sure 0 is included in the domain + // Otherwise, use user-specified values for forceY + if (scatter.forceY().length == 0) { + scatter.forceY().push(0); + } + + scatter + .width(availableWidth) + .height(availableHeight) + .x(getX) + .y(function(d) { return d.display.y + d.display.y0 }) + .forceY([0]) + .color(data.map(function(d,i) { + return d.color || color(d, d.seriesIndex); + })); + + var scatterWrap = g.select('.nv-scatterWrap') + .datum(data); + + scatterWrap.call(scatter); + + defsEnter.append('clipPath') + .attr('id', 'nv-edge-clip-' + id) + .append('rect'); + + wrap.select('#nv-edge-clip-' + id + ' rect') + .attr('width', availableWidth) + .attr('height', availableHeight); + + g.attr('clip-path', clipEdge ? 'url(#nv-edge-clip-' + id + ')' : ''); + + var area = d3.svg.area() + .x(function(d,i) { return x(getX(d,i)) }) + .y0(function(d) { + return y(d.display.y0) + }) + .y1(function(d) { + return y(d.display.y + d.display.y0) + }) + .interpolate(interpolate); + + var zeroArea = d3.svg.area() + .x(function(d,i) { return x(getX(d,i)) }) + .y0(function(d) { return y(d.display.y0) }) + .y1(function(d) { return y(d.display.y0) }); + + var path = g.select('.nv-areaWrap').selectAll('path.nv-area') + .data(function(d) { return d }); + + path.enter().append('path').attr('class', function(d,i) { return 'nv-area nv-area-' + i }) + .attr('d', function(d,i){ + return zeroArea(d.values, d.seriesIndex); + }) + .on('mouseover', function(d,i) { + d3.select(this).classed('hover', true); + dispatch.areaMouseover({ + point: d, + series: d.key, + pos: [d3.event.pageX, d3.event.pageY], + seriesIndex: d.seriesIndex + }); + }) + .on('mouseout', function(d,i) { + d3.select(this).classed('hover', false); + dispatch.areaMouseout({ + point: d, + series: d.key, + pos: [d3.event.pageX, d3.event.pageY], + seriesIndex: d.seriesIndex + }); + }) + .on('click', function(d,i) { + d3.select(this).classed('hover', false); + dispatch.areaClick({ + point: d, + series: d.key, + pos: [d3.event.pageX, d3.event.pageY], + seriesIndex: d.seriesIndex + }); + }); + + path.exit().remove(); + path.style('fill', function(d,i){ + return d.color || color(d, d.seriesIndex) + }) + .style('stroke', function(d,i){ return d.color || color(d, d.seriesIndex) }); + path.watchTransition(renderWatch,'stackedArea path') + .attr('d', function(d,i) { + return area(d.values,i) + }); + + //============================================================ + // Event Handling/Dispatching (in chart's scope) + //------------------------------------------------------------ + + scatter.dispatch.on('elementMouseover.area', function(e) { + g.select('.nv-chart-' + id + ' .nv-area-' + e.seriesIndex).classed('hover', true); + }); + scatter.dispatch.on('elementMouseout.area', function(e) { + g.select('.nv-chart-' + id + ' .nv-area-' + e.seriesIndex).classed('hover', false); + }); + + //Special offset functions + chart.d3_stackedOffset_stackPercent = function(stackData) { + var n = stackData.length, //How many series + m = stackData[0].length, //how many points per series + i, + j, + o, + y0 = []; + + for (j = 0; j < m; ++j) { //Looping through all points + for (i = 0, o = 0; i < dataRaw.length; i++) { //looping through all series + o += getY(dataRaw[i].values[j]); //total y value of all series at a certian point in time. + } + + if (o) for (i = 0; i < n; i++) { //(total y value of all series at point in time i) != 0 + stackData[i][j][1] /= o; + } else { //(total y value of all series at point in time i) == 0 + for (i = 0; i < n; i++) { + stackData[i][j][1] = 0; + } + } + } + for (j = 0; j < m; ++j) y0[j] = 0; + return y0; + }; + + }); + + renderWatch.renderEnd('stackedArea immediate'); + return chart; + } + + //============================================================ + // Global getters and setters + //------------------------------------------------------------ + + chart.dispatch = dispatch; + chart.scatter = scatter; + + scatter.dispatch.on('elementClick', function(){ dispatch.elementClick.apply(this, arguments); }); + scatter.dispatch.on('elementMouseover', function(){ dispatch.elementMouseover.apply(this, arguments); }); + scatter.dispatch.on('elementMouseout', function(){ dispatch.elementMouseout.apply(this, arguments); }); + + chart.interpolate = function(_) { + if (!arguments.length) return interpolate; + interpolate = _; + return chart; + }; + + chart.duration = function(_) { + if (!arguments.length) return duration; + duration = _; + renderWatch.reset(duration); + scatter.duration(duration); + return chart; + }; + + chart.dispatch = dispatch; + chart.scatter = scatter; + chart.options = nv.utils.optionsFunc.bind(chart); + + chart._options = Object.create({}, { + // simple options, just get/set the necessary values + width: {get: function(){return width;}, set: function(_){width=_;}}, + height: {get: function(){return height;}, set: function(_){height=_;}}, + clipEdge: {get: function(){return clipEdge;}, set: function(_){clipEdge=_;}}, + offset: {get: function(){return offset;}, set: function(_){offset=_;}}, + order: {get: function(){return order;}, set: function(_){order=_;}}, + interpolate: {get: function(){return interpolate;}, set: function(_){interpolate=_;}}, + + // simple functor options + x: {get: function(){return getX;}, set: function(_){getX = d3.functor(_);}}, + y: {get: function(){return getY;}, set: function(_){getY = d3.functor(_);}}, + + // options that require extra logic in the setter + margin: {get: function(){return margin;}, set: function(_){ + margin.top = _.top !== undefined ? _.top : margin.top; + margin.right = _.right !== undefined ? _.right : margin.right; + margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom; + margin.left = _.left !== undefined ? _.left : margin.left; + }}, + color: {get: function(){return color;}, set: function(_){ + color = nv.utils.getColor(_); + }}, + style: {get: function(){return style;}, set: function(_){ + style = _; + switch (style) { + case 'stack': + chart.offset('zero'); + chart.order('default'); + break; + case 'stream': + chart.offset('wiggle'); + chart.order('inside-out'); + break; + case 'stream-center': + chart.offset('silhouette'); + chart.order('inside-out'); + break; + case 'expand': + chart.offset('expand'); + chart.order('default'); + break; + case 'stack_percent': + chart.offset(chart.d3_stackedOffset_stackPercent); + chart.order('default'); + break; + } + }}, + duration: {get: function(){return duration;}, set: function(_){ + duration = _; + renderWatch.reset(duration); + scatter.duration(duration); + }} + }); + + nv.utils.inheritOptions(chart, scatter); + nv.utils.initOptions(chart); + + return chart; +}; + +nv.models.stackedAreaChart = function() { + "use strict"; + + //============================================================ + // Public Variables with Default Settings + //------------------------------------------------------------ + + var stacked = nv.models.stackedArea() + , xAxis = nv.models.axis() + , yAxis = nv.models.axis() + , legend = nv.models.legend() + , controls = nv.models.legend() + , interactiveLayer = nv.interactiveGuideline() + , tooltip = nv.models.tooltip() + ; + + var margin = {top: 30, right: 25, bottom: 50, left: 60} + , width = null + , height = null + , color = nv.utils.defaultColor() + , showControls = true + , showLegend = true + , showXAxis = true + , showYAxis = true + , rightAlignYAxis = false + , useInteractiveGuideline = false + , x //can be accessed via chart.xScale() + , y //can be accessed via chart.yScale() + , state = nv.utils.state() + , defaultState = null + , noData = null + , dispatch = d3.dispatch('stateChange', 'changeState','renderEnd') + , controlWidth = 250 + , controlOptions = ['Stacked','Stream','Expanded'] + , controlLabels = {} + , duration = 250 + ; + + state.style = stacked.style(); + xAxis.orient('bottom').tickPadding(7); + yAxis.orient((rightAlignYAxis) ? 'right' : 'left'); + + tooltip + .headerFormatter(function(d, i) { + return xAxis.tickFormat()(d, i); + }) + .valueFormatter(function(d, i) { + return yAxis.tickFormat()(d, i); + }); + + interactiveLayer.tooltip + .headerFormatter(function(d, i) { + return xAxis.tickFormat()(d, i); + }) + .valueFormatter(function(d, i) { + return yAxis.tickFormat()(d, i); + }); + + var oldYTickFormat = null, + oldValueFormatter = null; + + controls.updateState(false); + + //============================================================ + // Private Variables + //------------------------------------------------------------ + + var renderWatch = nv.utils.renderWatch(dispatch); + var style = stacked.style(); + + var stateGetter = function(data) { + return function(){ + return { + active: data.map(function(d) { return !d.disabled }), + style: stacked.style() + }; + } + }; + + var stateSetter = function(data) { + return function(state) { + if (state.style !== undefined) + style = state.style; + if (state.active !== undefined) + data.forEach(function(series,i) { + series.disabled = !state.active[i]; + }); + } + }; + + var percentFormatter = d3.format('%'); + + function chart(selection) { + renderWatch.reset(); + renderWatch.models(stacked); + if (showXAxis) renderWatch.models(xAxis); + if (showYAxis) renderWatch.models(yAxis); + + selection.each(function(data) { + var container = d3.select(this), + that = this; + nv.utils.initSVG(container); + + var availableWidth = nv.utils.availableWidth(width, container, margin), + availableHeight = nv.utils.availableHeight(height, container, margin); + + chart.update = function() { container.transition().duration(duration).call(chart); }; + chart.container = this; + + state + .setter(stateSetter(data), chart.update) + .getter(stateGetter(data)) + .update(); + + // DEPRECATED set state.disabled + state.disabled = data.map(function(d) { return !!d.disabled }); + + if (!defaultState) { + var key; + defaultState = {}; + for (key in state) { + if (state[key] instanceof Array) + defaultState[key] = state[key].slice(0); + else + defaultState[key] = state[key]; + } + } + + // Display No Data message if there's nothing to show. + if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) { + nv.utils.noData(chart, container) + return chart; + } else { + container.selectAll('.nv-noData').remove(); + } + + // Setup Scales + x = stacked.xScale(); + y = stacked.yScale(); + + // Setup containers and skeleton of chart + var wrap = container.selectAll('g.nv-wrap.nv-stackedAreaChart').data([data]); + var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-stackedAreaChart').append('g'); + var g = wrap.select('g'); + + gEnter.append("rect").style("opacity",0); + gEnter.append('g').attr('class', 'nv-x nv-axis'); + gEnter.append('g').attr('class', 'nv-y nv-axis'); + gEnter.append('g').attr('class', 'nv-stackedWrap'); + gEnter.append('g').attr('class', 'nv-legendWrap'); + gEnter.append('g').attr('class', 'nv-controlsWrap'); + gEnter.append('g').attr('class', 'nv-interactive'); + + g.select("rect").attr("width",availableWidth).attr("height",availableHeight); + + // Legend + if (showLegend) { + var legendWidth = (showControls) ? availableWidth - controlWidth : availableWidth; + + legend.width(legendWidth); + g.select('.nv-legendWrap').datum(data).call(legend); + + if ( margin.top != legend.height()) { + margin.top = legend.height(); + availableHeight = nv.utils.availableHeight(height, container, margin); + } + + g.select('.nv-legendWrap') + .attr('transform', 'translate(' + (availableWidth-legendWidth) + ',' + (-margin.top) +')'); + } + + // Controls + if (showControls) { + var controlsData = [ + { + key: controlLabels.stacked || 'Stacked', + metaKey: 'Stacked', + disabled: stacked.style() != 'stack', + style: 'stack' + }, + { + key: controlLabels.stream || 'Stream', + metaKey: 'Stream', + disabled: stacked.style() != 'stream', + style: 'stream' + }, + { + key: controlLabels.expanded || 'Expanded', + metaKey: 'Expanded', + disabled: stacked.style() != 'expand', + style: 'expand' + }, + { + key: controlLabels.stack_percent || 'Stack %', + metaKey: 'Stack_Percent', + disabled: stacked.style() != 'stack_percent', + style: 'stack_percent' + } + ]; + + controlWidth = (controlOptions.length/3) * 260; + controlsData = controlsData.filter(function(d) { + return controlOptions.indexOf(d.metaKey) !== -1; + }); + + controls + .width( controlWidth ) + .color(['#444', '#444', '#444']); + + g.select('.nv-controlsWrap') + .datum(controlsData) + .call(controls); + + if ( margin.top != Math.max(controls.height(), legend.height()) ) { + margin.top = Math.max(controls.height(), legend.height()); + availableHeight = nv.utils.availableHeight(height, container, margin); + } + + g.select('.nv-controlsWrap') + .attr('transform', 'translate(0,' + (-margin.top) +')'); + } + + wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); + + if (rightAlignYAxis) { + g.select(".nv-y.nv-axis") + .attr("transform", "translate(" + availableWidth + ",0)"); + } + + //Set up interactive layer + if (useInteractiveGuideline) { + interactiveLayer + .width(availableWidth) + .height(availableHeight) + .margin({left: margin.left, top: margin.top}) + .svgContainer(container) + .xScale(x); + wrap.select(".nv-interactive").call(interactiveLayer); + } + + stacked + .width(availableWidth) + .height(availableHeight); + + var stackedWrap = g.select('.nv-stackedWrap') + .datum(data); + + stackedWrap.transition().call(stacked); + + // Setup Axes + if (showXAxis) { + xAxis.scale(x) + ._ticks( nv.utils.calcTicksX(availableWidth/100, data) ) + .tickSize( -availableHeight, 0); + + g.select('.nv-x.nv-axis') + .attr('transform', 'translate(0,' + availableHeight + ')'); + + g.select('.nv-x.nv-axis') + .transition().duration(0) + .call(xAxis); + } + + if (showYAxis) { + var ticks; + if (stacked.offset() === 'wiggle') { + ticks = 0; + } + else { + ticks = nv.utils.calcTicksY(availableHeight/36, data); + } + yAxis.scale(y) + ._ticks(ticks) + .tickSize(-availableWidth, 0); + + if (stacked.style() === 'expand' || stacked.style() === 'stack_percent') { + var currentFormat = yAxis.tickFormat(); + + if ( !oldYTickFormat || currentFormat !== percentFormatter ) + oldYTickFormat = currentFormat; + + //Forces the yAxis to use percentage in 'expand' mode. + yAxis.tickFormat(percentFormatter); + } + else { + if (oldYTickFormat) { + yAxis.tickFormat(oldYTickFormat); + oldYTickFormat = null; + } + } + + g.select('.nv-y.nv-axis') + .transition().duration(0) + .call(yAxis); + } + + //============================================================ + // Event Handling/Dispatching (in chart's scope) + //------------------------------------------------------------ + + stacked.dispatch.on('areaClick.toggle', function(e) { + if (data.filter(function(d) { return !d.disabled }).length === 1) + data.forEach(function(d) { + d.disabled = false; + }); + else + data.forEach(function(d,i) { + d.disabled = (i != e.seriesIndex); + }); + + state.disabled = data.map(function(d) { return !!d.disabled }); + dispatch.stateChange(state); + + chart.update(); + }); + + legend.dispatch.on('stateChange', function(newState) { + for (var key in newState) + state[key] = newState[key]; + dispatch.stateChange(state); + chart.update(); + }); + + controls.dispatch.on('legendClick', function(d,i) { + if (!d.disabled) return; + + controlsData = controlsData.map(function(s) { + s.disabled = true; + return s; + }); + d.disabled = false; + + stacked.style(d.style); + + + state.style = stacked.style(); + dispatch.stateChange(state); + + chart.update(); + }); + + interactiveLayer.dispatch.on('elementMousemove', function(e) { + stacked.clearHighlights(); + var singlePoint, pointIndex, pointXLocation, allData = []; + data + .filter(function(series, i) { + series.seriesIndex = i; + return !series.disabled; + }) + .forEach(function(series,i) { + pointIndex = nv.interactiveBisect(series.values, e.pointXValue, chart.x()); + var point = series.values[pointIndex]; + var pointYValue = chart.y()(point, pointIndex); + if (pointYValue != null) { + stacked.highlightPoint(i, pointIndex, true); + } + if (typeof point === 'undefined') return; + if (typeof singlePoint === 'undefined') singlePoint = point; + if (typeof pointXLocation === 'undefined') pointXLocation = chart.xScale()(chart.x()(point,pointIndex)); + + //If we are in 'expand' mode, use the stacked percent value instead of raw value. + var tooltipValue = (stacked.style() == 'expand') ? point.display.y : chart.y()(point,pointIndex); + allData.push({ + key: series.key, + value: tooltipValue, + color: color(series,series.seriesIndex), + stackedValue: point.display + }); + }); + + allData.reverse(); + + //Highlight the tooltip entry based on which stack the mouse is closest to. + if (allData.length > 2) { + var yValue = chart.yScale().invert(e.mouseY); + var yDistMax = Infinity, indexToHighlight = null; + allData.forEach(function(series,i) { + + //To handle situation where the stacked area chart is negative, we need to use absolute values + //when checking if the mouse Y value is within the stack area. + yValue = Math.abs(yValue); + var stackedY0 = Math.abs(series.stackedValue.y0); + var stackedY = Math.abs(series.stackedValue.y); + if ( yValue >= stackedY0 && yValue <= (stackedY + stackedY0)) + { + indexToHighlight = i; + return; + } + }); + if (indexToHighlight != null) + allData[indexToHighlight].highlight = true; + } + + var xValue = xAxis.tickFormat()(chart.x()(singlePoint,pointIndex)); + + var valueFormatter = interactiveLayer.tooltip.valueFormatter(); + // Keeps track of the tooltip valueFormatter if the chart changes to expanded view + if (stacked.style() === 'expand' || stacked.style() === 'stack_percent') { + if ( !oldValueFormatter ) { + oldValueFormatter = valueFormatter; + } + //Forces the tooltip to use percentage in 'expand' mode. + valueFormatter = d3.format(".1%"); + } + else { + if (oldValueFormatter) { + valueFormatter = oldValueFormatter; + oldValueFormatter = null; + } + } + + interactiveLayer.tooltip + .position({left: pointXLocation + margin.left, top: e.mouseY + margin.top}) + .chartContainer(that.parentNode) + .valueFormatter(valueFormatter) + .data( + { + value: xValue, + series: allData + } + )(); + + interactiveLayer.renderGuideLine(pointXLocation); + + }); + + interactiveLayer.dispatch.on("elementMouseout",function(e) { + stacked.clearHighlights(); + }); + + // Update chart from a state object passed to event handler + dispatch.on('changeState', function(e) { + + if (typeof e.disabled !== 'undefined' && data.length === e.disabled.length) { + data.forEach(function(series,i) { + series.disabled = e.disabled[i]; + }); + + state.disabled = e.disabled; + } + + if (typeof e.style !== 'undefined') { + stacked.style(e.style); + style = e.style; + } + + chart.update(); + }); + + }); + + renderWatch.renderEnd('stacked Area chart immediate'); + return chart; + } + + //============================================================ + // Event Handling/Dispatching (out of chart's scope) + //------------------------------------------------------------ + + stacked.dispatch.on('elementMouseover.tooltip', function(evt) { + evt.point['x'] = stacked.x()(evt.point); + evt.point['y'] = stacked.y()(evt.point); + tooltip.data(evt).position(evt.pos).hidden(false); + }); + + stacked.dispatch.on('elementMouseout.tooltip', function(evt) { + tooltip.hidden(true) + }); + + //============================================================ + // Expose Public Variables + //------------------------------------------------------------ + + // expose chart's sub-components + chart.dispatch = dispatch; + chart.stacked = stacked; + chart.legend = legend; + chart.controls = controls; + chart.xAxis = xAxis; + chart.yAxis = yAxis; + chart.interactiveLayer = interactiveLayer; + chart.tooltip = tooltip; + + chart.dispatch = dispatch; + chart.options = nv.utils.optionsFunc.bind(chart); + + chart._options = Object.create({}, { + // simple options, just get/set the necessary values + width: {get: function(){return width;}, set: function(_){width=_;}}, + height: {get: function(){return height;}, set: function(_){height=_;}}, + showLegend: {get: function(){return showLegend;}, set: function(_){showLegend=_;}}, + showXAxis: {get: function(){return showXAxis;}, set: function(_){showXAxis=_;}}, + showYAxis: {get: function(){return showYAxis;}, set: function(_){showYAxis=_;}}, + defaultState: {get: function(){return defaultState;}, set: function(_){defaultState=_;}}, + noData: {get: function(){return noData;}, set: function(_){noData=_;}}, + showControls: {get: function(){return showControls;}, set: function(_){showControls=_;}}, + controlLabels: {get: function(){return controlLabels;}, set: function(_){controlLabels=_;}}, + controlOptions: {get: function(){return controlOptions;}, set: function(_){controlOptions=_;}}, + + // deprecated options + tooltips: {get: function(){return tooltip.enabled();}, set: function(_){ + // deprecated after 1.7.1 + nv.deprecated('tooltips', 'use chart.tooltip.enabled() instead'); + tooltip.enabled(!!_); + }}, + tooltipContent: {get: function(){return tooltip.contentGenerator();}, set: function(_){ + // deprecated after 1.7.1 + nv.deprecated('tooltipContent', 'use chart.tooltip.contentGenerator() instead'); + tooltip.contentGenerator(_); + }}, + + // options that require extra logic in the setter + margin: {get: function(){return margin;}, set: function(_){ + margin.top = _.top !== undefined ? _.top : margin.top; + margin.right = _.right !== undefined ? _.right : margin.right; + margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom; + margin.left = _.left !== undefined ? _.left : margin.left; + }}, + duration: {get: function(){return duration;}, set: function(_){ + duration = _; + renderWatch.reset(duration); + stacked.duration(duration); + xAxis.duration(duration); + yAxis.duration(duration); + }}, + color: {get: function(){return color;}, set: function(_){ + color = nv.utils.getColor(_); + legend.color(color); + stacked.color(color); + }}, + rightAlignYAxis: {get: function(){return rightAlignYAxis;}, set: function(_){ + rightAlignYAxis = _; + yAxis.orient( rightAlignYAxis ? 'right' : 'left'); + }}, + useInteractiveGuideline: {get: function(){return useInteractiveGuideline;}, set: function(_){ + useInteractiveGuideline = !!_; + chart.interactive(!_); + chart.useVoronoi(!_); + stacked.scatter.interactive(!_); + }} + }); + + nv.utils.inheritOptions(chart, stacked); + nv.utils.initOptions(chart); + + return chart; +}; +// based on http://bl.ocks.org/kerryrodden/477c1bfb081b783f80ad +nv.models.sunburst = function() { + "use strict"; + + //============================================================ + // Public Variables with Default Settings + //------------------------------------------------------------ + + var margin = {top: 0, right: 0, bottom: 0, left: 0} + , width = null + , height = null + , mode = "count" + , modes = {count: function(d) { return 1; }, size: function(d) { return d.size }} + , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one + , container = null + , color = nv.utils.defaultColor() + , duration = 500 + , dispatch = d3.dispatch('chartClick', 'elementClick', 'elementDblClick', 'elementMousemove', 'elementMouseover', 'elementMouseout', 'renderEnd') + ; + + var x = d3.scale.linear().range([0, 2 * Math.PI]); + var y = d3.scale.sqrt(); + + var partition = d3.layout.partition() + .sort(null) + .value(function(d) { return 1; }); + + var arc = d3.svg.arc() + .startAngle(function(d) { return Math.max(0, Math.min(2 * Math.PI, x(d.x))); }) + .endAngle(function(d) { return Math.max(0, Math.min(2 * Math.PI, x(d.x + d.dx))); }) + .innerRadius(function(d) { return Math.max(0, y(d.y)); }) + .outerRadius(function(d) { return Math.max(0, y(d.y + d.dy)); }); + + // Keep track of the current and previous node being displayed as the root. + var node, prevNode; + // Keep track of the root node + var rootNode; + + //============================================================ + // chart function + //------------------------------------------------------------ + + var renderWatch = nv.utils.renderWatch(dispatch); + + function chart(selection) { + renderWatch.reset(); + selection.each(function(data) { + container = d3.select(this); + var availableWidth = nv.utils.availableWidth(width, container, margin); + var availableHeight = nv.utils.availableHeight(height, container, margin); + var radius = Math.min(availableWidth, availableHeight) / 2; + var path; + + nv.utils.initSVG(container); + + // Setup containers and skeleton of chart + var wrap = container.selectAll('.nv-wrap.nv-sunburst').data(data); + var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-sunburst nv-chart-' + id); + + var g = wrapEnter.selectAll('nv-sunburst'); + + wrap.attr('transform', 'translate(' + availableWidth / 2 + ',' + availableHeight / 2 + ')'); + + container.on('click', function (d, i) { + dispatch.chartClick({ + data: d, + index: i, + pos: d3.event, + id: id + }); + }); + + y.range([0, radius]); + + node = node || data; + rootNode = data[0]; + partition.value(modes[mode] || modes["count"]); + path = g.data(partition.nodes).enter() + .append("path") + .attr("d", arc) + .style("fill", function (d) { + return color((d.children ? d : d.parent).name); + }) + .style("stroke", "#FFF") + .on("click", function(d) { + if (prevNode !== node && node !== d) prevNode = node; + node = d; + path.transition() + .duration(duration) + .attrTween("d", arcTweenZoom(d)); + }) + .each(stash) + .on("dblclick", function(d) { + if (prevNode.parent == d) { + path.transition() + .duration(duration) + .attrTween("d", arcTweenZoom(rootNode)); + } + }) + .each(stash) + .on('mouseover', function(d,i){ + d3.select(this).classed('hover', true).style('opacity', 0.8); + dispatch.elementMouseover({ + data: d, + color: d3.select(this).style("fill") + }); + }) + .on('mouseout', function(d,i){ + d3.select(this).classed('hover', false).style('opacity', 1); + dispatch.elementMouseout({ + data: d + }); + }) + .on('mousemove', function(d,i){ + dispatch.elementMousemove({ + data: d + }); + }); + + + + // Setup for switching data: stash the old values for transition. + function stash(d) { + d.x0 = d.x; + d.dx0 = d.dx; + } + + // When switching data: interpolate the arcs in data space. + function arcTweenData(a, i) { + var oi = d3.interpolate({x: a.x0, dx: a.dx0}, a); + + function tween(t) { + var b = oi(t); + a.x0 = b.x; + a.dx0 = b.dx; + return arc(b); + } + + if (i == 0) { + // If we are on the first arc, adjust the x domain to match the root node + // at the current zoom level. (We only need to do this once.) + var xd = d3.interpolate(x.domain(), [node.x, node.x + node.dx]); + return function (t) { + x.domain(xd(t)); + return tween(t); + }; + } else { + return tween; + } + } + + // When zooming: interpolate the scales. + function arcTweenZoom(d) { + var xd = d3.interpolate(x.domain(), [d.x, d.x + d.dx]), + yd = d3.interpolate(y.domain(), [d.y, 1]), + yr = d3.interpolate(y.range(), [d.y ? 20 : 0, radius]); + return function (d, i) { + return i + ? function (t) { + return arc(d); + } + : function (t) { + x.domain(xd(t)); + y.domain(yd(t)).range(yr(t)); + return arc(d); + }; + }; + } + + }); + + renderWatch.renderEnd('sunburst immediate'); + return chart; + } + + //============================================================ + // Expose Public Variables + //------------------------------------------------------------ + + chart.dispatch = dispatch; + chart.options = nv.utils.optionsFunc.bind(chart); + + chart._options = Object.create({}, { + // simple options, just get/set the necessary values + width: {get: function(){return width;}, set: function(_){width=_;}}, + height: {get: function(){return height;}, set: function(_){height=_;}}, + mode: {get: function(){return mode;}, set: function(_){mode=_;}}, + id: {get: function(){return id;}, set: function(_){id=_;}}, + duration: {get: function(){return duration;}, set: function(_){duration=_;}}, + + // options that require extra logic in the setter + margin: {get: function(){return margin;}, set: function(_){ + margin.top = _.top != undefined ? _.top : margin.top; + margin.right = _.right != undefined ? _.right : margin.right; + margin.bottom = _.bottom != undefined ? _.bottom : margin.bottom; + margin.left = _.left != undefined ? _.left : margin.left; + }}, + color: {get: function(){return color;}, set: function(_){ + color=nv.utils.getColor(_); + }} + }); + + nv.utils.initOptions(chart); + return chart; +}; +nv.models.sunburstChart = function() { + "use strict"; + + //============================================================ + // Public Variables with Default Settings + //------------------------------------------------------------ + + var sunburst = nv.models.sunburst(); + var tooltip = nv.models.tooltip(); + + var margin = {top: 30, right: 20, bottom: 20, left: 20} + , width = null + , height = null + , color = nv.utils.defaultColor() + , id = Math.round(Math.random() * 100000) + , defaultState = null + , noData = null + , duration = 250 + , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState','renderEnd') + ; + + //============================================================ + // Private Variables + //------------------------------------------------------------ + + var renderWatch = nv.utils.renderWatch(dispatch); + tooltip.headerEnabled(false).duration(0).valueFormatter(function(d, i) { + return d; + }); + + //============================================================ + // Chart function + //------------------------------------------------------------ + + function chart(selection) { + renderWatch.reset(); + renderWatch.models(sunburst); + + selection.each(function(data) { + var container = d3.select(this); + nv.utils.initSVG(container); + + var that = this; + var availableWidth = nv.utils.availableWidth(width, container, margin), + availableHeight = nv.utils.availableHeight(height, container, margin); + + chart.update = function() { + if (duration === 0) + container.call(chart); + else + container.transition().duration(duration).call(chart) + }; + chart.container = this; + + // Display No Data message if there's nothing to show. + if (!data || !data.length) { + nv.utils.noData(chart, container); + return chart; + } else { + container.selectAll('.nv-noData').remove(); + } + + // Setup containers and skeleton of chart + var wrap = container.selectAll('g.nv-wrap.nv-sunburstChart').data(data); + var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-sunburstChart').append('g'); + var g = wrap.select('g'); + + gEnter.append('g').attr('class', 'nv-sunburstWrap'); + + wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); + + // Main Chart Component(s) + sunburst.width(availableWidth).height(availableHeight); + var sunWrap = g.select('.nv-sunburstWrap').datum(data); + d3.transition(sunWrap).call(sunburst); + + }); + + renderWatch.renderEnd('sunburstChart immediate'); + return chart; + } + + //============================================================ + // Event Handling/Dispatching (out of chart's scope) + //------------------------------------------------------------ + + sunburst.dispatch.on('elementMouseover.tooltip', function(evt) { + evt['series'] = { + key: evt.data.name, + value: evt.data.size, + color: evt.color + }; + tooltip.data(evt).hidden(false); + }); + + sunburst.dispatch.on('elementMouseout.tooltip', function(evt) { + tooltip.hidden(true); + }); + + sunburst.dispatch.on('elementMousemove.tooltip', function(evt) { + tooltip.position({top: d3.event.pageY, left: d3.event.pageX})(); + }); + + //============================================================ + // Expose Public Variables + //------------------------------------------------------------ + + // expose chart's sub-components + chart.dispatch = dispatch; + chart.sunburst = sunburst; + chart.tooltip = tooltip; + chart.options = nv.utils.optionsFunc.bind(chart); + + // use Object get/set functionality to map between vars and chart functions + chart._options = Object.create({}, { + // simple options, just get/set the necessary values + noData: {get: function(){return noData;}, set: function(_){noData=_;}}, + defaultState: {get: function(){return defaultState;}, set: function(_){defaultState=_;}}, + + // options that require extra logic in the setter + color: {get: function(){return color;}, set: function(_){ + color = _; + sunburst.color(color); + }}, + duration: {get: function(){return duration;}, set: function(_){ + duration = _; + renderWatch.reset(duration); + sunburst.duration(duration); + }}, + margin: {get: function(){return margin;}, set: function(_){ + margin.top = _.top !== undefined ? _.top : margin.top; + margin.right = _.right !== undefined ? _.right : margin.right; + margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom; + margin.left = _.left !== undefined ? _.left : margin.left; + }} + }); + nv.utils.inheritOptions(chart, sunburst); + nv.utils.initOptions(chart); + return chart; +}; + +nv.version = "1.8.1"; +})();
\ No newline at end of file diff --git a/wqflask/wqflask/static/new/packages/nvd3/nv.d3.min.css b/wqflask/wqflask/static/new/packages/nvd3/nv.d3.min.css new file mode 100644 index 00000000..7a6f7fe9 --- /dev/null +++ b/wqflask/wqflask/static/new/packages/nvd3/nv.d3.min.css @@ -0,0 +1 @@ +.nvd3 .nv-axis{pointer-events:none;opacity:1}.nvd3 .nv-axis path{fill:none;stroke:#000;stroke-opacity:.75;shape-rendering:crispEdges}.nvd3 .nv-axis path.domain{stroke-opacity:.75}.nvd3 .nv-axis.nv-x path.domain{stroke-opacity:0}.nvd3 .nv-axis line{fill:none;stroke:#e5e5e5;shape-rendering:crispEdges}.nvd3 .nv-axis .zero line,.nvd3 .nv-axis line.zero{stroke-opacity:.75}.nvd3 .nv-axis .nv-axisMaxMin text{font-weight:700}.nvd3 .x .nv-axis .nv-axisMaxMin text,.nvd3 .x2 .nv-axis .nv-axisMaxMin text,.nvd3 .x3 .nv-axis .nv-axisMaxMin text{text-anchor:middle}.nvd3 .nv-axis.nv-disabled{opacity:0}.nvd3 .nv-bars rect{fill-opacity:.75;transition:fill-opacity 250ms linear;-moz-transition:fill-opacity 250ms linear;-webkit-transition:fill-opacity 250ms linear}.nvd3 .nv-bars rect.hover{fill-opacity:1}.nvd3 .nv-bars .hover rect{fill:#add8e6}.nvd3 .nv-bars text{fill:rgba(0,0,0,0)}.nvd3 .nv-bars .hover text{fill:rgba(0,0,0,1)}.nvd3 .nv-multibar .nv-groups rect,.nvd3 .nv-multibarHorizontal .nv-groups rect,.nvd3 .nv-discretebar .nv-groups rect{stroke-opacity:0;transition:fill-opacity 250ms linear;-moz-transition:fill-opacity 250ms linear;-webkit-transition:fill-opacity 250ms linear}.nvd3 .nv-multibar .nv-groups rect:hover,.nvd3 .nv-multibarHorizontal .nv-groups rect:hover,.nvd3 .nv-candlestickBar .nv-ticks rect:hover,.nvd3 .nv-discretebar .nv-groups rect:hover{fill-opacity:1}.nvd3 .nv-discretebar .nv-groups text,.nvd3 .nv-multibarHorizontal .nv-groups text{font-weight:700;fill:rgba(0,0,0,1);stroke:rgba(0,0,0,0)}.nvd3 .nv-boxplot circle{fill-opacity:.5}.nvd3 .nv-boxplot circle:hover{fill-opacity:1}.nvd3 .nv-boxplot rect:hover{fill-opacity:1}.nvd3 line.nv-boxplot-median{stroke:#000}.nv-boxplot-tick:hover{stroke-width:2.5px}.nvd3.nv-bullet{font:10px sans-serif}.nvd3.nv-bullet .nv-measure{fill-opacity:.8}.nvd3.nv-bullet .nv-measure:hover{fill-opacity:1}.nvd3.nv-bullet .nv-marker{stroke:#000;stroke-width:2px}.nvd3.nv-bullet .nv-markerTriangle{stroke:#000;fill:#fff;stroke-width:1.5px}.nvd3.nv-bullet .nv-tick line{stroke:#666;stroke-width:.5px}.nvd3.nv-bullet .nv-range.nv-s0{fill:#eee}.nvd3.nv-bullet .nv-range.nv-s1{fill:#ddd}.nvd3.nv-bullet .nv-range.nv-s2{fill:#ccc}.nvd3.nv-bullet .nv-title{font-size:14px;font-weight:700}.nvd3.nv-bullet .nv-subtitle{fill:#999}.nvd3.nv-bullet .nv-range{fill:#bababa;fill-opacity:.4}.nvd3.nv-bullet .nv-range:hover{fill-opacity:.7}.nvd3.nv-candlestickBar .nv-ticks .nv-tick{stroke-width:1px}.nvd3.nv-candlestickBar .nv-ticks .nv-tick.hover{stroke-width:2px}.nvd3.nv-candlestickBar .nv-ticks .nv-tick.positive rect{stroke:#2ca02c;fill:#2ca02c}.nvd3.nv-candlestickBar .nv-ticks .nv-tick.negative rect{stroke:#d62728;fill:#d62728}.with-transitions .nv-candlestickBar .nv-ticks .nv-tick{transition:stroke-width 250ms linear,stroke-opacity 250ms linear;-moz-transition:stroke-width 250ms linear,stroke-opacity 250ms linear;-webkit-transition:stroke-width 250ms linear,stroke-opacity 250ms linear}.nvd3.nv-candlestickBar .nv-ticks line{stroke:#333}.nvd3 .nv-legend .nv-disabled rect{}.nvd3 .nv-check-box .nv-box{fill-opacity:0;stroke-width:2}.nvd3 .nv-check-box .nv-check{fill-opacity:0;stroke-width:4}.nvd3 .nv-series.nv-disabled .nv-check-box .nv-check{fill-opacity:0;stroke-opacity:0}.nvd3 .nv-controlsWrap .nv-legend .nv-check-box .nv-check{opacity:0}.nvd3.nv-linePlusBar .nv-bar rect{fill-opacity:.75}.nvd3.nv-linePlusBar .nv-bar rect:hover{fill-opacity:1}.nvd3 .nv-groups path.nv-line{fill:none}.nvd3 .nv-groups path.nv-area{stroke:none}.nvd3.nv-line .nvd3.nv-scatter .nv-groups .nv-point{fill-opacity:0;stroke-opacity:0}.nvd3.nv-scatter.nv-single-point .nv-groups .nv-point{fill-opacity:.5!important;stroke-opacity:.5!important}.with-transitions .nvd3 .nv-groups .nv-point{transition:stroke-width 250ms linear,stroke-opacity 250ms linear;-moz-transition:stroke-width 250ms linear,stroke-opacity 250ms linear;-webkit-transition:stroke-width 250ms linear,stroke-opacity 250ms linear}.nvd3.nv-scatter .nv-groups .nv-point.hover,.nvd3 .nv-groups .nv-point.hover{stroke-width:7px;fill-opacity:.95!important;stroke-opacity:.95!important}.nvd3 .nv-point-paths path{stroke:#aaa;stroke-opacity:0;fill:#eee;fill-opacity:0}.nvd3 .nv-indexLine{cursor:ew-resize}svg.nvd3-svg{-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-ms-user-select:none;-moz-user-select:none;user-select:none;display:block;width:100%;height:100%}.nvtooltip.with-3d-shadow,.with-3d-shadow .nvtooltip{-moz-box-shadow:0 5px 10px rgba(0,0,0,.2);-webkit-box-shadow:0 5px 10px rgba(0,0,0,.2);box-shadow:0 5px 10px rgba(0,0,0,.2);-webkit-border-radius:5px;-moz-border-radius:5px;border-radius:5px}.nvd3 text{font:400 12px Arial}.nvd3 .title{font:700 14px Arial}.nvd3 .nv-background{fill:#fff;fill-opacity:0}.nvd3.nv-noData{font-size:18px;font-weight:700}.nv-brush .extent{fill-opacity:.125;shape-rendering:crispEdges}.nv-brush .resize path{fill:#eee;stroke:#666}.nvd3 .nv-legend .nv-series{cursor:pointer}.nvd3 .nv-legend .nv-disabled circle{fill-opacity:0}.nvd3 .nv-brush .extent{fill-opacity:0!important}.nvd3 .nv-brushBackground rect{stroke:#000;stroke-width:.4;fill:#fff;fill-opacity:.7}.nvd3.nv-ohlcBar .nv-ticks .nv-tick{stroke-width:1px}.nvd3.nv-ohlcBar .nv-ticks .nv-tick.hover{stroke-width:2px}.nvd3.nv-ohlcBar .nv-ticks .nv-tick.positive{stroke:#2ca02c}.nvd3.nv-ohlcBar .nv-ticks .nv-tick.negative{stroke:#d62728}.nvd3 .background path{fill:none;stroke:#EEE;stroke-opacity:.4;shape-rendering:crispEdges}.nvd3 .foreground path{fill:none;stroke-opacity:.7}.nvd3 .nv-parallelCoordinates-brush .extent{fill:#fff;fill-opacity:.6;stroke:gray;shape-rendering:crispEdges}.nvd3 .nv-parallelCoordinates .hover{fill-opacity:1;stroke-width:3px}.nvd3 .missingValuesline line{fill:none;stroke:#000;stroke-width:1;stroke-opacity:1;stroke-dasharray:5,5}.nvd3.nv-pie path{stroke-opacity:0;transition:fill-opacity 250ms linear,stroke-width 250ms linear,stroke-opacity 250ms linear;-moz-transition:fill-opacity 250ms linear,stroke-width 250ms linear,stroke-opacity 250ms linear;-webkit-transition:fill-opacity 250ms linear,stroke-width 250ms linear,stroke-opacity 250ms linear}.nvd3.nv-pie .nv-pie-title{font-size:24px;fill:rgba(19,196,249,.59)}.nvd3.nv-pie .nv-slice text{stroke:#000;stroke-width:0}.nvd3.nv-pie path{stroke:#fff;stroke-width:1px;stroke-opacity:1}.nvd3.nv-pie .hover path{fill-opacity:.7}.nvd3.nv-pie .nv-label{pointer-events:none}.nvd3.nv-pie .nv-label rect{fill-opacity:0;stroke-opacity:0}.nvd3 .nv-groups .nv-point.hover{stroke-width:20px;stroke-opacity:.5}.nvd3 .nv-scatter .nv-point.hover{fill-opacity:1}.nv-noninteractive{pointer-events:none}.nv-distx,.nv-disty{pointer-events:none}.nvd3.nv-sparkline path{fill:none}.nvd3.nv-sparklineplus g.nv-hoverValue{pointer-events:none}.nvd3.nv-sparklineplus .nv-hoverValue line{stroke:#333;stroke-width:1.5px}.nvd3.nv-sparklineplus,.nvd3.nv-sparklineplus g{pointer-events:all}.nvd3 .nv-hoverArea{fill-opacity:0;stroke-opacity:0}.nvd3.nv-sparklineplus .nv-xValue,.nvd3.nv-sparklineplus .nv-yValue{stroke-width:0;font-size:.9em;font-weight:400}.nvd3.nv-sparklineplus .nv-yValue{stroke:#f66}.nvd3.nv-sparklineplus .nv-maxValue{stroke:#2ca02c;fill:#2ca02c}.nvd3.nv-sparklineplus .nv-minValue{stroke:#d62728;fill:#d62728}.nvd3.nv-sparklineplus .nv-currentValue{font-weight:700;font-size:1.1em}.nvd3.nv-stackedarea path.nv-area{fill-opacity:.7;stroke-opacity:0;transition:fill-opacity 250ms linear,stroke-opacity 250ms linear;-moz-transition:fill-opacity 250ms linear,stroke-opacity 250ms linear;-webkit-transition:fill-opacity 250ms linear,stroke-opacity 250ms linear}.nvd3.nv-stackedarea path.nv-area.hover{fill-opacity:.9}.nvd3.nv-stackedarea .nv-groups .nv-point{stroke-opacity:0;fill-opacity:0}.nvtooltip{position:absolute;background-color:rgba(255,255,255,1);color:rgba(0,0,0,1);padding:1px;border:1px solid rgba(0,0,0,.2);z-index:10000;display:block;font-family:Arial;font-size:13px;text-align:left;pointer-events:none;white-space:nowrap;-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.nvtooltip{background:rgba(255,255,255,.8);border:1px solid rgba(0,0,0,.5);border-radius:4px}.nvtooltip.with-transitions,.with-transitions .nvtooltip{transition:opacity 50ms linear;-moz-transition:opacity 50ms linear;-webkit-transition:opacity 50ms linear;transition-delay:200ms;-moz-transition-delay:200ms;-webkit-transition-delay:200ms}.nvtooltip.x-nvtooltip,.nvtooltip.y-nvtooltip{padding:8px}.nvtooltip h3{margin:0;padding:4px 14px;line-height:18px;font-weight:400;background-color:rgba(247,247,247,.75);color:rgba(0,0,0,1);text-align:center;border-bottom:1px solid #ebebeb;-webkit-border-radius:5px 5px 0 0;-moz-border-radius:5px 5px 0 0;border-radius:5px 5px 0 0}.nvtooltip p{margin:0;padding:5px 14px;text-align:center}.nvtooltip span{display:inline-block;margin:2px 0}.nvtooltip table{margin:6px;border-spacing:0}.nvtooltip table td{padding:2px 9px 2px 0;vertical-align:middle}.nvtooltip table td.key{font-weight:400}.nvtooltip table td.value{text-align:right;font-weight:700}.nvtooltip table tr.highlight td{padding:1px 9px 1px 0;border-bottom-style:solid;border-bottom-width:1px;border-top-style:solid;border-top-width:1px}.nvtooltip table td.legend-color-guide div{width:8px;height:8px;vertical-align:middle}.nvtooltip table td.legend-color-guide div{width:12px;height:12px;border:1px solid #999}.nvtooltip .footer{padding:3px;text-align:center}.nvtooltip-pending-removal{pointer-events:none;display:none}.nvd3 .nv-interactiveGuideLine{pointer-events:none}.nvd3 line.nv-guideline{stroke:#ccc}
\ No newline at end of file diff --git a/wqflask/wqflask/static/new/packages/nvd3/nv.d3.min.js b/wqflask/wqflask/static/new/packages/nvd3/nv.d3.min.js new file mode 100644 index 00000000..ab17e9fe --- /dev/null +++ b/wqflask/wqflask/static/new/packages/nvd3/nv.d3.min.js @@ -0,0 +1,8 @@ +/* nvd3 version 1.8.1 (https://github.com/novus/nvd3) 2015-05-25 */ +!function(){var a={};a.dev=!1,a.tooltip=a.tooltip||{},a.utils=a.utils||{},a.models=a.models||{},a.charts={},a.logs={},a.dom={},a.dispatch=d3.dispatch("render_start","render_end"),Function.prototype.bind||(Function.prototype.bind=function(a){if("function"!=typeof this)throw new TypeError("Function.prototype.bind - what is trying to be bound is not callable");var b=Array.prototype.slice.call(arguments,1),c=this,d=function(){},e=function(){return c.apply(this instanceof d&&a?this:a,b.concat(Array.prototype.slice.call(arguments)))};return d.prototype=this.prototype,e.prototype=new d,e}),a.dev&&(a.dispatch.on("render_start",function(b){a.logs.startTime=+new Date}),a.dispatch.on("render_end",function(b){a.logs.endTime=+new Date,a.logs.totalTime=a.logs.endTime-a.logs.startTime,a.log("total",a.logs.totalTime)})),a.log=function(){if(a.dev&&window.console&&console.log&&console.log.apply)console.log.apply(console,arguments);else if(a.dev&&window.console&&"function"==typeof console.log&&Function.prototype.bind){var b=Function.prototype.bind.call(console.log,console);b.apply(console,arguments)}return arguments[arguments.length-1]},a.deprecated=function(a,b){console&&console.warn&&console.warn("nvd3 warning: `"+a+"` has been deprecated. ",b||"")},a.render=function(b){b=b||1,a.render.active=!0,a.dispatch.render_start();var c=function(){for(var d,e,f=0;b>f&&(e=a.render.queue[f]);f++)d=e.generate(),typeof e.callback==typeof Function&&e.callback(d);a.render.queue.splice(0,f),a.render.queue.length?setTimeout(c):(a.dispatch.render_end(),a.render.active=!1)};setTimeout(c)},a.render.active=!1,a.render.queue=[],a.addGraph=function(b){typeof arguments[0]==typeof Function&&(b={generate:arguments[0],callback:arguments[1]}),a.render.queue.push(b),a.render.active||a.render()},"undefined"!=typeof module&&"undefined"!=typeof exports&&(module.exports=a),"undefined"!=typeof window&&(window.nv=a),a.dom.write=function(a){return void 0!==window.fastdom?fastdom.write(a):a()},a.dom.read=function(a){return void 0!==window.fastdom?fastdom.read(a):a()},a.interactiveGuideline=function(){"use strict";function b(l){l.each(function(l){function m(){var a=d3.mouse(this),d=a[0],e=a[1],i=!0,j=!1;if(k&&(d=d3.event.offsetX,e=d3.event.offsetY,"svg"!==d3.event.target.tagName&&(i=!1),d3.event.target.className.baseVal.match("nv-legend")&&(j=!0)),i&&(d-=f.left,e-=f.top),0>d||0>e||d>o||e>p||d3.event.relatedTarget&&void 0===d3.event.relatedTarget.ownerSVGElement||j){if(k&&d3.event.relatedTarget&&void 0===d3.event.relatedTarget.ownerSVGElement&&(void 0===d3.event.relatedTarget.className||d3.event.relatedTarget.className.match(c.nvPointerEventsClass)))return;return h.elementMouseout({mouseX:d,mouseY:e}),b.renderGuideLine(null),void c.hidden(!0)}c.hidden(!1);var l=g.invert(d);h.elementMousemove({mouseX:d,mouseY:e,pointXValue:l}),"dblclick"===d3.event.type&&h.elementDblclick({mouseX:d,mouseY:e,pointXValue:l}),"click"===d3.event.type&&h.elementClick({mouseX:d,mouseY:e,pointXValue:l})}var n=d3.select(this),o=d||960,p=e||400,q=n.selectAll("g.nv-wrap.nv-interactiveLineLayer").data([l]),r=q.enter().append("g").attr("class"," nv-wrap nv-interactiveLineLayer");r.append("g").attr("class","nv-interactiveGuideLine"),j&&(j.on("touchmove",m).on("mousemove",m,!0).on("mouseout",m,!0).on("dblclick",m).on("click",m),b.guideLine=null,b.renderGuideLine=function(c){i&&(b.guideLine&&b.guideLine.attr("x1")===c||a.dom.write(function(){var b=q.select(".nv-interactiveGuideLine").selectAll("line").data(null!=c?[a.utils.NaNtoZero(c)]:[],String);b.enter().append("line").attr("class","nv-guideline").attr("x1",function(a){return a}).attr("x2",function(a){return a}).attr("y1",p).attr("y2",0),b.exit().remove()}))})})}var c=a.models.tooltip();c.duration(0).hideDelay(0)._isInteractiveLayer(!0).hidden(!1);var d=null,e=null,f={left:0,top:0},g=d3.scale.linear(),h=d3.dispatch("elementMousemove","elementMouseout","elementClick","elementDblclick"),i=!0,j=null,k="ActiveXObject"in window;return b.dispatch=h,b.tooltip=c,b.margin=function(a){return arguments.length?(f.top="undefined"!=typeof a.top?a.top:f.top,f.left="undefined"!=typeof a.left?a.left:f.left,b):f},b.width=function(a){return arguments.length?(d=a,b):d},b.height=function(a){return arguments.length?(e=a,b):e},b.xScale=function(a){return arguments.length?(g=a,b):g},b.showGuideLine=function(a){return arguments.length?(i=a,b):i},b.svgContainer=function(a){return arguments.length?(j=a,b):j},b},a.interactiveBisect=function(a,b,c){"use strict";if(!(a instanceof Array))return null;var d;d="function"!=typeof c?function(a){return a.x}:c;var e=function(a,b){return d(a)-b},f=d3.bisector(e).left,g=d3.max([0,f(a,b)-1]),h=d(a[g]);if("undefined"==typeof h&&(h=g),h===b)return g;var i=d3.min([g+1,a.length-1]),j=d(a[i]);return"undefined"==typeof j&&(j=i),Math.abs(j-b)>=Math.abs(h-b)?g:i},a.nearestValueIndex=function(a,b,c){"use strict";var d=1/0,e=null;return a.forEach(function(a,f){var g=Math.abs(b-a);null!=a&&d>=g&&c>g&&(d=g,e=f)}),e},function(){"use strict";a.models.tooltip=function(){function b(){if(k){var a=d3.select(k);"svg"!==a.node().tagName&&(a=a.select("svg"));var b=a.node()?a.attr("viewBox"):null;if(b){b=b.split(" ");var c=parseInt(a.style("width"),10)/b[2];p.left=p.left*c,p.top=p.top*c}}}function c(){if(!n){var a;a=k?k:document.body,n=d3.select(a).append("div").attr("class","nvtooltip "+(j?j:"xy-tooltip")).attr("id",v),n.style("top",0).style("left",0),n.style("opacity",0),n.selectAll("div, table, td, tr").classed(w,!0),n.classed(w,!0),o=n.node()}}function d(){if(r&&B(e)){b();var f=p.left,g=null!==i?i:p.top;return a.dom.write(function(){c();var b=A(e);b&&(o.innerHTML=b),k&&u?a.dom.read(function(){var a=k.getElementsByTagName("svg")[0],b={left:0,top:0};if(a){var c=a.getBoundingClientRect(),d=k.getBoundingClientRect(),e=c.top;if(0>e){var i=k.getBoundingClientRect();e=Math.abs(e)>i.height?0:e}b.top=Math.abs(e-d.top),b.left=Math.abs(c.left-d.left)}f+=k.offsetLeft+b.left-2*k.scrollLeft,g+=k.offsetTop+b.top-2*k.scrollTop,h&&h>0&&(g=Math.floor(g/h)*h),C([f,g])}):C([f,g])}),d}}var e=null,f="w",g=25,h=0,i=null,j=null,k=null,l=!0,m=400,n=null,o=null,p={left:null,top:null},q={left:0,top:0},r=!0,s=100,t=!0,u=!1,v="nvtooltip-"+Math.floor(1e5*Math.random()),w="nv-pointer-events-none",x=function(a,b){return a},y=function(a){return a},z=function(a,b){return a},A=function(a){if(null===a)return"";var b=d3.select(document.createElement("table"));if(t){var c=b.selectAll("thead").data([a]).enter().append("thead");c.append("tr").append("td").attr("colspan",3).append("strong").classed("x-value",!0).html(y(a.value))}var d=b.selectAll("tbody").data([a]).enter().append("tbody"),e=d.selectAll("tr").data(function(a){return a.series}).enter().append("tr").classed("highlight",function(a){return a.highlight});e.append("td").classed("legend-color-guide",!0).append("div").style("background-color",function(a){return a.color}),e.append("td").classed("key",!0).html(function(a,b){return z(a.key,b)}),e.append("td").classed("value",!0).html(function(a,b){return x(a.value,b)}),e.selectAll("td").each(function(a){if(a.highlight){var b=d3.scale.linear().domain([0,1]).range(["#fff",a.color]),c=.6;d3.select(this).style("border-bottom-color",b(c)).style("border-top-color",b(c))}});var f=b.node().outerHTML;return void 0!==a.footer&&(f+="<div class='footer'>"+a.footer+"</div>"),f},B=function(a){if(a&&a.series){if(a.series instanceof Array)return!!a.series.length;if(a.series instanceof Object)return a.series=[a.series],!0}return!1},C=function(b){o&&a.dom.read(function(){var c,d,e=parseInt(o.offsetHeight,10),h=parseInt(o.offsetWidth,10),i=a.utils.windowSize().width,j=a.utils.windowSize().height,k=window.pageYOffset,p=window.pageXOffset;j=window.innerWidth>=document.body.scrollWidth?j:j-16,i=window.innerHeight>=document.body.scrollHeight?i:i-16;var r,t,u=function(a){var b=d;do isNaN(a.offsetTop)||(b+=a.offsetTop),a=a.offsetParent;while(a);return b},v=function(a){var b=c;do isNaN(a.offsetLeft)||(b+=a.offsetLeft),a=a.offsetParent;while(a);return b};switch(f){case"e":c=b[0]-h-g,d=b[1]-e/2,r=v(o),t=u(o),p>r&&(c=b[0]+g>p?b[0]+g:p-r+c),k>t&&(d=k-t+d),t+e>k+j&&(d=k+j-t+d-e);break;case"w":c=b[0]+g,d=b[1]-e/2,r=v(o),t=u(o),r+h>i&&(c=b[0]-h-g),k>t&&(d=k+5),t+e>k+j&&(d=k+j-t+d-e);break;case"n":c=b[0]-h/2-5,d=b[1]+g,r=v(o),t=u(o),p>r&&(c=p+5),r+h>i&&(c=c-h/2+5),t+e>k+j&&(d=k+j-t+d-e);break;case"s":c=b[0]-h/2,d=b[1]-e-g,r=v(o),t=u(o),p>r&&(c=p+5),r+h>i&&(c=c-h/2+5),k>t&&(d=k);break;case"none":c=b[0],d=b[1]-g,r=v(o),t=u(o)}c-=q.left,d-=q.top;var w=o.getBoundingClientRect(),k=window.pageYOffset||document.documentElement.scrollTop,p=window.pageXOffset||document.documentElement.scrollLeft,x="translate("+(w.left+p)+"px, "+(w.top+k)+"px)",y="translate("+c+"px, "+d+"px)",z=d3.interpolateString(x,y),A=n.style("opacity")<.1;l?n.transition().delay(m).duration(0).style("opacity",0):n.interrupt().transition().duration(A?0:s).styleTween("transform",function(a){return z},"important").style("-webkit-transform",y).style("opacity",1)})};return d.nvPointerEventsClass=w,d.options=a.utils.optionsFunc.bind(d),d._options=Object.create({},{duration:{get:function(){return s},set:function(a){s=a}},gravity:{get:function(){return f},set:function(a){f=a}},distance:{get:function(){return g},set:function(a){g=a}},snapDistance:{get:function(){return h},set:function(a){h=a}},classes:{get:function(){return j},set:function(a){j=a}},chartContainer:{get:function(){return k},set:function(a){k=a}},fixedTop:{get:function(){return i},set:function(a){i=a}},enabled:{get:function(){return r},set:function(a){r=a}},hideDelay:{get:function(){return m},set:function(a){m=a}},contentGenerator:{get:function(){return A},set:function(a){A=a}},valueFormatter:{get:function(){return x},set:function(a){x=a}},headerFormatter:{get:function(){return y},set:function(a){y=a}},keyFormatter:{get:function(){return z},set:function(a){z=a}},headerEnabled:{get:function(){return t},set:function(a){t=a}},_isInteractiveLayer:{get:function(){return u},set:function(a){u=!!a}},position:{get:function(){return p},set:function(a){p.left=void 0!==a.left?a.left:p.left,p.top=void 0!==a.top?a.top:p.top}},offset:{get:function(){return q},set:function(a){q.left=void 0!==a.left?a.left:q.left,q.top=void 0!==a.top?a.top:q.top}},hidden:{get:function(){return l},set:function(a){l!=a&&(l=!!a,d())}},data:{get:function(){return e},set:function(a){a.point&&(a.value=a.point.x,a.series=a.series||{},a.series.value=a.point.y,a.series.color=a.point.color||a.series.color),e=a}},tooltipElem:{get:function(){return o},set:function(a){}},id:{get:function(){return v},set:function(a){}}}),a.utils.initOptions(d),d}}(),a.utils.windowSize=function(){var a={width:640,height:480};return window.innerWidth&&window.innerHeight?(a.width=window.innerWidth,a.height=window.innerHeight,a):"CSS1Compat"==document.compatMode&&document.documentElement&&document.documentElement.offsetWidth?(a.width=document.documentElement.offsetWidth,a.height=document.documentElement.offsetHeight,a):document.body&&document.body.offsetWidth?(a.width=document.body.offsetWidth,a.height=document.body.offsetHeight,a):a},a.utils.windowResize=function(b){return window.addEventListener?window.addEventListener("resize",b):a.log("ERROR: Failed to bind to window.resize with: ",b),{callback:b,clear:function(){window.removeEventListener("resize",b)}}},a.utils.getColor=function(b){if(void 0===b)return a.utils.defaultColor();if(Array.isArray(b)){var c=d3.scale.ordinal().range(b);return function(a,b){var d=void 0===b?a:b;return a.color||c(d)}}return b},a.utils.defaultColor=function(){return a.utils.getColor(d3.scale.category20().range())},a.utils.customTheme=function(a,b,c){b=b||function(a){return a.key},c=c||d3.scale.category20().range();var d=c.length;return function(e,f){var g=b(e);return"function"==typeof a[g]?a[g]():void 0!==a[g]?a[g]:(d||(d=c.length),d-=1,c[d])}},a.utils.pjax=function(b,c){var d=function(d){d3.html(d,function(d){var e=d3.select(c).node();e.parentNode.replaceChild(d3.select(d).select(c).node(),e),a.utils.pjax(b,c)})};d3.selectAll(b).on("click",function(){history.pushState(this.href,this.textContent,this.href),d(this.href),d3.event.preventDefault()}),d3.select(window).on("popstate",function(){d3.event.state&&d(d3.event.state)})},a.utils.calcApproxTextWidth=function(a){if("function"==typeof a.style&&"function"==typeof a.text){var b=parseInt(a.style("font-size").replace("px",""),10),c=a.text().length;return c*b*.5}return 0},a.utils.NaNtoZero=function(a){return"number"!=typeof a||isNaN(a)||null===a||a===1/0||a===-(1/0)?0:a},d3.selection.prototype.watchTransition=function(a){var b=[this].concat([].slice.call(arguments,1));return a.transition.apply(a,b)},a.utils.renderWatch=function(b,c){if(!(this instanceof a.utils.renderWatch))return new a.utils.renderWatch(b,c);var d=void 0!==c?c:250,e=[],f=this;this.models=function(a){return a=[].slice.call(arguments,0),a.forEach(function(a){a.__rendered=!1,function(a){a.dispatch.on("renderEnd",function(b){a.__rendered=!0,f.renderEnd("model")})}(a),e.indexOf(a)<0&&e.push(a)}),this},this.reset=function(a){void 0!==a&&(d=a),e=[]},this.transition=function(a,b,c){if(b=arguments.length>1?[].slice.call(arguments,1):[],c=b.length>1?b.pop():void 0!==d?d:250,a.__rendered=!1,e.indexOf(a)<0&&e.push(a),0===c)return a.__rendered=!0,a.delay=function(){return this},a.duration=function(){return this},a;0===a.length?a.__rendered=!0:a.every(function(a){return!a.length})?a.__rendered=!0:a.__rendered=!1;var g=0;return a.transition().duration(c).each(function(){++g}).each("end",function(c,d){0===--g&&(a.__rendered=!0,f.renderEnd.apply(this,b))})},this.renderEnd=function(){e.every(function(a){return a.__rendered})&&(e.forEach(function(a){a.__rendered=!1}),b.renderEnd.apply(this,arguments))}},a.utils.deepExtend=function(b){var c=arguments.length>1?[].slice.call(arguments,1):[];c.forEach(function(c){for(var d in c){var e=b[d]instanceof Array,f="object"==typeof b[d],g="object"==typeof c[d];f&&!e&&g?a.utils.deepExtend(b[d],c[d]):b[d]=c[d]}})},a.utils.state=function(){if(!(this instanceof a.utils.state))return new a.utils.state;var b={},c=function(){},d=function(){return{}},e=null,f=null;this.dispatch=d3.dispatch("change","set"),this.dispatch.on("set",function(a){c(a,!0)}),this.getter=function(a){return d=a,this},this.setter=function(a,b){return b||(b=function(){}),c=function(c,d){a(c),d&&b()},this},this.init=function(b){e=e||{},a.utils.deepExtend(e,b)};var g=function(){var a=d();if(JSON.stringify(a)===JSON.stringify(b))return!1;for(var c in a)void 0===b[c]&&(b[c]={}),b[c]=a[c],f=!0;return!0};this.update=function(){e&&(c(e,!1),e=null),g.call(this)&&this.dispatch.change(b)}},a.utils.optionsFunc=function(a){return a&&d3.map(a).forEach(function(a,b){"function"==typeof this[a]&&this[a](b)}.bind(this)),this},a.utils.calcTicksX=function(b,c){var d=1,e=0;for(e;e<c.length;e+=1){var f=c[e]&&c[e].values?c[e].values.length:0;d=f>d?f:d}return a.log("Requested number of ticks: ",b),a.log("Calculated max values to be: ",d),b=b>d?b=d-1:b,b=1>b?1:b,b=Math.floor(b),a.log("Calculating tick count as: ",b),b},a.utils.calcTicksY=function(b,c){return a.utils.calcTicksX(b,c)},a.utils.initOption=function(a,b){a._calls&&a._calls[b]?a[b]=a._calls[b]:(a[b]=function(c){return arguments.length?(a._overrides[b]=!0,a._options[b]=c,a):a._options[b]},a["_"+b]=function(c){return arguments.length?(a._overrides[b]||(a._options[b]=c),a):a._options[b]})},a.utils.initOptions=function(b){b._overrides=b._overrides||{};var c=Object.getOwnPropertyNames(b._options||{}),d=Object.getOwnPropertyNames(b._calls||{});c=c.concat(d);for(var e in c)a.utils.initOption(b,c[e])},a.utils.inheritOptionsD3=function(a,b,c){a._d3options=c.concat(a._d3options||[]),c.unshift(b),c.unshift(a),d3.rebind.apply(this,c)},a.utils.arrayUnique=function(a){return a.sort().filter(function(b,c){return!c||b!=a[c-1]})},a.utils.symbolMap=d3.map(),a.utils.symbol=function(){function b(b,e){var f=c.call(this,b,e),g=d.call(this,b,e);return-1!==d3.svg.symbolTypes.indexOf(f)?d3.svg.symbol().type(f).size(g)():a.utils.symbolMap.get(f)(g)}var c,d=64;return b.type=function(a){return arguments.length?(c=d3.functor(a),b):c},b.size=function(a){return arguments.length?(d=d3.functor(a),b):d},b},a.utils.inheritOptions=function(b,c){var d=Object.getOwnPropertyNames(c._options||{}),e=Object.getOwnPropertyNames(c._calls||{}),f=c._inherited||[],g=c._d3options||[],h=d.concat(e).concat(f).concat(g);h.unshift(c),h.unshift(b),d3.rebind.apply(this,h),b._inherited=a.utils.arrayUnique(d.concat(e).concat(f).concat(d).concat(b._inherited||[])),b._d3options=a.utils.arrayUnique(g.concat(b._d3options||[]))},a.utils.initSVG=function(a){a.classed({"nvd3-svg":!0})},a.utils.sanitizeHeight=function(a,b){return a||parseInt(b.style("height"),10)||400},a.utils.sanitizeWidth=function(a,b){return a||parseInt(b.style("width"),10)||960},a.utils.availableHeight=function(b,c,d){return a.utils.sanitizeHeight(b,c)-d.top-d.bottom},a.utils.availableWidth=function(b,c,d){return a.utils.sanitizeWidth(b,c)-d.left-d.right},a.utils.noData=function(b,c){var d=b.options(),e=d.margin(),f=d.noData(),g=null==f?["No Data Available."]:[f],h=a.utils.availableHeight(d.height(),c,e),i=a.utils.availableWidth(d.width(),c,e),j=e.left+i/2,k=e.top+h/2;c.selectAll("g").remove();var l=c.selectAll(".nv-noData").data(g);l.enter().append("text").attr("class","nvd3 nv-noData").attr("dy","-.7em").style("text-anchor","middle"),l.attr("x",j).attr("y",k).text(function(a){return a})},a.models.axis=function(){"use strict";function b(g){return s.reset(),g.each(function(b){var g=d3.select(this);a.utils.initSVG(g);var p=g.selectAll("g.nv-wrap.nv-axis").data([b]),q=p.enter().append("g").attr("class","nvd3 nv-wrap nv-axis"),t=(q.append("g"),p.select("g"));null!==n?c.ticks(n):("top"==c.orient()||"bottom"==c.orient())&&c.ticks(Math.abs(d.range()[1]-d.range()[0])/100),t.watchTransition(s,"axis").call(c),r=r||c.scale();var u=c.tickFormat();null==u&&(u=r.tickFormat());var v=t.selectAll("text.nv-axislabel").data([h||null]);v.exit().remove();var w,x,y;switch(c.orient()){case"top":v.enter().append("text").attr("class","nv-axislabel"),y=d.range().length<2?0:2===d.range().length?d.range()[1]:d.range()[d.range().length-1]+(d.range()[1]-d.range()[0]),v.attr("text-anchor","middle").attr("y",0).attr("x",y/2),i&&(x=p.selectAll("g.nv-axisMaxMin").data(d.domain()),x.enter().append("g").attr("class",function(a,b){return["nv-axisMaxMin","nv-axisMaxMin-x",0==b?"nv-axisMin-x":"nv-axisMax-x"].join(" ")}).append("text"),x.exit().remove(),x.attr("transform",function(b,c){return"translate("+a.utils.NaNtoZero(d(b))+",0)"}).select("text").attr("dy","-0.5em").attr("y",-c.tickPadding()).attr("text-anchor","middle").text(function(a,b){var c=u(a);return(""+c).match("NaN")?"":c}),x.watchTransition(s,"min-max top").attr("transform",function(b,c){return"translate("+a.utils.NaNtoZero(d.range()[c])+",0)"}));break;case"bottom":w=o+36;var z=30,A=0,B=t.selectAll("g").select("text"),C="";if(j%360){B.each(function(a,b){var c=this.getBoundingClientRect(),d=c.width;A=c.height,d>z&&(z=d)}),C="rotate("+j+" 0,"+(A/2+c.tickPadding())+")";var D=Math.abs(Math.sin(j*Math.PI/180));w=(D?D*z:z)+30,B.attr("transform",C).style("text-anchor",j%360>0?"start":"end")}v.enter().append("text").attr("class","nv-axislabel"),y=d.range().length<2?0:2===d.range().length?d.range()[1]:d.range()[d.range().length-1]+(d.range()[1]-d.range()[0]),v.attr("text-anchor","middle").attr("y",w).attr("x",y/2),i&&(x=p.selectAll("g.nv-axisMaxMin").data([d.domain()[0],d.domain()[d.domain().length-1]]),x.enter().append("g").attr("class",function(a,b){return["nv-axisMaxMin","nv-axisMaxMin-x",0==b?"nv-axisMin-x":"nv-axisMax-x"].join(" ")}).append("text"),x.exit().remove(),x.attr("transform",function(b,c){return"translate("+a.utils.NaNtoZero(d(b)+(m?d.rangeBand()/2:0))+",0)"}).select("text").attr("dy",".71em").attr("y",c.tickPadding()).attr("transform",C).style("text-anchor",j?j%360>0?"start":"end":"middle").text(function(a,b){var c=u(a);return(""+c).match("NaN")?"":c}),x.watchTransition(s,"min-max bottom").attr("transform",function(b,c){return"translate("+a.utils.NaNtoZero(d(b)+(m?d.rangeBand()/2:0))+",0)"})),l&&B.attr("transform",function(a,b){return"translate(0,"+(b%2==0?"0":"12")+")"});break;case"right":v.enter().append("text").attr("class","nv-axislabel"),v.style("text-anchor",k?"middle":"begin").attr("transform",k?"rotate(90)":"").attr("y",k?-Math.max(e.right,f)+12:-10).attr("x",k?d3.max(d.range())/2:c.tickPadding()),i&&(x=p.selectAll("g.nv-axisMaxMin").data(d.domain()),x.enter().append("g").attr("class",function(a,b){return["nv-axisMaxMin","nv-axisMaxMin-y",0==b?"nv-axisMin-y":"nv-axisMax-y"].join(" ")}).append("text").style("opacity",0),x.exit().remove(),x.attr("transform",function(b,c){return"translate(0,"+a.utils.NaNtoZero(d(b))+")"}).select("text").attr("dy",".32em").attr("y",0).attr("x",c.tickPadding()).style("text-anchor","start").text(function(a,b){var c=u(a);return(""+c).match("NaN")?"":c}),x.watchTransition(s,"min-max right").attr("transform",function(b,c){return"translate(0,"+a.utils.NaNtoZero(d.range()[c])+")"}).select("text").style("opacity",1));break;case"left":v.enter().append("text").attr("class","nv-axislabel"),v.style("text-anchor",k?"middle":"end").attr("transform",k?"rotate(-90)":"").attr("y",k?-Math.max(e.left,f)+25-(o||0):-10).attr("x",k?-d3.max(d.range())/2:-c.tickPadding()),i&&(x=p.selectAll("g.nv-axisMaxMin").data(d.domain()),x.enter().append("g").attr("class",function(a,b){return["nv-axisMaxMin","nv-axisMaxMin-y",0==b?"nv-axisMin-y":"nv-axisMax-y"].join(" ")}).append("text").style("opacity",0),x.exit().remove(),x.attr("transform",function(b,c){return"translate(0,"+a.utils.NaNtoZero(r(b))+")"}).select("text").attr("dy",".32em").attr("y",0).attr("x",-c.tickPadding()).attr("text-anchor","end").text(function(a,b){var c=u(a);return(""+c).match("NaN")?"":c}),x.watchTransition(s,"min-max right").attr("transform",function(b,c){return"translate(0,"+a.utils.NaNtoZero(d.range()[c])+")"}).select("text").style("opacity",1))}if(v.text(function(a){return a}),!i||"left"!==c.orient()&&"right"!==c.orient()||(t.selectAll("g").each(function(a,b){d3.select(this).select("text").attr("opacity",1),(d(a)<d.range()[1]+10||d(a)>d.range()[0]-10)&&((a>1e-10||-1e-10>a)&&d3.select(this).attr("opacity",0),d3.select(this).select("text").attr("opacity",0))}),d.domain()[0]==d.domain()[1]&&0==d.domain()[0]&&p.selectAll("g.nv-axisMaxMin").style("opacity",function(a,b){return b?0:1})),i&&("top"===c.orient()||"bottom"===c.orient())){var E=[];p.selectAll("g.nv-axisMaxMin").each(function(a,b){try{b?E.push(d(a)-this.getBoundingClientRect().width-4):E.push(d(a)+this.getBoundingClientRect().width+4)}catch(c){b?E.push(d(a)-4):E.push(d(a)+4)}}),t.selectAll("g").each(function(a,b){(d(a)<E[0]||d(a)>E[1])&&(a>1e-10||-1e-10>a?d3.select(this).remove():d3.select(this).select("text").remove())})}t.selectAll(".tick").filter(function(a){return!parseFloat(Math.round(1e5*a)/1e6)&&void 0!==a}).classed("zero",!0),r=d.copy()}),s.renderEnd("axis immediate"),b}var c=d3.svg.axis(),d=d3.scale.linear(),e={top:0,right:0,bottom:0,left:0},f=75,g=60,h=null,i=!0,j=0,k=!0,l=!1,m=!1,n=null,o=0,p=250,q=d3.dispatch("renderEnd");c.scale(d).orient("bottom").tickFormat(function(a){return a});var r,s=a.utils.renderWatch(q,p);return b.axis=c,b.dispatch=q,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{axisLabelDistance:{get:function(){return o},set:function(a){o=a}},staggerLabels:{get:function(){return l},set:function(a){l=a}},rotateLabels:{get:function(){return j},set:function(a){j=a}},rotateYLabel:{get:function(){return k},set:function(a){k=a}},showMaxMin:{get:function(){return i},set:function(a){i=a}},axisLabel:{get:function(){return h},set:function(a){h=a}},height:{get:function(){return g},set:function(a){g=a}},ticks:{get:function(){return n},set:function(a){n=a}},width:{get:function(){return f},set:function(a){f=a}},margin:{get:function(){return e},set:function(a){e.top=void 0!==a.top?a.top:e.top,e.right=void 0!==a.right?a.right:e.right,e.bottom=void 0!==a.bottom?a.bottom:e.bottom,e.left=void 0!==a.left?a.left:e.left}},duration:{get:function(){return p},set:function(a){p=a,s.reset(p)}},scale:{get:function(){return d},set:function(e){d=e,c.scale(d),m="function"==typeof d.rangeBands,a.utils.inheritOptionsD3(b,d,["domain","range","rangeBand","rangeBands"])}}}),a.utils.initOptions(b),a.utils.inheritOptionsD3(b,c,["orient","tickValues","tickSubdivide","tickSize","tickPadding","tickFormat"]),a.utils.inheritOptionsD3(b,d,["domain","range","rangeBand","rangeBands"]),b},a.models.boxPlot=function(){"use strict";function b(l){return v.reset(),l.each(function(b){var l=j-i.left-i.right,p=k-i.top-i.bottom;r=d3.select(this),a.utils.initSVG(r),m.domain(c||b.map(function(a,b){return o(a,b)})).rangeBands(e||[0,l],.1);var w=[];if(!d){var x=d3.min(b.map(function(a){var b=[];return b.push(a.values.Q1),a.values.hasOwnProperty("whisker_low")&&null!==a.values.whisker_low&&b.push(a.values.whisker_low),a.values.hasOwnProperty("outliers")&&null!==a.values.outliers&&(b=b.concat(a.values.outliers)),d3.min(b)})),y=d3.max(b.map(function(a){var b=[];return b.push(a.values.Q3),a.values.hasOwnProperty("whisker_high")&&null!==a.values.whisker_high&&b.push(a.values.whisker_high),a.values.hasOwnProperty("outliers")&&null!==a.values.outliers&&(b=b.concat(a.values.outliers)),d3.max(b)}));w=[x,y]}n.domain(d||w),n.range(f||[p,0]),g=g||m,h=h||n.copy().range([n(0),n(0)]);var z=r.selectAll("g.nv-wrap").data([b]);z.enter().append("g").attr("class","nvd3 nv-wrap");z.attr("transform","translate("+i.left+","+i.top+")");var A=z.selectAll(".nv-boxplot").data(function(a){return a}),B=A.enter().append("g").style("stroke-opacity",1e-6).style("fill-opacity",1e-6);A.attr("class","nv-boxplot").attr("transform",function(a,b,c){return"translate("+(m(o(a,b))+.05*m.rangeBand())+", 0)"}).classed("hover",function(a){return a.hover}),A.watchTransition(v,"nv-boxplot: boxplots").style("stroke-opacity",1).style("fill-opacity",.75).delay(function(a,c){return c*t/b.length}).attr("transform",function(a,b){return"translate("+(m(o(a,b))+.05*m.rangeBand())+", 0)"}),A.exit().remove(),B.each(function(a,b){var c=d3.select(this);["low","high"].forEach(function(d){a.values.hasOwnProperty("whisker_"+d)&&null!==a.values["whisker_"+d]&&(c.append("line").style("stroke",a.color?a.color:q(a,b)).attr("class","nv-boxplot-whisker nv-boxplot-"+d),c.append("line").style("stroke",a.color?a.color:q(a,b)).attr("class","nv-boxplot-tick nv-boxplot-"+d))})});var C=A.selectAll(".nv-boxplot-outlier").data(function(a){return a.values.hasOwnProperty("outliers")&&null!==a.values.outliers?a.values.outliers:[]});C.enter().append("circle").style("fill",function(a,b,c){return q(a,c)}).style("stroke",function(a,b,c){return q(a,c)}).on("mouseover",function(a,b,c){d3.select(this).classed("hover",!0),s.elementMouseover({series:{key:a,color:q(a,c)},e:d3.event})}).on("mouseout",function(a,b,c){d3.select(this).classed("hover",!1),s.elementMouseout({series:{key:a,color:q(a,c)},e:d3.event})}).on("mousemove",function(a,b){s.elementMousemove({e:d3.event})}),C.attr("class","nv-boxplot-outlier"),C.watchTransition(v,"nv-boxplot: nv-boxplot-outlier").attr("cx",.45*m.rangeBand()).attr("cy",function(a,b,c){return n(a)}).attr("r","3"),C.exit().remove();var D=function(){return null===u?.9*m.rangeBand():Math.min(75,.9*m.rangeBand())},E=function(){return.45*m.rangeBand()-D()/2},F=function(){return.45*m.rangeBand()+D()/2};["low","high"].forEach(function(a){var b="low"===a?"Q1":"Q3";A.select("line.nv-boxplot-whisker.nv-boxplot-"+a).watchTransition(v,"nv-boxplot: boxplots").attr("x1",.45*m.rangeBand()).attr("y1",function(b,c){return n(b.values["whisker_"+a])}).attr("x2",.45*m.rangeBand()).attr("y2",function(a,c){return n(a.values[b])}),A.select("line.nv-boxplot-tick.nv-boxplot-"+a).watchTransition(v,"nv-boxplot: boxplots").attr("x1",E).attr("y1",function(b,c){return n(b.values["whisker_"+a])}).attr("x2",F).attr("y2",function(b,c){return n(b.values["whisker_"+a])})}),["low","high"].forEach(function(a){B.selectAll(".nv-boxplot-"+a).on("mouseover",function(b,c,d){d3.select(this).classed("hover",!0),s.elementMouseover({series:{key:b.values["whisker_"+a],color:q(b,d)},e:d3.event})}).on("mouseout",function(b,c,d){d3.select(this).classed("hover",!1),s.elementMouseout({series:{key:b.values["whisker_"+a],color:q(b,d)},e:d3.event})}).on("mousemove",function(a,b){s.elementMousemove({e:d3.event})})}),B.append("rect").attr("class","nv-boxplot-box").on("mouseover",function(a,b){d3.select(this).classed("hover",!0),s.elementMouseover({key:a.label,value:a.label,series:[{key:"Q3",value:a.values.Q3,color:a.color||q(a,b)},{key:"Q2",value:a.values.Q2,color:a.color||q(a,b)},{key:"Q1",value:a.values.Q1,color:a.color||q(a,b)}],data:a,index:b,e:d3.event})}).on("mouseout",function(a,b){d3.select(this).classed("hover",!1),s.elementMouseout({key:a.label,value:a.label,series:[{key:"Q3",value:a.values.Q3,color:a.color||q(a,b)},{key:"Q2",value:a.values.Q2,color:a.color||q(a,b)},{key:"Q1",value:a.values.Q1,color:a.color||q(a,b)}],data:a,index:b,e:d3.event})}).on("mousemove",function(a,b){s.elementMousemove({e:d3.event})}),A.select("rect.nv-boxplot-box").watchTransition(v,"nv-boxplot: boxes").attr("y",function(a,b){return n(a.values.Q3)}).attr("width",D).attr("x",E).attr("height",function(a,b){return Math.abs(n(a.values.Q3)-n(a.values.Q1))||1}).style("fill",function(a,b){return a.color||q(a,b)}).style("stroke",function(a,b){return a.color||q(a,b)}),B.append("line").attr("class","nv-boxplot-median"),A.select("line.nv-boxplot-median").watchTransition(v,"nv-boxplot: boxplots line").attr("x1",E).attr("y1",function(a,b){return n(a.values.Q2)}).attr("x2",F).attr("y2",function(a,b){return n(a.values.Q2)}),g=m.copy(),h=n.copy()}),v.renderEnd("nv-boxplot immediate"),b}var c,d,e,f,g,h,i={top:0,right:0,bottom:0,left:0},j=960,k=500,l=Math.floor(1e4*Math.random()),m=d3.scale.ordinal(),n=d3.scale.linear(),o=function(a){return a.x},p=function(a){return a.y},q=a.utils.defaultColor(),r=null,s=d3.dispatch("elementMouseover","elementMouseout","elementMousemove","renderEnd"),t=250,u=null,v=a.utils.renderWatch(s,t);return b.dispatch=s,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return j},set:function(a){j=a}},height:{get:function(){return k},set:function(a){k=a}},maxBoxWidth:{get:function(){return u},set:function(a){u=a}},x:{get:function(){return o},set:function(a){o=a}},y:{get:function(){return p},set:function(a){p=a}},xScale:{get:function(){return m},set:function(a){m=a}},yScale:{get:function(){return n},set:function(a){n=a}},xDomain:{get:function(){return c},set:function(a){c=a}},yDomain:{get:function(){return d},set:function(a){d=a}},xRange:{get:function(){return e},set:function(a){e=a}},yRange:{get:function(){return f},set:function(a){f=a}},id:{get:function(){return l},set:function(a){l=a}},margin:{get:function(){return i},set:function(a){i.top=void 0!==a.top?a.top:i.top,i.right=void 0!==a.right?a.right:i.right,i.bottom=void 0!==a.bottom?a.bottom:i.bottom,i.left=void 0!==a.left?a.left:i.left}},color:{get:function(){return q},set:function(b){q=a.utils.getColor(b)}},duration:{get:function(){return t},set:function(a){t=a,v.reset(t)}}}),a.utils.initOptions(b),b},a.models.boxPlotChart=function(){"use strict";function b(k){return t.reset(),t.models(e),l&&t.models(f),m&&t.models(g),k.each(function(k){var p=d3.select(this);a.utils.initSVG(p);var t=(i||parseInt(p.style("width"))||960)-h.left-h.right,u=(j||parseInt(p.style("height"))||400)-h.top-h.bottom;if(b.update=function(){r.beforeUpdate(),p.transition().duration(s).call(b)},b.container=this,!(k&&k.length&&k.filter(function(a){return a.values.hasOwnProperty("Q1")&&a.values.hasOwnProperty("Q2")&&a.values.hasOwnProperty("Q3")}).length)){var v=p.selectAll(".nv-noData").data([q]);return v.enter().append("text").attr("class","nvd3 nv-noData").attr("dy","-.7em").style("text-anchor","middle"),v.attr("x",h.left+t/2).attr("y",h.top+u/2).text(function(a){return a}),b}p.selectAll(".nv-noData").remove(), +c=e.xScale(),d=e.yScale().clamp(!0);var w=p.selectAll("g.nv-wrap.nv-boxPlotWithAxes").data([k]),x=w.enter().append("g").attr("class","nvd3 nv-wrap nv-boxPlotWithAxes").append("g"),y=x.append("defs"),z=w.select("g");x.append("g").attr("class","nv-x nv-axis"),x.append("g").attr("class","nv-y nv-axis").append("g").attr("class","nv-zeroLine").append("line"),x.append("g").attr("class","nv-barsWrap"),z.attr("transform","translate("+h.left+","+h.top+")"),n&&z.select(".nv-y.nv-axis").attr("transform","translate("+t+",0)"),e.width(t).height(u);var A=z.select(".nv-barsWrap").datum(k.filter(function(a){return!a.disabled}));if(A.transition().call(e),y.append("clipPath").attr("id","nv-x-label-clip-"+e.id()).append("rect"),z.select("#nv-x-label-clip-"+e.id()+" rect").attr("width",c.rangeBand()*(o?2:1)).attr("height",16).attr("x",-c.rangeBand()/(o?1:2)),l){f.scale(c).ticks(a.utils.calcTicksX(t/100,k)).tickSize(-u,0),z.select(".nv-x.nv-axis").attr("transform","translate(0,"+d.range()[0]+")"),z.select(".nv-x.nv-axis").call(f);var B=z.select(".nv-x.nv-axis").selectAll("g");o&&B.selectAll("text").attr("transform",function(a,b,c){return"translate(0,"+(c%2==0?"5":"17")+")"})}m&&(g.scale(d).ticks(Math.floor(u/36)).tickSize(-t,0),z.select(".nv-y.nv-axis").call(g)),z.select(".nv-zeroLine line").attr("x1",0).attr("x2",t).attr("y1",d(0)).attr("y2",d(0))}),t.renderEnd("nv-boxplot chart immediate"),b}var c,d,e=a.models.boxPlot(),f=a.models.axis(),g=a.models.axis(),h={top:15,right:10,bottom:50,left:60},i=null,j=null,k=a.utils.getColor(),l=!0,m=!0,n=!1,o=!1,p=a.models.tooltip(),q="No Data Available.",r=d3.dispatch("tooltipShow","tooltipHide","beforeUpdate","renderEnd"),s=250;f.orient("bottom").showMaxMin(!1).tickFormat(function(a){return a}),g.orient(n?"right":"left").tickFormat(d3.format(",.1f")),p.duration(0);var t=a.utils.renderWatch(r,s);return e.dispatch.on("elementMouseover.tooltip",function(a){p.data(a).hidden(!1)}),e.dispatch.on("elementMouseout.tooltip",function(a){p.data(a).hidden(!0)}),e.dispatch.on("elementMousemove.tooltip",function(a){p.position({top:d3.event.pageY,left:d3.event.pageX})()}),b.dispatch=r,b.boxplot=e,b.xAxis=f,b.yAxis=g,b.tooltip=p,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return i},set:function(a){i=a}},height:{get:function(){return j},set:function(a){j=a}},staggerLabels:{get:function(){return o},set:function(a){o=a}},showXAxis:{get:function(){return l},set:function(a){l=a}},showYAxis:{get:function(){return m},set:function(a){m=a}},tooltips:{get:function(){return tooltips},set:function(a){tooltips=a}},tooltipContent:{get:function(){return p},set:function(a){p=a}},noData:{get:function(){return q},set:function(a){q=a}},margin:{get:function(){return h},set:function(a){h.top=void 0!==a.top?a.top:h.top,h.right=void 0!==a.right?a.right:h.right,h.bottom=void 0!==a.bottom?a.bottom:h.bottom,h.left=void 0!==a.left?a.left:h.left}},duration:{get:function(){return s},set:function(a){s=a,t.reset(s),e.duration(s),f.duration(s),g.duration(s)}},color:{get:function(){return k},set:function(b){k=a.utils.getColor(b),e.color(k)}},rightAlignYAxis:{get:function(){return n},set:function(a){n=a,g.orient(a?"right":"left")}}}),a.utils.inheritOptions(b,e),a.utils.initOptions(b),b},a.models.bullet=function(){"use strict";function b(d){return d.each(function(b,d){var p=m-c.left-c.right,s=n-c.top-c.bottom;o=d3.select(this),a.utils.initSVG(o);var t=f.call(this,b,d).slice().sort(d3.descending),u=g.call(this,b,d).slice().sort(d3.descending),v=h.call(this,b,d).slice().sort(d3.descending),w=i.call(this,b,d).slice(),x=j.call(this,b,d).slice(),y=k.call(this,b,d).slice(),z=d3.scale.linear().domain(d3.extent(d3.merge([l,t]))).range(e?[p,0]:[0,p]);this.__chart__||d3.scale.linear().domain([0,1/0]).range(z.range());this.__chart__=z;var A=d3.min(t),B=d3.max(t),C=t[1],D=o.selectAll("g.nv-wrap.nv-bullet").data([b]),E=D.enter().append("g").attr("class","nvd3 nv-wrap nv-bullet"),F=E.append("g"),G=D.select("g");F.append("rect").attr("class","nv-range nv-rangeMax"),F.append("rect").attr("class","nv-range nv-rangeAvg"),F.append("rect").attr("class","nv-range nv-rangeMin"),F.append("rect").attr("class","nv-measure"),D.attr("transform","translate("+c.left+","+c.top+")");var H=function(a){return Math.abs(z(a)-z(0))},I=function(a){return z(0>a?a:0)};G.select("rect.nv-rangeMax").attr("height",s).attr("width",H(B>0?B:A)).attr("x",I(B>0?B:A)).datum(B>0?B:A),G.select("rect.nv-rangeAvg").attr("height",s).attr("width",H(C)).attr("x",I(C)).datum(C),G.select("rect.nv-rangeMin").attr("height",s).attr("width",H(B)).attr("x",I(B)).attr("width",H(B>0?A:B)).attr("x",I(B>0?A:B)).datum(B>0?A:B),G.select("rect.nv-measure").style("fill",q).attr("height",s/3).attr("y",s/3).attr("width",0>v?z(0)-z(v[0]):z(v[0])-z(0)).attr("x",I(v)).on("mouseover",function(){r.elementMouseover({value:v[0],label:y[0]||"Current",color:d3.select(this).style("fill")})}).on("mousemove",function(){r.elementMousemove({value:v[0],label:y[0]||"Current",color:d3.select(this).style("fill")})}).on("mouseout",function(){r.elementMouseout({value:v[0],label:y[0]||"Current",color:d3.select(this).style("fill")})});var J=s/6,K=u.map(function(a,b){return{value:a,label:x[b]}});F.selectAll("path.nv-markerTriangle").data(K).enter().append("path").attr("class","nv-markerTriangle").attr("transform",function(a){return"translate("+z(a.value)+","+s/2+")"}).attr("d","M0,"+J+"L"+J+","+-J+" "+-J+","+-J+"Z").on("mouseover",function(a){r.elementMouseover({value:a.value,label:a.label||"Previous",color:d3.select(this).style("fill"),pos:[z(a.value),s/2]})}).on("mousemove",function(a){r.elementMousemove({value:a.value,label:a.label||"Previous",color:d3.select(this).style("fill")})}).on("mouseout",function(a,b){r.elementMouseout({value:a.value,label:a.label||"Previous",color:d3.select(this).style("fill")})}),D.selectAll(".nv-range").on("mouseover",function(a,b){var c=w[b]||(b?1==b?"Mean":"Minimum":"Maximum");r.elementMouseover({value:a,label:c,color:d3.select(this).style("fill")})}).on("mousemove",function(){r.elementMousemove({value:v[0],label:y[0]||"Previous",color:d3.select(this).style("fill")})}).on("mouseout",function(a,b){var c=w[b]||(b?1==b?"Mean":"Minimum":"Maximum");r.elementMouseout({value:a,label:c,color:d3.select(this).style("fill")})})}),b}var c={top:0,right:0,bottom:0,left:0},d="left",e=!1,f=function(a){return a.ranges},g=function(a){return a.markers?a.markers:[0]},h=function(a){return a.measures},i=function(a){return a.rangeLabels?a.rangeLabels:[]},j=function(a){return a.markerLabels?a.markerLabels:[]},k=function(a){return a.measureLabels?a.measureLabels:[]},l=[0],m=380,n=30,o=null,p=null,q=a.utils.getColor(["#1f77b4"]),r=d3.dispatch("elementMouseover","elementMouseout","elementMousemove");return b.dispatch=r,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{ranges:{get:function(){return f},set:function(a){f=a}},markers:{get:function(){return g},set:function(a){g=a}},measures:{get:function(){return h},set:function(a){h=a}},forceX:{get:function(){return l},set:function(a){l=a}},width:{get:function(){return m},set:function(a){m=a}},height:{get:function(){return n},set:function(a){n=a}},tickFormat:{get:function(){return p},set:function(a){p=a}},margin:{get:function(){return c},set:function(a){c.top=void 0!==a.top?a.top:c.top,c.right=void 0!==a.right?a.right:c.right,c.bottom=void 0!==a.bottom?a.bottom:c.bottom,c.left=void 0!==a.left?a.left:c.left}},orient:{get:function(){return d},set:function(a){d=a,e="right"==d||"bottom"==d}},color:{get:function(){return q},set:function(b){q=a.utils.getColor(b)}}}),a.utils.initOptions(b),b},a.models.bulletChart=function(){"use strict";function b(d){return d.each(function(e,o){var p=d3.select(this);a.utils.initSVG(p);var q=a.utils.availableWidth(k,p,g),r=l-g.top-g.bottom;if(b.update=function(){b(d)},b.container=this,!e||!h.call(this,e,o))return a.utils.noData(b,p),b;p.selectAll(".nv-noData").remove();var s=h.call(this,e,o).slice().sort(d3.descending),t=i.call(this,e,o).slice().sort(d3.descending),u=j.call(this,e,o).slice().sort(d3.descending),v=p.selectAll("g.nv-wrap.nv-bulletChart").data([e]),w=v.enter().append("g").attr("class","nvd3 nv-wrap nv-bulletChart"),x=w.append("g"),y=v.select("g");x.append("g").attr("class","nv-bulletWrap"),x.append("g").attr("class","nv-titles"),v.attr("transform","translate("+g.left+","+g.top+")");var z=d3.scale.linear().domain([0,Math.max(s[0],t[0],u[0])]).range(f?[q,0]:[0,q]),A=this.__chart__||d3.scale.linear().domain([0,1/0]).range(z.range());this.__chart__=z;var B=x.select(".nv-titles").append("g").attr("text-anchor","end").attr("transform","translate(-6,"+(l-g.top-g.bottom)/2+")");B.append("text").attr("class","nv-title").text(function(a){return a.title}),B.append("text").attr("class","nv-subtitle").attr("dy","1em").text(function(a){return a.subtitle}),c.width(q).height(r);var C=y.select(".nv-bulletWrap");d3.transition(C).call(c);var D=m||z.tickFormat(q/100),E=y.selectAll("g.nv-tick").data(z.ticks(n?n:q/50),function(a){return this.textContent||D(a)}),F=E.enter().append("g").attr("class","nv-tick").attr("transform",function(a){return"translate("+A(a)+",0)"}).style("opacity",1e-6);F.append("line").attr("y1",r).attr("y2",7*r/6),F.append("text").attr("text-anchor","middle").attr("dy","1em").attr("y",7*r/6).text(D);var G=d3.transition(E).attr("transform",function(a){return"translate("+z(a)+",0)"}).style("opacity",1);G.select("line").attr("y1",r).attr("y2",7*r/6),G.select("text").attr("y",7*r/6),d3.transition(E.exit()).attr("transform",function(a){return"translate("+z(a)+",0)"}).style("opacity",1e-6).remove()}),d3.timer.flush(),b}var c=a.models.bullet(),d=a.models.tooltip(),e="left",f=!1,g={top:5,right:40,bottom:20,left:120},h=function(a){return a.ranges},i=function(a){return a.markers?a.markers:[0]},j=function(a){return a.measures},k=null,l=55,m=null,n=null,o=null,p=d3.dispatch("tooltipShow","tooltipHide");return d.duration(0).headerEnabled(!1),c.dispatch.on("elementMouseover.tooltip",function(a){a.series={key:a.label,value:a.value,color:a.color},d.data(a).hidden(!1)}),c.dispatch.on("elementMouseout.tooltip",function(a){d.hidden(!0)}),c.dispatch.on("elementMousemove.tooltip",function(a){d.position({top:d3.event.pageY,left:d3.event.pageX})()}),b.bullet=c,b.dispatch=p,b.tooltip=d,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{ranges:{get:function(){return h},set:function(a){h=a}},markers:{get:function(){return i},set:function(a){i=a}},measures:{get:function(){return j},set:function(a){j=a}},width:{get:function(){return k},set:function(a){k=a}},height:{get:function(){return l},set:function(a){l=a}},tickFormat:{get:function(){return m},set:function(a){m=a}},ticks:{get:function(){return n},set:function(a){n=a}},noData:{get:function(){return o},set:function(a){o=a}},tooltips:{get:function(){return d.enabled()},set:function(b){a.deprecated("tooltips","use chart.tooltip.enabled() instead"),d.enabled(!!b)}},tooltipContent:{get:function(){return d.contentGenerator()},set:function(b){a.deprecated("tooltipContent","use chart.tooltip.contentGenerator() instead"),d.contentGenerator(b)}},margin:{get:function(){return g},set:function(a){g.top=void 0!==a.top?a.top:g.top,g.right=void 0!==a.right?a.right:g.right,g.bottom=void 0!==a.bottom?a.bottom:g.bottom,g.left=void 0!==a.left?a.left:g.left}},orient:{get:function(){return e},set:function(a){e=a,f="right"==e||"bottom"==e}}}),a.utils.inheritOptions(b,c),a.utils.initOptions(b),b},a.models.candlestickBar=function(){"use strict";function b(x){return x.each(function(b){c=d3.select(this);var x=a.utils.availableWidth(i,c,h),y=a.utils.availableHeight(j,c,h);a.utils.initSVG(c);var A=x/b[0].values.length*.45;l.domain(d||d3.extent(b[0].values.map(n).concat(t))),v?l.range(f||[.5*x/b[0].values.length,x*(b[0].values.length-.5)/b[0].values.length]):l.range(f||[5+A/2,x-A/2-5]),m.domain(e||[d3.min(b[0].values.map(s).concat(u)),d3.max(b[0].values.map(r).concat(u))]).range(g||[y,0]),l.domain()[0]===l.domain()[1]&&(l.domain()[0]?l.domain([l.domain()[0]-.01*l.domain()[0],l.domain()[1]+.01*l.domain()[1]]):l.domain([-1,1])),m.domain()[0]===m.domain()[1]&&(m.domain()[0]?m.domain([m.domain()[0]+.01*m.domain()[0],m.domain()[1]-.01*m.domain()[1]]):m.domain([-1,1]));var B=d3.select(this).selectAll("g.nv-wrap.nv-candlestickBar").data([b[0].values]),C=B.enter().append("g").attr("class","nvd3 nv-wrap nv-candlestickBar"),D=C.append("defs"),E=C.append("g"),F=B.select("g");E.append("g").attr("class","nv-ticks"),B.attr("transform","translate("+h.left+","+h.top+")"),c.on("click",function(a,b){z.chartClick({data:a,index:b,pos:d3.event,id:k})}),D.append("clipPath").attr("id","nv-chart-clip-path-"+k).append("rect"),B.select("#nv-chart-clip-path-"+k+" rect").attr("width",x).attr("height",y),F.attr("clip-path",w?"url(#nv-chart-clip-path-"+k+")":"");var G=B.select(".nv-ticks").selectAll(".nv-tick").data(function(a){return a});G.exit().remove();var H=G.enter().append("g").attr("class",function(a,b,c){return(p(a,b)>q(a,b)?"nv-tick negative":"nv-tick positive")+" nv-tick-"+c+"-"+b});H.append("line").attr("class","nv-candlestick-lines").attr("transform",function(a,b){return"translate("+l(n(a,b))+",0)"}).attr("x1",0).attr("y1",function(a,b){return m(r(a,b))}).attr("x2",0).attr("y2",function(a,b){return m(s(a,b))}),H.append("rect").attr("class","nv-candlestick-rects nv-bars").attr("transform",function(a,b){return"translate("+(l(n(a,b))-A/2)+","+(m(o(a,b))-(p(a,b)>q(a,b)?m(q(a,b))-m(p(a,b)):0))+")"}).attr("x",0).attr("y",0).attr("width",A).attr("height",function(a,b){var c=p(a,b),d=q(a,b);return c>d?m(d)-m(c):m(c)-m(d)});c.selectAll(".nv-candlestick-lines").transition().attr("transform",function(a,b){return"translate("+l(n(a,b))+",0)"}).attr("x1",0).attr("y1",function(a,b){return m(r(a,b))}).attr("x2",0).attr("y2",function(a,b){return m(s(a,b))}),c.selectAll(".nv-candlestick-rects").transition().attr("transform",function(a,b){return"translate("+(l(n(a,b))-A/2)+","+(m(o(a,b))-(p(a,b)>q(a,b)?m(q(a,b))-m(p(a,b)):0))+")"}).attr("x",0).attr("y",0).attr("width",A).attr("height",function(a,b){var c=p(a,b),d=q(a,b);return c>d?m(d)-m(c):m(c)-m(d)})}),b}var c,d,e,f,g,h={top:0,right:0,bottom:0,left:0},i=null,j=null,k=Math.floor(1e4*Math.random()),l=d3.scale.linear(),m=d3.scale.linear(),n=function(a){return a.x},o=function(a){return a.y},p=function(a){return a.open},q=function(a){return a.close},r=function(a){return a.high},s=function(a){return a.low},t=[],u=[],v=!1,w=!0,x=a.utils.defaultColor(),y=!1,z=d3.dispatch("tooltipShow","tooltipHide","stateChange","changeState","renderEnd","chartClick","elementClick","elementDblClick","elementMouseover","elementMouseout","elementMousemove");return b.highlightPoint=function(a,d){b.clearHighlights(),c.select(".nv-candlestickBar .nv-tick-0-"+a).classed("hover",d)},b.clearHighlights=function(){c.select(".nv-candlestickBar .nv-tick.hover").classed("hover",!1)},b.dispatch=z,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return i},set:function(a){i=a}},height:{get:function(){return j},set:function(a){j=a}},xScale:{get:function(){return l},set:function(a){l=a}},yScale:{get:function(){return m},set:function(a){m=a}},xDomain:{get:function(){return d},set:function(a){d=a}},yDomain:{get:function(){return e},set:function(a){e=a}},xRange:{get:function(){return f},set:function(a){f=a}},yRange:{get:function(){return g},set:function(a){g=a}},forceX:{get:function(){return t},set:function(a){t=a}},forceY:{get:function(){return u},set:function(a){u=a}},padData:{get:function(){return v},set:function(a){v=a}},clipEdge:{get:function(){return w},set:function(a){w=a}},id:{get:function(){return k},set:function(a){k=a}},interactive:{get:function(){return y},set:function(a){y=a}},x:{get:function(){return n},set:function(a){n=a}},y:{get:function(){return o},set:function(a){o=a}},open:{get:function(){return p()},set:function(a){p=a}},close:{get:function(){return q()},set:function(a){q=a}},high:{get:function(){return r},set:function(a){r=a}},low:{get:function(){return s},set:function(a){s=a}},margin:{get:function(){return h},set:function(a){h.top=void 0!=a.top?a.top:h.top,h.right=void 0!=a.right?a.right:h.right,h.bottom=void 0!=a.bottom?a.bottom:h.bottom,h.left=void 0!=a.left?a.left:h.left}},color:{get:function(){return x},set:function(b){x=a.utils.getColor(b)}}}),a.utils.initOptions(b),b},a.models.cumulativeLineChart=function(){"use strict";function b(l){return H.reset(),H.models(f),r&&H.models(g),s&&H.models(h),l.each(function(l){function A(a,c){d3.select(b.container).style("cursor","ew-resize")}function E(a,b){G.x=d3.event.x,G.i=Math.round(F.invert(G.x)),K()}function H(a,c){d3.select(b.container).style("cursor","auto"),y.index=G.i,C.stateChange(y)}function K(){ba.data([G]);var a=b.duration();b.duration(0),b.update(),b.duration(a)}var L=d3.select(this);a.utils.initSVG(L),L.classed("nv-chart-"+x,!0);var M=this,N=a.utils.availableWidth(o,L,m),O=a.utils.availableHeight(p,L,m);if(b.update=function(){0===D?L.call(b):L.transition().duration(D).call(b)},b.container=this,y.setter(J(l),b.update).getter(I(l)).update(),y.disabled=l.map(function(a){return!!a.disabled}),!z){var P;z={};for(P in y)y[P]instanceof Array?z[P]=y[P].slice(0):z[P]=y[P]}var Q=d3.behavior.drag().on("dragstart",A).on("drag",E).on("dragend",H);if(!(l&&l.length&&l.filter(function(a){return a.values.length}).length))return a.utils.noData(b,L),b;if(L.selectAll(".nv-noData").remove(),d=f.xScale(),e=f.yScale(),w)f.yDomain(null);else{var R=l.filter(function(a){return!a.disabled}).map(function(a,b){var c=d3.extent(a.values,f.y());return c[0]<-.95&&(c[0]=-.95),[(c[0]-c[1])/(1+c[1]),(c[1]-c[0])/(1+c[0])]}),S=[d3.min(R,function(a){return a[0]}),d3.max(R,function(a){return a[1]})];f.yDomain(S)}F.domain([0,l[0].values.length-1]).range([0,N]).clamp(!0);var l=c(G.i,l),T=v?"none":"all",U=L.selectAll("g.nv-wrap.nv-cumulativeLine").data([l]),V=U.enter().append("g").attr("class","nvd3 nv-wrap nv-cumulativeLine").append("g"),W=U.select("g");if(V.append("g").attr("class","nv-interactive"),V.append("g").attr("class","nv-x nv-axis").style("pointer-events","none"),V.append("g").attr("class","nv-y nv-axis"),V.append("g").attr("class","nv-background"),V.append("g").attr("class","nv-linesWrap").style("pointer-events",T),V.append("g").attr("class","nv-avgLinesWrap").style("pointer-events","none"),V.append("g").attr("class","nv-legendWrap"),V.append("g").attr("class","nv-controlsWrap"),q&&(i.width(N),W.select(".nv-legendWrap").datum(l).call(i),m.top!=i.height()&&(m.top=i.height(),O=a.utils.availableHeight(p,L,m)),W.select(".nv-legendWrap").attr("transform","translate(0,"+-m.top+")")),u){var X=[{key:"Re-scale y-axis",disabled:!w}];j.width(140).color(["#444","#444","#444"]).rightAlign(!1).margin({top:5,right:0,bottom:5,left:20}),W.select(".nv-controlsWrap").datum(X).attr("transform","translate(0,"+-m.top+")").call(j)}U.attr("transform","translate("+m.left+","+m.top+")"),t&&W.select(".nv-y.nv-axis").attr("transform","translate("+N+",0)");var Y=l.filter(function(a){return a.tempDisabled});U.select(".tempDisabled").remove(),Y.length&&U.append("text").attr("class","tempDisabled").attr("x",N/2).attr("y","-.71em").style("text-anchor","end").text(Y.map(function(a){return a.key}).join(", ")+" values cannot be calculated for this time period."),v&&(k.width(N).height(O).margin({left:m.left,top:m.top}).svgContainer(L).xScale(d),U.select(".nv-interactive").call(k)),V.select(".nv-background").append("rect"),W.select(".nv-background rect").attr("width",N).attr("height",O),f.y(function(a){return a.display.y}).width(N).height(O).color(l.map(function(a,b){return a.color||n(a,b)}).filter(function(a,b){return!l[b].disabled&&!l[b].tempDisabled}));var Z=W.select(".nv-linesWrap").datum(l.filter(function(a){return!a.disabled&&!a.tempDisabled}));Z.call(f),l.forEach(function(a,b){a.seriesIndex=b});var $=l.filter(function(a){return!a.disabled&&!!B(a)}),_=W.select(".nv-avgLinesWrap").selectAll("line").data($,function(a){return a.key}),aa=function(a){var b=e(B(a));return 0>b?0:b>O?O:b};_.enter().append("line").style("stroke-width",2).style("stroke-dasharray","10,10").style("stroke",function(a,b){return f.color()(a,a.seriesIndex)}).attr("x1",0).attr("x2",N).attr("y1",aa).attr("y2",aa),_.style("stroke-opacity",function(a){var b=e(B(a));return 0>b||b>O?0:1}).attr("x1",0).attr("x2",N).attr("y1",aa).attr("y2",aa),_.exit().remove();var ba=Z.selectAll(".nv-indexLine").data([G]);ba.enter().append("rect").attr("class","nv-indexLine").attr("width",3).attr("x",-2).attr("fill","red").attr("fill-opacity",.5).style("pointer-events","all").call(Q),ba.attr("transform",function(a){return"translate("+F(a.i)+",0)"}).attr("height",O),r&&(g.scale(d)._ticks(a.utils.calcTicksX(N/70,l)).tickSize(-O,0),W.select(".nv-x.nv-axis").attr("transform","translate(0,"+e.range()[0]+")"),W.select(".nv-x.nv-axis").call(g)),s&&(h.scale(e)._ticks(a.utils.calcTicksY(O/36,l)).tickSize(-N,0),W.select(".nv-y.nv-axis").call(h)),W.select(".nv-background rect").on("click",function(){G.x=d3.mouse(this)[0],G.i=Math.round(F.invert(G.x)),y.index=G.i,C.stateChange(y),K()}),f.dispatch.on("elementClick",function(a){G.i=a.pointIndex,G.x=F(G.i),y.index=G.i,C.stateChange(y),K()}),j.dispatch.on("legendClick",function(a,c){a.disabled=!a.disabled,w=!a.disabled,y.rescaleY=w,C.stateChange(y),b.update()}),i.dispatch.on("stateChange",function(a){for(var c in a)y[c]=a[c];C.stateChange(y),b.update()}),k.dispatch.on("elementMousemove",function(c){f.clearHighlights();var d,e,i,j=[];if(l.filter(function(a,b){return a.seriesIndex=b,!a.disabled}).forEach(function(g,h){e=a.interactiveBisect(g.values,c.pointXValue,b.x()),f.highlightPoint(h,e,!0);var k=g.values[e];"undefined"!=typeof k&&("undefined"==typeof d&&(d=k),"undefined"==typeof i&&(i=b.xScale()(b.x()(k,e))),j.push({key:g.key,value:b.y()(k,e),color:n(g,g.seriesIndex)}))}),j.length>2){var o=b.yScale().invert(c.mouseY),p=Math.abs(b.yScale().domain()[0]-b.yScale().domain()[1]),q=.03*p,r=a.nearestValueIndex(j.map(function(a){return a.value}),o,q);null!==r&&(j[r].highlight=!0)}var s=g.tickFormat()(b.x()(d,e),e);k.tooltip.position({left:i+m.left,top:c.mouseY+m.top}).chartContainer(M.parentNode).valueFormatter(function(a,b){return h.tickFormat()(a)}).data({value:s,series:j})(),k.renderGuideLine(i)}),k.dispatch.on("elementMouseout",function(a){f.clearHighlights()}),C.on("changeState",function(a){"undefined"!=typeof a.disabled&&(l.forEach(function(b,c){b.disabled=a.disabled[c]}),y.disabled=a.disabled),"undefined"!=typeof a.index&&(G.i=a.index,G.x=F(G.i),y.index=a.index,ba.data([G])),"undefined"!=typeof a.rescaleY&&(w=a.rescaleY),b.update()})}),H.renderEnd("cumulativeLineChart immediate"),b}function c(a,b){return K||(K=f.y()),b.map(function(b,c){if(!b.values)return b;var d=b.values[a];if(null==d)return b;var e=K(d,a);return-.95>e&&!E?(b.tempDisabled=!0,b):(b.tempDisabled=!1,b.values=b.values.map(function(a,b){return a.display={y:(K(a,b)-e)/(1+e)},a}),b)})}var d,e,f=a.models.line(),g=a.models.axis(),h=a.models.axis(),i=a.models.legend(),j=a.models.legend(),k=a.interactiveGuideline(),l=a.models.tooltip(),m={top:30,right:30,bottom:50,left:60},n=a.utils.defaultColor(),o=null,p=null,q=!0,r=!0,s=!0,t=!1,u=!0,v=!1,w=!0,x=f.id(),y=a.utils.state(),z=null,A=null,B=function(a){return a.average},C=d3.dispatch("stateChange","changeState","renderEnd"),D=250,E=!1;y.index=0,y.rescaleY=w,g.orient("bottom").tickPadding(7),h.orient(t?"right":"left"),l.valueFormatter(function(a,b){return h.tickFormat()(a,b)}).headerFormatter(function(a,b){return g.tickFormat()(a,b)}),j.updateState(!1);var F=d3.scale.linear(),G={i:0,x:0},H=a.utils.renderWatch(C,D),I=function(a){return function(){return{active:a.map(function(a){return!a.disabled}),index:G.i,rescaleY:w}}},J=function(a){return function(b){void 0!==b.index&&(G.i=b.index),void 0!==b.rescaleY&&(w=b.rescaleY),void 0!==b.active&&a.forEach(function(a,c){a.disabled=!b.active[c]})}};f.dispatch.on("elementMouseover.tooltip",function(a){var c={x:b.x()(a.point),y:b.y()(a.point),color:a.point.color};a.point=c,l.data(a).position(a.pos).hidden(!1)}),f.dispatch.on("elementMouseout.tooltip",function(a){l.hidden(!0)});var K=null;return b.dispatch=C,b.lines=f,b.legend=i,b.controls=j,b.xAxis=g,b.yAxis=h,b.interactiveLayer=k,b.state=y,b.tooltip=l,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return o},set:function(a){o=a}},height:{get:function(){return p},set:function(a){p=a}},rescaleY:{get:function(){return w},set:function(a){w=a}},showControls:{get:function(){return u},set:function(a){u=a}},showLegend:{get:function(){return q},set:function(a){q=a}},average:{get:function(){return B},set:function(a){B=a}},defaultState:{get:function(){return z},set:function(a){z=a}},noData:{get:function(){return A},set:function(a){A=a}},showXAxis:{get:function(){return r},set:function(a){r=a}},showYAxis:{get:function(){return s},set:function(a){s=a}},noErrorCheck:{get:function(){return E},set:function(a){E=a}},tooltips:{get:function(){return l.enabled()},set:function(b){a.deprecated("tooltips","use chart.tooltip.enabled() instead"),l.enabled(!!b)}},tooltipContent:{get:function(){return l.contentGenerator()},set:function(b){a.deprecated("tooltipContent","use chart.tooltip.contentGenerator() instead"),l.contentGenerator(b)}},margin:{get:function(){return m},set:function(a){m.top=void 0!==a.top?a.top:m.top,m.right=void 0!==a.right?a.right:m.right,m.bottom=void 0!==a.bottom?a.bottom:m.bottom,m.left=void 0!==a.left?a.left:m.left}},color:{get:function(){return n},set:function(b){n=a.utils.getColor(b),i.color(n)}},useInteractiveGuideline:{get:function(){return v},set:function(a){v=a,a===!0&&(b.interactive(!1),b.useVoronoi(!1))}},rightAlignYAxis:{get:function(){return t},set:function(a){t=a,h.orient(a?"right":"left")}},duration:{get:function(){return D},set:function(a){D=a,f.duration(D),g.duration(D),h.duration(D),H.reset(D)}}}),a.utils.inheritOptions(b,f),a.utils.initOptions(b),b},a.models.discreteBar=function(){"use strict";function b(m){return y.reset(),m.each(function(b){var m=k-j.left-j.right,x=l-j.top-j.bottom;c=d3.select(this),a.utils.initSVG(c),b.forEach(function(a,b){a.values.forEach(function(a){a.series=b})});var z=d&&e?[]:b.map(function(a){return a.values.map(function(a,b){return{x:p(a,b),y:q(a,b),y0:a.y0}})});n.domain(d||d3.merge(z).map(function(a){return a.x})).rangeBands(f||[0,m],.1),o.domain(e||d3.extent(d3.merge(z).map(function(a){return a.y}).concat(r))),t?o.range(g||[x-(o.domain()[0]<0?12:0),o.domain()[1]>0?12:0]):o.range(g||[x,0]),h=h||n,i=i||o.copy().range([o(0),o(0)]);var A=c.selectAll("g.nv-wrap.nv-discretebar").data([b]),B=A.enter().append("g").attr("class","nvd3 nv-wrap nv-discretebar"),C=B.append("g");A.select("g");C.append("g").attr("class","nv-groups"),A.attr("transform","translate("+j.left+","+j.top+")");var D=A.select(".nv-groups").selectAll(".nv-group").data(function(a){return a},function(a){return a.key});D.enter().append("g").style("stroke-opacity",1e-6).style("fill-opacity",1e-6),D.exit().watchTransition(y,"discreteBar: exit groups").style("stroke-opacity",1e-6).style("fill-opacity",1e-6).remove(),D.attr("class",function(a,b){return"nv-group nv-series-"+b}).classed("hover",function(a){return a.hover}),D.watchTransition(y,"discreteBar: groups").style("stroke-opacity",1).style("fill-opacity",.75);var E=D.selectAll("g.nv-bar").data(function(a){return a.values});E.exit().remove();var F=E.enter().append("g").attr("transform",function(a,b,c){return"translate("+(n(p(a,b))+.05*n.rangeBand())+", "+o(0)+")"}).on("mouseover",function(a,b){d3.select(this).classed("hover",!0),v.elementMouseover({data:a,index:b,color:d3.select(this).style("fill")})}).on("mouseout",function(a,b){d3.select(this).classed("hover",!1),v.elementMouseout({data:a,index:b,color:d3.select(this).style("fill")})}).on("mousemove",function(a,b){v.elementMousemove({data:a,index:b,color:d3.select(this).style("fill")})}).on("click",function(a,b){v.elementClick({data:a,index:b,color:d3.select(this).style("fill")}),d3.event.stopPropagation()}).on("dblclick",function(a,b){v.elementDblClick({data:a,index:b,color:d3.select(this).style("fill")}),d3.event.stopPropagation()});F.append("rect").attr("height",0).attr("width",.9*n.rangeBand()/b.length),t?(F.append("text").attr("text-anchor","middle"),E.select("text").text(function(a,b){return u(q(a,b))}).watchTransition(y,"discreteBar: bars text").attr("x",.9*n.rangeBand()/2).attr("y",function(a,b){return q(a,b)<0?o(q(a,b))-o(0)+12:-4})):E.selectAll("text").remove(),E.attr("class",function(a,b){return q(a,b)<0?"nv-bar negative":"nv-bar positive"}).style("fill",function(a,b){return a.color||s(a,b)}).style("stroke",function(a,b){return a.color||s(a,b)}).select("rect").attr("class",w).watchTransition(y,"discreteBar: bars rect").attr("width",.9*n.rangeBand()/b.length),E.watchTransition(y,"discreteBar: bars").attr("transform",function(a,b){var c=n(p(a,b))+.05*n.rangeBand(),d=q(a,b)<0?o(0):o(0)-o(q(a,b))<1?o(0)-1:o(q(a,b));return"translate("+c+", "+d+")"}).select("rect").attr("height",function(a,b){return Math.max(Math.abs(o(q(a,b))-o(e&&e[0]||0))||1)}),h=n.copy(),i=o.copy()}),y.renderEnd("discreteBar immediate"),b}var c,d,e,f,g,h,i,j={top:0,right:0,bottom:0,left:0},k=960,l=500,m=Math.floor(1e4*Math.random()),n=d3.scale.ordinal(),o=d3.scale.linear(),p=function(a){return a.x},q=function(a){return a.y},r=[0],s=a.utils.defaultColor(),t=!1,u=d3.format(",.2f"),v=d3.dispatch("chartClick","elementClick","elementDblClick","elementMouseover","elementMouseout","elementMousemove","renderEnd"),w="discreteBar",x=250,y=a.utils.renderWatch(v,x);return b.dispatch=v,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return k},set:function(a){k=a}},height:{get:function(){return l},set:function(a){l=a}},forceY:{get:function(){return r},set:function(a){r=a}},showValues:{get:function(){return t},set:function(a){t=a}},x:{get:function(){return p},set:function(a){p=a}},y:{get:function(){return q},set:function(a){q=a}},xScale:{get:function(){return n},set:function(a){n=a}},yScale:{get:function(){return o},set:function(a){o=a}},xDomain:{get:function(){return d},set:function(a){d=a}},yDomain:{get:function(){return e},set:function(a){e=a}},xRange:{get:function(){return f},set:function(a){f=a}},yRange:{get:function(){return g},set:function(a){g=a}},valueFormat:{get:function(){return u},set:function(a){u=a}},id:{get:function(){return m},set:function(a){m=a}},rectClass:{get:function(){return w},set:function(a){w=a}},margin:{get:function(){return j},set:function(a){j.top=void 0!==a.top?a.top:j.top,j.right=void 0!==a.right?a.right:j.right,j.bottom=void 0!==a.bottom?a.bottom:j.bottom,j.left=void 0!==a.left?a.left:j.left}},color:{get:function(){return s},set:function(b){s=a.utils.getColor(b)}},duration:{get:function(){return x},set:function(a){x=a,y.reset(x)}}}),a.utils.initOptions(b),b},a.models.discreteBarChart=function(){"use strict";function b(h){return t.reset(),t.models(e),m&&t.models(f),n&&t.models(g),h.each(function(h){var l=d3.select(this);a.utils.initSVG(l);var q=a.utils.availableWidth(j,l,i),t=a.utils.availableHeight(k,l,i);if(b.update=function(){r.beforeUpdate(),l.transition().duration(s).call(b)},b.container=this,!(h&&h.length&&h.filter(function(a){return a.values.length}).length))return a.utils.noData(b,l),b;l.selectAll(".nv-noData").remove(),c=e.xScale(),d=e.yScale().clamp(!0);var u=l.selectAll("g.nv-wrap.nv-discreteBarWithAxes").data([h]),v=u.enter().append("g").attr("class","nvd3 nv-wrap nv-discreteBarWithAxes").append("g"),w=v.append("defs"),x=u.select("g");v.append("g").attr("class","nv-x nv-axis"),v.append("g").attr("class","nv-y nv-axis").append("g").attr("class","nv-zeroLine").append("line"),v.append("g").attr("class","nv-barsWrap"),x.attr("transform","translate("+i.left+","+i.top+")"),o&&x.select(".nv-y.nv-axis").attr("transform","translate("+q+",0)"),e.width(q).height(t);var y=x.select(".nv-barsWrap").datum(h.filter(function(a){return!a.disabled}));if(y.transition().call(e),w.append("clipPath").attr("id","nv-x-label-clip-"+e.id()).append("rect"), +x.select("#nv-x-label-clip-"+e.id()+" rect").attr("width",c.rangeBand()*(p?2:1)).attr("height",16).attr("x",-c.rangeBand()/(p?1:2)),m){f.scale(c)._ticks(a.utils.calcTicksX(q/100,h)).tickSize(-t,0),x.select(".nv-x.nv-axis").attr("transform","translate(0,"+(d.range()[0]+(e.showValues()&&d.domain()[0]<0?16:0))+")"),x.select(".nv-x.nv-axis").call(f);var z=x.select(".nv-x.nv-axis").selectAll("g");p&&z.selectAll("text").attr("transform",function(a,b,c){return"translate(0,"+(c%2==0?"5":"17")+")"})}n&&(g.scale(d)._ticks(a.utils.calcTicksY(t/36,h)).tickSize(-q,0),x.select(".nv-y.nv-axis").call(g)),x.select(".nv-zeroLine line").attr("x1",0).attr("x2",q).attr("y1",d(0)).attr("y2",d(0))}),t.renderEnd("discreteBar chart immediate"),b}var c,d,e=a.models.discreteBar(),f=a.models.axis(),g=a.models.axis(),h=a.models.tooltip(),i={top:15,right:10,bottom:50,left:60},j=null,k=null,l=a.utils.getColor(),m=!0,n=!0,o=!1,p=!1,q=null,r=d3.dispatch("beforeUpdate","renderEnd"),s=250;f.orient("bottom").showMaxMin(!1).tickFormat(function(a){return a}),g.orient(o?"right":"left").tickFormat(d3.format(",.1f")),h.duration(0).headerEnabled(!1).valueFormatter(function(a,b){return g.tickFormat()(a,b)}).keyFormatter(function(a,b){return f.tickFormat()(a,b)});var t=a.utils.renderWatch(r,s);return e.dispatch.on("elementMouseover.tooltip",function(a){a.series={key:b.x()(a.data),value:b.y()(a.data),color:a.color},h.data(a).hidden(!1)}),e.dispatch.on("elementMouseout.tooltip",function(a){h.hidden(!0)}),e.dispatch.on("elementMousemove.tooltip",function(a){h.position({top:d3.event.pageY,left:d3.event.pageX})()}),b.dispatch=r,b.discretebar=e,b.xAxis=f,b.yAxis=g,b.tooltip=h,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return j},set:function(a){j=a}},height:{get:function(){return k},set:function(a){k=a}},staggerLabels:{get:function(){return p},set:function(a){p=a}},showXAxis:{get:function(){return m},set:function(a){m=a}},showYAxis:{get:function(){return n},set:function(a){n=a}},noData:{get:function(){return q},set:function(a){q=a}},tooltips:{get:function(){return h.enabled()},set:function(b){a.deprecated("tooltips","use chart.tooltip.enabled() instead"),h.enabled(!!b)}},tooltipContent:{get:function(){return h.contentGenerator()},set:function(b){a.deprecated("tooltipContent","use chart.tooltip.contentGenerator() instead"),h.contentGenerator(b)}},margin:{get:function(){return i},set:function(a){i.top=void 0!==a.top?a.top:i.top,i.right=void 0!==a.right?a.right:i.right,i.bottom=void 0!==a.bottom?a.bottom:i.bottom,i.left=void 0!==a.left?a.left:i.left}},duration:{get:function(){return s},set:function(a){s=a,t.reset(s),e.duration(s),f.duration(s),g.duration(s)}},color:{get:function(){return l},set:function(b){l=a.utils.getColor(b),e.color(l)}},rightAlignYAxis:{get:function(){return o},set:function(a){o=a,g.orient(a?"right":"left")}}}),a.utils.inheritOptions(b,e),a.utils.initOptions(b),b},a.models.distribution=function(){"use strict";function b(k){return m.reset(),k.each(function(b){var k=(e-("x"===g?d.left+d.right:d.top+d.bottom),"x"==g?"y":"x"),l=d3.select(this);a.utils.initSVG(l),c=c||j;var n=l.selectAll("g.nv-distribution").data([b]),o=n.enter().append("g").attr("class","nvd3 nv-distribution"),p=(o.append("g"),n.select("g"));n.attr("transform","translate("+d.left+","+d.top+")");var q=p.selectAll("g.nv-dist").data(function(a){return a},function(a){return a.key});q.enter().append("g"),q.attr("class",function(a,b){return"nv-dist nv-series-"+b}).style("stroke",function(a,b){return i(a,b)});var r=q.selectAll("line.nv-dist"+g).data(function(a){return a.values});r.enter().append("line").attr(g+"1",function(a,b){return c(h(a,b))}).attr(g+"2",function(a,b){return c(h(a,b))}),m.transition(q.exit().selectAll("line.nv-dist"+g),"dist exit").attr(g+"1",function(a,b){return j(h(a,b))}).attr(g+"2",function(a,b){return j(h(a,b))}).style("stroke-opacity",0).remove(),r.attr("class",function(a,b){return"nv-dist"+g+" nv-dist"+g+"-"+b}).attr(k+"1",0).attr(k+"2",f),m.transition(r,"dist").attr(g+"1",function(a,b){return j(h(a,b))}).attr(g+"2",function(a,b){return j(h(a,b))}),c=j.copy()}),m.renderEnd("distribution immediate"),b}var c,d={top:0,right:0,bottom:0,left:0},e=400,f=8,g="x",h=function(a){return a[g]},i=a.utils.defaultColor(),j=d3.scale.linear(),k=250,l=d3.dispatch("renderEnd"),m=a.utils.renderWatch(l,k);return b.options=a.utils.optionsFunc.bind(b),b.dispatch=l,b.margin=function(a){return arguments.length?(d.top="undefined"!=typeof a.top?a.top:d.top,d.right="undefined"!=typeof a.right?a.right:d.right,d.bottom="undefined"!=typeof a.bottom?a.bottom:d.bottom,d.left="undefined"!=typeof a.left?a.left:d.left,b):d},b.width=function(a){return arguments.length?(e=a,b):e},b.axis=function(a){return arguments.length?(g=a,b):g},b.size=function(a){return arguments.length?(f=a,b):f},b.getData=function(a){return arguments.length?(h=d3.functor(a),b):h},b.scale=function(a){return arguments.length?(j=a,b):j},b.color=function(c){return arguments.length?(i=a.utils.getColor(c),b):i},b.duration=function(a){return arguments.length?(k=a,m.reset(k),b):k},b},a.models.furiousLegend=function(){"use strict";function b(p){function q(a,b){return"furious"!=o?"#000":m?a.disengaged?g(a,b):"#fff":m?void 0:a.disabled?g(a,b):"#fff"}function r(a,b){return m&&"furious"==o?a.disengaged?"#fff":g(a,b):a.disabled?"#fff":g(a,b)}return p.each(function(b){var p=d-c.left-c.right,s=d3.select(this);a.utils.initSVG(s);var t=s.selectAll("g.nv-legend").data([b]),u=(t.enter().append("g").attr("class","nvd3 nv-legend").append("g"),t.select("g"));t.attr("transform","translate("+c.left+","+c.top+")");var v,w=u.selectAll(".nv-series").data(function(a){return"furious"!=o?a:a.filter(function(a){return m?!0:!a.disengaged})}),x=w.enter().append("g").attr("class","nv-series");if("classic"==o)x.append("circle").style("stroke-width",2).attr("class","nv-legend-symbol").attr("r",5),v=w.select("circle");else if("furious"==o){x.append("rect").style("stroke-width",2).attr("class","nv-legend-symbol").attr("rx",3).attr("ry",3),v=w.select("rect"),x.append("g").attr("class","nv-check-box").property("innerHTML",'<path d="M0.5,5 L22.5,5 L22.5,26.5 L0.5,26.5 L0.5,5 Z" class="nv-box"></path><path d="M5.5,12.8618467 L11.9185089,19.2803556 L31,0.198864511" class="nv-check"></path>').attr("transform","translate(-10,-8)scale(0.5)");var y=w.select(".nv-check-box");y.each(function(a,b){d3.select(this).selectAll("path").attr("stroke",q(a,b))})}x.append("text").attr("text-anchor","start").attr("class","nv-legend-text").attr("dy",".32em").attr("dx","8");var z=w.select("text.nv-legend-text");w.on("mouseover",function(a,b){n.legendMouseover(a,b)}).on("mouseout",function(a,b){n.legendMouseout(a,b)}).on("click",function(a,b){n.legendClick(a,b);var c=w.data();if(k){if("classic"==o)l?(c.forEach(function(a){a.disabled=!0}),a.disabled=!1):(a.disabled=!a.disabled,c.every(function(a){return a.disabled})&&c.forEach(function(a){a.disabled=!1}));else if("furious"==o)if(m)a.disengaged=!a.disengaged,a.userDisabled=void 0==a.userDisabled?!!a.disabled:a.userDisabled,a.disabled=a.disengaged||a.userDisabled;else if(!m){a.disabled=!a.disabled,a.userDisabled=a.disabled;var d=c.filter(function(a){return!a.disengaged});d.every(function(a){return a.userDisabled})&&c.forEach(function(a){a.disabled=a.userDisabled=!1})}n.stateChange({disabled:c.map(function(a){return!!a.disabled}),disengaged:c.map(function(a){return!!a.disengaged})})}}).on("dblclick",function(a,b){if(("furious"!=o||!m)&&(n.legendDblclick(a,b),k)){var c=w.data();c.forEach(function(a){a.disabled=!0,"furious"==o&&(a.userDisabled=a.disabled)}),a.disabled=!1,"furious"==o&&(a.userDisabled=a.disabled),n.stateChange({disabled:c.map(function(a){return!!a.disabled})})}}),w.classed("nv-disabled",function(a){return a.userDisabled}),w.exit().remove(),z.attr("fill",q).text(f);var A;switch(o){case"furious":A=23;break;case"classic":A=20}if(h){var B=[];w.each(function(b,c){var d,e=d3.select(this).select("text");try{if(d=e.node().getComputedTextLength(),0>=d)throw Error()}catch(f){d=a.utils.calcApproxTextWidth(e)}B.push(d+i)});for(var C=0,D=0,E=[];p>D&&C<B.length;)E[C]=B[C],D+=B[C++];for(0===C&&(C=1);D>p&&C>1;){E=[],C--;for(var F=0;F<B.length;F++)B[F]>(E[F%C]||0)&&(E[F%C]=B[F]);D=E.reduce(function(a,b,c,d){return a+b})}for(var G=[],H=0,I=0;C>H;H++)G[H]=I,I+=E[H];w.attr("transform",function(a,b){return"translate("+G[b%C]+","+(5+Math.floor(b/C)*A)+")"}),j?u.attr("transform","translate("+(d-c.right-D)+","+c.top+")"):u.attr("transform","translate(0,"+c.top+")"),e=c.top+c.bottom+Math.ceil(B.length/C)*A}else{var J,K=5,L=5,M=0;w.attr("transform",function(a,b){var e=d3.select(this).select("text").node().getComputedTextLength()+i;return J=L,d<c.left+c.right+J+e&&(L=J=5,K+=A),L+=e,L>M&&(M=L),"translate("+J+","+K+")"}),u.attr("transform","translate("+(d-c.right-M)+","+c.top+")"),e=c.top+c.bottom+K+15}"furious"==o&&v.attr("width",function(a,b){return z[0][b].getComputedTextLength()+27}).attr("height",18).attr("y",-9).attr("x",-15),v.style("fill",r).style("stroke",function(a,b){return a.color||g(a,b)})}),b}var c={top:5,right:0,bottom:5,left:0},d=400,e=20,f=function(a){return a.key},g=a.utils.getColor(),h=!0,i=28,j=!0,k=!0,l=!1,m=!1,n=d3.dispatch("legendClick","legendDblclick","legendMouseover","legendMouseout","stateChange"),o="classic";return b.dispatch=n,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return d},set:function(a){d=a}},height:{get:function(){return e},set:function(a){e=a}},key:{get:function(){return f},set:function(a){f=a}},align:{get:function(){return h},set:function(a){h=a}},rightAlign:{get:function(){return j},set:function(a){j=a}},padding:{get:function(){return i},set:function(a){i=a}},updateState:{get:function(){return k},set:function(a){k=a}},radioButtonMode:{get:function(){return l},set:function(a){l=a}},expanded:{get:function(){return m},set:function(a){m=a}},vers:{get:function(){return o},set:function(a){o=a}},margin:{get:function(){return c},set:function(a){c.top=void 0!==a.top?a.top:c.top,c.right=void 0!==a.right?a.right:c.right,c.bottom=void 0!==a.bottom?a.bottom:c.bottom,c.left=void 0!==a.left?a.left:c.left}},color:{get:function(){return g},set:function(b){g=a.utils.getColor(b)}}}),a.utils.initOptions(b),b},a.models.historicalBar=function(){"use strict";function b(x){return x.each(function(b){w.reset(),k=d3.select(this);var x=a.utils.availableWidth(h,k,g),y=a.utils.availableHeight(i,k,g);a.utils.initSVG(k),l.domain(c||d3.extent(b[0].values.map(n).concat(p))),r?l.range(e||[.5*x/b[0].values.length,x*(b[0].values.length-.5)/b[0].values.length]):l.range(e||[0,x]),m.domain(d||d3.extent(b[0].values.map(o).concat(q))).range(f||[y,0]),l.domain()[0]===l.domain()[1]&&(l.domain()[0]?l.domain([l.domain()[0]-.01*l.domain()[0],l.domain()[1]+.01*l.domain()[1]]):l.domain([-1,1])),m.domain()[0]===m.domain()[1]&&(m.domain()[0]?m.domain([m.domain()[0]+.01*m.domain()[0],m.domain()[1]-.01*m.domain()[1]]):m.domain([-1,1]));var z=k.selectAll("g.nv-wrap.nv-historicalBar-"+j).data([b[0].values]),A=z.enter().append("g").attr("class","nvd3 nv-wrap nv-historicalBar-"+j),B=A.append("defs"),C=A.append("g"),D=z.select("g");C.append("g").attr("class","nv-bars"),z.attr("transform","translate("+g.left+","+g.top+")"),k.on("click",function(a,b){u.chartClick({data:a,index:b,pos:d3.event,id:j})}),B.append("clipPath").attr("id","nv-chart-clip-path-"+j).append("rect"),z.select("#nv-chart-clip-path-"+j+" rect").attr("width",x).attr("height",y),D.attr("clip-path",s?"url(#nv-chart-clip-path-"+j+")":"");var E=z.select(".nv-bars").selectAll(".nv-bar").data(function(a){return a},function(a,b){return n(a,b)});E.exit().remove(),E.enter().append("rect").attr("x",0).attr("y",function(b,c){return a.utils.NaNtoZero(m(Math.max(0,o(b,c))))}).attr("height",function(b,c){return a.utils.NaNtoZero(Math.abs(m(o(b,c))-m(0)))}).attr("transform",function(a,c){return"translate("+(l(n(a,c))-x/b[0].values.length*.45)+",0)"}).on("mouseover",function(a,b){v&&(d3.select(this).classed("hover",!0),u.elementMouseover({data:a,index:b,color:d3.select(this).style("fill")}))}).on("mouseout",function(a,b){v&&(d3.select(this).classed("hover",!1),u.elementMouseout({data:a,index:b,color:d3.select(this).style("fill")}))}).on("mousemove",function(a,b){v&&u.elementMousemove({data:a,index:b,color:d3.select(this).style("fill")})}).on("click",function(a,b){v&&(u.elementClick({data:a,index:b,color:d3.select(this).style("fill")}),d3.event.stopPropagation())}).on("dblclick",function(a,b){v&&(u.elementDblClick({data:a,index:b,color:d3.select(this).style("fill")}),d3.event.stopPropagation())}),E.attr("fill",function(a,b){return t(a,b)}).attr("class",function(a,b,c){return(o(a,b)<0?"nv-bar negative":"nv-bar positive")+" nv-bar-"+c+"-"+b}).watchTransition(w,"bars").attr("transform",function(a,c){return"translate("+(l(n(a,c))-x/b[0].values.length*.45)+",0)"}).attr("width",x/b[0].values.length*.9),E.watchTransition(w,"bars").attr("y",function(b,c){var d=o(b,c)<0?m(0):m(0)-m(o(b,c))<1?m(0)-1:m(o(b,c));return a.utils.NaNtoZero(d)}).attr("height",function(b,c){return a.utils.NaNtoZero(Math.max(Math.abs(m(o(b,c))-m(0)),1))})}),w.renderEnd("historicalBar immediate"),b}var c,d,e,f,g={top:0,right:0,bottom:0,left:0},h=null,i=null,j=Math.floor(1e4*Math.random()),k=null,l=d3.scale.linear(),m=d3.scale.linear(),n=function(a){return a.x},o=function(a){return a.y},p=[],q=[0],r=!1,s=!0,t=a.utils.defaultColor(),u=d3.dispatch("chartClick","elementClick","elementDblClick","elementMouseover","elementMouseout","elementMousemove","renderEnd"),v=!0,w=a.utils.renderWatch(u,0);return b.highlightPoint=function(a,b){k.select(".nv-bars .nv-bar-0-"+a).classed("hover",b)},b.clearHighlights=function(){k.select(".nv-bars .nv-bar.hover").classed("hover",!1)},b.dispatch=u,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return h},set:function(a){h=a}},height:{get:function(){return i},set:function(a){i=a}},forceX:{get:function(){return p},set:function(a){p=a}},forceY:{get:function(){return q},set:function(a){q=a}},padData:{get:function(){return r},set:function(a){r=a}},x:{get:function(){return n},set:function(a){n=a}},y:{get:function(){return o},set:function(a){o=a}},xScale:{get:function(){return l},set:function(a){l=a}},yScale:{get:function(){return m},set:function(a){m=a}},xDomain:{get:function(){return c},set:function(a){c=a}},yDomain:{get:function(){return d},set:function(a){d=a}},xRange:{get:function(){return e},set:function(a){e=a}},yRange:{get:function(){return f},set:function(a){f=a}},clipEdge:{get:function(){return s},set:function(a){s=a}},id:{get:function(){return j},set:function(a){j=a}},interactive:{get:function(){return v},set:function(a){v=a}},margin:{get:function(){return g},set:function(a){g.top=void 0!==a.top?a.top:g.top,g.right=void 0!==a.right?a.right:g.right,g.bottom=void 0!==a.bottom?a.bottom:g.bottom,g.left=void 0!==a.left?a.left:g.left}},color:{get:function(){return t},set:function(b){t=a.utils.getColor(b)}}}),a.utils.initOptions(b),b},a.models.historicalBarChart=function(b){"use strict";function c(b){return b.each(function(k){z.reset(),z.models(f),q&&z.models(g),r&&z.models(h);var w=d3.select(this),A=this;a.utils.initSVG(w);var B=a.utils.availableWidth(n,w,l),C=a.utils.availableHeight(o,w,l);if(c.update=function(){w.transition().duration(y).call(c)},c.container=this,u.disabled=k.map(function(a){return!!a.disabled}),!v){var D;v={};for(D in u)u[D]instanceof Array?v[D]=u[D].slice(0):v[D]=u[D]}if(!(k&&k.length&&k.filter(function(a){return a.values.length}).length))return a.utils.noData(c,w),c;w.selectAll(".nv-noData").remove(),d=f.xScale(),e=f.yScale();var E=w.selectAll("g.nv-wrap.nv-historicalBarChart").data([k]),F=E.enter().append("g").attr("class","nvd3 nv-wrap nv-historicalBarChart").append("g"),G=E.select("g");F.append("g").attr("class","nv-x nv-axis"),F.append("g").attr("class","nv-y nv-axis"),F.append("g").attr("class","nv-barsWrap"),F.append("g").attr("class","nv-legendWrap"),F.append("g").attr("class","nv-interactive"),p&&(i.width(B),G.select(".nv-legendWrap").datum(k).call(i),l.top!=i.height()&&(l.top=i.height(),C=a.utils.availableHeight(o,w,l)),E.select(".nv-legendWrap").attr("transform","translate(0,"+-l.top+")")),E.attr("transform","translate("+l.left+","+l.top+")"),s&&G.select(".nv-y.nv-axis").attr("transform","translate("+B+",0)"),t&&(j.width(B).height(C).margin({left:l.left,top:l.top}).svgContainer(w).xScale(d),E.select(".nv-interactive").call(j)),f.width(B).height(C).color(k.map(function(a,b){return a.color||m(a,b)}).filter(function(a,b){return!k[b].disabled}));var H=G.select(".nv-barsWrap").datum(k.filter(function(a){return!a.disabled}));H.transition().call(f),q&&(g.scale(d)._ticks(a.utils.calcTicksX(B/100,k)).tickSize(-C,0),G.select(".nv-x.nv-axis").attr("transform","translate(0,"+e.range()[0]+")"),G.select(".nv-x.nv-axis").transition().call(g)),r&&(h.scale(e)._ticks(a.utils.calcTicksY(C/36,k)).tickSize(-B,0),G.select(".nv-y.nv-axis").transition().call(h)),j.dispatch.on("elementMousemove",function(b){f.clearHighlights();var d,e,i,n=[];k.filter(function(a,b){return a.seriesIndex=b,!a.disabled}).forEach(function(g,h){e=a.interactiveBisect(g.values,b.pointXValue,c.x()),f.highlightPoint(e,!0);var j=g.values[e];void 0!==j&&(void 0===d&&(d=j),void 0===i&&(i=c.xScale()(c.x()(j,e))),n.push({key:g.key,value:c.y()(j,e),color:m(g,g.seriesIndex),data:g.values[e]}))});var o=g.tickFormat()(c.x()(d,e));j.tooltip.position({left:i+l.left,top:b.mouseY+l.top}).chartContainer(A.parentNode).valueFormatter(function(a,b){return h.tickFormat()(a)}).data({value:o,index:e,series:n})(),j.renderGuideLine(i)}),j.dispatch.on("elementMouseout",function(a){x.tooltipHide(),f.clearHighlights()}),i.dispatch.on("legendClick",function(a,d){a.disabled=!a.disabled,k.filter(function(a){return!a.disabled}).length||k.map(function(a){return a.disabled=!1,E.selectAll(".nv-series").classed("disabled",!1),a}),u.disabled=k.map(function(a){return!!a.disabled}),x.stateChange(u),b.transition().call(c)}),i.dispatch.on("legendDblclick",function(a){k.forEach(function(a){a.disabled=!0}),a.disabled=!1,u.disabled=k.map(function(a){return!!a.disabled}),x.stateChange(u),c.update()}),x.on("changeState",function(a){"undefined"!=typeof a.disabled&&(k.forEach(function(b,c){b.disabled=a.disabled[c]}),u.disabled=a.disabled),c.update()})}),z.renderEnd("historicalBarChart immediate"),c}var d,e,f=b||a.models.historicalBar(),g=a.models.axis(),h=a.models.axis(),i=a.models.legend(),j=a.interactiveGuideline(),k=a.models.tooltip(),l={top:30,right:90,bottom:50,left:90},m=a.utils.defaultColor(),n=null,o=null,p=!1,q=!0,r=!0,s=!1,t=!1,u={},v=null,w=null,x=d3.dispatch("tooltipHide","stateChange","changeState","renderEnd"),y=250;g.orient("bottom").tickPadding(7),h.orient(s?"right":"left"),k.duration(0).headerEnabled(!1).valueFormatter(function(a,b){return h.tickFormat()(a,b)}).headerFormatter(function(a,b){return g.tickFormat()(a,b)});var z=a.utils.renderWatch(x,0);return f.dispatch.on("elementMouseover.tooltip",function(a){a.series={key:c.x()(a.data),value:c.y()(a.data),color:a.color},k.data(a).hidden(!1)}),f.dispatch.on("elementMouseout.tooltip",function(a){k.hidden(!0)}),f.dispatch.on("elementMousemove.tooltip",function(a){k.position({top:d3.event.pageY,left:d3.event.pageX})()}),c.dispatch=x,c.bars=f,c.legend=i,c.xAxis=g,c.yAxis=h,c.interactiveLayer=j,c.tooltip=k,c.options=a.utils.optionsFunc.bind(c),c._options=Object.create({},{width:{get:function(){return n},set:function(a){n=a}},height:{get:function(){return o},set:function(a){o=a}},showLegend:{get:function(){return p},set:function(a){p=a}},showXAxis:{get:function(){return q},set:function(a){q=a}},showYAxis:{get:function(){return r},set:function(a){r=a}},defaultState:{get:function(){return v},set:function(a){v=a}},noData:{get:function(){return w},set:function(a){w=a}},tooltips:{get:function(){return k.enabled()},set:function(b){a.deprecated("tooltips","use chart.tooltip.enabled() instead"),k.enabled(!!b)}},tooltipContent:{get:function(){return k.contentGenerator()},set:function(b){a.deprecated("tooltipContent","use chart.tooltip.contentGenerator() instead"),k.contentGenerator(b)}},margin:{get:function(){return l},set:function(a){l.top=void 0!==a.top?a.top:l.top,l.right=void 0!==a.right?a.right:l.right,l.bottom=void 0!==a.bottom?a.bottom:l.bottom,l.left=void 0!==a.left?a.left:l.left}},color:{get:function(){return m},set:function(b){m=a.utils.getColor(b),i.color(m),f.color(m)}},duration:{get:function(){return y},set:function(a){y=a,z.reset(y),h.duration(y),g.duration(y)}},rightAlignYAxis:{get:function(){return s},set:function(a){s=a,h.orient(a?"right":"left")}},useInteractiveGuideline:{get:function(){return t},set:function(a){t=a,a===!0&&c.interactive(!1)}}}),a.utils.inheritOptions(c,f),a.utils.initOptions(c),c},a.models.ohlcBarChart=function(){var b=a.models.historicalBarChart(a.models.ohlcBar());return b.useInteractiveGuideline(!0),b.interactiveLayer.tooltip.contentGenerator(function(a){var c=a.series[0].data,d=c.open<c.close?"2ca02c":"d62728";return'<h3 style="color: #'+d+'">'+a.value+"</h3><table><tr><td>open:</td><td>"+b.yAxis.tickFormat()(c.open)+"</td></tr><tr><td>close:</td><td>"+b.yAxis.tickFormat()(c.close)+"</td></tr><tr><td>high</td><td>"+b.yAxis.tickFormat()(c.high)+"</td></tr><tr><td>low:</td><td>"+b.yAxis.tickFormat()(c.low)+"</td></tr></table>"}),b},a.models.candlestickBarChart=function(){var b=a.models.historicalBarChart(a.models.candlestickBar());return b.useInteractiveGuideline(!0),b.interactiveLayer.tooltip.contentGenerator(function(a){var c=a.series[0].data,d=c.open<c.close?"2ca02c":"d62728";return'<h3 style="color: #'+d+'">'+a.value+"</h3><table><tr><td>open:</td><td>"+b.yAxis.tickFormat()(c.open)+"</td></tr><tr><td>close:</td><td>"+b.yAxis.tickFormat()(c.close)+"</td></tr><tr><td>high</td><td>"+b.yAxis.tickFormat()(c.high)+"</td></tr><tr><td>low:</td><td>"+b.yAxis.tickFormat()(c.low)+"</td></tr></table>"}),b},a.models.legend=function(){"use strict";function b(p){function q(a,b){return"furious"!=o?"#000":m?a.disengaged?"#000":"#fff":m?void 0:(a.color||(a.color=g(a,b)),a.disabled?a.color:"#fff")}function r(a,b){return m&&"furious"==o&&a.disengaged?"#eee":a.color||g(a,b)}function s(a,b){return m&&"furious"==o?1:a.disabled?0:1}return p.each(function(b){var g=d-c.left-c.right,p=d3.select(this);a.utils.initSVG(p);var t=p.selectAll("g.nv-legend").data([b]),u=t.enter().append("g").attr("class","nvd3 nv-legend").append("g"),v=t.select("g");t.attr("transform","translate("+c.left+","+c.top+")");var w,x,y=v.selectAll(".nv-series").data(function(a){return"furious"!=o?a:a.filter(function(a){return m?!0:!a.disengaged})}),z=y.enter().append("g").attr("class","nv-series");switch(o){case"furious":x=23;break;case"classic":x=20}if("classic"==o)z.append("circle").style("stroke-width",2).attr("class","nv-legend-symbol").attr("r",5),w=y.select("circle");else if("furious"==o){z.append("rect").style("stroke-width",2).attr("class","nv-legend-symbol").attr("rx",3).attr("ry",3),w=y.select(".nv-legend-symbol"),z.append("g").attr("class","nv-check-box").property("innerHTML",'<path d="M0.5,5 L22.5,5 L22.5,26.5 L0.5,26.5 L0.5,5 Z" class="nv-box"></path><path d="M5.5,12.8618467 L11.9185089,19.2803556 L31,0.198864511" class="nv-check"></path>').attr("transform","translate(-10,-8)scale(0.5)");var A=y.select(".nv-check-box");A.each(function(a,b){d3.select(this).selectAll("path").attr("stroke",q(a,b))})}z.append("text").attr("text-anchor","start").attr("class","nv-legend-text").attr("dy",".32em").attr("dx","8");var B=y.select("text.nv-legend-text");y.on("mouseover",function(a,b){n.legendMouseover(a,b)}).on("mouseout",function(a,b){n.legendMouseout(a,b)}).on("click",function(a,b){n.legendClick(a,b);var c=y.data();if(k){if("classic"==o)l?(c.forEach(function(a){a.disabled=!0}),a.disabled=!1):(a.disabled=!a.disabled,c.every(function(a){return a.disabled})&&c.forEach(function(a){a.disabled=!1}));else if("furious"==o)if(m)a.disengaged=!a.disengaged,a.userDisabled=void 0==a.userDisabled?!!a.disabled:a.userDisabled,a.disabled=a.disengaged||a.userDisabled;else if(!m){a.disabled=!a.disabled,a.userDisabled=a.disabled;var d=c.filter(function(a){return!a.disengaged});d.every(function(a){return a.userDisabled})&&c.forEach(function(a){a.disabled=a.userDisabled=!1})}n.stateChange({disabled:c.map(function(a){return!!a.disabled}),disengaged:c.map(function(a){return!!a.disengaged})})}}).on("dblclick",function(a,b){if(("furious"!=o||!m)&&(n.legendDblclick(a,b),k)){var c=y.data();c.forEach(function(a){a.disabled=!0,"furious"==o&&(a.userDisabled=a.disabled)}),a.disabled=!1,"furious"==o&&(a.userDisabled=a.disabled),n.stateChange({disabled:c.map(function(a){return!!a.disabled})})}}),y.classed("nv-disabled",function(a){return a.userDisabled}),y.exit().remove(),B.attr("fill",q).text(f);var C=0;if(h){var D=[];y.each(function(b,c){var d,e=d3.select(this).select("text");try{if(d=e.node().getComputedTextLength(),0>=d)throw Error()}catch(f){d=a.utils.calcApproxTextWidth(e)}D.push(d+i)});var E=0,F=[];for(C=0;g>C&&E<D.length;)F[E]=D[E],C+=D[E++];for(0===E&&(E=1);C>g&&E>1;){F=[],E--;for(var G=0;G<D.length;G++)D[G]>(F[G%E]||0)&&(F[G%E]=D[G]);C=F.reduce(function(a,b,c,d){return a+b})}for(var H=[],I=0,J=0;E>I;I++)H[I]=J,J+=F[I];y.attr("transform",function(a,b){return"translate("+H[b%E]+","+(5+Math.floor(b/E)*x)+")"}),j?v.attr("transform","translate("+(d-c.right-C)+","+c.top+")"):v.attr("transform","translate(0,"+c.top+")"),e=c.top+c.bottom+Math.ceil(D.length/E)*x}else{var K,L=5,M=5,N=0;y.attr("transform",function(a,b){var e=d3.select(this).select("text").node().getComputedTextLength()+i;return K=M,d<c.left+c.right+K+e&&(M=K=5,L+=x),M+=e,M>N&&(N=M),K+N>C&&(C=K+N),"translate("+K+","+L+")"}),v.attr("transform","translate("+(d-c.right-N)+","+c.top+")"),e=c.top+c.bottom+L+15}if("furious"==o){w.attr("width",function(a,b){return B[0][b].getComputedTextLength()+27}).attr("height",18).attr("y",-9).attr("x",-15),u.insert("rect",":first-child").attr("class","nv-legend-bg").attr("fill","#eee").attr("opacity",0);var O=v.select(".nv-legend-bg");O.transition().duration(300).attr("x",-x).attr("width",C+x-12).attr("height",e+10).attr("y",-c.top-10).attr("opacity",m?1:0)}w.style("fill",r).style("fill-opacity",s).style("stroke",r)}),b}var c={top:5,right:0,bottom:5,left:0},d=400,e=20,f=function(a){return a.key},g=a.utils.getColor(),h=!0,i=32,j=!0,k=!0,l=!1,m=!1,n=d3.dispatch("legendClick","legendDblclick","legendMouseover","legendMouseout","stateChange"),o="classic";return b.dispatch=n,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return d},set:function(a){d=a}},height:{get:function(){return e},set:function(a){e=a}},key:{get:function(){return f},set:function(a){f=a}},align:{get:function(){return h},set:function(a){h=a}},rightAlign:{get:function(){return j},set:function(a){j=a}},padding:{get:function(){return i},set:function(a){i=a}},updateState:{get:function(){return k},set:function(a){k=a}},radioButtonMode:{get:function(){return l},set:function(a){l=a}},expanded:{get:function(){return m},set:function(a){m=a}},vers:{get:function(){return o},set:function(a){o=a}},margin:{get:function(){return c},set:function(a){c.top=void 0!==a.top?a.top:c.top,c.right=void 0!==a.right?a.right:c.right,c.bottom=void 0!==a.bottom?a.bottom:c.bottom,c.left=void 0!==a.left?a.left:c.left}},color:{get:function(){return g},set:function(b){g=a.utils.getColor(b)}}}),a.utils.initOptions(b),b},a.models.line=function(){"use strict";function b(r){return v.reset(),v.models(e),r.each(function(b){i=d3.select(this);var r=a.utils.availableWidth(g,i,f),s=a.utils.availableHeight(h,i,f);a.utils.initSVG(i),c=e.xScale(),d=e.yScale(),t=t||c,u=u||d;var w=i.selectAll("g.nv-wrap.nv-line").data([b]),x=w.enter().append("g").attr("class","nvd3 nv-wrap nv-line"),y=x.append("defs"),z=x.append("g"),A=w.select("g");z.append("g").attr("class","nv-groups"),z.append("g").attr("class","nv-scatterWrap"),w.attr("transform","translate("+f.left+","+f.top+")"),e.width(r).height(s);var B=w.select(".nv-scatterWrap");B.call(e),y.append("clipPath").attr("id","nv-edge-clip-"+e.id()).append("rect"),w.select("#nv-edge-clip-"+e.id()+" rect").attr("width",r).attr("height",s>0?s:0),A.attr("clip-path",p?"url(#nv-edge-clip-"+e.id()+")":""),B.attr("clip-path",p?"url(#nv-edge-clip-"+e.id()+")":"");var C=w.select(".nv-groups").selectAll(".nv-group").data(function(a){return a},function(a){return a.key});C.enter().append("g").style("stroke-opacity",1e-6).style("stroke-width",function(a){return a.strokeWidth||j}).style("fill-opacity",1e-6),C.exit().remove(),C.attr("class",function(a,b){return(a.classed||"")+" nv-group nv-series-"+b}).classed("hover",function(a){return a.hover}).style("fill",function(a,b){return k(a,b)}).style("stroke",function(a,b){return k(a,b)}),C.watchTransition(v,"line: groups").style("stroke-opacity",1).style("fill-opacity",function(a){return a.fillOpacity||.5});var D=C.selectAll("path.nv-area").data(function(a){return o(a)?[a]:[]});D.enter().append("path").attr("class","nv-area").attr("d",function(b){return d3.svg.area().interpolate(q).defined(n).x(function(b,c){return a.utils.NaNtoZero(t(l(b,c)))}).y0(function(b,c){return a.utils.NaNtoZero(u(m(b,c)))}).y1(function(a,b){return u(d.domain()[0]<=0?d.domain()[1]>=0?0:d.domain()[1]:d.domain()[0])}).apply(this,[b.values])}),C.exit().selectAll("path.nv-area").remove(),D.watchTransition(v,"line: areaPaths").attr("d",function(b){return d3.svg.area().interpolate(q).defined(n).x(function(b,d){return a.utils.NaNtoZero(c(l(b,d)))}).y0(function(b,c){return a.utils.NaNtoZero(d(m(b,c)))}).y1(function(a,b){return d(d.domain()[0]<=0?d.domain()[1]>=0?0:d.domain()[1]:d.domain()[0])}).apply(this,[b.values])});var E=C.selectAll("path.nv-line").data(function(a){return[a.values]});E.enter().append("path").attr("class","nv-line").attr("d",d3.svg.line().interpolate(q).defined(n).x(function(b,c){return a.utils.NaNtoZero(t(l(b,c)))}).y(function(b,c){return a.utils.NaNtoZero(u(m(b,c)))})),E.watchTransition(v,"line: linePaths").attr("d",d3.svg.line().interpolate(q).defined(n).x(function(b,d){return a.utils.NaNtoZero(c(l(b,d)))}).y(function(b,c){return a.utils.NaNtoZero(d(m(b,c)))})),t=c.copy(),u=d.copy()}),v.renderEnd("line immediate"),b}var c,d,e=a.models.scatter(),f={top:0,right:0,bottom:0,left:0},g=960,h=500,i=null,j=1.5,k=a.utils.defaultColor(),l=function(a){return a.x},m=function(a){return a.y},n=function(a,b){return!isNaN(m(a,b))&&null!==m(a,b)},o=function(a){return a.area},p=!1,q="linear",r=250,s=d3.dispatch("elementClick","elementMouseover","elementMouseout","renderEnd");e.pointSize(16).pointDomain([16,256]);var t,u,v=a.utils.renderWatch(s,r);return b.dispatch=s,b.scatter=e,e.dispatch.on("elementClick",function(){s.elementClick.apply(this,arguments)}),e.dispatch.on("elementMouseover",function(){s.elementMouseover.apply(this,arguments)}),e.dispatch.on("elementMouseout",function(){s.elementMouseout.apply(this,arguments)}),b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return g},set:function(a){g=a}},height:{get:function(){return h},set:function(a){h=a}},defined:{get:function(){return n},set:function(a){n=a}},interpolate:{get:function(){return q},set:function(a){q=a}},clipEdge:{get:function(){return p},set:function(a){p=a}},margin:{get:function(){return f},set:function(a){f.top=void 0!==a.top?a.top:f.top,f.right=void 0!==a.right?a.right:f.right,f.bottom=void 0!==a.bottom?a.bottom:f.bottom,f.left=void 0!==a.left?a.left:f.left}},duration:{get:function(){return r},set:function(a){r=a,v.reset(r),e.duration(r)}},isArea:{get:function(){return o},set:function(a){o=d3.functor(a)}},x:{get:function(){return l},set:function(a){l=a,e.x(a)}},y:{get:function(){return m},set:function(a){m=a,e.y(a)}},color:{get:function(){return k},set:function(b){k=a.utils.getColor(b),e.color(k)}}}),a.utils.inheritOptions(b,e),a.utils.initOptions(b),b},a.models.lineChart=function(){"use strict";function b(j){return y.reset(),y.models(e), +p&&y.models(f),q&&y.models(g),j.each(function(j){var v=d3.select(this),y=this;a.utils.initSVG(v);var B=a.utils.availableWidth(m,v,k),C=a.utils.availableHeight(n,v,k);if(b.update=function(){0===x?v.call(b):v.transition().duration(x).call(b)},b.container=this,t.setter(A(j),b.update).getter(z(j)).update(),t.disabled=j.map(function(a){return!!a.disabled}),!u){var D;u={};for(D in t)t[D]instanceof Array?u[D]=t[D].slice(0):u[D]=t[D]}if(!(j&&j.length&&j.filter(function(a){return a.values.length}).length))return a.utils.noData(b,v),b;v.selectAll(".nv-noData").remove(),c=e.xScale(),d=e.yScale();var E=v.selectAll("g.nv-wrap.nv-lineChart").data([j]),F=E.enter().append("g").attr("class","nvd3 nv-wrap nv-lineChart").append("g"),G=E.select("g");F.append("rect").style("opacity",0),F.append("g").attr("class","nv-x nv-axis"),F.append("g").attr("class","nv-y nv-axis"),F.append("g").attr("class","nv-linesWrap"),F.append("g").attr("class","nv-legendWrap"),F.append("g").attr("class","nv-interactive"),G.select("rect").attr("width",B).attr("height",C>0?C:0),o&&(h.width(B),G.select(".nv-legendWrap").datum(j).call(h),k.top!=h.height()&&(k.top=h.height(),C=a.utils.availableHeight(n,v,k)),E.select(".nv-legendWrap").attr("transform","translate(0,"+-k.top+")")),E.attr("transform","translate("+k.left+","+k.top+")"),r&&G.select(".nv-y.nv-axis").attr("transform","translate("+B+",0)"),s&&(i.width(B).height(C).margin({left:k.left,top:k.top}).svgContainer(v).xScale(c),E.select(".nv-interactive").call(i)),e.width(B).height(C).color(j.map(function(a,b){return a.color||l(a,b)}).filter(function(a,b){return!j[b].disabled}));var H=G.select(".nv-linesWrap").datum(j.filter(function(a){return!a.disabled}));H.call(e),p&&(f.scale(c)._ticks(a.utils.calcTicksX(B/100,j)).tickSize(-C,0),G.select(".nv-x.nv-axis").attr("transform","translate(0,"+d.range()[0]+")"),G.select(".nv-x.nv-axis").call(f)),q&&(g.scale(d)._ticks(a.utils.calcTicksY(C/36,j)).tickSize(-B,0),G.select(".nv-y.nv-axis").call(g)),h.dispatch.on("stateChange",function(a){for(var c in a)t[c]=a[c];w.stateChange(t),b.update()}),i.dispatch.on("elementMousemove",function(c){e.clearHighlights();var d,h,m,n=[];if(j.filter(function(a,b){return a.seriesIndex=b,!a.disabled}).forEach(function(f,g){h=a.interactiveBisect(f.values,c.pointXValue,b.x());var i=f.values[h],j=b.y()(i,h);null!=j&&e.highlightPoint(g,h,!0),void 0!==i&&(void 0===d&&(d=i),void 0===m&&(m=b.xScale()(b.x()(i,h))),n.push({key:f.key,value:j,color:l(f,f.seriesIndex)}))}),n.length>2){var o=b.yScale().invert(c.mouseY),p=Math.abs(b.yScale().domain()[0]-b.yScale().domain()[1]),q=.03*p,r=a.nearestValueIndex(n.map(function(a){return a.value}),o,q);null!==r&&(n[r].highlight=!0)}var s=f.tickFormat()(b.x()(d,h));i.tooltip.position({left:c.mouseX+k.left,top:c.mouseY+k.top}).chartContainer(y.parentNode).valueFormatter(function(a,b){return null==a?"N/A":g.tickFormat()(a)}).data({value:s,index:h,series:n})(),i.renderGuideLine(m)}),i.dispatch.on("elementClick",function(c){var d,f=[];j.filter(function(a,b){return a.seriesIndex=b,!a.disabled}).forEach(function(e){var g=a.interactiveBisect(e.values,c.pointXValue,b.x()),h=e.values[g];if("undefined"!=typeof h){"undefined"==typeof d&&(d=b.xScale()(b.x()(h,g)));var i=b.yScale()(b.y()(h,g));f.push({point:h,pointIndex:g,pos:[d,i],seriesIndex:e.seriesIndex,series:e})}}),e.dispatch.elementClick(f)}),i.dispatch.on("elementMouseout",function(a){e.clearHighlights()}),w.on("changeState",function(a){"undefined"!=typeof a.disabled&&j.length===a.disabled.length&&(j.forEach(function(b,c){b.disabled=a.disabled[c]}),t.disabled=a.disabled),b.update()})}),y.renderEnd("lineChart immediate"),b}var c,d,e=a.models.line(),f=a.models.axis(),g=a.models.axis(),h=a.models.legend(),i=a.interactiveGuideline(),j=a.models.tooltip(),k={top:30,right:20,bottom:50,left:60},l=a.utils.defaultColor(),m=null,n=null,o=!0,p=!0,q=!0,r=!1,s=!1,t=a.utils.state(),u=null,v=null,w=d3.dispatch("tooltipShow","tooltipHide","stateChange","changeState","renderEnd"),x=250;f.orient("bottom").tickPadding(7),g.orient(r?"right":"left"),j.valueFormatter(function(a,b){return g.tickFormat()(a,b)}).headerFormatter(function(a,b){return f.tickFormat()(a,b)});var y=a.utils.renderWatch(w,x),z=function(a){return function(){return{active:a.map(function(a){return!a.disabled})}}},A=function(a){return function(b){void 0!==b.active&&a.forEach(function(a,c){a.disabled=!b.active[c]})}};return e.dispatch.on("elementMouseover.tooltip",function(a){j.data(a).position(a.pos).hidden(!1)}),e.dispatch.on("elementMouseout.tooltip",function(a){j.hidden(!0)}),b.dispatch=w,b.lines=e,b.legend=h,b.xAxis=f,b.yAxis=g,b.interactiveLayer=i,b.tooltip=j,b.dispatch=w,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return m},set:function(a){m=a}},height:{get:function(){return n},set:function(a){n=a}},showLegend:{get:function(){return o},set:function(a){o=a}},showXAxis:{get:function(){return p},set:function(a){p=a}},showYAxis:{get:function(){return q},set:function(a){q=a}},defaultState:{get:function(){return u},set:function(a){u=a}},noData:{get:function(){return v},set:function(a){v=a}},tooltips:{get:function(){return j.enabled()},set:function(b){a.deprecated("tooltips","use chart.tooltip.enabled() instead"),j.enabled(!!b)}},tooltipContent:{get:function(){return j.contentGenerator()},set:function(b){a.deprecated("tooltipContent","use chart.tooltip.contentGenerator() instead"),j.contentGenerator(b)}},margin:{get:function(){return k},set:function(a){k.top=void 0!==a.top?a.top:k.top,k.right=void 0!==a.right?a.right:k.right,k.bottom=void 0!==a.bottom?a.bottom:k.bottom,k.left=void 0!==a.left?a.left:k.left}},duration:{get:function(){return x},set:function(a){x=a,y.reset(x),e.duration(x),f.duration(x),g.duration(x)}},color:{get:function(){return l},set:function(b){l=a.utils.getColor(b),h.color(l),e.color(l)}},rightAlignYAxis:{get:function(){return r},set:function(a){r=a,g.orient(r?"right":"left")}},useInteractiveGuideline:{get:function(){return s},set:function(a){s=a,s&&(e.interactive(!1),e.useVoronoi(!1))}}}),a.utils.inheritOptions(b,e),a.utils.initOptions(b),b},a.models.linePlusBarChart=function(){"use strict";function b(v){return v.each(function(v){function J(a){var b=+("e"==a),c=b?1:-1,d=X/3;return"M"+.5*c+","+d+"A6,6 0 0 "+b+" "+6.5*c+","+(d+6)+"V"+(2*d-6)+"A6,6 0 0 "+b+" "+.5*c+","+2*d+"ZM"+2.5*c+","+(d+8)+"V"+(2*d-8)+"M"+4.5*c+","+(d+8)+"V"+(2*d-8)}function S(){u.empty()||u.extent(I),ka.data([u.empty()?e.domain():I]).each(function(a,b){var c=e(a[0])-e.range()[0],d=e.range()[1]-e(a[1]);d3.select(this).select(".left").attr("width",0>c?0:c),d3.select(this).select(".right").attr("x",e(a[1])).attr("width",0>d?0:d)})}function T(){I=u.empty()?null:u.extent(),c=u.empty()?e.domain():u.extent(),K.brush({extent:c,brush:u}),S(),l.width(V).height(W).color(v.map(function(a,b){return a.color||C(a,b)}).filter(function(a,b){return!v[b].disabled&&v[b].bar})),j.width(V).height(W).color(v.map(function(a,b){return a.color||C(a,b)}).filter(function(a,b){return!v[b].disabled&&!v[b].bar}));var b=da.select(".nv-focus .nv-barsWrap").datum(Z.length?Z.map(function(a,b){return{key:a.key,values:a.values.filter(function(a,b){return l.x()(a,b)>=c[0]&&l.x()(a,b)<=c[1]})}}):[{values:[]}]),h=da.select(".nv-focus .nv-linesWrap").datum($[0].disabled?[{values:[]}]:$.map(function(a,b){return{area:a.area,fillOpacity:a.fillOpacity,key:a.key,values:a.values.filter(function(a,b){return j.x()(a,b)>=c[0]&&j.x()(a,b)<=c[1]})}}));d=Z.length?l.xScale():j.xScale(),n.scale(d)._ticks(a.utils.calcTicksX(V/100,v)).tickSize(-W,0),n.domain([Math.ceil(c[0]),Math.floor(c[1])]),da.select(".nv-x.nv-axis").transition().duration(L).call(n),b.transition().duration(L).call(l),h.transition().duration(L).call(j),da.select(".nv-focus .nv-x.nv-axis").attr("transform","translate(0,"+f.range()[0]+")"),p.scale(f)._ticks(a.utils.calcTicksY(W/36,v)).tickSize(-V,0),q.scale(g)._ticks(a.utils.calcTicksY(W/36,v)).tickSize(Z.length?0:-V,0),da.select(".nv-focus .nv-y1.nv-axis").style("opacity",Z.length?1:0),da.select(".nv-focus .nv-y2.nv-axis").style("opacity",$.length&&!$[0].disabled?1:0).attr("transform","translate("+d.range()[1]+",0)"),da.select(".nv-focus .nv-y1.nv-axis").transition().duration(L).call(p),da.select(".nv-focus .nv-y2.nv-axis").transition().duration(L).call(q)}var U=d3.select(this);a.utils.initSVG(U);var V=a.utils.availableWidth(y,U,w),W=a.utils.availableHeight(z,U,w)-(E?H:0),X=H-x.top-x.bottom;if(b.update=function(){U.transition().duration(L).call(b)},b.container=this,M.setter(R(v),b.update).getter(Q(v)).update(),M.disabled=v.map(function(a){return!!a.disabled}),!N){var Y;N={};for(Y in M)M[Y]instanceof Array?N[Y]=M[Y].slice(0):N[Y]=M[Y]}if(!(v&&v.length&&v.filter(function(a){return a.values.length}).length))return a.utils.noData(b,U),b;U.selectAll(".nv-noData").remove();var Z=v.filter(function(a){return!a.disabled&&a.bar}),$=v.filter(function(a){return!a.bar});d=l.xScale(),e=o.scale(),f=l.yScale(),g=j.yScale(),h=m.yScale(),i=k.yScale();var _=v.filter(function(a){return!a.disabled&&a.bar}).map(function(a){return a.values.map(function(a,b){return{x:A(a,b),y:B(a,b)}})}),aa=v.filter(function(a){return!a.disabled&&!a.bar}).map(function(a){return a.values.map(function(a,b){return{x:A(a,b),y:B(a,b)}})});d.range([0,V]),e.domain(d3.extent(d3.merge(_.concat(aa)),function(a){return a.x})).range([0,V]);var ba=U.selectAll("g.nv-wrap.nv-linePlusBar").data([v]),ca=ba.enter().append("g").attr("class","nvd3 nv-wrap nv-linePlusBar").append("g"),da=ba.select("g");ca.append("g").attr("class","nv-legendWrap");var ea=ca.append("g").attr("class","nv-focus");ea.append("g").attr("class","nv-x nv-axis"),ea.append("g").attr("class","nv-y1 nv-axis"),ea.append("g").attr("class","nv-y2 nv-axis"),ea.append("g").attr("class","nv-barsWrap"),ea.append("g").attr("class","nv-linesWrap");var fa=ca.append("g").attr("class","nv-context");if(fa.append("g").attr("class","nv-x nv-axis"),fa.append("g").attr("class","nv-y1 nv-axis"),fa.append("g").attr("class","nv-y2 nv-axis"),fa.append("g").attr("class","nv-barsWrap"),fa.append("g").attr("class","nv-linesWrap"),fa.append("g").attr("class","nv-brushBackground"),fa.append("g").attr("class","nv-x nv-brush"),D){var ga=t.align()?V/2:V,ha=t.align()?ga:0;t.width(ga),da.select(".nv-legendWrap").datum(v.map(function(a){return a.originalKey=void 0===a.originalKey?a.key:a.originalKey,a.key=a.originalKey+(a.bar?O:P),a})).call(t),w.top!=t.height()&&(w.top=t.height(),W=a.utils.availableHeight(z,U,w)-H),da.select(".nv-legendWrap").attr("transform","translate("+ha+","+-w.top+")")}ba.attr("transform","translate("+w.left+","+w.top+")"),da.select(".nv-context").style("display",E?"initial":"none"),m.width(V).height(X).color(v.map(function(a,b){return a.color||C(a,b)}).filter(function(a,b){return!v[b].disabled&&v[b].bar})),k.width(V).height(X).color(v.map(function(a,b){return a.color||C(a,b)}).filter(function(a,b){return!v[b].disabled&&!v[b].bar}));var ia=da.select(".nv-context .nv-barsWrap").datum(Z.length?Z:[{values:[]}]),ja=da.select(".nv-context .nv-linesWrap").datum($[0].disabled?[{values:[]}]:$);da.select(".nv-context").attr("transform","translate(0,"+(W+w.bottom+x.top)+")"),ia.transition().call(m),ja.transition().call(k),G&&(o._ticks(a.utils.calcTicksX(V/100,v)).tickSize(-X,0),da.select(".nv-context .nv-x.nv-axis").attr("transform","translate(0,"+h.range()[0]+")"),da.select(".nv-context .nv-x.nv-axis").transition().call(o)),F&&(r.scale(h)._ticks(X/36).tickSize(-V,0),s.scale(i)._ticks(X/36).tickSize(Z.length?0:-V,0),da.select(".nv-context .nv-y3.nv-axis").style("opacity",Z.length?1:0).attr("transform","translate(0,"+e.range()[0]+")"),da.select(".nv-context .nv-y2.nv-axis").style("opacity",$.length?1:0).attr("transform","translate("+e.range()[1]+",0)"),da.select(".nv-context .nv-y1.nv-axis").transition().call(r),da.select(".nv-context .nv-y2.nv-axis").transition().call(s)),u.x(e).on("brush",T),I&&u.extent(I);var ka=da.select(".nv-brushBackground").selectAll("g").data([I||u.extent()]),la=ka.enter().append("g");la.append("rect").attr("class","left").attr("x",0).attr("y",0).attr("height",X),la.append("rect").attr("class","right").attr("x",0).attr("y",0).attr("height",X);var ma=da.select(".nv-x.nv-brush").call(u);ma.selectAll("rect").attr("height",X),ma.selectAll(".resize").append("path").attr("d",J),t.dispatch.on("stateChange",function(a){for(var c in a)M[c]=a[c];K.stateChange(M),b.update()}),K.on("changeState",function(a){"undefined"!=typeof a.disabled&&(v.forEach(function(b,c){b.disabled=a.disabled[c]}),M.disabled=a.disabled),b.update()}),T()}),b}var c,d,e,f,g,h,i,j=a.models.line(),k=a.models.line(),l=a.models.historicalBar(),m=a.models.historicalBar(),n=a.models.axis(),o=a.models.axis(),p=a.models.axis(),q=a.models.axis(),r=a.models.axis(),s=a.models.axis(),t=a.models.legend(),u=d3.svg.brush(),v=a.models.tooltip(),w={top:30,right:30,bottom:30,left:60},x={top:0,right:30,bottom:20,left:60},y=null,z=null,A=function(a){return a.x},B=function(a){return a.y},C=a.utils.defaultColor(),D=!0,E=!0,F=!1,G=!0,H=50,I=null,J=null,K=d3.dispatch("brush","stateChange","changeState"),L=0,M=a.utils.state(),N=null,O=" (left axis)",P=" (right axis)";j.clipEdge(!0),k.interactive(!1),n.orient("bottom").tickPadding(5),p.orient("left"),q.orient("right"),o.orient("bottom").tickPadding(5),r.orient("left"),s.orient("right"),v.headerEnabled(!0).headerFormatter(function(a,b){return n.tickFormat()(a,b)});var Q=function(a){return function(){return{active:a.map(function(a){return!a.disabled})}}},R=function(a){return function(b){void 0!==b.active&&a.forEach(function(a,c){a.disabled=!b.active[c]})}};return j.dispatch.on("elementMouseover.tooltip",function(a){v.duration(100).valueFormatter(function(a,b){return q.tickFormat()(a,b)}).data(a).position(a.pos).hidden(!1)}),j.dispatch.on("elementMouseout.tooltip",function(a){v.hidden(!0)}),l.dispatch.on("elementMouseover.tooltip",function(a){a.value=b.x()(a.data),a.series={value:b.y()(a.data),color:a.color},v.duration(0).valueFormatter(function(a,b){return p.tickFormat()(a,b)}).data(a).hidden(!1)}),l.dispatch.on("elementMouseout.tooltip",function(a){v.hidden(!0)}),l.dispatch.on("elementMousemove.tooltip",function(a){v.position({top:d3.event.pageY,left:d3.event.pageX})()}),b.dispatch=K,b.legend=t,b.lines=j,b.lines2=k,b.bars=l,b.bars2=m,b.xAxis=n,b.x2Axis=o,b.y1Axis=p,b.y2Axis=q,b.y3Axis=r,b.y4Axis=s,b.tooltip=v,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return y},set:function(a){y=a}},height:{get:function(){return z},set:function(a){z=a}},showLegend:{get:function(){return D},set:function(a){D=a}},brushExtent:{get:function(){return I},set:function(a){I=a}},noData:{get:function(){return J},set:function(a){J=a}},focusEnable:{get:function(){return E},set:function(a){E=a}},focusHeight:{get:function(){return H},set:function(a){H=a}},focusShowAxisX:{get:function(){return G},set:function(a){G=a}},focusShowAxisY:{get:function(){return F},set:function(a){F=a}},legendLeftAxisHint:{get:function(){return O},set:function(a){O=a}},legendRightAxisHint:{get:function(){return P},set:function(a){P=a}},tooltips:{get:function(){return v.enabled()},set:function(b){a.deprecated("tooltips","use chart.tooltip.enabled() instead"),v.enabled(!!b)}},tooltipContent:{get:function(){return v.contentGenerator()},set:function(b){a.deprecated("tooltipContent","use chart.tooltip.contentGenerator() instead"),v.contentGenerator(b)}},margin:{get:function(){return w},set:function(a){w.top=void 0!==a.top?a.top:w.top,w.right=void 0!==a.right?a.right:w.right,w.bottom=void 0!==a.bottom?a.bottom:w.bottom,w.left=void 0!==a.left?a.left:w.left}},duration:{get:function(){return L},set:function(a){L=a}},color:{get:function(){return C},set:function(b){C=a.utils.getColor(b),t.color(C)}},x:{get:function(){return A},set:function(a){A=a,j.x(a),k.x(a),l.x(a),m.x(a)}},y:{get:function(){return B},set:function(a){B=a,j.y(a),k.y(a),l.y(a),m.y(a)}}}),a.utils.inheritOptions(b,j),a.utils.initOptions(b),b},a.models.lineWithFocusChart=function(){"use strict";function b(o){return o.each(function(o){function z(a){var b=+("e"==a),c=b?1:-1,d=M/3;return"M"+.5*c+","+d+"A6,6 0 0 "+b+" "+6.5*c+","+(d+6)+"V"+(2*d-6)+"A6,6 0 0 "+b+" "+.5*c+","+2*d+"ZM"+2.5*c+","+(d+8)+"V"+(2*d-8)+"M"+4.5*c+","+(d+8)+"V"+(2*d-8)}function G(){n.empty()||n.extent(y),U.data([n.empty()?e.domain():y]).each(function(a,b){var d=e(a[0])-c.range()[0],f=K-e(a[1]);d3.select(this).select(".left").attr("width",0>d?0:d),d3.select(this).select(".right").attr("x",e(a[1])).attr("width",0>f?0:f)})}function H(){y=n.empty()?null:n.extent();var a=n.empty()?e.domain():n.extent();if(!(Math.abs(a[0]-a[1])<=1)){A.brush({extent:a,brush:n}),G();var b=Q.select(".nv-focus .nv-linesWrap").datum(o.filter(function(a){return!a.disabled}).map(function(b,c){return{key:b.key,area:b.area,values:b.values.filter(function(b,c){return g.x()(b,c)>=a[0]&&g.x()(b,c)<=a[1]})}}));b.transition().duration(B).call(g),Q.select(".nv-focus .nv-x.nv-axis").transition().duration(B).call(i),Q.select(".nv-focus .nv-y.nv-axis").transition().duration(B).call(j)}}var I=d3.select(this),J=this;a.utils.initSVG(I);var K=a.utils.availableWidth(t,I,q),L=a.utils.availableHeight(u,I,q)-v,M=v-r.top-r.bottom;if(b.update=function(){I.transition().duration(B).call(b)},b.container=this,C.setter(F(o),b.update).getter(E(o)).update(),C.disabled=o.map(function(a){return!!a.disabled}),!D){var N;D={};for(N in C)C[N]instanceof Array?D[N]=C[N].slice(0):D[N]=C[N]}if(!(o&&o.length&&o.filter(function(a){return a.values.length}).length))return a.utils.noData(b,I),b;I.selectAll(".nv-noData").remove(),c=g.xScale(),d=g.yScale(),e=h.xScale(),f=h.yScale();var O=I.selectAll("g.nv-wrap.nv-lineWithFocusChart").data([o]),P=O.enter().append("g").attr("class","nvd3 nv-wrap nv-lineWithFocusChart").append("g"),Q=O.select("g");P.append("g").attr("class","nv-legendWrap");var R=P.append("g").attr("class","nv-focus");R.append("g").attr("class","nv-x nv-axis"),R.append("g").attr("class","nv-y nv-axis"),R.append("g").attr("class","nv-linesWrap"),R.append("g").attr("class","nv-interactive");var S=P.append("g").attr("class","nv-context");S.append("g").attr("class","nv-x nv-axis"),S.append("g").attr("class","nv-y nv-axis"),S.append("g").attr("class","nv-linesWrap"),S.append("g").attr("class","nv-brushBackground"),S.append("g").attr("class","nv-x nv-brush"),x&&(m.width(K),Q.select(".nv-legendWrap").datum(o).call(m),q.top!=m.height()&&(q.top=m.height(),L=a.utils.availableHeight(u,I,q)-v),Q.select(".nv-legendWrap").attr("transform","translate(0,"+-q.top+")")),O.attr("transform","translate("+q.left+","+q.top+")"),w&&(p.width(K).height(L).margin({left:q.left,top:q.top}).svgContainer(I).xScale(c),O.select(".nv-interactive").call(p)),g.width(K).height(L).color(o.map(function(a,b){return a.color||s(a,b)}).filter(function(a,b){return!o[b].disabled})),h.defined(g.defined()).width(K).height(M).color(o.map(function(a,b){return a.color||s(a,b)}).filter(function(a,b){return!o[b].disabled})),Q.select(".nv-context").attr("transform","translate(0,"+(L+q.bottom+r.top)+")");var T=Q.select(".nv-context .nv-linesWrap").datum(o.filter(function(a){return!a.disabled}));d3.transition(T).call(h),i.scale(c)._ticks(a.utils.calcTicksX(K/100,o)).tickSize(-L,0),j.scale(d)._ticks(a.utils.calcTicksY(L/36,o)).tickSize(-K,0),Q.select(".nv-focus .nv-x.nv-axis").attr("transform","translate(0,"+L+")"),n.x(e).on("brush",function(){H()}),y&&n.extent(y);var U=Q.select(".nv-brushBackground").selectAll("g").data([y||n.extent()]),V=U.enter().append("g");V.append("rect").attr("class","left").attr("x",0).attr("y",0).attr("height",M),V.append("rect").attr("class","right").attr("x",0).attr("y",0).attr("height",M);var W=Q.select(".nv-x.nv-brush").call(n);W.selectAll("rect").attr("height",M),W.selectAll(".resize").append("path").attr("d",z),H(),k.scale(e)._ticks(a.utils.calcTicksX(K/100,o)).tickSize(-M,0),Q.select(".nv-context .nv-x.nv-axis").attr("transform","translate(0,"+f.range()[0]+")"),d3.transition(Q.select(".nv-context .nv-x.nv-axis")).call(k),l.scale(f)._ticks(a.utils.calcTicksY(M/36,o)).tickSize(-K,0),d3.transition(Q.select(".nv-context .nv-y.nv-axis")).call(l),Q.select(".nv-context .nv-x.nv-axis").attr("transform","translate(0,"+f.range()[0]+")"),m.dispatch.on("stateChange",function(a){for(var c in a)C[c]=a[c];A.stateChange(C),b.update()}),p.dispatch.on("elementMousemove",function(c){g.clearHighlights();var d,f,h,k=[];if(o.filter(function(a,b){return a.seriesIndex=b,!a.disabled}).forEach(function(i,j){var l=n.empty()?e.domain():n.extent(),m=i.values.filter(function(a,b){return g.x()(a,b)>=l[0]&&g.x()(a,b)<=l[1]});f=a.interactiveBisect(m,c.pointXValue,g.x());var o=m[f],p=b.y()(o,f);null!=p&&g.highlightPoint(j,f,!0),void 0!==o&&(void 0===d&&(d=o),void 0===h&&(h=b.xScale()(b.x()(o,f))),k.push({key:i.key,value:b.y()(o,f),color:s(i,i.seriesIndex)}))}),k.length>2){var l=b.yScale().invert(c.mouseY),m=Math.abs(b.yScale().domain()[0]-b.yScale().domain()[1]),r=.03*m,t=a.nearestValueIndex(k.map(function(a){return a.value}),l,r);null!==t&&(k[t].highlight=!0)}var u=i.tickFormat()(b.x()(d,f));p.tooltip.position({left:c.mouseX+q.left,top:c.mouseY+q.top}).chartContainer(J.parentNode).valueFormatter(function(a,b){return null==a?"N/A":j.tickFormat()(a)}).data({value:u,index:f,series:k})(),p.renderGuideLine(h)}),p.dispatch.on("elementMouseout",function(a){g.clearHighlights()}),A.on("changeState",function(a){"undefined"!=typeof a.disabled&&o.forEach(function(b,c){b.disabled=a.disabled[c]}),b.update()})}),b}var c,d,e,f,g=a.models.line(),h=a.models.line(),i=a.models.axis(),j=a.models.axis(),k=a.models.axis(),l=a.models.axis(),m=a.models.legend(),n=d3.svg.brush(),o=a.models.tooltip(),p=a.interactiveGuideline(),q={top:30,right:30,bottom:30,left:60},r={top:0,right:30,bottom:20,left:60},s=a.utils.defaultColor(),t=null,u=null,v=50,w=!1,x=!0,y=null,z=null,A=d3.dispatch("brush","stateChange","changeState"),B=250,C=a.utils.state(),D=null;g.clipEdge(!0).duration(0),h.interactive(!1),i.orient("bottom").tickPadding(5),j.orient("left"),k.orient("bottom").tickPadding(5),l.orient("left"),o.valueFormatter(function(a,b){return j.tickFormat()(a,b)}).headerFormatter(function(a,b){return i.tickFormat()(a,b)});var E=function(a){return function(){return{active:a.map(function(a){return!a.disabled})}}},F=function(a){return function(b){void 0!==b.active&&a.forEach(function(a,c){a.disabled=!b.active[c]})}};return g.dispatch.on("elementMouseover.tooltip",function(a){o.data(a).position(a.pos).hidden(!1)}),g.dispatch.on("elementMouseout.tooltip",function(a){o.hidden(!0)}),b.dispatch=A,b.legend=m,b.lines=g,b.lines2=h,b.xAxis=i,b.yAxis=j,b.x2Axis=k,b.y2Axis=l,b.interactiveLayer=p,b.tooltip=o,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return t},set:function(a){t=a}},height:{get:function(){return u},set:function(a){u=a}},focusHeight:{get:function(){return v},set:function(a){v=a}},showLegend:{get:function(){return x},set:function(a){x=a}},brushExtent:{get:function(){return y},set:function(a){y=a}},defaultState:{get:function(){return D},set:function(a){D=a}},noData:{get:function(){return z},set:function(a){z=a}},tooltips:{get:function(){return o.enabled()},set:function(b){a.deprecated("tooltips","use chart.tooltip.enabled() instead"),o.enabled(!!b)}},tooltipContent:{get:function(){return o.contentGenerator()},set:function(b){a.deprecated("tooltipContent","use chart.tooltip.contentGenerator() instead"),o.contentGenerator(b)}},margin:{get:function(){return q},set:function(a){q.top=void 0!==a.top?a.top:q.top,q.right=void 0!==a.right?a.right:q.right,q.bottom=void 0!==a.bottom?a.bottom:q.bottom,q.left=void 0!==a.left?a.left:q.left}},color:{get:function(){return s},set:function(b){s=a.utils.getColor(b),m.color(s)}},interpolate:{get:function(){return g.interpolate()},set:function(a){g.interpolate(a),h.interpolate(a)}},xTickFormat:{get:function(){return i.tickFormat()},set:function(a){i.tickFormat(a),k.tickFormat(a)}},yTickFormat:{get:function(){return j.tickFormat()},set:function(a){j.tickFormat(a),l.tickFormat(a)}},duration:{get:function(){return B},set:function(a){B=a,j.duration(B),l.duration(B),i.duration(B),k.duration(B)}},x:{get:function(){return g.x()},set:function(a){g.x(a),h.x(a)}},y:{get:function(){return g.y()},set:function(a){g.y(a),h.y(a)}},useInteractiveGuideline:{get:function(){return w},set:function(a){w=a,w&&(g.interactive(!1),g.useVoronoi(!1))}}}),a.utils.inheritOptions(b,g),a.utils.initOptions(b),b},a.models.multiBar=function(){"use strict";function b(G){return E.reset(),G.each(function(b){var G=k-j.left-j.right,H=l-j.top-j.bottom;p=d3.select(this),a.utils.initSVG(p);var I=0;if(z&&b.length&&(z=[{values:b[0].values.map(function(a){return{x:a.x,y:0,series:a.series,size:.01}})}]),v){var J=d3.layout.stack().offset(w).values(function(a){return a.values}).y(r)(!b.length&&z?z:b);J.forEach(function(a,c){a.nonStackable?(b[c].nonStackableSeries=I++,J[c]=b[c]):c>0&&J[c-1].nonStackable&&J[c].values.map(function(a,b){a.y0-=J[c-1].values[b].y,a.y1=a.y0+a.y})}),b=J}b.forEach(function(a,b){a.values.forEach(function(c){c.series=b,c.key=a.key})}),v&&b[0].values.map(function(a,c){var d=0,e=0;b.map(function(a,f){if(!b[f].nonStackable){var g=a.values[c];g.size=Math.abs(g.y),g.y<0?(g.y1=e,e-=g.size):(g.y1=g.size+d,d+=g.size)}})});var K=d&&e?[]:b.map(function(a,b){return a.values.map(function(a,c){return{x:q(a,c),y:r(a,c),y0:a.y0,y1:a.y1,idx:b,yErr:s(a,c)}})});m.domain(d||d3.merge(K).map(function(a){return a.x})).rangeBands(f||[0,G],C),n.domain(e||d3.extent(d3.merge(d3.merge(K).map(function(a){var c=a.y;v&&!b[a.idx].nonStackable&&(c=a.y>0?a.y1:a.y1+a.y);var d=a.yErr;return d?d.length?[c+d[0],c+d[1]]:(d=Math.abs(d),[c-d,c+d]):[c]})).concat(t))).range(g||[H,0]),m.domain()[0]===m.domain()[1]&&(m.domain()[0]?m.domain([m.domain()[0]-.01*m.domain()[0],m.domain()[1]+.01*m.domain()[1]]):m.domain([-1,1])),n.domain()[0]===n.domain()[1]&&(n.domain()[0]?n.domain([n.domain()[0]+.01*n.domain()[0],n.domain()[1]-.01*n.domain()[1]]):n.domain([-1,1])),h=h||m,i=i||n;var L=p.selectAll("g.nv-wrap.nv-multibar").data([b]),M=L.enter().append("g").attr("class","nvd3 nv-wrap nv-multibar"),N=M.append("defs"),O=M.append("g"),P=L.select("g");O.append("g").attr("class","nv-groups"),L.attr("transform","translate("+j.left+","+j.top+")"),N.append("clipPath").attr("id","nv-edge-clip-"+o).append("rect"),L.select("#nv-edge-clip-"+o+" rect").attr("width",G).attr("height",H),P.attr("clip-path",u?"url(#nv-edge-clip-"+o+")":"");var Q=L.select(".nv-groups").selectAll(".nv-group").data(function(a){return a},function(a,b){return b});Q.enter().append("g").style("stroke-opacity",1e-6).style("fill-opacity",1e-6);var R=E.transition(Q.exit().selectAll("g.nv-bar"),"multibarExit",Math.min(100,B)).attr("y",function(a,c,d){var e=i(0)||0;return v&&b[a.series]&&!b[a.series].nonStackable&&(e=i(a.y0)),e}).attr("height",0).remove();R.delay&&R.delay(function(a,b){var c=b*(B/(F+1))-b;return c}),Q.attr("class",function(a,b){return"nv-group nv-series-"+b}).classed("hover",function(a){return a.hover}).style("fill",function(a,b){return x(a,b)}).style("stroke",function(a,b){return x(a,b)}),Q.style("stroke-opacity",1).style("fill-opacity",.75);var S=Q.selectAll("g.nv-bar").data(function(a){return z&&!b.length?z.values:a.values});S.exit().remove();var T=S.enter().append("g").attr("class",function(a,b){return r(a,b)<0?"nv-bar negative":"nv-bar positive"}).attr("transform",function(a,c,d){var e=v&&!b[d].nonStackable?0:d*m.rangeBand()/b.length,f=i(v&&!b[d].nonStackable?a.y0:0)||0;return"translate("+e+","+f+")"});T.append("rect").attr("height",0).attr("width",function(a,c,d){return m.rangeBand()/(v&&!b[d].nonStackable?1:b.length)}).style("fill",function(a,b,c){return x(a,c,b)}).style("stroke",function(a,b,c){return x(a,c,b)}),S.on("mouseover",function(a,b){d3.select(this).classed("hover",!0),D.elementMouseover({data:a,index:b,color:d3.select(this).style("fill")})}).on("mouseout",function(a,b){d3.select(this).classed("hover",!1),D.elementMouseout({data:a,index:b,color:d3.select(this).style("fill")})}).on("mousemove",function(a,b){D.elementMousemove({data:a,index:b,color:d3.select(this).style("fill")})}).on("click",function(a,b){D.elementClick({data:a,index:b,color:d3.select(this).style("fill")}),d3.event.stopPropagation()}).on("dblclick",function(a,b){D.elementDblClick({data:a,index:b,color:d3.select(this).style("fill")}),d3.event.stopPropagation()}),s(b[0].values[0],0)&&(T.append("polyline"),S.select("polyline").attr("fill","none").attr("stroke",function(a,b,c){return y(a,c,b)}).attr("points",function(a,c){var d=s(a,c),e=.8*m.rangeBand()/(2*(v?1:b.length));d=d.length?d:[-Math.abs(d),Math.abs(d)],d=d.map(function(a){return n(a)-n(0)});var f=[[-e,d[0]],[e,d[0]],[0,d[0]],[0,d[1]],[-e,d[1]],[e,d[1]]];return f.map(function(a){return a.join(",")}).join(" ")}).attr("transform",function(a,c){var d=m.rangeBand()/(2*(v?1:b.length)),e=r(a,c)<0?n(r(a,c))-n(0):0;return"translate("+d+", "+e+")"})),S.attr("class",function(a,b){return r(a,b)<0?"nv-bar negative":"nv-bar positive"}),A&&(c||(c=b.map(function(){return!0})),S.select("rect").style("fill",function(a,b,d){return d3.rgb(A(a,b)).darker(c.map(function(a,b){return b}).filter(function(a,b){return!c[b]})[d]).toString()}).style("stroke",function(a,b,d){return d3.rgb(A(a,b)).darker(c.map(function(a,b){return b}).filter(function(a,b){return!c[b]})[d]).toString()}));var U=S.watchTransition(E,"multibar",Math.min(250,B)).delay(function(a,c){return c*B/b[0].values.length});v?U.attr("transform",function(a,c,d){var e=0;e=b[d].nonStackable?r(a,c)<0?n(0):n(0)-n(r(a,c))<-1?n(0)-1:n(r(a,c))||0:n(a.y1);var f=0;b[d].nonStackable&&(f=a.series*m.rangeBand()/b.length,b.length!==I&&(f=b[d].nonStackableSeries*m.rangeBand()/(2*I)));var g=f+m(q(a,c));return"translate("+g+","+e+")"}).select("rect").attr("height",function(a,c,d){return b[d].nonStackable?Math.max(Math.abs(n(r(a,c))-n(0)),1)||0:Math.max(Math.abs(n(a.y+a.y0)-n(a.y0)),1)}).attr("width",function(a,c,d){if(b[d].nonStackable){var e=m.rangeBand()/I;return b.length!==I&&(e=m.rangeBand()/(2*I)),e}return m.rangeBand()}):U.attr("transform",function(a,c){var d=a.series*m.rangeBand()/b.length+m(q(a,c)),e=r(a,c)<0?n(0):n(0)-n(r(a,c))<1?n(0)-1:n(r(a,c))||0;return"translate("+d+","+e+")"}).select("rect").attr("width",m.rangeBand()/b.length).attr("height",function(a,b){return Math.max(Math.abs(n(r(a,b))-n(0)),1)||0}),h=m.copy(),i=n.copy(),b[0]&&b[0].values&&(F=b[0].values.length)}),E.renderEnd("multibar immediate"),b}var c,d,e,f,g,h,i,j={top:0,right:0,bottom:0,left:0},k=960,l=500,m=d3.scale.ordinal(),n=d3.scale.linear(),o=Math.floor(1e4*Math.random()),p=null,q=function(a){return a.x},r=function(a){return a.y},s=function(a){return a.yErr},t=[0],u=!0,v=!1,w="zero",x=a.utils.defaultColor(),y=a.utils.defaultColor(),z=!1,A=null,B=500,C=.1,D=d3.dispatch("chartClick","elementClick","elementDblClick","elementMouseover","elementMouseout","elementMousemove","renderEnd"),E=a.utils.renderWatch(D,B),F=0;return b.dispatch=D,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return k},set:function(a){k=a}},height:{get:function(){return l},set:function(a){l=a}},x:{get:function(){return q},set:function(a){q=a}},y:{get:function(){return r},set:function(a){r=a}},yErr:{get:function(){return s},set:function(a){s=a}},xScale:{get:function(){return m},set:function(a){m=a}},yScale:{get:function(){return n},set:function(a){n=a}},xDomain:{get:function(){return d},set:function(a){d=a}},yDomain:{get:function(){return e},set:function(a){e=a}},xRange:{get:function(){return f},set:function(a){f=a}},yRange:{get:function(){return g},set:function(a){g=a}},forceY:{get:function(){return t},set:function(a){t=a}},stacked:{get:function(){return v},set:function(a){v=a}},stackOffset:{get:function(){return w},set:function(a){w=a}},clipEdge:{get:function(){return u},set:function(a){u=a}},disabled:{get:function(){return c},set:function(a){c=a}},id:{get:function(){return o},set:function(a){o=a}},hideable:{get:function(){ +return z},set:function(a){z=a}},groupSpacing:{get:function(){return C},set:function(a){C=a}},margin:{get:function(){return j},set:function(a){j.top=void 0!==a.top?a.top:j.top,j.right=void 0!==a.right?a.right:j.right,j.bottom=void 0!==a.bottom?a.bottom:j.bottom,j.left=void 0!==a.left?a.left:j.left}},duration:{get:function(){return B},set:function(a){B=a,E.reset(B)}},color:{get:function(){return x},set:function(b){x=a.utils.getColor(b)}},barColor:{get:function(){return A},set:function(b){A=b?a.utils.getColor(b):null}},errorBarColor:{get:function(){return y},set:function(b){y=b?a.utils.getColor(b):null}}}),a.utils.initOptions(b),b},a.models.multiBarChart=function(){"use strict";function b(j){return D.reset(),D.models(e),r&&D.models(f),s&&D.models(g),j.each(function(j){var z=d3.select(this);a.utils.initSVG(z);var D=a.utils.availableWidth(l,z,k),H=a.utils.availableHeight(m,z,k);if(b.update=function(){0===C?z.call(b):z.transition().duration(C).call(b)},b.container=this,x.setter(G(j),b.update).getter(F(j)).update(),x.disabled=j.map(function(a){return!!a.disabled}),!y){var I;y={};for(I in x)x[I]instanceof Array?y[I]=x[I].slice(0):y[I]=x[I]}if(!(j&&j.length&&j.filter(function(a){return a.values.length}).length))return a.utils.noData(b,z),b;z.selectAll(".nv-noData").remove(),c=e.xScale(),d=e.yScale();var J=z.selectAll("g.nv-wrap.nv-multiBarWithLegend").data([j]),K=J.enter().append("g").attr("class","nvd3 nv-wrap nv-multiBarWithLegend").append("g"),L=J.select("g");if(K.append("g").attr("class","nv-x nv-axis"),K.append("g").attr("class","nv-y nv-axis"),K.append("g").attr("class","nv-barsWrap"),K.append("g").attr("class","nv-legendWrap"),K.append("g").attr("class","nv-controlsWrap"),q&&(h.width(D-B()),L.select(".nv-legendWrap").datum(j).call(h),k.top!=h.height()&&(k.top=h.height(),H=a.utils.availableHeight(m,z,k)),L.select(".nv-legendWrap").attr("transform","translate("+B()+","+-k.top+")")),o){var M=[{key:p.grouped||"Grouped",disabled:e.stacked()},{key:p.stacked||"Stacked",disabled:!e.stacked()}];i.width(B()).color(["#444","#444","#444"]),L.select(".nv-controlsWrap").datum(M).attr("transform","translate(0,"+-k.top+")").call(i)}J.attr("transform","translate("+k.left+","+k.top+")"),t&&L.select(".nv-y.nv-axis").attr("transform","translate("+D+",0)"),e.disabled(j.map(function(a){return a.disabled})).width(D).height(H).color(j.map(function(a,b){return a.color||n(a,b)}).filter(function(a,b){return!j[b].disabled}));var N=L.select(".nv-barsWrap").datum(j.filter(function(a){return!a.disabled}));if(N.call(e),r){f.scale(c)._ticks(a.utils.calcTicksX(D/100,j)).tickSize(-H,0),L.select(".nv-x.nv-axis").attr("transform","translate(0,"+d.range()[0]+")"),L.select(".nv-x.nv-axis").call(f);var O=L.select(".nv-x.nv-axis > g").selectAll("g");if(O.selectAll("line, text").style("opacity",1),v){var P=function(a,b){return"translate("+a+","+b+")"},Q=5,R=17;O.selectAll("text").attr("transform",function(a,b,c){return P(0,c%2==0?Q:R)});var S=d3.selectAll(".nv-x.nv-axis .nv-wrap g g text")[0].length;L.selectAll(".nv-x.nv-axis .nv-axisMaxMin text").attr("transform",function(a,b){return P(0,0===b||S%2!==0?R:Q)})}u&&O.filter(function(a,b){return b%Math.ceil(j[0].values.length/(D/100))!==0}).selectAll("text, line").style("opacity",0),w&&O.selectAll(".tick text").attr("transform","rotate("+w+" 0,0)").style("text-anchor",w>0?"start":"end"),L.select(".nv-x.nv-axis").selectAll("g.nv-axisMaxMin text").style("opacity",1)}s&&(g.scale(d)._ticks(a.utils.calcTicksY(H/36,j)).tickSize(-D,0),L.select(".nv-y.nv-axis").call(g)),h.dispatch.on("stateChange",function(a){for(var c in a)x[c]=a[c];A.stateChange(x),b.update()}),i.dispatch.on("legendClick",function(a,c){if(a.disabled){switch(M=M.map(function(a){return a.disabled=!0,a}),a.disabled=!1,a.key){case"Grouped":case p.grouped:e.stacked(!1);break;case"Stacked":case p.stacked:e.stacked(!0)}x.stacked=e.stacked(),A.stateChange(x),b.update()}}),A.on("changeState",function(a){"undefined"!=typeof a.disabled&&(j.forEach(function(b,c){b.disabled=a.disabled[c]}),x.disabled=a.disabled),"undefined"!=typeof a.stacked&&(e.stacked(a.stacked),x.stacked=a.stacked,E=a.stacked),b.update()})}),D.renderEnd("multibarchart immediate"),b}var c,d,e=a.models.multiBar(),f=a.models.axis(),g=a.models.axis(),h=a.models.legend(),i=a.models.legend(),j=a.models.tooltip(),k={top:30,right:20,bottom:50,left:60},l=null,m=null,n=a.utils.defaultColor(),o=!0,p={},q=!0,r=!0,s=!0,t=!1,u=!0,v=!1,w=0,x=a.utils.state(),y=null,z=null,A=d3.dispatch("stateChange","changeState","renderEnd"),B=function(){return o?180:0},C=250;x.stacked=!1,e.stacked(!1),f.orient("bottom").tickPadding(7).showMaxMin(!1).tickFormat(function(a){return a}),g.orient(t?"right":"left").tickFormat(d3.format(",.1f")),j.duration(0).valueFormatter(function(a,b){return g.tickFormat()(a,b)}).headerFormatter(function(a,b){return f.tickFormat()(a,b)}),i.updateState(!1);var D=a.utils.renderWatch(A),E=!1,F=function(a){return function(){return{active:a.map(function(a){return!a.disabled}),stacked:E}}},G=function(a){return function(b){void 0!==b.stacked&&(E=b.stacked),void 0!==b.active&&a.forEach(function(a,c){a.disabled=!b.active[c]})}};return e.dispatch.on("elementMouseover.tooltip",function(a){a.value=b.x()(a.data),a.series={key:a.data.key,value:b.y()(a.data),color:a.color},j.data(a).hidden(!1)}),e.dispatch.on("elementMouseout.tooltip",function(a){j.hidden(!0)}),e.dispatch.on("elementMousemove.tooltip",function(a){j.position({top:d3.event.pageY,left:d3.event.pageX})()}),b.dispatch=A,b.multibar=e,b.legend=h,b.controls=i,b.xAxis=f,b.yAxis=g,b.state=x,b.tooltip=j,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return l},set:function(a){l=a}},height:{get:function(){return m},set:function(a){m=a}},showLegend:{get:function(){return q},set:function(a){q=a}},showControls:{get:function(){return o},set:function(a){o=a}},controlLabels:{get:function(){return p},set:function(a){p=a}},showXAxis:{get:function(){return r},set:function(a){r=a}},showYAxis:{get:function(){return s},set:function(a){s=a}},defaultState:{get:function(){return y},set:function(a){y=a}},noData:{get:function(){return z},set:function(a){z=a}},reduceXTicks:{get:function(){return u},set:function(a){u=a}},rotateLabels:{get:function(){return w},set:function(a){w=a}},staggerLabels:{get:function(){return v},set:function(a){v=a}},tooltips:{get:function(){return j.enabled()},set:function(b){a.deprecated("tooltips","use chart.tooltip.enabled() instead"),j.enabled(!!b)}},tooltipContent:{get:function(){return j.contentGenerator()},set:function(b){a.deprecated("tooltipContent","use chart.tooltip.contentGenerator() instead"),j.contentGenerator(b)}},margin:{get:function(){return k},set:function(a){k.top=void 0!==a.top?a.top:k.top,k.right=void 0!==a.right?a.right:k.right,k.bottom=void 0!==a.bottom?a.bottom:k.bottom,k.left=void 0!==a.left?a.left:k.left}},duration:{get:function(){return C},set:function(a){C=a,e.duration(C),f.duration(C),g.duration(C),D.reset(C)}},color:{get:function(){return n},set:function(b){n=a.utils.getColor(b),h.color(n)}},rightAlignYAxis:{get:function(){return t},set:function(a){t=a,g.orient(t?"right":"left")}},barColor:{get:function(){return e.barColor},set:function(a){e.barColor(a),h.color(function(a,b){return d3.rgb("#ccc").darker(1.5*b).toString()})}}}),a.utils.inheritOptions(b,e),a.utils.initOptions(b),b},a.models.multiBarHorizontal=function(){"use strict";function b(m){return F.reset(),m.each(function(b){var m=k-j.left-j.right,D=l-j.top-j.bottom;n=d3.select(this),a.utils.initSVG(n),x&&(b=d3.layout.stack().offset("zero").values(function(a){return a.values}).y(r)(b)),b.forEach(function(a,b){a.values.forEach(function(c){c.series=b,c.key=a.key})}),x&&b[0].values.map(function(a,c){var d=0,e=0;b.map(function(a){var b=a.values[c];b.size=Math.abs(b.y),b.y<0?(b.y1=e-b.size,e-=b.size):(b.y1=d,d+=b.size)})});var G=d&&e?[]:b.map(function(a){return a.values.map(function(a,b){return{x:q(a,b),y:r(a,b),y0:a.y0,y1:a.y1,yErr:s(a,b)}})});o.domain(d||d3.merge(G).map(function(a){return a.x})).rangeBands(f||[0,D],B),p.domain(e||d3.extent(d3.merge(d3.merge(G).map(function(a){var b=a.y;x&&(b=a.y>0?a.y1+a.y:a.y1);var c=a.yErr;return c?c.length?[b+c[0],b+c[1]]:(c=Math.abs(c),[b-c,b+c]):[b]})).concat(t))),y&&!x?p.range(g||[p.domain()[0]<0?A:0,m-(p.domain()[1]>0?A:0)]):p.range(g||[0,m]),h=h||o,i=i||d3.scale.linear().domain(p.domain()).range([p(0),p(0)]);var H=d3.select(this).selectAll("g.nv-wrap.nv-multibarHorizontal").data([b]),I=H.enter().append("g").attr("class","nvd3 nv-wrap nv-multibarHorizontal"),J=(I.append("defs"),I.append("g"));H.select("g");J.append("g").attr("class","nv-groups"),H.attr("transform","translate("+j.left+","+j.top+")");var K=H.select(".nv-groups").selectAll(".nv-group").data(function(a){return a},function(a,b){return b});K.enter().append("g").style("stroke-opacity",1e-6).style("fill-opacity",1e-6),K.exit().watchTransition(F,"multibarhorizontal: exit groups").style("stroke-opacity",1e-6).style("fill-opacity",1e-6).remove(),K.attr("class",function(a,b){return"nv-group nv-series-"+b}).classed("hover",function(a){return a.hover}).style("fill",function(a,b){return u(a,b)}).style("stroke",function(a,b){return u(a,b)}),K.watchTransition(F,"multibarhorizontal: groups").style("stroke-opacity",1).style("fill-opacity",.75);var L=K.selectAll("g.nv-bar").data(function(a){return a.values});L.exit().remove();var M=L.enter().append("g").attr("transform",function(a,c,d){return"translate("+i(x?a.y0:0)+","+(x?0:d*o.rangeBand()/b.length+o(q(a,c)))+")"});M.append("rect").attr("width",0).attr("height",o.rangeBand()/(x?1:b.length)),L.on("mouseover",function(a,b){d3.select(this).classed("hover",!0),E.elementMouseover({data:a,index:b,color:d3.select(this).style("fill")})}).on("mouseout",function(a,b){d3.select(this).classed("hover",!1),E.elementMouseout({data:a,index:b,color:d3.select(this).style("fill")})}).on("mouseout",function(a,b){E.elementMouseout({data:a,index:b,color:d3.select(this).style("fill")})}).on("mousemove",function(a,b){E.elementMousemove({data:a,index:b,color:d3.select(this).style("fill")})}).on("click",function(a,b){E.elementClick({data:a,index:b,color:d3.select(this).style("fill")}),d3.event.stopPropagation()}).on("dblclick",function(a,b){E.elementDblClick({data:a,index:b,color:d3.select(this).style("fill")}),d3.event.stopPropagation()}),s(b[0].values[0],0)&&(M.append("polyline"),L.select("polyline").attr("fill","none").attr("stroke",function(a,b,c){return w(a,c,b)}).attr("points",function(a,c){var d=s(a,c),e=.8*o.rangeBand()/(2*(x?1:b.length));d=d.length?d:[-Math.abs(d),Math.abs(d)],d=d.map(function(a){return p(a)-p(0)});var f=[[d[0],-e],[d[0],e],[d[0],0],[d[1],0],[d[1],-e],[d[1],e]];return f.map(function(a){return a.join(",")}).join(" ")}).attr("transform",function(a,c){var d=o.rangeBand()/(2*(x?1:b.length));return"translate("+(r(a,c)<0?0:p(r(a,c))-p(0))+", "+d+")"})),M.append("text"),y&&!x?(L.select("text").attr("text-anchor",function(a,b){return r(a,b)<0?"end":"start"}).attr("y",o.rangeBand()/(2*b.length)).attr("dy",".32em").text(function(a,b){var c=C(r(a,b)),d=s(a,b);return void 0===d?c:d.length?c+"+"+C(Math.abs(d[1]))+"-"+C(Math.abs(d[0])):c+"±"+C(Math.abs(d))}),L.watchTransition(F,"multibarhorizontal: bars").select("text").attr("x",function(a,b){return r(a,b)<0?-4:p(r(a,b))-p(0)+4})):L.selectAll("text").text(""),z&&!x?(M.append("text").classed("nv-bar-label",!0),L.select("text.nv-bar-label").attr("text-anchor",function(a,b){return r(a,b)<0?"start":"end"}).attr("y",o.rangeBand()/(2*b.length)).attr("dy",".32em").text(function(a,b){return q(a,b)}),L.watchTransition(F,"multibarhorizontal: bars").select("text.nv-bar-label").attr("x",function(a,b){return r(a,b)<0?p(0)-p(r(a,b))+4:-4})):L.selectAll("text.nv-bar-label").text(""),L.attr("class",function(a,b){return r(a,b)<0?"nv-bar negative":"nv-bar positive"}),v&&(c||(c=b.map(function(){return!0})),L.style("fill",function(a,b,d){return d3.rgb(v(a,b)).darker(c.map(function(a,b){return b}).filter(function(a,b){return!c[b]})[d]).toString()}).style("stroke",function(a,b,d){return d3.rgb(v(a,b)).darker(c.map(function(a,b){return b}).filter(function(a,b){return!c[b]})[d]).toString()})),x?L.watchTransition(F,"multibarhorizontal: bars").attr("transform",function(a,b){return"translate("+p(a.y1)+","+o(q(a,b))+")"}).select("rect").attr("width",function(a,b){return Math.abs(p(r(a,b)+a.y0)-p(a.y0))}).attr("height",o.rangeBand()):L.watchTransition(F,"multibarhorizontal: bars").attr("transform",function(a,c){return"translate("+p(r(a,c)<0?r(a,c):0)+","+(a.series*o.rangeBand()/b.length+o(q(a,c)))+")"}).select("rect").attr("height",o.rangeBand()/b.length).attr("width",function(a,b){return Math.max(Math.abs(p(r(a,b))-p(0)),1)}),h=o.copy(),i=p.copy()}),F.renderEnd("multibarHorizontal immediate"),b}var c,d,e,f,g,h,i,j={top:0,right:0,bottom:0,left:0},k=960,l=500,m=Math.floor(1e4*Math.random()),n=null,o=d3.scale.ordinal(),p=d3.scale.linear(),q=function(a){return a.x},r=function(a){return a.y},s=function(a){return a.yErr},t=[0],u=a.utils.defaultColor(),v=null,w=a.utils.defaultColor(),x=!1,y=!1,z=!1,A=60,B=.1,C=d3.format(",.2f"),D=250,E=d3.dispatch("chartClick","elementClick","elementDblClick","elementMouseover","elementMouseout","elementMousemove","renderEnd"),F=a.utils.renderWatch(E,D);return b.dispatch=E,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return k},set:function(a){k=a}},height:{get:function(){return l},set:function(a){l=a}},x:{get:function(){return q},set:function(a){q=a}},y:{get:function(){return r},set:function(a){r=a}},yErr:{get:function(){return s},set:function(a){s=a}},xScale:{get:function(){return o},set:function(a){o=a}},yScale:{get:function(){return p},set:function(a){p=a}},xDomain:{get:function(){return d},set:function(a){d=a}},yDomain:{get:function(){return e},set:function(a){e=a}},xRange:{get:function(){return f},set:function(a){f=a}},yRange:{get:function(){return g},set:function(a){g=a}},forceY:{get:function(){return t},set:function(a){t=a}},stacked:{get:function(){return x},set:function(a){x=a}},showValues:{get:function(){return y},set:function(a){y=a}},disabled:{get:function(){return c},set:function(a){c=a}},id:{get:function(){return m},set:function(a){m=a}},valueFormat:{get:function(){return C},set:function(a){C=a}},valuePadding:{get:function(){return A},set:function(a){A=a}},groupSpacing:{get:function(){return B},set:function(a){B=a}},margin:{get:function(){return j},set:function(a){j.top=void 0!==a.top?a.top:j.top,j.right=void 0!==a.right?a.right:j.right,j.bottom=void 0!==a.bottom?a.bottom:j.bottom,j.left=void 0!==a.left?a.left:j.left}},duration:{get:function(){return D},set:function(a){D=a,F.reset(D)}},color:{get:function(){return u},set:function(b){u=a.utils.getColor(b)}},barColor:{get:function(){return v},set:function(b){v=b?a.utils.getColor(b):null}},errorBarColor:{get:function(){return w},set:function(b){w=b?a.utils.getColor(b):null}}}),a.utils.initOptions(b),b},a.models.multiBarHorizontalChart=function(){"use strict";function b(j){return C.reset(),C.models(e),r&&C.models(f),s&&C.models(g),j.each(function(j){var w=d3.select(this);a.utils.initSVG(w);var C=a.utils.availableWidth(l,w,k),D=a.utils.availableHeight(m,w,k);if(b.update=function(){w.transition().duration(z).call(b)},b.container=this,t=e.stacked(),u.setter(B(j),b.update).getter(A(j)).update(),u.disabled=j.map(function(a){return!!a.disabled}),!v){var E;v={};for(E in u)u[E]instanceof Array?v[E]=u[E].slice(0):v[E]=u[E]}if(!(j&&j.length&&j.filter(function(a){return a.values.length}).length))return a.utils.noData(b,w),b;w.selectAll(".nv-noData").remove(),c=e.xScale(),d=e.yScale();var F=w.selectAll("g.nv-wrap.nv-multiBarHorizontalChart").data([j]),G=F.enter().append("g").attr("class","nvd3 nv-wrap nv-multiBarHorizontalChart").append("g"),H=F.select("g");if(G.append("g").attr("class","nv-x nv-axis"),G.append("g").attr("class","nv-y nv-axis").append("g").attr("class","nv-zeroLine").append("line"),G.append("g").attr("class","nv-barsWrap"),G.append("g").attr("class","nv-legendWrap"),G.append("g").attr("class","nv-controlsWrap"),q&&(h.width(C-y()),H.select(".nv-legendWrap").datum(j).call(h),k.top!=h.height()&&(k.top=h.height(),D=a.utils.availableHeight(m,w,k)),H.select(".nv-legendWrap").attr("transform","translate("+y()+","+-k.top+")")),o){var I=[{key:p.grouped||"Grouped",disabled:e.stacked()},{key:p.stacked||"Stacked",disabled:!e.stacked()}];i.width(y()).color(["#444","#444","#444"]),H.select(".nv-controlsWrap").datum(I).attr("transform","translate(0,"+-k.top+")").call(i)}F.attr("transform","translate("+k.left+","+k.top+")"),e.disabled(j.map(function(a){return a.disabled})).width(C).height(D).color(j.map(function(a,b){return a.color||n(a,b)}).filter(function(a,b){return!j[b].disabled}));var J=H.select(".nv-barsWrap").datum(j.filter(function(a){return!a.disabled}));if(J.transition().call(e),r){f.scale(c)._ticks(a.utils.calcTicksY(D/24,j)).tickSize(-C,0),H.select(".nv-x.nv-axis").call(f);var K=H.select(".nv-x.nv-axis").selectAll("g");K.selectAll("line, text")}s&&(g.scale(d)._ticks(a.utils.calcTicksX(C/100,j)).tickSize(-D,0),H.select(".nv-y.nv-axis").attr("transform","translate(0,"+D+")"),H.select(".nv-y.nv-axis").call(g)),H.select(".nv-zeroLine line").attr("x1",d(0)).attr("x2",d(0)).attr("y1",0).attr("y2",-D),h.dispatch.on("stateChange",function(a){for(var c in a)u[c]=a[c];x.stateChange(u),b.update()}),i.dispatch.on("legendClick",function(a,c){if(a.disabled){switch(I=I.map(function(a){return a.disabled=!0,a}),a.disabled=!1,a.key){case"Grouped":e.stacked(!1);break;case"Stacked":e.stacked(!0)}u.stacked=e.stacked(),x.stateChange(u),t=e.stacked(),b.update()}}),x.on("changeState",function(a){"undefined"!=typeof a.disabled&&(j.forEach(function(b,c){b.disabled=a.disabled[c]}),u.disabled=a.disabled),"undefined"!=typeof a.stacked&&(e.stacked(a.stacked),u.stacked=a.stacked,t=a.stacked),b.update()})}),C.renderEnd("multibar horizontal chart immediate"),b}var c,d,e=a.models.multiBarHorizontal(),f=a.models.axis(),g=a.models.axis(),h=a.models.legend().height(30),i=a.models.legend().height(30),j=a.models.tooltip(),k={top:30,right:20,bottom:50,left:60},l=null,m=null,n=a.utils.defaultColor(),o=!0,p={},q=!0,r=!0,s=!0,t=!1,u=a.utils.state(),v=null,w=null,x=d3.dispatch("stateChange","changeState","renderEnd"),y=function(){return o?180:0},z=250;u.stacked=!1,e.stacked(t),f.orient("left").tickPadding(5).showMaxMin(!1).tickFormat(function(a){return a}),g.orient("bottom").tickFormat(d3.format(",.1f")),j.duration(0).valueFormatter(function(a,b){return g.tickFormat()(a,b)}).headerFormatter(function(a,b){return f.tickFormat()(a,b)}),i.updateState(!1);var A=function(a){return function(){return{active:a.map(function(a){return!a.disabled}),stacked:t}}},B=function(a){return function(b){void 0!==b.stacked&&(t=b.stacked),void 0!==b.active&&a.forEach(function(a,c){a.disabled=!b.active[c]})}},C=a.utils.renderWatch(x,z);return e.dispatch.on("elementMouseover.tooltip",function(a){a.value=b.x()(a.data),a.series={key:a.data.key,value:b.y()(a.data),color:a.color},j.data(a).hidden(!1)}),e.dispatch.on("elementMouseout.tooltip",function(a){j.hidden(!0)}),e.dispatch.on("elementMousemove.tooltip",function(a){j.position({top:d3.event.pageY,left:d3.event.pageX})()}),b.dispatch=x,b.multibar=e,b.legend=h,b.controls=i,b.xAxis=f,b.yAxis=g,b.state=u,b.tooltip=j,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return l},set:function(a){l=a}},height:{get:function(){return m},set:function(a){m=a}},showLegend:{get:function(){return q},set:function(a){q=a}},showControls:{get:function(){return o},set:function(a){o=a}},controlLabels:{get:function(){return p},set:function(a){p=a}},showXAxis:{get:function(){return r},set:function(a){r=a}},showYAxis:{get:function(){return s},set:function(a){s=a}},defaultState:{get:function(){return v},set:function(a){v=a}},noData:{get:function(){return w},set:function(a){w=a}},tooltips:{get:function(){return j.enabled()},set:function(b){a.deprecated("tooltips","use chart.tooltip.enabled() instead"),j.enabled(!!b)}},tooltipContent:{get:function(){return j.contentGenerator()},set:function(b){a.deprecated("tooltipContent","use chart.tooltip.contentGenerator() instead"),j.contentGenerator(b)}},margin:{get:function(){return k},set:function(a){k.top=void 0!==a.top?a.top:k.top,k.right=void 0!==a.right?a.right:k.right,k.bottom=void 0!==a.bottom?a.bottom:k.bottom,k.left=void 0!==a.left?a.left:k.left}},duration:{get:function(){return z},set:function(a){z=a,C.reset(z),e.duration(z),f.duration(z),g.duration(z)}},color:{get:function(){return n},set:function(b){n=a.utils.getColor(b),h.color(n)}},barColor:{get:function(){return e.barColor},set:function(a){e.barColor(a),h.color(function(a,b){return d3.rgb("#ccc").darker(1.5*b).toString()})}}}),a.utils.inheritOptions(b,e),a.utils.initOptions(b),b},a.models.multiChart=function(){"use strict";function b(j){return j.each(function(j){function k(a){var b=2===j[a.seriesIndex].yAxis?z:y;a.value=a.point.x,a.series={value:a.point.y,color:a.point.color},B.duration(100).valueFormatter(function(a,c){return b.tickFormat()(a,c)}).data(a).position(a.pos).hidden(!1)}function l(a){var b=2===j[a.seriesIndex].yAxis?z:y;a.point.x=v.x()(a.point),a.point.y=v.y()(a.point),B.duration(100).valueFormatter(function(a,c){return b.tickFormat()(a,c)}).data(a).position(a.pos).hidden(!1)}function n(a){var b=2===j[a.data.series].yAxis?z:y;a.value=t.x()(a.data),a.series={value:t.y()(a.data),color:a.color},B.duration(0).valueFormatter(function(a,c){return b.tickFormat()(a,c)}).data(a).hidden(!1)}var C=d3.select(this);a.utils.initSVG(C),b.update=function(){C.transition().call(b)},b.container=this;var D=a.utils.availableWidth(g,C,e),E=a.utils.availableHeight(h,C,e),F=j.filter(function(a){return"line"==a.type&&1==a.yAxis}),G=j.filter(function(a){return"line"==a.type&&2==a.yAxis}),H=j.filter(function(a){return"bar"==a.type&&1==a.yAxis}),I=j.filter(function(a){return"bar"==a.type&&2==a.yAxis}),J=j.filter(function(a){return"area"==a.type&&1==a.yAxis}),K=j.filter(function(a){return"area"==a.type&&2==a.yAxis});if(!(j&&j.length&&j.filter(function(a){return a.values.length}).length))return a.utils.noData(b,C),b;C.selectAll(".nv-noData").remove();var L=j.filter(function(a){return!a.disabled&&1==a.yAxis}).map(function(a){return a.values.map(function(a,b){return{x:a.x,y:a.y}})}),M=j.filter(function(a){return!a.disabled&&2==a.yAxis}).map(function(a){return a.values.map(function(a,b){return{x:a.x,y:a.y}})});o.domain(d3.extent(d3.merge(L.concat(M)),function(a){return a.x})).range([0,D]);var N=C.selectAll("g.wrap.multiChart").data([j]),O=N.enter().append("g").attr("class","wrap nvd3 multiChart").append("g");O.append("g").attr("class","nv-x nv-axis"),O.append("g").attr("class","nv-y1 nv-axis"),O.append("g").attr("class","nv-y2 nv-axis"),O.append("g").attr("class","lines1Wrap"),O.append("g").attr("class","lines2Wrap"),O.append("g").attr("class","bars1Wrap"),O.append("g").attr("class","bars2Wrap"),O.append("g").attr("class","stack1Wrap"),O.append("g").attr("class","stack2Wrap"),O.append("g").attr("class","legendWrap");var P=N.select("g"),Q=j.map(function(a,b){return j[b].color||f(a,b)});if(i){var R=A.align()?D/2:D,S=A.align()?R:0;A.width(R),A.color(Q),P.select(".legendWrap").datum(j.map(function(a){return a.originalKey=void 0===a.originalKey?a.key:a.originalKey,a.key=a.originalKey+(1==a.yAxis?"":" (right axis)"),a})).call(A),e.top!=A.height()&&(e.top=A.height(),E=a.utils.availableHeight(h,C,e)),P.select(".legendWrap").attr("transform","translate("+S+","+-e.top+")")}r.width(D).height(E).interpolate(m).color(Q.filter(function(a,b){return!j[b].disabled&&1==j[b].yAxis&&"line"==j[b].type})),s.width(D).height(E).interpolate(m).color(Q.filter(function(a,b){return!j[b].disabled&&2==j[b].yAxis&&"line"==j[b].type})),t.width(D).height(E).color(Q.filter(function(a,b){return!j[b].disabled&&1==j[b].yAxis&&"bar"==j[b].type})),u.width(D).height(E).color(Q.filter(function(a,b){return!j[b].disabled&&2==j[b].yAxis&&"bar"==j[b].type})),v.width(D).height(E).color(Q.filter(function(a,b){return!j[b].disabled&&1==j[b].yAxis&&"area"==j[b].type})),w.width(D).height(E).color(Q.filter(function(a,b){return!j[b].disabled&&2==j[b].yAxis&&"area"==j[b].type})),P.attr("transform","translate("+e.left+","+e.top+")");var T=P.select(".lines1Wrap").datum(F.filter(function(a){return!a.disabled})),U=P.select(".bars1Wrap").datum(H.filter(function(a){return!a.disabled})),V=P.select(".stack1Wrap").datum(J.filter(function(a){return!a.disabled})),W=P.select(".lines2Wrap").datum(G.filter(function(a){return!a.disabled})),X=P.select(".bars2Wrap").datum(I.filter(function(a){return!a.disabled})),Y=P.select(".stack2Wrap").datum(K.filter(function(a){return!a.disabled})),Z=J.length?J.map(function(a){return a.values}).reduce(function(a,b){return a.map(function(a,c){return{x:a.x,y:a.y+b[c].y}})}).concat([{x:0,y:0}]):[],$=K.length?K.map(function(a){return a.values}).reduce(function(a,b){return a.map(function(a,c){return{x:a.x,y:a.y+b[c].y}})}).concat([{x:0,y:0}]):[];p.domain(c||d3.extent(d3.merge(L).concat(Z),function(a){return a.y})).range([0,E]),q.domain(d||d3.extent(d3.merge(M).concat($),function(a){return a.y})).range([0,E]),r.yDomain(p.domain()),t.yDomain(p.domain()),v.yDomain(p.domain()),s.yDomain(q.domain()),u.yDomain(q.domain()),w.yDomain(q.domain()),J.length&&d3.transition(V).call(v),K.length&&d3.transition(Y).call(w),H.length&&d3.transition(U).call(t),I.length&&d3.transition(X).call(u),F.length&&d3.transition(T).call(r),G.length&&d3.transition(W).call(s),x._ticks(a.utils.calcTicksX(D/100,j)).tickSize(-E,0),P.select(".nv-x.nv-axis").attr("transform","translate(0,"+E+")"),d3.transition(P.select(".nv-x.nv-axis")).call(x),y._ticks(a.utils.calcTicksY(E/36,j)).tickSize(-D,0),d3.transition(P.select(".nv-y1.nv-axis")).call(y),z._ticks(a.utils.calcTicksY(E/36,j)).tickSize(-D,0),d3.transition(P.select(".nv-y2.nv-axis")).call(z),P.select(".nv-y1.nv-axis").classed("nv-disabled",L.length?!1:!0).attr("transform","translate("+o.range()[0]+",0)"),P.select(".nv-y2.nv-axis").classed("nv-disabled",M.length?!1:!0).attr("transform","translate("+o.range()[1]+",0)"),A.dispatch.on("stateChange",function(a){b.update()}),r.dispatch.on("elementMouseover.tooltip",k),s.dispatch.on("elementMouseover.tooltip",k),r.dispatch.on("elementMouseout.tooltip",function(a){B.hidden(!0)}),s.dispatch.on("elementMouseout.tooltip",function(a){B.hidden(!0)}),v.dispatch.on("elementMouseover.tooltip",l),w.dispatch.on("elementMouseover.tooltip",l),v.dispatch.on("elementMouseout.tooltip",function(a){B.hidden(!0)}),w.dispatch.on("elementMouseout.tooltip",function(a){B.hidden(!0)}),t.dispatch.on("elementMouseover.tooltip",n),u.dispatch.on("elementMouseover.tooltip",n),t.dispatch.on("elementMouseout.tooltip",function(a){B.hidden(!0)}),u.dispatch.on("elementMouseout.tooltip",function(a){B.hidden(!0)}),t.dispatch.on("elementMousemove.tooltip",function(a){B.position({top:d3.event.pageY,left:d3.event.pageX})()}),u.dispatch.on("elementMousemove.tooltip",function(a){B.position({top:d3.event.pageY,left:d3.event.pageX})()})}),b}var c,d,e={top:30,right:20,bottom:50,left:60},f=a.utils.defaultColor(),g=null,h=null,i=!0,j=null,k=function(a){return a.x},l=function(a){return a.y},m="monotone",n=!0,o=d3.scale.linear(),p=d3.scale.linear(),q=d3.scale.linear(),r=a.models.line().yScale(p),s=a.models.line().yScale(q),t=a.models.multiBar().stacked(!1).yScale(p),u=a.models.multiBar().stacked(!1).yScale(q),v=a.models.stackedArea().yScale(p),w=a.models.stackedArea().yScale(q),x=a.models.axis().scale(o).orient("bottom").tickPadding(5),y=a.models.axis().scale(p).orient("left"),z=a.models.axis().scale(q).orient("right"),A=a.models.legend().height(30),B=a.models.tooltip(),C=d3.dispatch();return b.dispatch=C,b.lines1=r,b.lines2=s,b.bars1=t,b.bars2=u,b.stack1=v,b.stack2=w,b.xAxis=x,b.yAxis1=y,b.yAxis2=z,b.tooltip=B,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return g},set:function(a){g=a}},height:{get:function(){return h},set:function(a){h=a}},showLegend:{get:function(){return i},set:function(a){i=a}},yDomain1:{get:function(){return c},set:function(a){c=a}},yDomain2:{get:function(){return d},set:function(a){d=a}},noData:{get:function(){return j},set:function(a){j=a}},interpolate:{get:function(){return m},set:function(a){m=a}},tooltips:{get:function(){return B.enabled()},set:function(b){a.deprecated("tooltips","use chart.tooltip.enabled() instead"),B.enabled(!!b)}},tooltipContent:{get:function(){return B.contentGenerator()},set:function(b){a.deprecated("tooltipContent","use chart.tooltip.contentGenerator() instead"),B.contentGenerator(b)}},margin:{get:function(){return e},set:function(a){e.top=void 0!==a.top?a.top:e.top,e.right=void 0!==a.right?a.right:e.right,e.bottom=void 0!==a.bottom?a.bottom:e.bottom,e.left=void 0!==a.left?a.left:e.left}},color:{get:function(){return f},set:function(b){f=a.utils.getColor(b)}},x:{get:function(){return k},set:function(a){k=a,r.x(a),s.x(a),t.x(a),u.x(a),v.x(a),w.x(a)}},y:{get:function(){return l},set:function(a){l=a,r.y(a),s.y(a),v.y(a),w.y(a),t.y(a),u.y(a)}},useVoronoi:{get:function(){return n},set:function(a){n=a,r.useVoronoi(a),s.useVoronoi(a),v.useVoronoi(a),w.useVoronoi(a)}}}),a.utils.initOptions(b),b},a.models.ohlcBar=function(){"use strict";function b(y){return y.each(function(b){k=d3.select(this);var y=a.utils.availableWidth(h,k,g),A=a.utils.availableHeight(i,k,g);a.utils.initSVG(k);var B=y/b[0].values.length*.9;l.domain(c||d3.extent(b[0].values.map(n).concat(t))),v?l.range(e||[.5*y/b[0].values.length,y*(b[0].values.length-.5)/b[0].values.length]):l.range(e||[5+B/2,y-B/2-5]),m.domain(d||[d3.min(b[0].values.map(s).concat(u)),d3.max(b[0].values.map(r).concat(u))]).range(f||[A,0]),l.domain()[0]===l.domain()[1]&&(l.domain()[0]?l.domain([l.domain()[0]-.01*l.domain()[0],l.domain()[1]+.01*l.domain()[1]]):l.domain([-1,1])),m.domain()[0]===m.domain()[1]&&(m.domain()[0]?m.domain([m.domain()[0]+.01*m.domain()[0],m.domain()[1]-.01*m.domain()[1]]):m.domain([-1,1]));var C=d3.select(this).selectAll("g.nv-wrap.nv-ohlcBar").data([b[0].values]),D=C.enter().append("g").attr("class","nvd3 nv-wrap nv-ohlcBar"),E=D.append("defs"),F=D.append("g"),G=C.select("g");F.append("g").attr("class","nv-ticks"),C.attr("transform","translate("+g.left+","+g.top+")"),k.on("click",function(a,b){z.chartClick({data:a,index:b,pos:d3.event,id:j})}),E.append("clipPath").attr("id","nv-chart-clip-path-"+j).append("rect"),C.select("#nv-chart-clip-path-"+j+" rect").attr("width",y).attr("height",A),G.attr("clip-path",w?"url(#nv-chart-clip-path-"+j+")":"");var H=C.select(".nv-ticks").selectAll(".nv-tick").data(function(a){return a});H.exit().remove(),H.enter().append("path").attr("class",function(a,b,c){return(p(a,b)>q(a,b)?"nv-tick negative":"nv-tick positive")+" nv-tick-"+c+"-"+b}).attr("d",function(a,b){return"m0,0l0,"+(m(p(a,b))-m(r(a,b)))+"l"+-B/2+",0l"+B/2+",0l0,"+(m(s(a,b))-m(p(a,b)))+"l0,"+(m(q(a,b))-m(s(a,b)))+"l"+B/2+",0l"+-B/2+",0z"}).attr("transform",function(a,b){return"translate("+l(n(a,b))+","+m(r(a,b))+")"}).attr("fill",function(a,b){return x[0]}).attr("stroke",function(a,b){return x[0]}).attr("x",0).attr("y",function(a,b){return m(Math.max(0,o(a,b)))}).attr("height",function(a,b){return Math.abs(m(o(a,b))-m(0))}),H.attr("class",function(a,b,c){return(p(a,b)>q(a,b)?"nv-tick negative":"nv-tick positive")+" nv-tick-"+c+"-"+b}),d3.transition(H).attr("transform",function(a,b){return"translate("+l(n(a,b))+","+m(r(a,b))+")"}).attr("d",function(a,c){var d=y/b[0].values.length*.9;return"m0,0l0,"+(m(p(a,c))-m(r(a,c)))+"l"+-d/2+",0l"+d/2+",0l0,"+(m(s(a,c))-m(p(a,c)))+"l0,"+(m(q(a,c))-m(s(a,c)))+"l"+d/2+",0l"+-d/2+",0z"})}),b}var c,d,e,f,g={top:0,right:0,bottom:0,left:0},h=null,i=null,j=Math.floor(1e4*Math.random()),k=null,l=d3.scale.linear(),m=d3.scale.linear(),n=function(a){return a.x},o=function(a){return a.y},p=function(a){return a.open},q=function(a){return a.close},r=function(a){return a.high},s=function(a){return a.low; +},t=[],u=[],v=!1,w=!0,x=a.utils.defaultColor(),y=!1,z=d3.dispatch("tooltipShow","tooltipHide","stateChange","changeState","renderEnd","chartClick","elementClick","elementDblClick","elementMouseover","elementMouseout","elementMousemove");return b.highlightPoint=function(a,c){b.clearHighlights(),k.select(".nv-ohlcBar .nv-tick-0-"+a).classed("hover",c)},b.clearHighlights=function(){k.select(".nv-ohlcBar .nv-tick.hover").classed("hover",!1)},b.dispatch=z,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return h},set:function(a){h=a}},height:{get:function(){return i},set:function(a){i=a}},xScale:{get:function(){return l},set:function(a){l=a}},yScale:{get:function(){return m},set:function(a){m=a}},xDomain:{get:function(){return c},set:function(a){c=a}},yDomain:{get:function(){return d},set:function(a){d=a}},xRange:{get:function(){return e},set:function(a){e=a}},yRange:{get:function(){return f},set:function(a){f=a}},forceX:{get:function(){return t},set:function(a){t=a}},forceY:{get:function(){return u},set:function(a){u=a}},padData:{get:function(){return v},set:function(a){v=a}},clipEdge:{get:function(){return w},set:function(a){w=a}},id:{get:function(){return j},set:function(a){j=a}},interactive:{get:function(){return y},set:function(a){y=a}},x:{get:function(){return n},set:function(a){n=a}},y:{get:function(){return o},set:function(a){o=a}},open:{get:function(){return p()},set:function(a){p=a}},close:{get:function(){return q()},set:function(a){q=a}},high:{get:function(){return r},set:function(a){r=a}},low:{get:function(){return s},set:function(a){s=a}},margin:{get:function(){return g},set:function(a){g.top=void 0!=a.top?a.top:g.top,g.right=void 0!=a.right?a.right:g.right,g.bottom=void 0!=a.bottom?a.bottom:g.bottom,g.left=void 0!=a.left?a.left:g.left}},color:{get:function(){return x},set:function(b){x=a.utils.getColor(b)}}}),a.utils.initOptions(b),b},a.models.parallelCoordinates=function(){"use strict";function b(p){return p.each(function(b){function p(a){return F(h.map(function(b){if(isNaN(a[b])||isNaN(parseFloat(a[b]))){var c=g[b].domain(),d=g[b].range(),e=c[0]-(c[1]-c[0])/9;if(J.indexOf(b)<0){var h=d3.scale.linear().domain([e,c[1]]).range([x-12,d[1]]);g[b].brush.y(h),J.push(b)}return[f(b),g[b](e)]}return J.length>0?(D.style("display","inline"),E.style("display","inline")):(D.style("display","none"),E.style("display","none")),[f(b),g[b](a[b])]}))}function q(){var a=h.filter(function(a){return!g[a].brush.empty()}),b=a.map(function(a){return g[a].brush.extent()});k=[],a.forEach(function(a,c){k[c]={dimension:a,extent:b[c]}}),l=[],M.style("display",function(c){var d=a.every(function(a,d){return isNaN(c[a])&&b[d][0]==g[a].brush.y().domain()[0]?!0:b[d][0]<=c[a]&&c[a]<=b[d][1]});return d&&l.push(c),d?null:"none"}),o.brush({filters:k,active:l})}function r(a,b){m[a]=this.parentNode.__origin__=f(a),L.attr("visibility","hidden")}function s(a,b){m[a]=Math.min(w,Math.max(0,this.parentNode.__origin__+=d3.event.x)),M.attr("d",p),h.sort(function(a,b){return u(a)-u(b)}),f.domain(h),N.attr("transform",function(a){return"translate("+u(a)+")"})}function t(a,b){delete this.parentNode.__origin__,delete m[a],d3.select(this.parentNode).attr("transform","translate("+f(a)+")"),M.attr("d",p),L.attr("d",p).attr("visibility",null)}function u(a){var b=m[a];return null==b?f(a):b}var v=d3.select(this),w=a.utils.availableWidth(d,v,c),x=a.utils.availableHeight(e,v,c);a.utils.initSVG(v),l=b,f.rangePoints([0,w],1).domain(h);var y={};h.forEach(function(a){var c=d3.extent(b,function(b){return+b[a]});return y[a]=!1,void 0===c[0]&&(y[a]=!0,c[0]=0,c[1]=0),c[0]===c[1]&&(c[0]=c[0]-1,c[1]=c[1]+1),g[a]=d3.scale.linear().domain(c).range([.9*(x-12),0]),g[a].brush=d3.svg.brush().y(g[a]).on("brush",q),"name"!=a});var z=v.selectAll("g.nv-wrap.nv-parallelCoordinates").data([b]),A=z.enter().append("g").attr("class","nvd3 nv-wrap nv-parallelCoordinates"),B=A.append("g"),C=z.select("g");B.append("g").attr("class","nv-parallelCoordinates background"),B.append("g").attr("class","nv-parallelCoordinates foreground"),B.append("g").attr("class","nv-parallelCoordinates missingValuesline"),z.attr("transform","translate("+c.left+","+c.top+")");var D,E,F=d3.svg.line().interpolate("cardinal").tension(n),G=d3.svg.axis().orient("left"),H=d3.behavior.drag().on("dragstart",r).on("drag",s).on("dragend",t),I=f.range()[1]-f.range()[0],J=[],K=[0+I/2,x-12,w-I/2,x-12];D=z.select(".missingValuesline").selectAll("line").data([K]),D.enter().append("line"),D.exit().remove(),D.attr("x1",function(a){return a[0]}).attr("y1",function(a){return a[1]}).attr("x2",function(a){return a[2]}).attr("y2",function(a){return a[3]}),E=z.select(".missingValuesline").selectAll("text").data(["undefined values"]),E.append("text").data(["undefined values"]),E.enter().append("text"),E.exit().remove(),E.attr("y",x).attr("x",w-92-I/2).text(function(a){return a});var L=z.select(".background").selectAll("path").data(b);L.enter().append("path"),L.exit().remove(),L.attr("d",p);var M=z.select(".foreground").selectAll("path").data(b);M.enter().append("path"),M.exit().remove(),M.attr("d",p).attr("stroke",j),M.on("mouseover",function(a,b){d3.select(this).classed("hover",!0),o.elementMouseover({label:a.name,data:a.data,index:b,pos:[d3.mouse(this.parentNode)[0],d3.mouse(this.parentNode)[1]]})}),M.on("mouseout",function(a,b){d3.select(this).classed("hover",!1),o.elementMouseout({label:a.name,data:a.data,index:b})});var N=C.selectAll(".dimension").data(h),O=N.enter().append("g").attr("class","nv-parallelCoordinates dimension");O.append("g").attr("class","nv-parallelCoordinates nv-axis"),O.append("g").attr("class","nv-parallelCoordinates-brush"),O.append("text").attr("class","nv-parallelCoordinates nv-label"),N.attr("transform",function(a){return"translate("+f(a)+",0)"}),N.exit().remove(),N.select(".nv-label").style("cursor","move").attr("dy","-1em").attr("text-anchor","middle").text(String).on("mouseover",function(a,b){o.elementMouseover({dim:a,pos:[d3.mouse(this.parentNode.parentNode)[0],d3.mouse(this.parentNode.parentNode)[1]]})}).on("mouseout",function(a,b){o.elementMouseout({dim:a})}).call(H),N.select(".nv-axis").each(function(a,b){d3.select(this).call(G.scale(g[a]).tickFormat(d3.format(i[b])))}),N.select(".nv-parallelCoordinates-brush").each(function(a){d3.select(this).call(g[a].brush)}).selectAll("rect").attr("x",-8).attr("width",16)}),b}var c={top:30,right:0,bottom:10,left:0},d=null,e=null,f=d3.scale.ordinal(),g={},h=[],i=[],j=a.utils.defaultColor(),k=[],l=[],m=[],n=1,o=d3.dispatch("brush","elementMouseover","elementMouseout");return b.dispatch=o,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return d},set:function(a){d=a}},height:{get:function(){return e},set:function(a){e=a}},dimensionNames:{get:function(){return h},set:function(a){h=a}},dimensionFormats:{get:function(){return i},set:function(a){i=a}},lineTension:{get:function(){return n},set:function(a){n=a}},dimensions:{get:function(){return h},set:function(b){a.deprecated("dimensions","use dimensionNames instead"),h=b}},margin:{get:function(){return c},set:function(a){c.top=void 0!==a.top?a.top:c.top,c.right=void 0!==a.right?a.right:c.right,c.bottom=void 0!==a.bottom?a.bottom:c.bottom,c.left=void 0!==a.left?a.left:c.left}},color:{get:function(){return j},set:function(b){j=a.utils.getColor(b)}}}),a.utils.initOptions(b),b},a.models.pie=function(){"use strict";function b(E){return D.reset(),E.each(function(b){function E(a,b){a.endAngle=isNaN(a.endAngle)?0:a.endAngle,a.startAngle=isNaN(a.startAngle)?0:a.startAngle,p||(a.innerRadius=0);var c=d3.interpolate(this._current,a);return this._current=c(0),function(a){return B[b](c(a))}}var F=d-c.left-c.right,G=e-c.top-c.bottom,H=Math.min(F,G)/2,I=[],J=[];if(i=d3.select(this),0===z.length)for(var K=H-H/5,L=y*H,M=0;M<b[0].length;M++)I.push(K),J.push(L);else I=z.map(function(a){return(a.outer-a.outer/5)*H}),J=z.map(function(a){return(a.inner-a.inner/5)*H}),y=d3.min(z.map(function(a){return a.inner-a.inner/5}));a.utils.initSVG(i);var N=i.selectAll(".nv-wrap.nv-pie").data(b),O=N.enter().append("g").attr("class","nvd3 nv-wrap nv-pie nv-chart-"+h),P=O.append("g"),Q=N.select("g"),R=P.append("g").attr("class","nv-pie");P.append("g").attr("class","nv-pieLabels"),N.attr("transform","translate("+c.left+","+c.top+")"),Q.select(".nv-pie").attr("transform","translate("+F/2+","+G/2+")"),Q.select(".nv-pieLabels").attr("transform","translate("+F/2+","+G/2+")"),i.on("click",function(a,b){A.chartClick({data:a,index:b,pos:d3.event,id:h})}),B=[],C=[];for(var M=0;M<b[0].length;M++){var S=d3.svg.arc().outerRadius(I[M]),T=d3.svg.arc().outerRadius(I[M]+5);u!==!1&&(S.startAngle(u),T.startAngle(u)),w!==!1&&(S.endAngle(w),T.endAngle(w)),p&&(S.innerRadius(J[M]),T.innerRadius(J[M])),S.cornerRadius&&x&&(S.cornerRadius(x),T.cornerRadius(x)),B.push(S),C.push(T)}var U=d3.layout.pie().sort(null).value(function(a){return a.disabled?0:g(a)});U.padAngle&&v&&U.padAngle(v),p&&q&&(R.append("text").attr("class","nv-pie-title"),N.select(".nv-pie-title").style("text-anchor","middle").text(function(a){return q}).style("font-size",Math.min(F,G)*y*2/(q.length+2)+"px").attr("dy","0.35em").attr("transform",function(a,b){return"translate(0, "+s+")"}));var V=N.select(".nv-pie").selectAll(".nv-slice").data(U),W=N.select(".nv-pieLabels").selectAll(".nv-label").data(U);V.exit().remove(),W.exit().remove();var X=V.enter().append("g");X.attr("class","nv-slice"),X.on("mouseover",function(a,b){d3.select(this).classed("hover",!0),r&&d3.select(this).select("path").transition().duration(70).attr("d",C[b]),A.elementMouseover({data:a.data,index:b,color:d3.select(this).style("fill")})}),X.on("mouseout",function(a,b){d3.select(this).classed("hover",!1),r&&d3.select(this).select("path").transition().duration(50).attr("d",B[b]),A.elementMouseout({data:a.data,index:b})}),X.on("mousemove",function(a,b){A.elementMousemove({data:a.data,index:b})}),X.on("click",function(a,b){A.elementClick({data:a.data,index:b,color:d3.select(this).style("fill")})}),X.on("dblclick",function(a,b){A.elementDblClick({data:a.data,index:b,color:d3.select(this).style("fill")})}),V.attr("fill",function(a,b){return j(a.data,b)}),V.attr("stroke",function(a,b){return j(a.data,b)});X.append("path").each(function(a){this._current=a});if(V.select("path").transition().attr("d",function(a,b){return B[b](a)}).attrTween("d",E),l){for(var Y=[],M=0;M<b[0].length;M++)Y.push(B[M]),m?p&&(Y[M]=d3.svg.arc().outerRadius(B[M].outerRadius()),u!==!1&&Y[M].startAngle(u),w!==!1&&Y[M].endAngle(w)):p||Y[M].innerRadius(0);W.enter().append("g").classed("nv-label",!0).each(function(a,b){var c=d3.select(this);c.attr("transform",function(a,b){if(t){a.outerRadius=I[b]+10,a.innerRadius=I[b]+15;var c=(a.startAngle+a.endAngle)/2*(180/Math.PI);return(a.startAngle+a.endAngle)/2<Math.PI?c-=90:c+=90,"translate("+Y[b].centroid(a)+") rotate("+c+")"}return a.outerRadius=H+10,a.innerRadius=H+15,"translate("+Y[b].centroid(a)+")"}),c.append("rect").style("stroke","#fff").style("fill","#fff").attr("rx",3).attr("ry",3),c.append("text").style("text-anchor",t?(a.startAngle+a.endAngle)/2<Math.PI?"start":"end":"middle").style("fill","#000")});var Z={},$=14,_=140,aa=function(a){return Math.floor(a[0]/_)*_+","+Math.floor(a[1]/$)*$};W.watchTransition(D,"pie labels").attr("transform",function(a,b){if(t){a.outerRadius=I[b]+10,a.innerRadius=I[b]+15;var c=(a.startAngle+a.endAngle)/2*(180/Math.PI);return(a.startAngle+a.endAngle)/2<Math.PI?c-=90:c+=90,"translate("+Y[b].centroid(a)+") rotate("+c+")"}a.outerRadius=H+10,a.innerRadius=H+15;var d=Y[b].centroid(a);if(a.value){var e=aa(d);Z[e]&&(d[1]-=$),Z[aa(d)]=!0}return"translate("+d+")"}),W.select(".nv-label text").style("text-anchor",function(a,b){return t?(a.startAngle+a.endAngle)/2<Math.PI?"start":"end":"middle"}).text(function(a,b){var c=(a.endAngle-a.startAngle)/(2*Math.PI),d="";if(!a.value||o>c)return"";if("function"==typeof n)d=n(a,b,{key:f(a.data),value:g(a.data),percent:k(c)});else switch(n){case"key":d=f(a.data);break;case"value":d=k(g(a.data));break;case"percent":d=d3.format("%")(c)}return d})}}),D.renderEnd("pie immediate"),b}var c={top:0,right:0,bottom:0,left:0},d=500,e=500,f=function(a){return a.x},g=function(a){return a.y},h=Math.floor(1e4*Math.random()),i=null,j=a.utils.defaultColor(),k=d3.format(",.2f"),l=!0,m=!1,n="key",o=.02,p=!1,q=!1,r=!0,s=0,t=!1,u=!1,v=!1,w=!1,x=0,y=.5,z=[],A=d3.dispatch("chartClick","elementClick","elementDblClick","elementMouseover","elementMouseout","elementMousemove","renderEnd"),B=[],C=[],D=a.utils.renderWatch(A);return b.dispatch=A,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{arcsRadius:{get:function(){return z},set:function(a){z=a}},width:{get:function(){return d},set:function(a){d=a}},height:{get:function(){return e},set:function(a){e=a}},showLabels:{get:function(){return l},set:function(a){l=a}},title:{get:function(){return q},set:function(a){q=a}},titleOffset:{get:function(){return s},set:function(a){s=a}},labelThreshold:{get:function(){return o},set:function(a){o=a}},valueFormat:{get:function(){return k},set:function(a){k=a}},x:{get:function(){return f},set:function(a){f=a}},id:{get:function(){return h},set:function(a){h=a}},endAngle:{get:function(){return w},set:function(a){w=a}},startAngle:{get:function(){return u},set:function(a){u=a}},padAngle:{get:function(){return v},set:function(a){v=a}},cornerRadius:{get:function(){return x},set:function(a){x=a}},donutRatio:{get:function(){return y},set:function(a){y=a}},labelsOutside:{get:function(){return m},set:function(a){m=a}},labelSunbeamLayout:{get:function(){return t},set:function(a){t=a}},donut:{get:function(){return p},set:function(a){p=a}},growOnHover:{get:function(){return r},set:function(a){r=a}},pieLabelsOutside:{get:function(){return m},set:function(b){m=b,a.deprecated("pieLabelsOutside","use labelsOutside instead")}},donutLabelsOutside:{get:function(){return m},set:function(b){m=b,a.deprecated("donutLabelsOutside","use labelsOutside instead")}},labelFormat:{get:function(){return k},set:function(b){k=b,a.deprecated("labelFormat","use valueFormat instead")}},margin:{get:function(){return c},set:function(a){c.top="undefined"!=typeof a.top?a.top:c.top,c.right="undefined"!=typeof a.right?a.right:c.right,c.bottom="undefined"!=typeof a.bottom?a.bottom:c.bottom,c.left="undefined"!=typeof a.left?a.left:c.left}},y:{get:function(){return g},set:function(a){g=d3.functor(a)}},color:{get:function(){return j},set:function(b){j=a.utils.getColor(b)}},labelType:{get:function(){return n},set:function(a){n=a||"key"}}}),a.utils.initOptions(b),b},a.models.pieChart=function(){"use strict";function b(e){return q.reset(),q.models(c),e.each(function(e){var k=d3.select(this);a.utils.initSVG(k);var n=a.utils.availableWidth(g,k,f),o=a.utils.availableHeight(h,k,f);if(b.update=function(){k.transition().call(b)},b.container=this,l.setter(s(e),b.update).getter(r(e)).update(),l.disabled=e.map(function(a){return!!a.disabled}),!m){var q;m={};for(q in l)l[q]instanceof Array?m[q]=l[q].slice(0):m[q]=l[q]}if(!e||!e.length)return a.utils.noData(b,k),b;k.selectAll(".nv-noData").remove();var t=k.selectAll("g.nv-wrap.nv-pieChart").data([e]),u=t.enter().append("g").attr("class","nvd3 nv-wrap nv-pieChart").append("g"),v=t.select("g");if(u.append("g").attr("class","nv-pieWrap"),u.append("g").attr("class","nv-legendWrap"),i)if("top"===j)d.width(n).key(c.x()),t.select(".nv-legendWrap").datum(e).call(d),f.top!=d.height()&&(f.top=d.height(),o=a.utils.availableHeight(h,k,f)),t.select(".nv-legendWrap").attr("transform","translate(0,"+-f.top+")");else if("right"===j){var w=a.models.legend().width();w>n/2&&(w=n/2),d.height(o).key(c.x()),d.width(w),n-=d.width(),t.select(".nv-legendWrap").datum(e).call(d).attr("transform","translate("+n+",0)")}t.attr("transform","translate("+f.left+","+f.top+")"),c.width(n).height(o);var x=v.select(".nv-pieWrap").datum([e]);d3.transition(x).call(c),d.dispatch.on("stateChange",function(a){for(var c in a)l[c]=a[c];p.stateChange(l),b.update()}),p.on("changeState",function(a){"undefined"!=typeof a.disabled&&(e.forEach(function(b,c){b.disabled=a.disabled[c]}),l.disabled=a.disabled),b.update()})}),q.renderEnd("pieChart immediate"),b}var c=a.models.pie(),d=a.models.legend(),e=a.models.tooltip(),f={top:30,right:20,bottom:20,left:20},g=null,h=null,i=!0,j="top",k=a.utils.defaultColor(),l=a.utils.state(),m=null,n=null,o=250,p=d3.dispatch("tooltipShow","tooltipHide","stateChange","changeState","renderEnd");e.headerEnabled(!1).duration(0).valueFormatter(function(a,b){return c.valueFormat()(a,b)});var q=a.utils.renderWatch(p),r=function(a){return function(){return{active:a.map(function(a){return!a.disabled})}}},s=function(a){return function(b){void 0!==b.active&&a.forEach(function(a,c){a.disabled=!b.active[c]})}};return c.dispatch.on("elementMouseover.tooltip",function(a){a.series={key:b.x()(a.data),value:b.y()(a.data),color:a.color},e.data(a).hidden(!1)}),c.dispatch.on("elementMouseout.tooltip",function(a){e.hidden(!0)}),c.dispatch.on("elementMousemove.tooltip",function(a){e.position({top:d3.event.pageY,left:d3.event.pageX})()}),b.legend=d,b.dispatch=p,b.pie=c,b.tooltip=e,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{noData:{get:function(){return n},set:function(a){n=a}},showLegend:{get:function(){return i},set:function(a){i=a}},legendPosition:{get:function(){return j},set:function(a){j=a}},defaultState:{get:function(){return m},set:function(a){m=a}},tooltips:{get:function(){return e.enabled()},set:function(b){a.deprecated("tooltips","use chart.tooltip.enabled() instead"),e.enabled(!!b)}},tooltipContent:{get:function(){return e.contentGenerator()},set:function(b){a.deprecated("tooltipContent","use chart.tooltip.contentGenerator() instead"),e.contentGenerator(b)}},color:{get:function(){return k},set:function(a){k=a,d.color(k),c.color(k)}},duration:{get:function(){return o},set:function(a){o=a,q.reset(o)}},margin:{get:function(){return f},set:function(a){f.top=void 0!==a.top?a.top:f.top,f.right=void 0!==a.right?a.right:f.right,f.bottom=void 0!==a.bottom?a.bottom:f.bottom,f.left=void 0!==a.left?a.left:f.left}}}),a.utils.inheritOptions(b,c),a.utils.initOptions(b),b},a.models.scatter=function(){"use strict";function b(N){return P.reset(),N.each(function(b){function N(){if(O=!1,!w)return!1;if(M===!0){var a=d3.merge(b.map(function(a,b){return a.values.map(function(a,c){var d=p(a,c),e=q(a,c);return[m(d)+1e-4*Math.random(),n(e)+1e-4*Math.random(),b,c,a]}).filter(function(a,b){return x(a[4],b)})}));if(0==a.length)return!1;a.length<3&&(a.push([m.range()[0]-20,n.range()[0]-20,null,null]),a.push([m.range()[1]+20,n.range()[1]+20,null,null]),a.push([m.range()[0]-20,n.range()[0]+20,null,null]),a.push([m.range()[1]+20,n.range()[1]-20,null,null]));var c=d3.geom.polygon([[-10,-10],[-10,i+10],[h+10,i+10],[h+10,-10]]),d=d3.geom.voronoi(a).map(function(b,d){return{data:c.clip(b),series:a[d][2],point:a[d][3]}});U.select(".nv-point-paths").selectAll("path").remove();var e=U.select(".nv-point-paths").selectAll("path").data(d),f=e.enter().append("svg:path").attr("d",function(a){return a&&a.data&&0!==a.data.length?"M"+a.data.join(",")+"Z":"M 0 0"}).attr("id",function(a,b){return"nv-path-"+b}).attr("clip-path",function(a,b){return"url(#nv-clip-"+b+")"});C&&f.style("fill",d3.rgb(230,230,230)).style("fill-opacity",.4).style("stroke-opacity",1).style("stroke",d3.rgb(200,200,200)),B&&(U.select(".nv-point-clips").selectAll("clipPath").remove(),U.select(".nv-point-clips").selectAll("clipPath").data(a).enter().append("svg:clipPath").attr("id",function(a,b){return"nv-clip-"+b}).append("svg:circle").attr("cx",function(a){return a[0]}).attr("cy",function(a){return a[1]}).attr("r",D));var k=function(a,c){if(O)return 0;var d=b[a.series];if(void 0!==d){var e=d.values[a.point];e.color=j(d,a.series),e.x=p(e),e.y=q(e);var f=l.node().getBoundingClientRect(),h=window.pageYOffset||document.documentElement.scrollTop,i=window.pageXOffset||document.documentElement.scrollLeft,k={left:m(p(e,a.point))+f.left+i+g.left+10,top:n(q(e,a.point))+f.top+h+g.top+10};c({point:e,series:d,pos:k,seriesIndex:a.series,pointIndex:a.point})}};e.on("click",function(a){k(a,L.elementClick)}).on("dblclick",function(a){k(a,L.elementDblClick)}).on("mouseover",function(a){k(a,L.elementMouseover)}).on("mouseout",function(a,b){k(a,L.elementMouseout)})}else U.select(".nv-groups").selectAll(".nv-group").selectAll(".nv-point").on("click",function(a,c){if(O||!b[a.series])return 0;var d=b[a.series],e=d.values[c];L.elementClick({point:e,series:d,pos:[m(p(e,c))+g.left,n(q(e,c))+g.top],seriesIndex:a.series,pointIndex:c})}).on("dblclick",function(a,c){if(O||!b[a.series])return 0;var d=b[a.series],e=d.values[c];L.elementDblClick({point:e,series:d,pos:[m(p(e,c))+g.left,n(q(e,c))+g.top],seriesIndex:a.series,pointIndex:c})}).on("mouseover",function(a,c){if(O||!b[a.series])return 0;var d=b[a.series],e=d.values[c];L.elementMouseover({point:e,series:d,pos:[m(p(e,c))+g.left,n(q(e,c))+g.top],seriesIndex:a.series,pointIndex:c,color:j(a,c)})}).on("mouseout",function(a,c){if(O||!b[a.series])return 0;var d=b[a.series],e=d.values[c];L.elementMouseout({point:e,series:d,seriesIndex:a.series,pointIndex:c,color:j(a,c)})})}l=d3.select(this);var R=a.utils.availableWidth(h,l,g),S=a.utils.availableHeight(i,l,g);a.utils.initSVG(l),b.forEach(function(a,b){a.values.forEach(function(a){a.series=b})});var T=E&&F&&I?[]:d3.merge(b.map(function(a){return a.values.map(function(a,b){return{x:p(a,b),y:q(a,b),size:r(a,b)}})}));m.domain(E||d3.extent(T.map(function(a){return a.x}).concat(t))),y&&b[0]?m.range(G||[(R*z+R)/(2*b[0].values.length),R-R*(1+z)/(2*b[0].values.length)]):m.range(G||[0,R]),n.domain(F||d3.extent(T.map(function(a){return a.y}).concat(u))).range(H||[S,0]),o.domain(I||d3.extent(T.map(function(a){return a.size}).concat(v))).range(J||Q),K=m.domain()[0]===m.domain()[1]||n.domain()[0]===n.domain()[1],m.domain()[0]===m.domain()[1]&&(m.domain()[0]?m.domain([m.domain()[0]-.01*m.domain()[0],m.domain()[1]+.01*m.domain()[1]]):m.domain([-1,1])),n.domain()[0]===n.domain()[1]&&(n.domain()[0]?n.domain([n.domain()[0]-.01*n.domain()[0],n.domain()[1]+.01*n.domain()[1]]):n.domain([-1,1])),isNaN(m.domain()[0])&&m.domain([-1,1]),isNaN(n.domain()[0])&&n.domain([-1,1]),c=c||m,d=d||n,e=e||o;var U=l.selectAll("g.nv-wrap.nv-scatter").data([b]),V=U.enter().append("g").attr("class","nvd3 nv-wrap nv-scatter nv-chart-"+k),W=V.append("defs"),X=V.append("g"),Y=U.select("g");U.classed("nv-single-point",K),X.append("g").attr("class","nv-groups"),X.append("g").attr("class","nv-point-paths"),V.append("g").attr("class","nv-point-clips"),U.attr("transform","translate("+g.left+","+g.top+")"),W.append("clipPath").attr("id","nv-edge-clip-"+k).append("rect"),U.select("#nv-edge-clip-"+k+" rect").attr("width",R).attr("height",S>0?S:0),Y.attr("clip-path",A?"url(#nv-edge-clip-"+k+")":""),O=!0;var Z=U.select(".nv-groups").selectAll(".nv-group").data(function(a){return a},function(a){return a.key});Z.enter().append("g").style("stroke-opacity",1e-6).style("fill-opacity",1e-6),Z.exit().remove(),Z.attr("class",function(a,b){return"nv-group nv-series-"+b}).classed("hover",function(a){return a.hover}),Z.watchTransition(P,"scatter: groups").style("fill",function(a,b){return j(a,b)}).style("stroke",function(a,b){return j(a,b)}).style("stroke-opacity",1).style("fill-opacity",.5);var $=Z.selectAll("path.nv-point").data(function(a){return a.values.map(function(a,b){return[a,b]}).filter(function(a,b){return x(a[0],b)})});$.enter().append("path").style("fill",function(a){return a.color}).style("stroke",function(a){return a.color}).attr("transform",function(a){return"translate("+c(p(a[0],a[1]))+","+d(q(a[0],a[1]))+")"}).attr("d",a.utils.symbol().type(function(a){return s(a[0])}).size(function(a){return o(r(a[0],a[1]))})),$.exit().remove(),Z.exit().selectAll("path.nv-point").watchTransition(P,"scatter exit").attr("transform",function(a){return"translate("+m(p(a[0],a[1]))+","+n(q(a[0],a[1]))+")"}).remove(),$.each(function(a){d3.select(this).classed("nv-point",!0).classed("nv-point-"+a[1],!0).classed("nv-noninteractive",!w).classed("hover",!1)}),$.watchTransition(P,"scatter points").attr("transform",function(a){return"translate("+m(p(a[0],a[1]))+","+n(q(a[0],a[1]))+")"}).attr("d",a.utils.symbol().type(function(a){return s(a[0])}).size(function(a){return o(r(a[0],a[1]))})),clearTimeout(f),f=setTimeout(N,300),c=m.copy(),d=n.copy(),e=o.copy()}),P.renderEnd("scatter immediate"),b}var c,d,e,f,g={top:0,right:0,bottom:0,left:0},h=null,i=null,j=a.utils.defaultColor(),k=Math.floor(1e5*Math.random()),l=null,m=d3.scale.linear(),n=d3.scale.linear(),o=d3.scale.linear(),p=function(a){return a.x},q=function(a){return a.y},r=function(a){return a.size||1},s=function(a){return a.shape||"circle"},t=[],u=[],v=[],w=!0,x=function(a){return!a.notActive},y=!1,z=.1,A=!1,B=!0,C=!1,D=function(){return 25},E=null,F=null,G=null,H=null,I=null,J=null,K=!1,L=d3.dispatch("elementClick","elementDblClick","elementMouseover","elementMouseout","renderEnd"),M=!0,N=250,O=!1,P=a.utils.renderWatch(L,N),Q=[16,256];return b.dispatch=L,b.options=a.utils.optionsFunc.bind(b),b._calls=new function(){this.clearHighlights=function(){return a.dom.write(function(){l.selectAll(".nv-point.hover").classed("hover",!1)}),null},this.highlightPoint=function(b,c,d){a.dom.write(function(){l.select(" .nv-series-"+b+" .nv-point-"+c).classed("hover",d)})}},L.on("elementMouseover.point",function(a){w&&b._calls.highlightPoint(a.seriesIndex,a.pointIndex,!0)}),L.on("elementMouseout.point",function(a){w&&b._calls.highlightPoint(a.seriesIndex,a.pointIndex,!1)}),b._options=Object.create({},{width:{get:function(){return h},set:function(a){h=a}},height:{get:function(){return i},set:function(a){i=a}},xScale:{get:function(){return m},set:function(a){m=a}},yScale:{get:function(){return n},set:function(a){n=a}},pointScale:{get:function(){return o},set:function(a){o=a}},xDomain:{get:function(){return E},set:function(a){E=a}},yDomain:{get:function(){return F},set:function(a){F=a}},pointDomain:{get:function(){return I},set:function(a){I=a}},xRange:{get:function(){return G},set:function(a){G=a}},yRange:{get:function(){return H},set:function(a){H=a}},pointRange:{get:function(){return J},set:function(a){J=a}},forceX:{get:function(){return t},set:function(a){t=a}},forceY:{get:function(){return u},set:function(a){u=a}},forcePoint:{get:function(){return v},set:function(a){v=a}},interactive:{get:function(){return w},set:function(a){w=a}},pointActive:{get:function(){return x},set:function(a){x=a}},padDataOuter:{get:function(){return z},set:function(a){z=a}},padData:{get:function(){return y},set:function(a){y=a}},clipEdge:{get:function(){return A},set:function(a){A=a}},clipVoronoi:{get:function(){return B},set:function(a){B=a}},clipRadius:{get:function(){return D},set:function(a){D=a}},showVoronoi:{get:function(){return C},set:function(a){C=a}},id:{get:function(){return k},set:function(a){k=a}},x:{get:function(){return p},set:function(a){p=d3.functor(a)}},y:{get:function(){return q},set:function(a){q=d3.functor(a)}},pointSize:{get:function(){return r},set:function(a){r=d3.functor(a)}},pointShape:{get:function(){return s},set:function(a){s=d3.functor(a)}},margin:{get:function(){return g},set:function(a){g.top=void 0!==a.top?a.top:g.top,g.right=void 0!==a.right?a.right:g.right,g.bottom=void 0!==a.bottom?a.bottom:g.bottom,g.left=void 0!==a.left?a.left:g.left}},duration:{get:function(){return N},set:function(a){N=a,P.reset(N)}},color:{get:function(){return j},set:function(b){j=a.utils.getColor(b)}},useVoronoi:{get:function(){return M},set:function(a){M=a,M===!1&&(B=!1)}}}),a.utils.initOptions(b),b},a.models.scatterChart=function(){"use strict";function b(z){return D.reset(),D.models(c),t&&D.models(d),u&&D.models(e),q&&D.models(g),r&&D.models(h),z.each(function(z){m=d3.select(this),a.utils.initSVG(m);var G=a.utils.availableWidth(k,m,j),H=a.utils.availableHeight(l,m,j);if(b.update=function(){0===A?m.call(b):m.transition().duration(A).call(b)},b.container=this,w.setter(F(z),b.update).getter(E(z)).update(),w.disabled=z.map(function(a){return!!a.disabled}),!x){var I;x={};for(I in w)w[I]instanceof Array?x[I]=w[I].slice(0):x[I]=w[I]}if(!(z&&z.length&&z.filter(function(a){return a.values.length}).length))return a.utils.noData(b,m),D.renderEnd("scatter immediate"),b;m.selectAll(".nv-noData").remove(),o=c.xScale(),p=c.yScale();var J=m.selectAll("g.nv-wrap.nv-scatterChart").data([z]),K=J.enter().append("g").attr("class","nvd3 nv-wrap nv-scatterChart nv-chart-"+c.id()),L=K.append("g"),M=J.select("g");if(L.append("rect").attr("class","nvd3 nv-background").style("pointer-events","none"),L.append("g").attr("class","nv-x nv-axis"),L.append("g").attr("class","nv-y nv-axis"),L.append("g").attr("class","nv-scatterWrap"),L.append("g").attr("class","nv-regressionLinesWrap"),L.append("g").attr("class","nv-distWrap"),L.append("g").attr("class","nv-legendWrap"),v&&M.select(".nv-y.nv-axis").attr("transform","translate("+G+",0)"),s){var N=G;f.width(N),J.select(".nv-legendWrap").datum(z).call(f),j.top!=f.height()&&(j.top=f.height(),H=a.utils.availableHeight(l,m,j)),J.select(".nv-legendWrap").attr("transform","translate(0,"+-j.top+")")}J.attr("transform","translate("+j.left+","+j.top+")"),c.width(G).height(H).color(z.map(function(a,b){return a.color||n(a,b)}).filter(function(a,b){return!z[b].disabled})),J.select(".nv-scatterWrap").datum(z.filter(function(a){return!a.disabled})).call(c),J.select(".nv-regressionLinesWrap").attr("clip-path","url(#nv-edge-clip-"+c.id()+")");var O=J.select(".nv-regressionLinesWrap").selectAll(".nv-regLines").data(function(a){return a});O.enter().append("g").attr("class","nv-regLines");var P=O.selectAll(".nv-regLine").data(function(a){return[a]});P.enter().append("line").attr("class","nv-regLine").style("stroke-opacity",0),P.filter(function(a){return a.intercept&&a.slope}).watchTransition(D,"scatterPlusLineChart: regline").attr("x1",o.range()[0]).attr("x2",o.range()[1]).attr("y1",function(a,b){return p(o.domain()[0]*a.slope+a.intercept)}).attr("y2",function(a,b){return p(o.domain()[1]*a.slope+a.intercept)}).style("stroke",function(a,b,c){return n(a,c)}).style("stroke-opacity",function(a,b){return a.disabled||"undefined"==typeof a.slope||"undefined"==typeof a.intercept?0:1}),t&&(d.scale(o)._ticks(a.utils.calcTicksX(G/100,z)).tickSize(-H,0),M.select(".nv-x.nv-axis").attr("transform","translate(0,"+p.range()[0]+")").call(d)),u&&(e.scale(p)._ticks(a.utils.calcTicksY(H/36,z)).tickSize(-G,0),M.select(".nv-y.nv-axis").call(e)),q&&(g.getData(c.x()).scale(o).width(G).color(z.map(function(a,b){return a.color||n(a,b)}).filter(function(a,b){return!z[b].disabled})),L.select(".nv-distWrap").append("g").attr("class","nv-distributionX"),M.select(".nv-distributionX").attr("transform","translate(0,"+p.range()[0]+")").datum(z.filter(function(a){return!a.disabled})).call(g)),r&&(h.getData(c.y()).scale(p).width(H).color(z.map(function(a,b){return a.color||n(a,b)}).filter(function(a,b){return!z[b].disabled})),L.select(".nv-distWrap").append("g").attr("class","nv-distributionY"),M.select(".nv-distributionY").attr("transform","translate("+(v?G:-h.size())+",0)").datum(z.filter(function(a){return!a.disabled})).call(h)),f.dispatch.on("stateChange",function(a){for(var c in a)w[c]=a[c];y.stateChange(w),b.update()}),y.on("changeState",function(a){"undefined"!=typeof a.disabled&&(z.forEach(function(b,c){b.disabled=a.disabled[c]}),w.disabled=a.disabled),b.update()}),c.dispatch.on("elementMouseout.tooltip",function(a){i.hidden(!0),m.select(".nv-chart-"+c.id()+" .nv-series-"+a.seriesIndex+" .nv-distx-"+a.pointIndex).attr("y1",0),m.select(".nv-chart-"+c.id()+" .nv-series-"+a.seriesIndex+" .nv-disty-"+a.pointIndex).attr("x2",h.size())}),c.dispatch.on("elementMouseover.tooltip",function(a){m.select(".nv-series-"+a.seriesIndex+" .nv-distx-"+a.pointIndex).attr("y1",a.pos.top-H-j.top),m.select(".nv-series-"+a.seriesIndex+" .nv-disty-"+a.pointIndex).attr("x2",a.pos.left+g.size()-j.left),i.position(a.pos).data(a).hidden(!1); +}),B=o.copy(),C=p.copy()}),D.renderEnd("scatter with line immediate"),b}var c=a.models.scatter(),d=a.models.axis(),e=a.models.axis(),f=a.models.legend(),g=a.models.distribution(),h=a.models.distribution(),i=a.models.tooltip(),j={top:30,right:20,bottom:50,left:75},k=null,l=null,m=null,n=a.utils.defaultColor(),o=c.xScale(),p=c.yScale(),q=!1,r=!1,s=!0,t=!0,u=!0,v=!1,w=a.utils.state(),x=null,y=d3.dispatch("stateChange","changeState","renderEnd"),z=null,A=250;c.xScale(o).yScale(p),d.orient("bottom").tickPadding(10),e.orient(v?"right":"left").tickPadding(10),g.axis("x"),h.axis("y"),i.headerFormatter(function(a,b){return d.tickFormat()(a,b)}).valueFormatter(function(a,b){return e.tickFormat()(a,b)});var B,C,D=a.utils.renderWatch(y,A),E=function(a){return function(){return{active:a.map(function(a){return!a.disabled})}}},F=function(a){return function(b){void 0!==b.active&&a.forEach(function(a,c){a.disabled=!b.active[c]})}};return b.dispatch=y,b.scatter=c,b.legend=f,b.xAxis=d,b.yAxis=e,b.distX=g,b.distY=h,b.tooltip=i,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return k},set:function(a){k=a}},height:{get:function(){return l},set:function(a){l=a}},container:{get:function(){return m},set:function(a){m=a}},showDistX:{get:function(){return q},set:function(a){q=a}},showDistY:{get:function(){return r},set:function(a){r=a}},showLegend:{get:function(){return s},set:function(a){s=a}},showXAxis:{get:function(){return t},set:function(a){t=a}},showYAxis:{get:function(){return u},set:function(a){u=a}},defaultState:{get:function(){return x},set:function(a){x=a}},noData:{get:function(){return z},set:function(a){z=a}},duration:{get:function(){return A},set:function(a){A=a}},tooltips:{get:function(){return i.enabled()},set:function(b){a.deprecated("tooltips","use chart.tooltip.enabled() instead"),i.enabled(!!b)}},tooltipContent:{get:function(){return i.contentGenerator()},set:function(b){a.deprecated("tooltipContent","use chart.tooltip.contentGenerator() instead"),i.contentGenerator(b)}},tooltipXContent:{get:function(){return i.contentGenerator()},set:function(b){a.deprecated("tooltipContent","This option is removed, put values into main tooltip.")}},tooltipYContent:{get:function(){return i.contentGenerator()},set:function(b){a.deprecated("tooltipContent","This option is removed, put values into main tooltip.")}},margin:{get:function(){return j},set:function(a){j.top=void 0!==a.top?a.top:j.top,j.right=void 0!==a.right?a.right:j.right,j.bottom=void 0!==a.bottom?a.bottom:j.bottom,j.left=void 0!==a.left?a.left:j.left}},rightAlignYAxis:{get:function(){return v},set:function(a){v=a,e.orient(a?"right":"left")}},color:{get:function(){return n},set:function(b){n=a.utils.getColor(b),f.color(n),g.color(n),h.color(n)}}}),a.utils.inheritOptions(b,c),a.utils.initOptions(b),b},a.models.sparkline=function(){"use strict";function b(k){return k.each(function(b){var k=h-g.left-g.right,q=i-g.top-g.bottom;j=d3.select(this),a.utils.initSVG(j),l.domain(c||d3.extent(b,n)).range(e||[0,k]),m.domain(d||d3.extent(b,o)).range(f||[q,0]);var r=j.selectAll("g.nv-wrap.nv-sparkline").data([b]),s=r.enter().append("g").attr("class","nvd3 nv-wrap nv-sparkline");s.append("g"),r.select("g");r.attr("transform","translate("+g.left+","+g.top+")");var t=r.selectAll("path").data(function(a){return[a]});t.enter().append("path"),t.exit().remove(),t.style("stroke",function(a,b){return a.color||p(a,b)}).attr("d",d3.svg.line().x(function(a,b){return l(n(a,b))}).y(function(a,b){return m(o(a,b))}));var u=r.selectAll("circle.nv-point").data(function(a){function b(b){if(-1!=b){var c=a[b];return c.pointIndex=b,c}return null}var c=a.map(function(a,b){return o(a,b)}),d=b(c.lastIndexOf(m.domain()[1])),e=b(c.indexOf(m.domain()[0])),f=b(c.length-1);return[e,d,f].filter(function(a){return null!=a})});u.enter().append("circle"),u.exit().remove(),u.attr("cx",function(a,b){return l(n(a,a.pointIndex))}).attr("cy",function(a,b){return m(o(a,a.pointIndex))}).attr("r",2).attr("class",function(a,b){return n(a,a.pointIndex)==l.domain()[1]?"nv-point nv-currentValue":o(a,a.pointIndex)==m.domain()[0]?"nv-point nv-minValue":"nv-point nv-maxValue"})}),b}var c,d,e,f,g={top:2,right:0,bottom:2,left:0},h=400,i=32,j=null,k=!0,l=d3.scale.linear(),m=d3.scale.linear(),n=function(a){return a.x},o=function(a){return a.y},p=a.utils.getColor(["#000"]);return b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return h},set:function(a){h=a}},height:{get:function(){return i},set:function(a){i=a}},xDomain:{get:function(){return c},set:function(a){c=a}},yDomain:{get:function(){return d},set:function(a){d=a}},xRange:{get:function(){return e},set:function(a){e=a}},yRange:{get:function(){return f},set:function(a){f=a}},xScale:{get:function(){return l},set:function(a){l=a}},yScale:{get:function(){return m},set:function(a){m=a}},animate:{get:function(){return k},set:function(a){k=a}},x:{get:function(){return n},set:function(a){n=d3.functor(a)}},y:{get:function(){return o},set:function(a){o=d3.functor(a)}},margin:{get:function(){return g},set:function(a){g.top=void 0!==a.top?a.top:g.top,g.right=void 0!==a.right?a.right:g.right,g.bottom=void 0!==a.bottom?a.bottom:g.bottom,g.left=void 0!==a.left?a.left:g.left}},color:{get:function(){return p},set:function(b){p=a.utils.getColor(b)}}}),a.utils.initOptions(b),b},a.models.sparklinePlus=function(){"use strict";function b(p){return p.each(function(q){function r(){if(!j){var a=A.selectAll(".nv-hoverValue").data(i),b=a.enter().append("g").attr("class","nv-hoverValue").style("stroke-opacity",0).style("fill-opacity",0);a.exit().transition().duration(250).style("stroke-opacity",0).style("fill-opacity",0).remove(),a.attr("transform",function(a){return"translate("+c(e.x()(q[a],a))+",0)"}).transition().duration(250).style("stroke-opacity",1).style("fill-opacity",1),i.length&&(b.append("line").attr("x1",0).attr("y1",-f.top).attr("x2",0).attr("y2",v),b.append("text").attr("class","nv-xValue").attr("x",-6).attr("y",-f.top).attr("text-anchor","end").attr("dy",".9em"),A.select(".nv-hoverValue .nv-xValue").text(k(e.x()(q[i[0]],i[0]))),b.append("text").attr("class","nv-yValue").attr("x",6).attr("y",-f.top).attr("text-anchor","start").attr("dy",".9em"),A.select(".nv-hoverValue .nv-yValue").text(l(e.y()(q[i[0]],i[0]))))}}function s(){function a(a,b){for(var c=Math.abs(e.x()(a[0],0)-b),d=0,f=0;f<a.length;f++)Math.abs(e.x()(a[f],f)-b)<c&&(c=Math.abs(e.x()(a[f],f)-b),d=f);return d}if(!j){var b=d3.mouse(this)[0]-f.left;i=[a(q,Math.round(c.invert(b)))],r()}}var t=d3.select(this);a.utils.initSVG(t);var u=a.utils.availableWidth(g,t,f),v=a.utils.availableHeight(h,t,f);if(b.update=function(){b(p)},b.container=this,!q||!q.length)return a.utils.noData(b,t),b;t.selectAll(".nv-noData").remove();var w=e.y()(q[q.length-1],q.length-1);c=e.xScale(),d=e.yScale();var x=t.selectAll("g.nv-wrap.nv-sparklineplus").data([q]),y=x.enter().append("g").attr("class","nvd3 nv-wrap nv-sparklineplus"),z=y.append("g"),A=x.select("g");z.append("g").attr("class","nv-sparklineWrap"),z.append("g").attr("class","nv-valueWrap"),z.append("g").attr("class","nv-hoverArea"),x.attr("transform","translate("+f.left+","+f.top+")");var B=A.select(".nv-sparklineWrap");if(e.width(u).height(v),B.call(e),m){var C=A.select(".nv-valueWrap"),D=C.selectAll(".nv-currentValue").data([w]);D.enter().append("text").attr("class","nv-currentValue").attr("dx",o?-8:8).attr("dy",".9em").style("text-anchor",o?"end":"start"),D.attr("x",u+(o?f.right:0)).attr("y",n?function(a){return d(a)}:0).style("fill",e.color()(q[q.length-1],q.length-1)).text(l(w))}z.select(".nv-hoverArea").append("rect").on("mousemove",s).on("click",function(){j=!j}).on("mouseout",function(){i=[],r()}),A.select(".nv-hoverArea rect").attr("transform",function(a){return"translate("+-f.left+","+-f.top+")"}).attr("width",u+f.left+f.right).attr("height",v+f.top)}),b}var c,d,e=a.models.sparkline(),f={top:15,right:100,bottom:10,left:50},g=null,h=null,i=[],j=!1,k=d3.format(",r"),l=d3.format(",.2f"),m=!0,n=!0,o=!1,p=null;return b.sparkline=e,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return g},set:function(a){g=a}},height:{get:function(){return h},set:function(a){h=a}},xTickFormat:{get:function(){return k},set:function(a){k=a}},yTickFormat:{get:function(){return l},set:function(a){l=a}},showLastValue:{get:function(){return m},set:function(a){m=a}},alignValue:{get:function(){return n},set:function(a){n=a}},rightAlignValue:{get:function(){return o},set:function(a){o=a}},noData:{get:function(){return p},set:function(a){p=a}},margin:{get:function(){return f},set:function(a){f.top=void 0!==a.top?a.top:f.top,f.right=void 0!==a.right?a.right:f.right,f.bottom=void 0!==a.bottom?a.bottom:f.bottom,f.left=void 0!==a.left?a.left:f.left}}}),a.utils.inheritOptions(b,e),a.utils.initOptions(b),b},a.models.stackedArea=function(){"use strict";function b(m){return u.reset(),u.models(r),m.each(function(m){var s=f-e.left-e.right,v=g-e.top-e.bottom;j=d3.select(this),a.utils.initSVG(j),c=r.xScale(),d=r.yScale();var w=m;m.forEach(function(a,b){a.seriesIndex=b,a.values=a.values.map(function(a,c){return a.index=c,a.seriesIndex=b,a})});var x=m.filter(function(a){return!a.disabled});m=d3.layout.stack().order(o).offset(n).values(function(a){return a.values}).x(k).y(l).out(function(a,b,c){a.display={y:c,y0:b}})(x);var y=j.selectAll("g.nv-wrap.nv-stackedarea").data([m]),z=y.enter().append("g").attr("class","nvd3 nv-wrap nv-stackedarea"),A=z.append("defs"),B=z.append("g"),C=y.select("g");B.append("g").attr("class","nv-areaWrap"),B.append("g").attr("class","nv-scatterWrap"),y.attr("transform","translate("+e.left+","+e.top+")"),0==r.forceY().length&&r.forceY().push(0),r.width(s).height(v).x(k).y(function(a){return a.display.y+a.display.y0}).forceY([0]).color(m.map(function(a,b){return a.color||h(a,a.seriesIndex)}));var D=C.select(".nv-scatterWrap").datum(m);D.call(r),A.append("clipPath").attr("id","nv-edge-clip-"+i).append("rect"),y.select("#nv-edge-clip-"+i+" rect").attr("width",s).attr("height",v),C.attr("clip-path",q?"url(#nv-edge-clip-"+i+")":"");var E=d3.svg.area().x(function(a,b){return c(k(a,b))}).y0(function(a){return d(a.display.y0)}).y1(function(a){return d(a.display.y+a.display.y0)}).interpolate(p),F=d3.svg.area().x(function(a,b){return c(k(a,b))}).y0(function(a){return d(a.display.y0)}).y1(function(a){return d(a.display.y0)}),G=C.select(".nv-areaWrap").selectAll("path.nv-area").data(function(a){return a});G.enter().append("path").attr("class",function(a,b){return"nv-area nv-area-"+b}).attr("d",function(a,b){return F(a.values,a.seriesIndex)}).on("mouseover",function(a,b){d3.select(this).classed("hover",!0),t.areaMouseover({point:a,series:a.key,pos:[d3.event.pageX,d3.event.pageY],seriesIndex:a.seriesIndex})}).on("mouseout",function(a,b){d3.select(this).classed("hover",!1),t.areaMouseout({point:a,series:a.key,pos:[d3.event.pageX,d3.event.pageY],seriesIndex:a.seriesIndex})}).on("click",function(a,b){d3.select(this).classed("hover",!1),t.areaClick({point:a,series:a.key,pos:[d3.event.pageX,d3.event.pageY],seriesIndex:a.seriesIndex})}),G.exit().remove(),G.style("fill",function(a,b){return a.color||h(a,a.seriesIndex)}).style("stroke",function(a,b){return a.color||h(a,a.seriesIndex)}),G.watchTransition(u,"stackedArea path").attr("d",function(a,b){return E(a.values,b)}),r.dispatch.on("elementMouseover.area",function(a){C.select(".nv-chart-"+i+" .nv-area-"+a.seriesIndex).classed("hover",!0)}),r.dispatch.on("elementMouseout.area",function(a){C.select(".nv-chart-"+i+" .nv-area-"+a.seriesIndex).classed("hover",!1)}),b.d3_stackedOffset_stackPercent=function(a){var b,c,d,e=a.length,f=a[0].length,g=[];for(c=0;f>c;++c){for(b=0,d=0;b<w.length;b++)d+=l(w[b].values[c]);if(d)for(b=0;e>b;b++)a[b][c][1]/=d;else for(b=0;e>b;b++)a[b][c][1]=0}for(c=0;f>c;++c)g[c]=0;return g}}),u.renderEnd("stackedArea immediate"),b}var c,d,e={top:0,right:0,bottom:0,left:0},f=960,g=500,h=a.utils.defaultColor(),i=Math.floor(1e5*Math.random()),j=null,k=function(a){return a.x},l=function(a){return a.y},m="stack",n="zero",o="default",p="linear",q=!1,r=a.models.scatter(),s=250,t=d3.dispatch("areaClick","areaMouseover","areaMouseout","renderEnd","elementClick","elementMouseover","elementMouseout");r.pointSize(2.2).pointDomain([2.2,2.2]);var u=a.utils.renderWatch(t,s);return b.dispatch=t,b.scatter=r,r.dispatch.on("elementClick",function(){t.elementClick.apply(this,arguments)}),r.dispatch.on("elementMouseover",function(){t.elementMouseover.apply(this,arguments)}),r.dispatch.on("elementMouseout",function(){t.elementMouseout.apply(this,arguments)}),b.interpolate=function(a){return arguments.length?(p=a,b):p},b.duration=function(a){return arguments.length?(s=a,u.reset(s),r.duration(s),b):s},b.dispatch=t,b.scatter=r,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return f},set:function(a){f=a}},height:{get:function(){return g},set:function(a){g=a}},clipEdge:{get:function(){return q},set:function(a){q=a}},offset:{get:function(){return n},set:function(a){n=a}},order:{get:function(){return o},set:function(a){o=a}},interpolate:{get:function(){return p},set:function(a){p=a}},x:{get:function(){return k},set:function(a){k=d3.functor(a)}},y:{get:function(){return l},set:function(a){l=d3.functor(a)}},margin:{get:function(){return e},set:function(a){e.top=void 0!==a.top?a.top:e.top,e.right=void 0!==a.right?a.right:e.right,e.bottom=void 0!==a.bottom?a.bottom:e.bottom,e.left=void 0!==a.left?a.left:e.left}},color:{get:function(){return h},set:function(b){h=a.utils.getColor(b)}},style:{get:function(){return m},set:function(a){switch(m=a){case"stack":b.offset("zero"),b.order("default");break;case"stream":b.offset("wiggle"),b.order("inside-out");break;case"stream-center":b.offset("silhouette"),b.order("inside-out");break;case"expand":b.offset("expand"),b.order("default");break;case"stack_percent":b.offset(b.d3_stackedOffset_stackPercent),b.order("default")}}},duration:{get:function(){return s},set:function(a){s=a,u.reset(s),r.duration(s)}}}),a.utils.inheritOptions(b,r),a.utils.initOptions(b),b},a.models.stackedAreaChart=function(){"use strict";function b(k){return F.reset(),F.models(e),r&&F.models(f),s&&F.models(g),k.each(function(k){var x=d3.select(this),F=this;a.utils.initSVG(x);var K=a.utils.availableWidth(m,x,l),L=a.utils.availableHeight(n,x,l);if(b.update=function(){x.transition().duration(C).call(b)},b.container=this,v.setter(I(k),b.update).getter(H(k)).update(),v.disabled=k.map(function(a){return!!a.disabled}),!w){var M;w={};for(M in v)v[M]instanceof Array?w[M]=v[M].slice(0):w[M]=v[M]}if(!(k&&k.length&&k.filter(function(a){return a.values.length}).length))return a.utils.noData(b,x),b;x.selectAll(".nv-noData").remove(),c=e.xScale(),d=e.yScale();var N=x.selectAll("g.nv-wrap.nv-stackedAreaChart").data([k]),O=N.enter().append("g").attr("class","nvd3 nv-wrap nv-stackedAreaChart").append("g"),P=N.select("g");if(O.append("rect").style("opacity",0),O.append("g").attr("class","nv-x nv-axis"),O.append("g").attr("class","nv-y nv-axis"),O.append("g").attr("class","nv-stackedWrap"),O.append("g").attr("class","nv-legendWrap"),O.append("g").attr("class","nv-controlsWrap"),O.append("g").attr("class","nv-interactive"),P.select("rect").attr("width",K).attr("height",L),q){var Q=p?K-z:K;h.width(Q),P.select(".nv-legendWrap").datum(k).call(h),l.top!=h.height()&&(l.top=h.height(),L=a.utils.availableHeight(n,x,l)),P.select(".nv-legendWrap").attr("transform","translate("+(K-Q)+","+-l.top+")")}if(p){var R=[{key:B.stacked||"Stacked",metaKey:"Stacked",disabled:"stack"!=e.style(),style:"stack"},{key:B.stream||"Stream",metaKey:"Stream",disabled:"stream"!=e.style(),style:"stream"},{key:B.expanded||"Expanded",metaKey:"Expanded",disabled:"expand"!=e.style(),style:"expand"},{key:B.stack_percent||"Stack %",metaKey:"Stack_Percent",disabled:"stack_percent"!=e.style(),style:"stack_percent"}];z=A.length/3*260,R=R.filter(function(a){return-1!==A.indexOf(a.metaKey)}),i.width(z).color(["#444","#444","#444"]),P.select(".nv-controlsWrap").datum(R).call(i),l.top!=Math.max(i.height(),h.height())&&(l.top=Math.max(i.height(),h.height()),L=a.utils.availableHeight(n,x,l)),P.select(".nv-controlsWrap").attr("transform","translate(0,"+-l.top+")")}N.attr("transform","translate("+l.left+","+l.top+")"),t&&P.select(".nv-y.nv-axis").attr("transform","translate("+K+",0)"),u&&(j.width(K).height(L).margin({left:l.left,top:l.top}).svgContainer(x).xScale(c),N.select(".nv-interactive").call(j)),e.width(K).height(L);var S=P.select(".nv-stackedWrap").datum(k);if(S.transition().call(e),r&&(f.scale(c)._ticks(a.utils.calcTicksX(K/100,k)).tickSize(-L,0),P.select(".nv-x.nv-axis").attr("transform","translate(0,"+L+")"),P.select(".nv-x.nv-axis").transition().duration(0).call(f)),s){var T;if(T="wiggle"===e.offset()?0:a.utils.calcTicksY(L/36,k),g.scale(d)._ticks(T).tickSize(-K,0),"expand"===e.style()||"stack_percent"===e.style()){var U=g.tickFormat();D&&U===J||(D=U),g.tickFormat(J)}else D&&(g.tickFormat(D),D=null);P.select(".nv-y.nv-axis").transition().duration(0).call(g)}e.dispatch.on("areaClick.toggle",function(a){1===k.filter(function(a){return!a.disabled}).length?k.forEach(function(a){a.disabled=!1}):k.forEach(function(b,c){b.disabled=c!=a.seriesIndex}),v.disabled=k.map(function(a){return!!a.disabled}),y.stateChange(v),b.update()}),h.dispatch.on("stateChange",function(a){for(var c in a)v[c]=a[c];y.stateChange(v),b.update()}),i.dispatch.on("legendClick",function(a,c){a.disabled&&(R=R.map(function(a){return a.disabled=!0,a}),a.disabled=!1,e.style(a.style),v.style=e.style(),y.stateChange(v),b.update())}),j.dispatch.on("elementMousemove",function(c){e.clearHighlights();var d,g,h,i=[];if(k.filter(function(a,b){return a.seriesIndex=b,!a.disabled}).forEach(function(f,j){g=a.interactiveBisect(f.values,c.pointXValue,b.x());var k=f.values[g],l=b.y()(k,g);if(null!=l&&e.highlightPoint(j,g,!0),"undefined"!=typeof k){"undefined"==typeof d&&(d=k),"undefined"==typeof h&&(h=b.xScale()(b.x()(k,g)));var m="expand"==e.style()?k.display.y:b.y()(k,g);i.push({key:f.key,value:m,color:o(f,f.seriesIndex),stackedValue:k.display})}}),i.reverse(),i.length>2){var m=b.yScale().invert(c.mouseY),n=null;i.forEach(function(a,b){m=Math.abs(m);var c=Math.abs(a.stackedValue.y0),d=Math.abs(a.stackedValue.y);return m>=c&&d+c>=m?void(n=b):void 0}),null!=n&&(i[n].highlight=!0)}var p=f.tickFormat()(b.x()(d,g)),q=j.tooltip.valueFormatter();"expand"===e.style()||"stack_percent"===e.style()?(E||(E=q),q=d3.format(".1%")):E&&(q=E,E=null),j.tooltip.position({left:h+l.left,top:c.mouseY+l.top}).chartContainer(F.parentNode).valueFormatter(q).data({value:p,series:i})(),j.renderGuideLine(h)}),j.dispatch.on("elementMouseout",function(a){e.clearHighlights()}),y.on("changeState",function(a){"undefined"!=typeof a.disabled&&k.length===a.disabled.length&&(k.forEach(function(b,c){b.disabled=a.disabled[c]}),v.disabled=a.disabled),"undefined"!=typeof a.style&&(e.style(a.style),G=a.style),b.update()})}),F.renderEnd("stacked Area chart immediate"),b}var c,d,e=a.models.stackedArea(),f=a.models.axis(),g=a.models.axis(),h=a.models.legend(),i=a.models.legend(),j=a.interactiveGuideline(),k=a.models.tooltip(),l={top:30,right:25,bottom:50,left:60},m=null,n=null,o=a.utils.defaultColor(),p=!0,q=!0,r=!0,s=!0,t=!1,u=!1,v=a.utils.state(),w=null,x=null,y=d3.dispatch("stateChange","changeState","renderEnd"),z=250,A=["Stacked","Stream","Expanded"],B={},C=250;v.style=e.style(),f.orient("bottom").tickPadding(7),g.orient(t?"right":"left"),k.headerFormatter(function(a,b){return f.tickFormat()(a,b)}).valueFormatter(function(a,b){return g.tickFormat()(a,b)}),j.tooltip.headerFormatter(function(a,b){return f.tickFormat()(a,b)}).valueFormatter(function(a,b){return g.tickFormat()(a,b)});var D=null,E=null;i.updateState(!1);var F=a.utils.renderWatch(y),G=e.style(),H=function(a){return function(){return{active:a.map(function(a){return!a.disabled}),style:e.style()}}},I=function(a){return function(b){void 0!==b.style&&(G=b.style),void 0!==b.active&&a.forEach(function(a,c){a.disabled=!b.active[c]})}},J=d3.format("%");return e.dispatch.on("elementMouseover.tooltip",function(a){a.point.x=e.x()(a.point),a.point.y=e.y()(a.point),k.data(a).position(a.pos).hidden(!1)}),e.dispatch.on("elementMouseout.tooltip",function(a){k.hidden(!0)}),b.dispatch=y,b.stacked=e,b.legend=h,b.controls=i,b.xAxis=f,b.yAxis=g,b.interactiveLayer=j,b.tooltip=k,b.dispatch=y,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return m},set:function(a){m=a}},height:{get:function(){return n},set:function(a){n=a}},showLegend:{get:function(){return q},set:function(a){q=a}},showXAxis:{get:function(){return r},set:function(a){r=a}},showYAxis:{get:function(){return s},set:function(a){s=a}},defaultState:{get:function(){return w},set:function(a){w=a}},noData:{get:function(){return x},set:function(a){x=a}},showControls:{get:function(){return p},set:function(a){p=a}},controlLabels:{get:function(){return B},set:function(a){B=a}},controlOptions:{get:function(){return A},set:function(a){A=a}},tooltips:{get:function(){return k.enabled()},set:function(b){a.deprecated("tooltips","use chart.tooltip.enabled() instead"),k.enabled(!!b)}},tooltipContent:{get:function(){return k.contentGenerator()},set:function(b){a.deprecated("tooltipContent","use chart.tooltip.contentGenerator() instead"),k.contentGenerator(b)}},margin:{get:function(){return l},set:function(a){l.top=void 0!==a.top?a.top:l.top,l.right=void 0!==a.right?a.right:l.right,l.bottom=void 0!==a.bottom?a.bottom:l.bottom,l.left=void 0!==a.left?a.left:l.left}},duration:{get:function(){return C},set:function(a){C=a,F.reset(C),e.duration(C),f.duration(C),g.duration(C)}},color:{get:function(){return o},set:function(b){o=a.utils.getColor(b),h.color(o),e.color(o)}},rightAlignYAxis:{get:function(){return t},set:function(a){t=a,g.orient(t?"right":"left")}},useInteractiveGuideline:{get:function(){return u},set:function(a){u=!!a,b.interactive(!a),b.useVoronoi(!a),e.scatter.interactive(!a)}}}),a.utils.inheritOptions(b,e),a.utils.initOptions(b),b},a.models.sunburst=function(){"use strict";function b(u){return t.reset(),u.each(function(b){function t(a){a.x0=a.x,a.dx0=a.dx}function u(a){var b=d3.interpolate(p.domain(),[a.x,a.x+a.dx]),c=d3.interpolate(q.domain(),[a.y,1]),d=d3.interpolate(q.range(),[a.y?20:0,y]);return function(a,e){return e?function(b){return s(a)}:function(e){return p.domain(b(e)),q.domain(c(e)).range(d(e)),s(a)}}}l=d3.select(this);var v,w=a.utils.availableWidth(g,l,f),x=a.utils.availableHeight(h,l,f),y=Math.min(w,x)/2;a.utils.initSVG(l);var z=l.selectAll(".nv-wrap.nv-sunburst").data(b),A=z.enter().append("g").attr("class","nvd3 nv-wrap nv-sunburst nv-chart-"+k),B=A.selectAll("nv-sunburst");z.attr("transform","translate("+w/2+","+x/2+")"),l.on("click",function(a,b){o.chartClick({data:a,index:b,pos:d3.event,id:k})}),q.range([0,y]),c=c||b,e=b[0],r.value(j[i]||j.count),v=B.data(r.nodes).enter().append("path").attr("d",s).style("fill",function(a){return m((a.children?a:a.parent).name)}).style("stroke","#FFF").on("click",function(a){d!==c&&c!==a&&(d=c),c=a,v.transition().duration(n).attrTween("d",u(a))}).each(t).on("dblclick",function(a){d.parent==a&&v.transition().duration(n).attrTween("d",u(e))}).each(t).on("mouseover",function(a,b){d3.select(this).classed("hover",!0).style("opacity",.8),o.elementMouseover({data:a,color:d3.select(this).style("fill")})}).on("mouseout",function(a,b){d3.select(this).classed("hover",!1).style("opacity",1),o.elementMouseout({data:a})}).on("mousemove",function(a,b){o.elementMousemove({data:a})})}),t.renderEnd("sunburst immediate"),b}var c,d,e,f={top:0,right:0,bottom:0,left:0},g=null,h=null,i="count",j={count:function(a){return 1},size:function(a){return a.size}},k=Math.floor(1e4*Math.random()),l=null,m=a.utils.defaultColor(),n=500,o=d3.dispatch("chartClick","elementClick","elementDblClick","elementMousemove","elementMouseover","elementMouseout","renderEnd"),p=d3.scale.linear().range([0,2*Math.PI]),q=d3.scale.sqrt(),r=d3.layout.partition().sort(null).value(function(a){return 1}),s=d3.svg.arc().startAngle(function(a){return Math.max(0,Math.min(2*Math.PI,p(a.x)))}).endAngle(function(a){return Math.max(0,Math.min(2*Math.PI,p(a.x+a.dx)))}).innerRadius(function(a){return Math.max(0,q(a.y))}).outerRadius(function(a){return Math.max(0,q(a.y+a.dy))}),t=a.utils.renderWatch(o);return b.dispatch=o,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return g},set:function(a){g=a}},height:{get:function(){return h},set:function(a){h=a}},mode:{get:function(){return i},set:function(a){i=a}},id:{get:function(){return k},set:function(a){k=a}},duration:{get:function(){return n},set:function(a){n=a}},margin:{get:function(){return f},set:function(a){f.top=void 0!=a.top?a.top:f.top,f.right=void 0!=a.right?a.right:f.right,f.bottom=void 0!=a.bottom?a.bottom:f.bottom,f.left=void 0!=a.left?a.left:f.left}},color:{get:function(){return m},set:function(b){m=a.utils.getColor(b)}}}),a.utils.initOptions(b),b},a.models.sunburstChart=function(){"use strict";function b(d){return m.reset(),m.models(c),d.each(function(d){var h=d3.select(this);a.utils.initSVG(h);var i=a.utils.availableWidth(f,h,e),j=a.utils.availableHeight(g,h,e);if(b.update=function(){0===k?h.call(b):h.transition().duration(k).call(b)},b.container=this,!d||!d.length)return a.utils.noData(b,h),b;h.selectAll(".nv-noData").remove();var l=h.selectAll("g.nv-wrap.nv-sunburstChart").data(d),m=l.enter().append("g").attr("class","nvd3 nv-wrap nv-sunburstChart").append("g"),n=l.select("g");m.append("g").attr("class","nv-sunburstWrap"),l.attr("transform","translate("+e.left+","+e.top+")"),c.width(i).height(j);var o=n.select(".nv-sunburstWrap").datum(d);d3.transition(o).call(c)}),m.renderEnd("sunburstChart immediate"),b}var c=a.models.sunburst(),d=a.models.tooltip(),e={top:30,right:20,bottom:20,left:20},f=null,g=null,h=a.utils.defaultColor(),i=(Math.round(1e5*Math.random()),null),j=null,k=250,l=d3.dispatch("tooltipShow","tooltipHide","stateChange","changeState","renderEnd"),m=a.utils.renderWatch(l);return d.headerEnabled(!1).duration(0).valueFormatter(function(a,b){return a}),c.dispatch.on("elementMouseover.tooltip",function(a){a.series={key:a.data.name,value:a.data.size,color:a.color},d.data(a).hidden(!1)}),c.dispatch.on("elementMouseout.tooltip",function(a){d.hidden(!0)}),c.dispatch.on("elementMousemove.tooltip",function(a){d.position({top:d3.event.pageY,left:d3.event.pageX})()}),b.dispatch=l,b.sunburst=c,b.tooltip=d,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{noData:{get:function(){return j},set:function(a){j=a}},defaultState:{get:function(){return i},set:function(a){i=a}},color:{get:function(){return h},set:function(a){h=a,c.color(h)}},duration:{get:function(){return k},set:function(a){k=a,m.reset(k),c.duration(k)}},margin:{get:function(){return e},set:function(a){e.top=void 0!==a.top?a.top:e.top,e.right=void 0!==a.right?a.right:e.right,e.bottom=void 0!==a.bottom?a.bottom:e.bottom,e.left=void 0!==a.left?a.left:e.left}}}),a.utils.inheritOptions(b,c),a.utils.initOptions(b),b},a.version="1.8.1"}();
\ No newline at end of file diff --git a/wqflask/wqflask/static/packages/bootstrap/css/bootstrap.css b/wqflask/wqflask/static/packages/bootstrap/css/bootstrap.css index 8326e83f..dd6fa736 100755 --- a/wqflask/wqflask/static/packages/bootstrap/css/bootstrap.css +++ b/wqflask/wqflask/static/packages/bootstrap/css/bootstrap.css @@ -903,7 +903,7 @@ body { font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; font-size: 14px; line-height: 1.42857143; - color: #333; + color: #000; background-color: #fff; } input, @@ -2365,7 +2365,7 @@ output { padding: 6px 12px; font-size: 14px; line-height: 1.42857143; - color: #555; + color: #000; background-color: #fff; background-image: none; border: 1px solid #ccc; @@ -5133,6 +5133,7 @@ a.list-group-item-danger.active:focus { border-bottom: 1px solid transparent; border-top-left-radius: 3px; border-top-right-radius: 3px; + cursor: pointer; } .panel-heading > .dropdown .dropdown-toggle { color: inherit; diff --git a/wqflask/wqflask/templates/base.html b/wqflask/wqflask/templates/base.html index 462a59a2..78682710 100755 --- a/wqflask/wqflask/templates/base.html +++ b/wqflask/wqflask/templates/base.html @@ -1,3 +1,4 @@ +{% from "base_macro.html" import header, flash_me, timeago %} <!DOCTYPE HTML> <html lang="en"> <html xmlns="http://www.w3.org/1999/xhtml"> @@ -10,12 +11,6 @@ <link REL="stylesheet" TYPE="text/css" href="/static/packages/bootstrap/css/bootstrap.css" /> <link REL="stylesheet" TYPE="text/css" href="/static/packages/bootstrap/css/non-responsive.css" /> <link REL="stylesheet" TYPE="text/css" href="/static/packages/bootstrap/css/docs.css" /> - - <!-- HTML5 shim, for IE6-8 support of HTML5 elements --> - <!--[if lt IE 9]> - <script src="http://html5shim.googlecode.com/svn/trunk/html5.js"></script> - <![endif]--> - <link rel="stylesheet" type="text/css" href="/static/packages/colorbox/example4/colorbox.css" /> <link rel="stylesheet" type="text/css" href="/static/new/css/main.css" /> <link rel="stylesheet" type="text/css" href="/static/new/css/parsley.css" /> @@ -25,37 +20,6 @@ </head> -{% macro header(main, second) %} - <header class="jumbotron subhead" id="overview" > - <div class="container"> - <h1>{{ main }}</h1> - <p class="lead"> - {{ second }} - </p> - </div> - </header> - - {{ flash_me() }} -{% endmacro %} - - -{% macro flash_me() -%} - {% with messages = get_flashed_messages(with_categories=true) %} - {% if messages %} - <div class="container"> - {% for category, message in messages %} - <div class="alert {{ category }}">{{ message }}</div> - {% endfor %} - </div> - {% endif %} - {% endwith %} -{% endmacro %} - -{% macro timeago(timestamp) %} -<time class="timeago" datetime="{{ timestamp }}">{{ timestamp }}</time> -{% endmacro %} - - <body style="width: 1500px !important;"> <!-- Navbar ================================================== --> diff --git a/wqflask/wqflask/templates/base_macro.html b/wqflask/wqflask/templates/base_macro.html new file mode 100644 index 00000000..c2905ebe --- /dev/null +++ b/wqflask/wqflask/templates/base_macro.html @@ -0,0 +1,28 @@ +{% macro flash_me() -%} + {% with messages = get_flashed_messages(with_categories=true) %} + {% if messages %} + <div class="container"> + {% for category, message in messages %} + <div class="alert {{ category }}">{{ message }}</div> + {% endfor %} + </div> + {% endif %} + {% endwith %} +{% endmacro %} + +{% macro header(main, second) %} + <header class="jumbotron subhead" id="overview" > + <div class="container"> + <h1>{{ main }}</h1> + <p class="lead"> + {{ second }} + </p> + </div> + </header> + + {{ flash_me() }} +{% endmacro %} + +{% macro timeago(timestamp) %} +<time class="timeago" datetime="{{ timestamp }}">{{ timestamp }}</time> +{% endmacro %} diff --git a/wqflask/wqflask/templates/collections/list.html b/wqflask/wqflask/templates/collections/list.html index 354723d0..f14c0921 100755 --- a/wqflask/wqflask/templates/collections/list.html +++ b/wqflask/wqflask/templates/collections/list.html @@ -15,6 +15,7 @@ <table class="table table-hover" id='trait_table'> <thead> <tr> + <th>Index</th> <th>Name</th> <th>Created</th> <th>Last Changed</th> @@ -25,6 +26,7 @@ <tbody> {% for uc in user_collections %} <tr class="collection_line"> + <td>{{ loop.index }} <td><a class="collection_name" href="{{ url_for('view_collection', uc_id=uc.id) }}">{{ uc.name }}</a></td> <td>{{ timeago(uc.created_timestamp.isoformat() + "Z") }}</td> <td>{{ timeago(uc.changed_timestamp.isoformat() + "Z") }}</td> diff --git a/wqflask/wqflask/templates/collections/view.html b/wqflask/wqflask/templates/collections/view.html index e41ade7c..053861ae 100755 --- a/wqflask/wqflask/templates/collections/view.html +++ b/wqflask/wqflask/templates/collections/view.html @@ -67,7 +67,7 @@ <table class="table table-hover table-striped" id='trait_table'> <thead> <tr> - <th></th> + <th>Index</th> <th>Record</th> <th>Description</th> <th>Location</th> @@ -81,7 +81,7 @@ <tbody> {% for this_trait in trait_obs %} <TR id="trait:{{ this_trait.name }}:{{ this_trait.dataset.name }}"> - <TD> + <TD>{{ loop.index }} <INPUT TYPE="checkbox" NAME="searchResult" class="checkbox trait_checkbox" VALUE="{{ data_hmac('{}:{}'.format(this_trait.name, this_trait.dataset.name)) }}"> </TD> diff --git a/wqflask/wqflask/templates/correlation_page.html b/wqflask/wqflask/templates/correlation_page.html index 169371be..445ad428 100755 --- a/wqflask/wqflask/templates/correlation_page.html +++ b/wqflask/wqflask/templates/correlation_page.html @@ -85,13 +85,29 @@ <td>{{ trait.description }} <br><br> <b>Aliases</b>: {{ trait.alias }}</td> <td>Chr{{ trait.chr }}: {{'%0.6f'|format(trait.mb) if trait.mb != None }}</td> <td>{{'%0.3f'|format(trait.mean)}}</td> + {% if trait.lrs == "" or trait.lrs == 0.000 %} + <td>--</td> + {% else %} <td>{{'%0.3f'|format(trait.lrs)}}</td> - <td>Chr{{ trait.locus_chr }}: {{'%0.3f'|format(trait.locus_mb) }}</td> + {% endif %} + {% if trait.locus_mb == "" %} + <td>--</td> + {% else %} + <td>Chr{{ trait.locus_chr if trait.locus_chr != None }}: {{'%0.3f'|format(trait.locus_mb)}}</td> + {% endif %} <td><a target="_blank" href="corr_scatter_plot?dataset_1={{dataset.name}}&dataset_2={{trait.dataset.name}}&trait_1={{this_trait.name}}&trait_2={{trait.name}}">{{'%0.3f'|format(trait.sample_r)}}</a></td> <td>{{ trait.num_overlap }}</td> <td>{{'%0.3e'|format(trait.sample_p)}}</td> + {% if trait.lit_corr == "" or trait.lit_corr == 0.000 %} + <td>--</td> + {% else %} <td>{{'%0.3f'|format(trait.lit_corr)}}</td> + {% endif %} + {% if trait.tissue_corr == "" or trait.tissue_corr == 0.000 %} + <td>--</td> + {% else %} <td>{{'%0.3f'|format(trait.tissue_corr)}}</td> + {% endif %} <td>{{'%0.3e'|format(trait.tissue_pvalue)}}</td> {% elif target_dataset.type == "Publish" %} <td><a href="/show_trait?trait_id={{trait.name}}&dataset={{trait.dataset.name}}">{{ trait.name }}</a></td> diff --git a/wqflask/wqflask/templates/index_page.html b/wqflask/wqflask/templates/index_page.html index cb29a07c..3d5ddde4 100755 --- a/wqflask/wqflask/templates/index_page.html +++ b/wqflask/wqflask/templates/index_page.html @@ -31,10 +31,10 @@ <label for="species" class="col-xs-1 control-label" style="width: 65px !important;">Species:</label> <div class="col-xs-10 controls input-append" style="padding-right: 0px;"> <div class="col-xs-8"> - <select name="species" id="species" class="form-control selectpicker span3" style="width: 280px !important;"></select> + <select name="species" id="species" class="form-control span3" style="width: 280px !important;"></select> </div> <div class="col-xs-4"> - <input id="make_default" class="btn btn-primary form-control" value="Make Default"> + <button type="button" id="make_default" class="btn btn-primary form-control">Make Default</button> </div> </div> </div> @@ -43,7 +43,7 @@ <label for="group" class="col-xs-1 control-label" style="width: 65px !important;">Group:</label> <div class="col-xs-10 controls input-append"> <div class="col-xs-8"> - <select name="group" id="group" class="form-control selectpicker span3" style="width: 280px !important;"></select> + <select name="group" id="group" class="form-control span3" style="width: 280px !important;"></select> <i class="icon-question-sign"></i> </div> </div> @@ -53,7 +53,7 @@ <label for="tissue" class="col-xs-1 control-label" style="width: 65px !important;">Type:</label> <div class="col-xs-10 controls"> <div class="col-xs-8"> - <select name="type" id="type" class="form-control selectpicker span3" style="width: 280px !important;"></select> + <select name="type" id="type" class="form-control span3" style="width: 280px !important;"></select> </div> </div> </div> @@ -62,7 +62,7 @@ <label for="dataset" class="col-xs-1 control-label" style="width: 65px !important;">Dataset:</label> <div class="col-xs-10 controls input-append"> <div class="col-xs-8"> - <select name="dataset" id="dataset" class="form-control selectpicker span5" style="width: 450px !important;"></select> + <select name="dataset" id="dataset" class="form-control span5" style="width: 450px !important;"></select> <i class="icon-question-sign"></i> </div> </div> @@ -76,10 +76,10 @@ <!-- GET ANY SEARCH --> <div class="form-group"> - <label for="tfor" class="col-xs-1 control-label" style="padding-left: 0px; padding-right: 0px; width: 65px !important;">Search for:</label> + <label for="or_search" class="col-xs-1 control-label" style="padding-left: 0px; padding-right: 0px; width: 65px !important;">Get Any:</label> <div class="col-xs-10 controls"> <div class="col-xs-8"> - <textarea name="search_terms" rows="2" class="form-control search-query" style="max-width: 550px; width: 450px !important;" id="tfor"></textarea> + <textarea name="search_terms_or" rows="1" class="form-control search-query" style="max-width: 550px; width: 450px !important;" id="or_search"></textarea> </div> </div> </div> @@ -87,19 +87,33 @@ <!-- GET ANY HELP --> <div class="form-group"> <label for="btsearch" class="col-xs-1 control-label" style="width: 65px !important;"></label> + <div class="col-xs-10 controls"> + <div class="col-xs-12 controls"> + Enter terms, genes, ID numbers in the <b>Search</b> field.<br> + Use <b>*</b> or <b>?</b> wildcards (Cyp*a?, synap*).<br> + Use <b>quotes</b> for terms such as <i>"tyrosine kinase"</i>. + </div> + </div> + </div> + + <div class="form-group"> + <label for="and_search" class="col-xs-1 control-label" style="padding-left: 0px; padding-right: 0px; width: 65px !important;">Combined:</label> + <div class="col-xs-10 controls"> + <div class="col-xs-8"> + <textarea name="search_terms_and" rows="1" class="form-control search-query" style="max-width: 550px; width: 450px !important;" id="and_search"></textarea> + </div> + </div> + </div> + + <div class="form-group"> + <label for="btsearch" class="col-xs-1 control-label" style="width: 65px !important;"></label> <div class="col-xs-10 controls"> <div class="col-xs-2 controls" style="width: 100px !important;"> <input id="btsearch" type="submit" class="btn btn-primary form-control" value="Search"> </div> - <div class="col-xs-9 controls"> - Enter terms, genes, ID numbers in the <b>Search</b> field - Use <b>*</b> or <b>?</b> wildcards (Cyp*a?, synap*) - Use <b>quotes</b> for terms such as <i>"tyrosine kinase"</i> - </div> </div> </div> - <!-- SEARCH, MAKE DEFAULT --> <div class="form-group"> @@ -116,21 +130,15 @@ <h2>Advanced commands</h2> </div> - <p>GeneNetwork supports a variety of advanced searches.</p> - - <p>To try them out copy these examples into the search field:</p> + <p>You can also use advanced commands. Copy these simple examples into the Get Any or Combined search fields:</p> <ul> - <!--<li><b>POSITION=(chr1 25 30)</b> finds genes, markers, or transcripts on + <li><b>POSITION=(chr1 25 30)</b> finds genes, markers, or transcripts on chromosome 1 between 25 and 30 Mb.</li> <li><b>MEAN=(15 16) LRS=(23 46)</b> in the <b>Combined</b> field finds highly expressed genes (15 to 16 log2 units) AND with peak <a href="http://www.genenetwork.org/glossary.html#L" target="_blank">LRS</a> - linkage between 23 and 46.</li>--> - - <li><b>MEAN=(15 16)</b> finds highly expressed genes (15 to 16 log2 units).</li> - - <li><b>LRS=(23 46)</b> finds genes with peak <a href="http://www.genenetwork.org/glossary.html#L" target="_blank">LRS</a> linkage between 23 and 46.</li> + linkage between 23 and 46.</li> <li><b>RIF=mitochondrial</b> searches RNA databases for <a href="http://www.ncbi.nlm.nih.gov/projects/GeneRIF/GeneRIFhelp.html" target="_blank"> GeneRIF</a> links.</li> @@ -146,12 +154,12 @@ <li><b>GO:0045202 LRS=(9 99 Chr4 122 155) cisLRS=(9 999 10)</b> finds synapse-associated genes with <a href="http://www.genenetwork.org/glossary.html#E" target="_blank"> cis eQTL</a> on Chr 4 from 122 and 155 Mb with LRS scores - between 9 and 999.</li> + between 9 and 999.</li>--> <li><b>RIF=diabetes LRS=(9 999 Chr2 100 105) transLRS=(9 999 10)</b> finds diabetes-associated transcripts with peak <a href="http://www.genenetwork.org/glossary.html#E" target="_blank"> trans eQTLs</a> on Chr 2 between 100 and 105 Mb with LRS - scores between 9 and 999.</li>--> + scores between 9 and 999.</li> </ul> </section> </div> @@ -207,11 +215,8 @@ <ul> <li><a href="http://www.genenetwork.org/" target="_blank">Main GN1 site at UTHSC</a> (main site)</li> - <li><a href="http://www.genenetwork.waimr.uwa.edu.au/" target="_blank">Australia at the UWA</a></li> - <li><a href="http://gn.genetics.ucla.edu/" target="_blank">California at UCLA</a></li> <li><a href="http://genenetwork.helmholtz-hzi.de/" target="_blank">Germany at the HZI</a></li> <li><a href="http://genenetwork.memphis.edu/" target="_blank">Memphis at the U of M</a></li> - <li><a href="http://genenetwork.epfl.ch/" target="_blank">Switzerland at the EPFL</a></li> </ul> </section> diff --git a/wqflask/wqflask/templates/interval_mapping.html b/wqflask/wqflask/templates/interval_mapping.html deleted file mode 100755 index 82a96ba1..00000000 --- a/wqflask/wqflask/templates/interval_mapping.html +++ /dev/null @@ -1,116 +0,0 @@ -{% block css %} -<!-- <link rel="stylesheet" type="text/css" href="/static/new/css/interval_mapping.css" />--> - <link rel="stylesheet" type="text/css" href="/static/new/packages/DataTables/css/jquery.dataTables.css" /> - <link rel="stylesheet" type="text/css" href="/static/packages/DT_bootstrap/DT_bootstrap.css" /> - <link rel="stylesheet" type="text/css" href="/static/packages/TableTools/media/css/TableTools.css" /> - <link rel="stylesheet" type="text/css" href="/static/new/css/d3-tip.min.css" /> - <link rel="stylesheet" type="text/css" href="/static/new/css/panelutil.css" /> -{% endblock %} -{% block content %} <!-- Start of body --> - - - <div id="mapping_results" class="container"> - <div> - <h2> - Whole Genome Mapping - </h2> - <form style ='float: left; padding: 5px;' id="exportform" action="export" method="post"> - <input type="hidden" id="data" name="data" value=""> - <input type="hidden" id="filename" name="filename" value=""> - <input type="submit" id="export" value="Download SVG"> - </form> - <form style ='float: left; padding: 5px;' id="exportpdfform" action="export_pdf" method="post"> - <input type="hidden" id="data" name="data" value=""> - <input type="hidden" id="filename" name="filename" value=""> - <input type="submit" id="export_pdf" value="Download PDF"> - </form> - </div> - <div id="chart_container"> - <div class="qtlcharts" id="topchart"> - - </div> - </div> - <div> - <h2> - Results - </h2> - </div> - <table cellpadding="0" cellspacing="0" border="0" id="qtl_results" class="table table-hover table-striped table-bordered"> - <thead> - <tr> - <td>Index</td> - <td>LRS Score</td> - <td>Chr</td> - <td>Mb</td> - <td>Locus</td> - <td>Additive Effect</td> - </tr> - </thead> - <tbody> - {% for marker in qtl_results %} - <tr> - <td>{{ loop.index }}</td> - <td>{{ marker.lrs_value|float }}</td> - <td>{{ marker.chr|int }}</td> - <td>{{ marker.Mb|float }}</td> - <td>{{ marker.name }}</td> - <td>{{ marker.additive|float }}</td> - </tr> - {% endfor %} - </tbody> - </table> - - </div> - - <!-- End of body --> - -{% endblock %} - -{% block js %} - <script> - js_data = {{ js_data | safe }} - </script> - - <!--[if lt IE 9]> -<!-- <script language="javascript" type="text/javascript" src="/static/packages/jqplot/excanvas.js"></script>--> - <![endif]--> - <script language="javascript" type="text/javascript" src="http://d3js.org/d3.v3.min.js"></script> - <script language="javascript" type="text/javascript" src="/static/new/js_external/d3-tip.min.js"></script> - <script language="javascript" type="text/javascript" src="/static/new/javascript/panelutil.js"></script> - <script language="javascript" type="text/javascript" src="/static/new/javascript/chr_interval_map.js"></script> - <script language="javascript" type="text/javascript" src="/static/new/javascript/lod_chart.js"></script> - <script language="javascript" type="text/javascript" src="/static/new/javascript/create_lodchart.js"></script> - <script language="javascript" type="text/javascript" src="/static/new/packages/DataTables/js/jquery.js"></script> - <script language="javascript" type="text/javascript" src="/static/new/packages/DataTables/js/jquery.dataTables.min.js"></script> - <script language="javascript" type="text/javascript" src="/static/new/packages/DataTables/js/dataTables.scientific.js"></script> - <script language="javascript" type="text/javascript" src="/static/packages/DT_bootstrap/DT_bootstrap.js"></script> - <script language="javascript" type="text/javascript" src="/static/packages/TableTools/media/js/TableTools.min.js"></script> - <script language="javascript" type="text/javascript" src="/static/packages/underscore/underscore-min.js"></script> - - <script type="text/javascript" charset="utf-8"> - $(document).ready( function () { - console.time("Creating table"); - $('#qtl_results').dataTable( { - //"sDom": "<<'span3'l><'span3'T><'span4'f>'row-fluid'r>t<'row-fluid'<'span6'i><'span6'p>>", - "sDom": "lTftipr", - "oTableTools": { - "aButtons": [ - "copy", - "print", - { - "sExtends": "collection", - "sButtonText": 'Save <span class="caret" />', - "aButtons": [ "csv", "xls", "pdf" ] - } - ], - "sSwfPath": "/static/packages/TableTools/media/swf/copy_csv_xls_pdf.swf" - }, - "iDisplayLength": 50, - "bLengthChange": true, - "bDeferRender": true, - "bSortClasses": false - } ); - console.timeEnd("Creating table"); - }); - </script> -{% endblock %}
\ No newline at end of file diff --git a/wqflask/wqflask/templates/marker_regression.html b/wqflask/wqflask/templates/marker_regression.html index 6aed69d5..d8f64c20 100755 --- a/wqflask/wqflask/templates/marker_regression.html +++ b/wqflask/wqflask/templates/marker_regression.html @@ -1,15 +1,5 @@ -{% extends "base.html" %} -{% block title %}Interval Mapping{% endblock %} -{% block css %} -<!-- <link rel="stylesheet" type="text/css" href="/static/new/css/interval_mapping.css" />--> - <link rel="stylesheet" type="text/css" href="/static/new/packages/DataTables/css/jquery.dataTables.css" /> - <link rel="stylesheet" type="text/css" href="/static/packages/DT_bootstrap/DT_bootstrap.css" /> - <link rel="stylesheet" type="text/css" href="/static/packages/TableTools/media/css/TableTools.css" /> - <link rel="stylesheet" type="text/css" href="/static/new/css/d3-tip.min.css" /> - <link rel="stylesheet" type="text/css" href="/static/new/css/panelutil.css" /> -{% endblock %} -{% block content %} <!-- Start of body --> - +{% from "base_macro.html" import header %} +{% block content %} {{ header("Mapping", '{}: {}'.format(this_trait.name, this_trait.description_fmt)) }} @@ -29,6 +19,7 @@ <input type="submit" id="export_pdf" value="Download PDF"> </form> <!-- <button id="export_pdf" class="btn">Export PDF</button>--> + <button id="return_to_full_view" class="btn" style="display:none">Return to full view</button> </div> <div id="chart_container"> <div class="qtlcharts" id="topchart"> @@ -75,28 +66,6 @@ js_data = {{ js_data | safe }} </script> - <!--[if lt IE 9]> -<!-- <script language="javascript" type="text/javascript" src="/static/packages/jqplot/excanvas.js"></script>--> - <![endif]--> - <script language="javascript" type="text/javascript" src="http://d3js.org/d3.v3.min.js"></script> - <script language="javascript" type="text/javascript" src="/static/new/js_external/d3-tip.min.js"></script> -<!-- <script language="javascript" type="text/javascript" src="/static/new/packages/jsPDF/jspdf.js"></script> - <script language="javascript" type="text/javascript" src="/static/new/packages/jsPDF/libs/FileSaver.js/FileSaver.js"></script> - <script language="javascript" type="text/javascript" src="/static/new/packages/jsPDF/libs/Blob.js/BlobBuilder.js"></script> - <script language="javascript" type="text/javascript" src="/static/new/packages/jsPDF/jspdf.plugin.standard_fonts_metrics.js"></script> - <script language="javascript" type="text/javascript" src="/static/new/packages/jsPDF/jspdf.plugin.from_html.js"></script>--> - <script language="javascript" type="text/javascript" src="/static/new/javascript/panelutil.js"></script> - <script language="javascript" type="text/javascript" src="/static/new/javascript/chr_lod_chart.js"></script> -<!-- <script language="javascript" type="text/javascript" src="/static/new/javascript/manhattan_plot.js"></script>--> - <script language="javascript" type="text/javascript" src="/static/new/javascript/lod_chart.js"></script> - <script language="javascript" type="text/javascript" src="/static/new/javascript/create_lodchart.js"></script> - <script language="javascript" type="text/javascript" src="/static/new/packages/DataTables/js/jquery.js"></script> - <script language="javascript" type="text/javascript" src="/static/new/packages/DataTables/js/jquery.dataTables.min.js"></script> - <script language="javascript" type="text/javascript" src="/static/new/packages/DataTables/js/dataTables.scientific.js"></script> - <script language="javascript" type="text/javascript" src="/static/packages/DT_bootstrap/DT_bootstrap.js"></script> - <script language="javascript" type="text/javascript" src="/static/packages/TableTools/media/js/TableTools.min.js"></script> - <script language="javascript" type="text/javascript" src="/static/packages/underscore/underscore-min.js"></script> - <script type="text/javascript" charset="utf-8"> $(document).ready( function () { console.time("Creating table"); @@ -123,4 +92,4 @@ console.timeEnd("Creating table"); }); </script> -{% endblock %}
\ No newline at end of file +{% endblock %} diff --git a/wqflask/wqflask/templates/old_index_page.html b/wqflask/wqflask/templates/old_index_page.html deleted file mode 100755 index db0b2d9e..00000000 --- a/wqflask/wqflask/templates/old_index_page.html +++ /dev/null @@ -1,320 +0,0 @@ -{% extends "base.html" %} -{% block title %}GeneNetwork{% endblock %} -{% block content %} - <!-- Start of body --> - <tr> - <td bgcolor="#EEEEEE" class="solidBorder"> - <table width="100%" cellspacing="0" cellpadding="5"> - <tr> - <td valign="top" width="40%" align="left" height="10" bgcolor="#EEEEEE"> - <p style="font-size:18px;font-family:verdana;color:black"><b>Select and - Search</b></p> - - <form method="get" action="/search" class="form-search" name="SEARCHFORM"> - <table width="100%"> - <!-- SPECIES SELECTION --> - - <tr> - <td align="right" height="35" style= - "font-size:14px;font-family:verdana;color:black" width="16%"> - <b>Species:</b></td> - - <td width="3%"></td> - - <td nowrap width="85%" align="left"> - <div id="menu0"> - <select name="species" size="1" id="species" onchange= - "fillOptions('species');"> - </select> - </div> - </td> - </tr><!-- GROUP SELECTION --> - - <tr> - <td align="right" height="35" style= - "font-size:14px;font-family:verdana;color:black"><b>Group:</b></td> - - <td width="3%"></td> - - <td nowrap width="85%" align="left"> - <div id="menu1"> - <select name="cross" size="1" id="cross" onchange="fillOptions('cross');"> - </select> <input type="button" class="btn" value="Info" onclick= - "javascript:crossinfo();"> - </div> - </td> - </tr><!-- TYPE SELECTION --> - - <tr> - <td align="right" height="35" style= - "font-size:14px;font-family:verdana;color:black"><b>Type:</b></td> - - <td width="3%"></td> - - <td nowrap width="85%" align="left"> - <div id="menu2"> - <select name="tissue" size="1" id="tissue" onchange= - "fillOptions('tissue');"> - </select> - </div> - </td> - </tr><!-- DATABASE SELECTION --> - - <tr> - <td align="right" height="35" style= - "font-size:14px;font-family:verdana;color:black"><b>Database:</b></td> - - <td width="3%"></td> - - <td nowrap width="85%" align="left"> - <div id="menu3"> - <select name="database" size="1" id="database"> - </select> <input type="submit" class="btn" value="Info" name= - "info_database"> - </div> - </td> - </tr><!-- USER HELP --> - - <tr> - <td align="right" height="20" width="10%"></td> - - <td width="3%"></td> - - <td align="left" width="85%"> - <p class="fs12"> Databases marked with <b>**</b> - suffix are not public yet.<br> - Access requires <a href="/account.html" target= - "_blank" class="fs14"><small>user login</small></a>.</p> - </td> - </tr><!-- GET ANY SEARCH --> - - <tr> - <td align="right" height="35" nowrap="on" style= - "font-size:14px;font-family:verdana;color:black" width="10%"> - <b>Search:</b></td> - - <td width="3%"></td> - - <td nowrap width="85%" align="left"><input class="input-medium search-query" - id="tfor" name="search_terms" style= - "width:420px; background-color:white; font-family:verdana; font-size:14px" - type="text" maxlength="500"></td> - </tr><!-- GET ANY HELP --> - - <tr> - <td align="right" height="20" width="10%"></td> - - <td width="3%"></td> - - <td width="85%" align="left"> - <p class="fs12"> Enter terms, genes, ID numbers in the - <b>Search</b> field.<br> - Use <b>*</b> or <b>?</b> wildcards (Cyp*a?, - synap*).<br> - Use <b>quotes</b> for terms such as <i>"tyrosine - kinase"</i>.</p> - </td> - </tr><!-- SEARCH, MAKE DEFAULT, ADVANCED SEARCH --> - - <tr align="center"> - <td width="3%"></td> - - <td width="3%"></td> - - <td align="left" height="40" colspan="3"><input id="btsearch" type="submit" - class="btn btn-primary" value="Search"> <input type= - "button" class="btn" value="Make Default" onclick= - "setDefault(this.form);"> <input type="button" class= - "btn" value="Advanced Search" onclick= - "javascript:window.open('/index3.html', '_self');"></td> - </tr> - </table><input type="hidden" name="FormID" value="searchResult"> <input type= - "hidden" name="RISet" value="BXD"> <script src="/javascript/selectDatasetMenu.js" - type="text/javascript"> -</script> - </form><!-- QUICK HELP --> - - <p> ______________________________________________________</p> - - <p style="font-size:13px;font-family:verdana;color:black"><b> Quick HELP - Examples and</b> <a href="http://www.genenetwork.org/index4.html" target="_blank" - class="fs14"><b>User's Guide</b></a></p> You can also use advanced - commands. Copy these simple examples<br> - into the <b>Get Any</b> or <b>Combined</b> search fields: - - <ul style="font-size:12px;font-family:verdana;color:black"> - <li><b><i>POSITION=(chr1 25 30)</i></b> finds genes, markers, or transcripts on - chromosome 1 between 25 and 30 Mb.</li> - - <li><b><i>MEAN=(15 16) LRS=(23 46)</i></b> in the <b>Combined</b> field finds - highly expressed genes (15 to 16 log2 units) AND with peak <a href= - "http://www.genenetwork.org/glossary.html#L" target="_blank" class= - "fs14"><small>LRS</small></a> linkage between 23 and 46.</li> - - <li><b><i>RIF=mitochondrial</i></b> searches RNA databases for <a href= - "http://www.ncbi.nlm.nih.gov/projects/GeneRIF/GeneRIFhelp.html" target="_blank" - class="fs14"><small>GeneRIF</small></a> links.</li> - - <li><b><i>WIKI=nicotine</i></b> searches <a href= - "http://www.genenetwork.org/webqtl/main.py?FormID=geneWiki" target="_blank" class= - "fs14"><small>GeneWiki</small></a> for genes that you or other users have annotated - with the word <i>nicotine</i>.</li> - - <li><b><i>GO:0045202</i></b> searches for synapse-associated genes listed in the - <a href="http://www.godatabase.org/cgi-bin/amigo/go.cgi" target="_blank" class= - "fs14"><small>Gene Ontology</small></a>.</li> - - <li><b><i>GO:0045202 LRS=(9 99 Chr4 122 155) cisLRS=(9 999 10)</i></b><br> - in <b>Combined</b> finds synapse-associated genes with <a href= - "http://www.genenetwork.org/glossary.html#E" target="_blank" class= - "fs14"><small>cis eQTL</small></a> on Chr 4 from 122 and 155 Mb with LRS scores - between 9 and 999.</li> - - <li><b><i>RIF=diabetes LRS=(9 999 Chr2 100 105) transLRS=(9 999 10)</i></b><br> - in <b>Combined</b> finds diabetes-associated transcripts with peak <a href= - "http://www.genenetwork.org/glossary.html#E" target="_blank" class= - "fs14"><small>trans eQTLs</small></a> on Chr 2 between 100 and 105 Mb with LRS - scores between 9 and 999.</li> - </ul> - </td><!-- END OF FIND SELECTOR PULL-DOWN PANEL (LEFT SIDE) --> - <!-- START OF TOP RIGHT PANEL --> - - <td valign="top" width="40%" bgcolor="#FFFFFF"> - <p style="font-size:15px;font-family:verdana;color:black"><b>Websites Affiliated with - GeneNetwork</b></p> - - <p style="font-size:12px;font-family:verdana;color:black"></p> - - <ul> - <li><a href="http://ucscbrowser.genenetwork.org/" target="_blank">Genome - Browser</a> at UTHSC</li> - - <li><a href="http://galaxy.genenetwork.org/" target="_blank">Galaxy</a> at - UTHSC</li> - - <li>GeneNetwork at <a href="http://ec2.genenetwork.org/" target="_blank">Amazon - Cloud (EC2)</a></li> - - <li>GeneNetwork Source Codes at <a href= - "http://sourceforge.net/projects/genenetwork/" target="_blank">SourceForge</a></li> - - <li>GeneNetwork Source Codes at <a href= - "https://github.com/genenetwork/genenetwork" target="_blank">GitHub</a></li> - </ul> - - <p>____________________________</p> - - <p style="font-size:15px;font-family:verdana;color:black"><b>Getting Started</b> - </p> - - <ol style="font-size:12px;font-family:verdana;color:black"> - <li>Select <b>Species</b> (or select All)</li> - - <li>Select <b>Group</b> (a specific sample)</li> - - <li>Select <b>Type</b> of data: - - <ul> - <li>Phenotype (traits)</li> - - <li>Genotype (markers)</li> - - <li>Expression (mRNAs)</li> - </ul> - </li> - - <li>Select a <b>Database</b></li> - - <li>Enter search terms in the <b>Get Any</b> or <b>Combined</b> field: words, - genes, ID numbers, probes, advanced search commands</li> - - <li>Click on the <b>Search</b> button</li> - - <li>Optional: Use the <b>Make Default</b> button to save your preferences</li> - </ol> - - <p>____________________________</p> - - <p style="font-size:14px;font-family:verdana;color:black"><b>How to Use - GeneNetwork</b></p> - - <blockquote> - <p style="font-size:12px;font-family:verdana;color:black">Take a 20-40 minute - GeneNetwork <a href="http://www.genenetwork.org/tutorial/WebQTLTour/" target= - "_blank" class="fs14"><small>Tour</small></a> that includes screen shots and - typical steps in the analysis.</p> - </blockquote> - - <blockquote> - <p style="font-size:12px;font-family:verdana;color:black">For information about - resources and methods, select the <img src= - "http://www.genenetwork.org/images/upload/Info.png" alt="INFO" border="0" valign= - "middle"> buttons.</p> - - <p style="font-size:12px;font-family:verdana;color:black">Try the <a href= - "http://alexandria.uthsc.edu/" target="_blank" class= - "fs14"><small>Workstation</small></a> site to explore data and features that are - being implemented.</p> - - <p style="font-size:12px;font-family:verdana;color:black">Review the <a href= - "/conditionsofUse.html" target="_blank" class="fs14"><small>Conditions</small></a> - and <a href="/statusandContact.html" target="_blank" class= - "fs14"><small>Contacts</small></a> pages for information on the status of data sets - and advice on their use and citation.</p> - </blockquote> - - <p style="font-size:14px;font-family:verdana;color:black"><b>Mirror and Development - Sites</b></p> - - <ul> - <li><a href="http://www.genenetwork.org/" target="_blank" style= - "font-size:12px;font-family:verdana;color:blue">Main GN site at UTHSC</a> (main - site)</li> - - <li><a href="http://www.genenetwork.waimr.uwa.edu.au/" target="_blank" style= - "font-size:12px;font-family:verdana;color:blue">Australia at the UWA</a></li> - - <li><a href="http://gn.genetics.ucla.edu/" target="_blank" style= - "font-size:12px;font-family:verdana;color:blue">California at UCLA</a></li> - - <li><a href="http://genenetwork.helmholtz-hzi.de/" target="_blank" style= - "font-size:12px;font-family:verdana;color:blue">Germany at the HZI</a></li> - - <li><a href="https://genenetwork.hubrecht.eu/" target="_blank" style= - "font-size:12px;font-family:verdana;color:blue">Netherlands at the Hubrecht</a> - (Development)</li> - - <li><a href="http://genenetwork.memphis.edu/" target="_blank" style= - "font-size:12px;font-family:verdana;color:blue">Memphis at the U of M</a></li> - - <li><a href="http://webqtl.bic.nus.edu.sg/" target="_blank" style= - "font-size:12px;font-family:verdana;color:blue">Singapore at the NUS</a></li> - - <li><a href="http://genenetwork.epfl.ch/" target="_blank" style= - "font-size:12px;font-family:verdana;color:blue">Switzerland at the EPFL</a></li> - </ul> - - <p style="font-size:14px;font-family:verdana;color:black"><b>History and - Archive</b></p> - - <blockquote> - <p style="font-size:12px;font-family:verdana;color:black">GeneNetwork's <a href= - "http://artemis.uthsc.edu" target="_blank" class="fs14"><small>Time - Machine</small></a> links to earlier versions that correspond to specific - publication dates.</p> - </blockquote> - </td> - </tr> - </table> - </td> - </tr> - <!-- End of body --> - <script src="/javascript/searchtip.js" type="text/javascript"> - </script> - <script type="text/javascript"> - $(document).ready(function () { - initialDatasetSelection(); - }); - </script> -{% endblock %} - diff --git a/wqflask/wqflask/templates/search_error.html b/wqflask/wqflask/templates/search_error.html new file mode 100644 index 00000000..22416580 --- /dev/null +++ b/wqflask/wqflask/templates/search_error.html @@ -0,0 +1,21 @@ +{% extends "base.html" %} +{% block title %}Search Results{% endblock %} +{% block css %} + <link rel="stylesheet" type="text/css" href="/static/new/packages/DataTables/css/jquery.dataTables.css" /> + <link rel="stylesheet" type="text/css" href="/static/packages/DT_bootstrap/DT_bootstrap.css" /> + <link rel="stylesheet" type="text/css" href="/static/packages/TableTools/media/css/TableTools.css" /> +{% endblock %} +{% block content %} +<!-- Start of body --> + {{ header("Error") }} + + <div class="container"> + <input type="hidden" name="uc_id" id="uc_id" value="{{ uc_id }}"> + <p>You entered at least one incorrect search command.</p> + </div> + + <div id="myModal"></div> + +<!-- End of body --> + +{% endblock %} diff --git a/wqflask/wqflask/templates/search_result_page.html b/wqflask/wqflask/templates/search_result_page.html index c7c2a62f..7c39ac61 100755 --- a/wqflask/wqflask/templates/search_result_page.html +++ b/wqflask/wqflask/templates/search_result_page.html @@ -63,7 +63,7 @@ <tbody> {% for this_trait in trait_list %} <TR id="trait:{{ this_trait.name }}:{{ this_trait.dataset.name }}"> - <TD> + <TD>{{ loop.index }} <INPUT TYPE="checkbox" NAME="searchResult" class="checkbox trait_checkbox" VALUE="{{ data_hmac('{}:{}'.format(this_trait.name, this_trait.dataset.name)) }}"> </TD> @@ -82,7 +82,7 @@ <TD>{{ this_trait.mean }}</TD> <TD align="right">{{ this_trait.LRS_score_repr }}</TD> <TD>{{ this_trait.LRS_location_repr }}</TD> - <TD>{{ this_trait.additive }}</TD> + <TD>{{ '%0.3f' % this_trait.additive|float }}</TD> {% elif dataset.type == 'Publish' %} <TD>{{ this_trait.description_display }}</TD> <TD>{{ this_trait.authors }}</TD> @@ -93,7 +93,7 @@ </TD> <TD>{{ this_trait.LRS_score_repr }}</TD> <TD>{{ this_trait.LRS_location_repr }}</TD> - <TD>{{ this_trait.additive }}</TD> + <TD>{{ '%0.3f' % this_trait.additive|float }}</TD> {% elif dataset.type == 'Geno' %} <TD>{{ this_trait.location_repr }}</TD> {% endif %} @@ -119,22 +119,37 @@ <script language="javascript" type="text/javascript" src="/static/packages/DT_bootstrap/DT_bootstrap.js"></script> <script language="javascript" type="text/javascript" src="/static/packages/TableTools/media/js/TableTools.min.js"></script> <script type="text/javascript" charset="utf-8"> + + console.log("TESTING:", parseFloat("TESTING")) + function getValue(x) { if (x.indexOf('input') >= 0) { if ($(x).val() == 'x') { - return 0 + return 0; } else { return parseFloat($(x).val()); } } + else if (isNaN(x)) { + return x; + } return parseFloat(x); } jQuery.fn.dataTableExt.oSort['cust-txt-asc'] = function (a, b) { var x = getValue(a); - var y = getValue(b); - return ((x < y) ? -1 : ((x > y) ? 1 : 0)); + var y = getValue(b); + + if (x == 'N/A' || x == '') { + return 1; + } + else if (y == 'N/A' || y == '') { + return -1; + } + else { + return ((x < y) ? -1 : ((x > y) ? 1 : 0)); + } }; jQuery.fn.dataTableExt.oSort['cust-txt-desc'] = function (a, b) { @@ -164,21 +179,12 @@ "sWidth": "35%" }, { "sType": "natural", "sWidth": "15%" }, - { "sType": "cust-txt" }, + { "sType": "natural" }, { "sType": "natural", "sWidth": "12%" }, { "sType": "natural", - "sWidth": "15%" }, - { "sType": "cust-txt" } - ], - "columns": [ - { "width": "50%" }, - null, - null, - null, - null, - null, - null + "sWidth": "20%" }, + { "sType": "natural" } ], "sDom": "tir", "iDisplayLength": -1, @@ -187,7 +193,6 @@ "bDeferRender": true, "bSortClasses": false } ); - {% elif dataset.type == 'Publish' %} $('#trait_table').dataTable( { //"sDom": "<<'span3'l><'span3'T><'span4'f>'row-fluid'r>t<'row-fluid'<'span6'i><'span6'p>>", @@ -200,16 +205,8 @@ "sWidth": "20%" }, { "sType": "natural" }, { "sType": "cust-txt" }, - { "sType": "natural" } - ], - "columns": [ - { "width": "50%" }, - null, - null, - null, - null, - null, - null + { "sType": "natural" }, + { "sType": "cust-txt" } ], "sDom": "tir", "iDisplayLength": -1, diff --git a/wqflask/wqflask/templates/show_trait.html b/wqflask/wqflask/templates/show_trait.html index d6f22f41..cdde5d9d 100755 --- a/wqflask/wqflask/templates/show_trait.html +++ b/wqflask/wqflask/templates/show_trait.html @@ -5,11 +5,15 @@ <link rel="stylesheet" type="text/css" href="/static/new/css/show_trait.css" /> <link rel="stylesheet" type="text/css" href="/static/new/css/bar_chart.css" /> <link rel="stylesheet" type="text/css" href="/static/new/css/box_plot.css" /> + <link rel="stylesheet" type="text/css" href="/static/new/css/prob_plot.css" /> <link rel="stylesheet" type="text/css" href="/static/new/css/panelutil.css" /> <link rel="stylesheet" type="text/css" href="/static/new/css/scatter-matrix.css" /> <link rel="stylesheet" type="text/css" href="/static/new/css/d3-tip.min.css" /> + <link rel="stylesheet" type="text/css" href="/static/new/packages/nvd3/nv.d3.min.css" /> <link rel="stylesheet" type="text/css" href="/static/new/packages/DataTables/css/jquery.dataTables.css" /> <link rel="stylesheet" type="text/css" href="/static/packages/DT_bootstrap/DT_bootstrap.css" /> + <link rel="stylesheet" type="text/css" href="/static/packages/TableTools/media/css/TableTools.css" /> + {% endblock %} {% block content %} <!-- Start of body --> @@ -24,6 +28,7 @@ <form method="post" action="/corr_compute" name="trait_page" id="trait_data_form" class="form-horizontal"> <div id="hidden_inputs"> + <input type="hidden" name="trait_hmac" value="{{ data_hmac('{}:{}'.format(this_trait.name, dataset.name)) }}"> {% for key in hddn %} <input type="hidden" name="{{ key }}" value="{{ hddn[key] }}"> {% endfor %} @@ -42,11 +47,9 @@ {% include 'show_trait_details.html' %} <div class="panel-group" id="accordion"> <div class="panel panel-default"> - <div class="panel-heading"> + <div class="panel-heading" data-toggle="collapse" data-parent="#accordion" data-target="#collapseOne"> <h3 class="panel-title"> - <a data-toggle="collapse" data-parent="#accordion" href="#collapseOne"> - <span class="glyphicon glyphicon-chevron-down"></span> Statistics - </a> + <span class="glyphicon glyphicon-chevron-down"></span> Statistics </h3> </div> <div id="collapseOne" class="panel-collapse collapse in"> @@ -56,11 +59,9 @@ </div> </div> <div class="panel panel-default"> - <div class="panel-heading"> + <div class="panel-heading" data-toggle="collapse" data-parent="#accordion" data-target="#collapseTwo"> <h3 class="panel-title"> - <a data-toggle="collapse" data-parent="#accordion" href="#collapseTwo"> - <span class="glyphicon glyphicon-chevron-down"></span> Calculate Correlations - </a> + <span class="glyphicon glyphicon-chevron-down"></span> Calculate Correlations </h3> </div> <div id="collapseTwo" class="panel-collapse collapse in"> @@ -70,11 +71,9 @@ </div> </div> <div class="panel panel-default"> - <div class="panel-heading"> + <div class="panel-heading" data-toggle="collapse" data-parent="#accordion" data-target="#collapseThree"> <h3 class="panel-title"> - <a data-toggle="collapse" data-parent="#accordion" href="#collapseThree"> - <span class="glyphicon glyphicon-chevron-down"></span> Mapping Tools - </a> + <span class="glyphicon glyphicon-chevron-down"></span> Mapping Tools </h3> </div> <div id="collapseThree" class="panel-collapse collapse in"> @@ -84,11 +83,9 @@ </div> </div> <div class="panel panel-default"> - <div class="panel-heading"> + <div class="panel-heading" data-toggle="collapse" data-parent="#accordion" data-target="#collapseFour" aria-expanded="true"> <h3 class="panel-title"> - <a data-toggle="collapse" data-parent="#accordion" href="#collapseFour" aria-expanded="true"> - <span class="glyphicon glyphicon-chevron-up"></span> Review and Edit Data - </a> + <span class="glyphicon glyphicon-chevron-up"></span> Review and Edit Data </h3> </div> <div id="collapseFour" class="panel-collapse collapse" aria-expanded="true"> @@ -114,9 +111,12 @@ </script> <script type="text/javascript" src="http://d3js.org/d3.v3.min.js"></script> + <script type="text/javascript" src="/static/new/packages/nvd3/nv.d3.js"></script> <script type="text/javascript" src="/static/new/js_external/underscore-min.js"></script> <script type="text/javascript" src="/static/new/js_external/underscore.string.min.js"></script> <script type="text/javascript" src="/static/new/js_external/d3-tip.min.js"></script> + <script type="text/javascript" src="/static/new/js_external/jstat.min.js"></script> + <script type="text/javascript" src="/static/new/js_external/shapiro-wilk.js"></script> <script type="text/javascript" src="/static/new/javascript/colorbrewer.js"></script> <script type="text/javascript" src="/static/new/packages/ValidationPlugin/dist/jquery.validate.min.js"></script> <script type="text/javascript" src="/static/new/javascript/panelutil.js"></script> @@ -135,9 +135,16 @@ <script type="text/javascript" src="/static/new/javascript/show_trait.js"></script> <script type="text/javascript" src="/static/new/javascript/get_traits_from_collection.js"></script> <script type="text/javascript" src="/static/new/javascript/validation.js"></script> - - <script language="javascript" type="text/javascript" src="/static/packages/bootstrap/js/bootstrap.min.js"></script> + <script language="javascript" type="text/javascript" src="/static/new/packages/jsPDF/libs/FileSaver.js/FileSaver.js"></script> + <script language="javascript" type="text/javascript" src="/static/new/packages/jsPDF/libs/Blob.js/BlobBuilder.js"></script> + <script language="javascript" type="text/javascript" src="/static/new/packages/jsPDF/jspdf.plugin.standard_fonts_metrics.js"></script> + <script language="javascript" type="text/javascript" src="/static/new/packages/jsPDF/jspdf.plugin.from_html.js"></script> + <script language="javascript" type="text/javascript" src="/static/new/javascript/chr_lod_chart.js"></script> + <script language="javascript" type="text/javascript" src="/static/new/javascript/create_lodchart.js"></script> + <script language="javascript" type="text/javascript" src="/static/new/javascript/lod_chart.js"></script> + <script language="javascript" type="text/javascript" src="/static/new/packages/DataTables/js/jquery.dataTables.min.js"></script> + <script language="javascript" type="text/javascript" src="/static/new/packages/DataTables/js/dataTables.scientific.js"></script> <script language="javascript" type="text/javascript" src="/static/new/packages/DataTables/js/dataTables.naturalSort.js"></script> <script language="javascript" type="text/javascript" src="/static/packages/DT_bootstrap/DT_bootstrap.js"></script> <script language="javascript" type="text/javascript" src="/static/packages/TableTools/media/js/TableTools.min.js"></script> @@ -146,7 +153,7 @@ function getValue(x) { if (x.indexOf('input') >= 0) { if ($(x).val() == 'x') { - return 0 + return 'x' } else { return parseFloat($(x).val()); @@ -164,12 +171,21 @@ jQuery.fn.dataTableExt.oSort['cust-txt-desc'] = function (a, b) { var x = getValue(a); var y = getValue(b); - return ((x < y) ? 1 : ((x > y) ? -1 : 0)); + + if (x == 'x') { + return 1; + } + else if (y == 'x') { + return -1; + } + else { + return ((x < y) ? 1 : ((x > y) ? -1 : 0)); + } }; $(document).ready( function () { - $('.panel-heading').find('a').click(function () { + $('.panel-heading').click(function () { if ($(this).hasClass('collapsed')){ $(this).find('.glyphicon-chevron-down').removeClass('glyphicon-chevron-down').addClass('glyphicon-chevron-up'); } @@ -241,7 +257,6 @@ "bDeferRender": true, "bSortClasses": false } ); - console.timeEnd("Creating table"); {% endif %} }); </script> diff --git a/wqflask/wqflask/templates/show_trait_details.html b/wqflask/wqflask/templates/show_trait_details.html index e1780e42..108fcd92 100755 --- a/wqflask/wqflask/templates/show_trait_details.html +++ b/wqflask/wqflask/templates/show_trait_details.html @@ -29,42 +29,87 @@ <dt>BLAT Score</dt> <dd>{{ "%0.3f" | format(this_trait.probe_set_blat_score|float) }}</dd> {% endif %} + <dt>Resource Links</dt> + {% if this_trait.dataset.type == 'ProbeSet' %} + <dd> + {% if this_trait.symbol != None %} + <a href="http://bioinformatics-dev/Getd2g.pl?gene_list={{ this_trait.symbol }}" target="_blank" title="Related descriptive, genomic, clinical, functional and drug-therapy information"> + Genotation + </a> + + {% endif %} + {% if this_trait.geneid != None %} + <a href="http://www.ncbi.nlm.nih.gov/entrez/query.fcgi?db=gene&cmd=Retrieve&dopt=Graphics&list_uids={{ this_trait.geneid }}" target="_blank" title="Info from NCBI Entrez Gene"> + Gene + </a> + + {% endif %} + {% if this_trait.omim != None %} + <a href="http://www.ncbi.nlm.nih.gov/omim/{{ this_trait.omim }}" target="_blank" title="Summary from On Mendelion Inheritance in Man"> + OMIM + </a> + + {% endif %} + {% if this_trait.genbankid != None %} + <a href="http://www.ncbi.nlm.nih.gov/entrez/query.fcgi?db=Nucleotide&cmd=search&doptcmdl=DocSum&term={{ this_trait.genbankid }}" target="_blank" title="Find the original GenBank sequence used to design the probes"> + GenBank + </a> + + {% endif %} + </dd> + {% endif %} </dl> - -<!--<div class="btn-toolbar"> +<div style="margin-bottom:15px;" class="btn-toolbar"> <div class="btn-group"> - <button class="btn btn-primary" title="Add to collection"> + <a href="#redirect"> + <button type="button" id="add_to_collection" class="btn btn-primary" title="Add to collection"> <i class="icon-plus-sign icon-white"></i> Add </button> - - <button class="btn" title="Find similar expression data"> + </a> + {% if this_trait.dataset.type == 'ProbeSet' %} + {% if this_trait.symbol != None %} + <a href="#redirect" onclick="window.open('http://www.genenetwork.org/webqtl/main.py?cmd=sch&gene={{ this_trait.symbol }}&alias=1&species={{ species_name }}')"> + <button type="button" class="btn btn-default" title="Find similar expression data"> <i class="icon-search"></i> Find </button> - - <button class="btn" title="Check probe locations at UCSC"> + </a> + {% endif %} + {% if UCSC_BLAT_URL != "" %} + <a href="#redirect" onclick="window.open('{{ UCSC_BLAT_URL }}')"> + <button type="button" class="btn btn-default" title="Check probe locations at UCSC"> <i class="icon-ok"></i> Verify </button> - </div> - - <div class="btn-group"> - - <button class="btn" title="Write or review comments about this gene"> + </a> + {% endif %} + {% if this_trait.symbol != None %} + <a href="#redirect" onclick="window.open('http://genenetwork.org/webqtl/main.py?FormID=geneWiki&symbol={{ this_trait.symbol }}')"> + <button type="button" class="btn btn-default" title="Write or review comments about this gene"> <i class="icon-edit"></i> GeneWiki </button> - - <button class="btn" title="View SNPs and Indels"> + </a> + <a href="#redirect" onclick="window.open('http://genenetwork.org/webqtl/main.py?FormID=SnpBrowserResultPage&submitStatus=1&diffAlleles=True&customStrain=True&geneName={{ this_trait.symbol }}')"> + <button type="button" class="btn btn-default" title="View SNPs and Indels"> <i class="icon-road"></i> SNPs </button> - - <button class="btn" title="View probes, SNPs, and RNA-seq at UTHSC"> + </a> + {% endif %} + {% if UTHSC_BLAT_URL != "" %} + <a href="#redirect" onclick="window.open('{{ UTHSC_BLAT_URL }}')"> + <button type="button" class="btn btn-default" title="View probes, SNPs, and RNA-seq at UTHSC"> <i class="icon-eye-close"></i> RNA-seq </button> - - <button class="btn" title="Check sequence of probes"> + </a> + {% endif %} + {% if show_probes == "True" %} + <a href="#redirect" onclick="window.open('http://genenetwork.org/webqtl/main.py?FormID=showProbeInfo&database={{ this_trait.dataset.name }}&ProbeSetID={{ this_trait.name }}&CellID={{ this_trait.cellid }}&RISet={{ dataset.group.name }}&incparentsf1=ON')"> + <button type="button" class="btn btn-default" title="Check sequence of probes"> <i class="icon-list"></i> Probes </button> - + </a> + {% endif %} + {% endif %} </div> -</div>--> +</div> + diff --git a/wqflask/wqflask/templates/show_trait_edit_data.html b/wqflask/wqflask/templates/show_trait_edit_data.html index 12a6a48e..a02dc409 100755 --- a/wqflask/wqflask/templates/show_trait_edit_data.html +++ b/wqflask/wqflask/templates/show_trait_edit_data.html @@ -14,10 +14,10 @@ <input type="text" id="remove_samples_field"> <select id="block_group" size="1"> <option value="primary"> - {{ sample_group_types['primary_only'] }} + {{ sample_group_types['samples_primary'] }} </option> <option value="other"> - {{ sample_group_types['other_only'] }} + {{ sample_group_types['samples_other'] }} </option> </select> <input type="button" id="block_by_index" class="btn" value="Block"> @@ -44,11 +44,11 @@ {% endif %} <br> <div> - <input type="button" id="hide_no_value" class="btn" value="Hide No Value"> - <input type="button" id="block_outliers" class="btn" value="Block Outliers"> - <input type="button" id="reset" class="btn btn-inverse" value="Reset"> + <input type="button" id="hide_no_value" class="btn btn-default" value="Hide No Value"> + <input type="button" id="block_outliers" class="btn btn-default" value="Block Outliers"> + <input type="button" id="reset" class="btn btn-primary" value="Reset"> <span class="input-append"> - <input type="button" id="export" class="btn" value="Export"> + <input type="button" id="export" class="btn btn-default" value="Export"> <select id="export_format" class="select optional span2"> <option value="excel">Excel</option> <option value="csv">CSV</option> @@ -75,7 +75,7 @@ <div id="edit_sample_lists"> {% for sample_type in sample_groups %} - <div> + <div class="sample_group"> <h3>{{ sample_type.header }}</h3> <table cellpadding="0" cellspacing="0" border="0" class="table table-hover table-striped table-bordered" diff --git a/wqflask/wqflask/templates/show_trait_mapping_tools.html b/wqflask/wqflask/templates/show_trait_mapping_tools.html index bd1e6f1e..6f69bcfa 100755 --- a/wqflask/wqflask/templates/show_trait_mapping_tools.html +++ b/wqflask/wqflask/templates/show_trait_mapping_tools.html @@ -1,20 +1,21 @@ <div> + {% if (use_pylmm_rqtl and dataset.group.species != "human") or use_plink_gemma %} <div class="col-xs-6"> <div class="tabbable"> <!-- Only required for left/right tabs --> <ul class="nav nav-pills"> + {% if use_pylmm_rqtl and not use_plink_gemma and dataset.group.species != "human" %} <li class="active"> <a href="#pylmm" data-toggle="tab">pyLMM</a> </li> <li> <a href="#rqtl_geno" data-toggle="tab">rqtl</a> </li> - {% if dataset.group.species != 'human' %} <li> <a href="#interval_mapping" data-toggle="tab">Interval Mapping</a> </li> {% endif %} - {% if dataset.group.species == 'human' %} + {% if use_plink_gemma %} <li> <a href="#plink" data-toggle="tab">PLINK</a> </li> @@ -28,8 +29,8 @@ </ul> <div class="tab-content"> + {% if use_pylmm_rqtl and not use_plink_gemma and dataset.group.species != "human" %} <div class="tab-pane active" id="pylmm"> - <div style="padding: 20px" class="form-horizontal"> <div class="mapping_method_fields form-group"> <label for="mapping_permutations" class="col-xs-2 control-label">Permutations</label> @@ -66,7 +67,7 @@ </div> <div class="form-group"> <div style="padding-left:15px;" class="controls"> - <button id="pylmm_compute" class="btn submit_special btn-primary" data-url="/marker_regression" title="Compute Marker Regression"> + <button id="pylmm_compute" class="btn submit_special btn-primary" title="Compute Marker Regression"> <i class="icon-ok-circle icon-white"></i> Open Mapping Tool </button> </div> @@ -160,7 +161,6 @@ </div> </div> </div> - {% if dataset.group.species != 'human' %} <div class="tab-pane" id="interval_mapping"> <div style="padding: 20px" class="form-horizontal"> <div class="mapping_method_fields form-group"> @@ -214,7 +214,7 @@ </div> </div> {% endif %} - {% if dataset.group.species == 'human' %} + {% if use_plink_gemma %} <div class="tab-pane" id="plink"> <div style="padding: 20px" class="form-horizontal"> <div class="mapping_method_fields form-group"> @@ -272,4 +272,7 @@ <div id="mapping_result_holder_wrapper" style="display:none;"> <div id="mapping_result_holder"></div> </div> + {% else %} + Mapping options are disabled for data not matched with genotypes. + {% endif %} </div> diff --git a/wqflask/wqflask/templates/show_trait_progress_bar.html b/wqflask/wqflask/templates/show_trait_progress_bar.html index 99906338..f9a34070 100755 --- a/wqflask/wqflask/templates/show_trait_progress_bar.html +++ b/wqflask/wqflask/templates/show_trait_progress_bar.html @@ -32,17 +32,4 @@ </div> </div> </div> -</div> - -<!--<div id="static_progress_bar_container" class="modal hide fade" tabindex="-1" role="dialog" aria-labelledby="progress_bar" aria-hidden="true"> - <div class="modal-header"> - <h3 id="progress_bar">Loading... (Estimated time ~10-15m)</h3> - </div> - <div class="modal-body"> - <div class="progress progress-striped active"> - <div id="marker_regression_progress" class="bar" style="width: 100%"></div> - </div> - <div id="time_remaining"> - </div> - </div> -</div>-->
\ No newline at end of file +</div>
\ No newline at end of file diff --git a/wqflask/wqflask/templates/show_trait_statistics_new.html b/wqflask/wqflask/templates/show_trait_statistics_new.html index f2ebbbef..4eb74e5a 100755 --- a/wqflask/wqflask/templates/show_trait_statistics_new.html +++ b/wqflask/wqflask/templates/show_trait_statistics_new.html @@ -32,7 +32,7 @@ </div> <div class="tab-pane" id="histogram_tab"> <div style="padding: 20px" class="form-horizontal"> - {% if sample_groups|length == 1 %} + {% if sample_groups|length != 1 %} <select class="histogram_samples_group"> {% for group, pretty_group in sample_group_types.items() %} <option value="{{ group }}">{{ pretty_group }}</option> @@ -47,7 +47,7 @@ </div> <div class="tab-pane" id="bar_chart_tab"> <div style="padding: 20px" class="form-horizontal"> - {% if sample_groups|length == 1 %} + {% if sample_groups|length != 1 %} <select class="bar_chart_samples_group"> {% for group, pretty_group in sample_group_types.items() %} <option value="{{ group }}">{{ pretty_group }}</option> @@ -79,32 +79,40 @@ </button> </div> </div> - <div class="row" style="height: 0px"> - <div id="bar_chart_legend" style="margin-left: 900px; margin-top:50px; positive: relative;"> + <div class="row" style="position:relative"> + <div id="bar_chart_legend" style="margin-left: 150px; margin-top:20px; position: absolute;"> <span id="legend-left"></span> - <span id="legend-colors"> - <!-- - <svg height="10" width="90"> - <rect x="0" width="15" height="10" style="fill: rgb(0, 0, 0);"></rect> - <rect x="15" width="15" height="10" style="fill: rgb(50, 0, 0);"></rect> - <rect x="30" width="15" height="10" style="fill: rgb(100, 0, 0);"></rect> - <rect x="45" width="15" height="10" style="fill: rgb(150, 0, 0);"></rect> - <rect x="60" width="15" height="10" style="fill: rgb(200, 0, 0);"></rect> - <rect x="75" width="15" height="10" style="fill: rgb(255, 0, 0);"></rect> - </svg> - --> - </span> + <span id="legend-colors"></span> <span id="legend-right"></span> </div> </div> <div style="margin-left: 20px; margin-right: 20px;" class="row" id="bar_chart_container"> - <div id="bar_chart" class="barchart"></div> + <svg></svg> </div> </div> <div class="tab-pane" id="probability_plot"> - <div id="probability_plot_container"> - <div id="prob_plot" class="qtlcharts"></div> + <div style="padding: 20px" class="form-horizontal"> + {% if sample_groups|length != 1 %} + <select class="prob_plot_samples_group"> + {% for group, pretty_group in sample_group_types.items() %} + <option value="{{ group }}">{{ pretty_group }}</option> + {% endfor %} + </select> + <br><br> + {% endif %} + + <div id="prob_plot_container"> + <div id="prob_plot_title"></div> + <svg></svg> + </div> + + <div> + More about <a href="http://en.wikipedia.org/wiki/Normal_probability_plot" target="_blank">Normal Probability Plots</a> and more + about interpreting these plots from the <a href="/glossary.html#normal_probability" target="_blank">glossary</a></td> + </div> + </div> + </div> <!-- <div class="tab-pane" id="box_plot_tab"> {% if sample_groups|length > 1 %} @@ -140,4 +148,4 @@ <div id="collections_holder"></div> </div> -</div>
\ No newline at end of file +</div> diff --git a/wqflask/wqflask/views.py b/wqflask/wqflask/views.py index f9fa1e12..e00c3e80 100755 --- a/wqflask/wqflask/views.py +++ b/wqflask/wqflask/views.py @@ -129,7 +129,7 @@ def search_page(): else: return render_template("data_sharing.html", **template_vars.__dict__) else: - key = "search_results:v1:" + json.dumps(request.args, sort_keys=True) + key = "search_results:v2:" + json.dumps(request.args, sort_keys=True) print("key is:", pf(key)) with Bench("Loading cache"): result = Redis.get(key) @@ -150,8 +150,10 @@ def search_page(): if result['quick']: return render_template("quick_search.html", **result) - else: + elif result['search_term_exists']: return render_template("search_result_page.html", **result) + else: + return render_template("search_error.html") @app.route("/gsearch", methods=('GET',)) def gsearchreq(): @@ -309,6 +311,10 @@ def heatmap_page(): return rendered_template +@app.route("/mapping_results_container") +def mapping_results_container_page(): + return render_template("mapping_results_container.html") + @app.route("/marker_regression", methods=('POST',)) def marker_regression_page(): initial_start_vars = request.form @@ -333,7 +339,7 @@ def marker_regression_page(): if key in wanted or key.startswith(('value:')): start_vars[key] = value - version = "v4" + version = "v3" key = "marker_regression:{}:".format(version) + json.dumps(start_vars, sort_keys=True) print("key is:", pf(key)) with Bench("Loading cache"): @@ -394,7 +400,7 @@ def export(): svg_xml = request.form.get("data", "Invalid data") filename = request.form.get("filename", "manhattan_plot_snp") response = Response(svg_xml, mimetype="image/svg+xml") - response.headers["Content-Disposition"] = "attchment; filename=%s"%filename + response.headers["Content-Disposition"] = "attachment; filename=%s"%filename return response @app.route("/export_pdf", methods = ('POST',)) @@ -407,7 +413,7 @@ def export_pdf(): filepath = "/home/zas1024/gene/wqflask/output/"+filename pdf_file = cairosvg.svg2pdf(bytestring=svg_xml) response = Response(pdf_file, mimetype="application/pdf") - response.headers["Content-Disposition"] = "attchment; filename=%s"%filename + response.headers["Content-Disposition"] = "attachment; filename=%s"%filename return response @app.route("/interval_mapping", methods=('POST',)) @@ -460,7 +466,7 @@ def interval_mapping_page(): Redis.expire(key, 60*60) with Bench("Rendering template"): - rendered_template = render_template("interval_mapping.html", **result) + rendered_template = render_template("marker_regression.html", **result) return rendered_template |