From a67b786f3f52c33c59a26570b06c342840366c2f Mon Sep 17 00:00:00 2001 From: Kyle Ellrott Date: Mon, 14 Jan 2013 22:39:31 -0800 Subject: [PATCH 001/523] Adding in the starting work for a unified search system. --- lib/galaxy/model/search.py | 242 ++++++++++++++++++ lib/galaxy/webapps/galaxy/api/search.py | 113 ++++++++ lib/galaxy/webapps/galaxy/buildapp.py | 2 + .../webapps/galaxy/controllers/search.py | 18 ++ templates/search/index.mako | 21 ++ templates/search/search.mako | 146 +++++++++++ templates/webapps/galaxy/base_panels.mako | 4 +- 7 files changed, 545 insertions(+), 1 deletion(-) create mode 100644 lib/galaxy/model/search.py create mode 100644 lib/galaxy/webapps/galaxy/api/search.py create mode 100644 lib/galaxy/webapps/galaxy/controllers/search.py create mode 100644 templates/search/index.mako create mode 100644 templates/search/search.mako diff --git a/lib/galaxy/model/search.py b/lib/galaxy/model/search.py new file mode 100644 index 00000000000..b61d7330045 --- /dev/null +++ b/lib/galaxy/model/search.py @@ -0,0 +1,242 @@ +""" +Classes for implmenting search methods for various data models +""" + +import logging +from galaxy.model.orm import * +from galaxy.model import * + +from sqlalchemy import or_, and_ + +log = logging.getLogger( __name__ ) + + +""" + +The search model is a simplified view of the SQL schema. A search domain +typicaly covers a base data table, but it's fields can be constructs of multiple +tables that are joined togeather. For example, a History dataset may have tags associated with it. +The Search class simplifies the joining process between the history dataset and the tags table, +and presents the 'tag' field as a single concept that can be filtered against. + + +""" + +class SearchField(object): + + def __init__(self, name): + self.name = name + self.other = None + self.mode = None + + def __eq__(self, other ): + self.other = other + self.mode = "==" + return self + + def like(self, other): + self.other = other + self.mode = "like" + return self + +class QueryBaseClass(object): + OUTPUT_COLUMNS = [] + + def __init__(self, base_query): + self.query = base_query + self.do_query = False + + def get_results(self, force_query=False): + if self.query is not None and (force_query or self.do_query): + print self.query + for row in self.query.distinct().all(): + out = {} + for col in self.OUTPUT_COLUMNS: + out[col] = getattr(row, col) + yield out + + +class SearchBaseClass(object): + + def get_field(self, name): + for f_obj in self.FIELDS: + if isinstance(f_obj, SearchField): + if f_obj.name == name: + return f_obj + else: + print "Not SearchField", f_obj + return None + + @staticmethod + def search(trans): + return None + + +################## +#Library Searching +################## + +class LibraryQuery(QueryBaseClass): + OUTPUT_COLUMNS = [ 'extended_metadata', 'name', 'id' ] + def filter(self, arg): + if arg.name == 'extended_metadata': + self.do_query = True + self.query = self.query.join( ExtendedMetadata ) + ex_meta = arg.other + for f in ex_meta: + alias = aliased( ExtendedMetadataIndex ) + self.query = self.query.filter( + and_( + ExtendedMetadata.id == alias.extended_metadata_id, + alias.path == "/" + f, + alias.value == str(ex_meta[f]) + ) + ) + + +class LibrarySearch(SearchBaseClass): + FIELDS = [ + SearchField("name"), + SearchField("id"), + SearchField("extended_metadata") + ] + + @staticmethod + def search(trans): + query = trans.sa_session.query( LibraryDatasetDatasetAssociation ) + return LibraryQuery(query) + +################## +#History Dataset Searching +################## + + +class HistoryDatasetQuery(QueryBaseClass): + OUTPUT_COLUMNS = ['name', 'id'] + + def filter(self, arg): + if arg.name == 'name': + if arg.mode == "==": + self.do_query = True + self.query= self.query.filter( HistoryDatasetAssociation.name == arg.other ) + if arg.mode == "like": + self.do_query = True + self.query= self.query.filter( HistoryDatasetAssociation.name.like(arg.other) ) + +class HistoryDatasetSearch(SearchBaseClass): + FIELDS = [ + SearchField("name") + ] + @staticmethod + def search(trans): + query = trans.sa_session.query( HistoryDatasetAssociation ) + return HistoryDatasetQuery(query) + + +################## +#History Searching +################## + + +class HistoryQuery(QueryBaseClass): + OUTPUT_COLUMNS = ['name', 'id'] + + def filter(self, arg): + if arg.name == 'name': + if arg.mode == "==": + self.do_query = True + self.query = self.query.filter( History.name == arg.other ) + if arg.mode == "like": + self.do_query = True + self.query = self.query.filter( History.name.like(arg.other) ) + + if arg.name == 'tag': + self.do_query = True + self.query = self.query.filter( + History.id == HistoryTagAssociation.history_id + ) + tmp = arg.other.split(":") + self.query = self.query.filter( HistoryTagAssociation.user_tname == tmp[0] ) + if len(tmp) > 1: + self.query = self.query.filter( HistoryTagAssociation.user_value == tmp[1] ) + + if arg.name == 'annotation': + if arg.mode == "==": + self.do_query = True + self.query = self.query.filter( and_( + HistoryAnnotationAssociation.history_id == History.id, + HistoryAnnotationAssociation.annotation == arg.other + ) + ) + + if arg.mode == "like": + self.do_query = True + self.query = self.query.filter( and_( + HistoryAnnotationAssociation.history_id == History.id, + HistoryAnnotationAssociation.annotation.like( arg.other ) + ) + ) + + +class HistorySearch(SearchBaseClass): + FIELDS = [ + SearchField("name"), + SearchField("tag"), + SearchField("annotation") + ] + + @staticmethod + def search(trans): + query = trans.sa_session.query( History ) + return HistoryQuery(query) + +################## +#Workflow Searching +################## + + + +class WorkflowQuery(QueryBaseClass): + OUTPUT_COLUMNS = ['name', 'id'] + + def filter(self, arg): + if arg.name == 'name': + self.do_query = True + self.query = self.query.filter( StoredWorkflow.name == arg.other ) + if arg.name == 'tag': + self.do_query = True + self.query = self.query.filter( and_( + Tag.name == arg.other, + Tag.id == StoredWorkflowTagAssociation.tag_id, + StoredWorkflowTagAssociation.stored_workflow_id == StoredWorkflow.id ) + ) + self.query = self.query.filter( + Workflow.id == StoredWorkflowTagAssociation.workflow_id + ) + tmp = arg.other.split(":") + self.query = self.query.filter( StoredWorkflowTagAssociation.user_tname == tmp[0] ) + if len(tmp) > 1: + self.query = self.query.filter( StoredWorkflowTagAssociation.user_value == tmp[1] ) + + +class WorkflowSearch(SearchBaseClass): + + FIELDS = [ + SearchField("name"), + SearchField("tag") + ] + + @staticmethod + def search(trans): + query = trans.sa_session.query( StoredWorkflow ) + return WorkflowQuery(query) + + +search_mapping = { + 'library' : LibrarySearch, + 'history_dataset' : HistoryDatasetSearch, + 'history' : HistorySearch, + 'workflow' : WorkflowSearch +} + + diff --git a/lib/galaxy/webapps/galaxy/api/search.py b/lib/galaxy/webapps/galaxy/api/search.py new file mode 100644 index 00000000000..66f1836cee8 --- /dev/null +++ b/lib/galaxy/webapps/galaxy/api/search.py @@ -0,0 +1,113 @@ +""" +API for searching Galaxy Datasets +""" +import logging, os, string, shutil, urllib, re, socket +from cgi import escape, FieldStorage +from galaxy import util, datatypes, jobs, web, util +from galaxy.web.base.controller import * +from galaxy.util.sanitize_html import sanitize_html +from galaxy.model.orm import * +from galaxy.model.search import * + + +from galaxy.model import * + +from sqlalchemy import or_, and_ + +log = logging.getLogger( __name__ ) + +class SearchController( BaseAPIController ): + """ + The search API accepts a POST with a payload of: + - search_type : The type of data to be searched, "library", "history_dataset", "history", "workflow" + - result_fields : The fields in the return structure that should be populated, includes: + "name", "id", "extended_metadata", "annotation", "tags" + - query : The query structure to be used + + Example query data structures: + + Find elements such that name == "test" + { + "name" : "test" + } + + + Find elements such that annotation contains the work 'world' + + { + "annotation" : { "$like" : "%world%" } + } + + Find elements such that the extended metadata field dataSubType/@id == 'geneExp' + + { + "extended_metadata" : { + "dataSubType/@id" : "geneExp" + } + } + + """ + + FIELD_NAME = "name" + FIELD_ID = "id" + FIELD_EXTENDED_METADATA = "extended_metadata" + FIELD_ANNOTATION = "annotation" + FIELD_TAGS = "tags" + + @web.expose_api + def create( self, trans, payload, **kwd ): + """ + POST /api/search + Do a search of the various elements of Galaxy. + """ + domains = payload.get("domain", []) + result_fields = payload.get("result_fields", [self.FIELD_EXTENDED_METADATA]) + query = payload.get("query", {}) + print payload + out = [] + for domain in domains: + if domain in search_mapping: + search_base = search_mapping[domain]() + search_query = search_base.search(trans) + for field in query: + search_field = search_base.get_field(field) + if search_field is not None: + if isinstance(query[field], dict) and '$like' in query[field]: + search_query.filter( search_field.like(query[field]['$like']) ) + else: + search_query.filter( search_field == query[field] ) + out += list(search_query.get_results()) + + return self._create_response(trans, out, result_fields) + + def _create_response(self, trans, rows, result_fields): + out = [] + for row in rows: + o = {} + o[ self.FIELD_ID ] = trans.security.encode_id(row['id']) + o[ self.FIELD_NAME ] = row['name'] + + if self.FIELD_EXTENDED_METADATA in result_fields and 'extended_metadata' in row: + o[ self.FIELD_EXTENDED_METADATA ] = row['extended_metadata.data'] + + if self.FIELD_ANNOTATION in result_fields: + try: + o[self.FIELD_ANNOTATION] = [] + for a in row['annotations']: + o[self.FIELD_ANNOTATION].append(a.annotation) + except AttributeError: + del o[self.FIELD_ANNOTATION] + + if self.FIELD_TAGS in result_fields: + try: + o[self.FIELD_TAGS] = [] + for t in row['tags']: + s = t.user_tname + if t.user_value is not None: + s += ":" + t.user_value + o[self.FIELD_TAGS].append(s) + except AttributeError: + del o[self.FIELD_TAGS] + + out.append(o) + return out diff --git a/lib/galaxy/webapps/galaxy/buildapp.py b/lib/galaxy/webapps/galaxy/buildapp.py index 15bb1026b4d..89686de1366 100644 --- a/lib/galaxy/webapps/galaxy/buildapp.py +++ b/lib/galaxy/webapps/galaxy/buildapp.py @@ -59,6 +59,7 @@ def app_factory( global_conf, **kwargs ): webapp.add_route( '/u/:username/h/:slug', controller='history', action='display_by_username_and_slug' ) webapp.add_route( '/u/:username/w/:slug', controller='workflow', action='display_by_username_and_slug' ) webapp.add_route( '/u/:username/v/:slug', controller='visualization', action='display_by_username_and_slug' ) + webapp.add_route( '/search', controller='search', action='index' ) # Add the web API webapp.add_api_controllers( 'galaxy.webapps.galaxy.api', app ) @@ -122,6 +123,7 @@ def app_factory( global_conf, **kwargs ): webapp.api_mapper.resource( 'visualization', 'visualizations', path_prefix='/api' ) webapp.api_mapper.resource( 'workflow', 'workflows', path_prefix='/api' ) webapp.api_mapper.resource_with_deleted( 'history', 'histories', path_prefix='/api' ) + webapp.api_mapper.resource( 'search', 'search', path_prefix='/api' ) webapp.api_mapper.resource( 'configuration', 'configuration', path_prefix='/api' ) #webapp.api_mapper.connect( 'run_workflow', '/api/workflow/{workflow_id}/library/{library_id}', controller='workflows', action='run', workflow_id=None, library_id=None, conditions=dict(method=["GET"]) ) diff --git a/lib/galaxy/webapps/galaxy/controllers/search.py b/lib/galaxy/webapps/galaxy/controllers/search.py new file mode 100644 index 00000000000..28d92487491 --- /dev/null +++ b/lib/galaxy/webapps/galaxy/controllers/search.py @@ -0,0 +1,18 @@ + +""" +Contains the main interface in the Universe class +""" +import logging, os, string, shutil, urllib, re, socket +from cgi import escape, FieldStorage +from galaxy import util, datatypes, jobs, web, util +from galaxy.web.base.controller import * +from galaxy.util.sanitize_html import sanitize_html +from galaxy.model.orm import * +from galaxy.model.item_attrs import UsesAnnotations + +log = logging.getLogger( __name__ ) + +class SearchController( BaseUIController ): + @web.expose + def index(self, trans): + return trans.fill_template( "search/index.mako") \ No newline at end of file diff --git a/templates/search/index.mako b/templates/search/index.mako new file mode 100644 index 00000000000..b8926d7387e --- /dev/null +++ b/templates/search/index.mako @@ -0,0 +1,21 @@ + +<%inherit file="/webapps/galaxy/base_panels.mako"/> +<%namespace file="/search/search.mako" import="search_init" /> +<%namespace file="/search/search.mako" import="search_dialog" /> + +<%def name="init()"> +<% + self.has_left_panel = False + self.has_right_panel = False + self.active_view = "profile" +%> + + +<%def name="center_panel()"> + +${search_init()} + +${search_dialog()} + + + diff --git a/templates/search/search.mako b/templates/search/search.mako new file mode 100644 index 00000000000..b10f1a63c6c --- /dev/null +++ b/templates/search/search.mako @@ -0,0 +1,146 @@ + +<%def name="search_init()"> + + ${h.js( + 'libs/jquery/jquery', + 'libs/json2' +)} + + + +<%def name="search_dialog()"> + + + + +
+ + + +
+ + \ No newline at end of file diff --git a/templates/webapps/galaxy/base_panels.mako b/templates/webapps/galaxy/base_panels.mako index f77e7dbb1f3..24f402ab5de 100644 --- a/templates/webapps/galaxy/base_panels.mako +++ b/templates/webapps/galaxy/base_panels.mako @@ -80,7 +80,9 @@ [ _('Published Histories'), h.url_for( controller='/history', action='list_published' ) ], [ _('Published Workflows'), h.url_for( controller='/workflow', action='list_published' ) ], [ _('Published Visualizations'), h.url_for( controller='/visualization', action='list_published' ) ], - [ _('Published Pages'), h.url_for( controller='/page', action='list_published' ) ] + [ _('Published Pages'), h.url_for( controller='/page', action='list_published' ) ], + None, + [ _('Search'), h.url_for( controller='/search', action='index' ) ] ] tab( "shared", _("Shared Data"), h.url_for( controller='/library', action='index'), menu_options=menu_options ) %> From a6c84caa6c3b2ea55b0b3eab01affc63feb5a15f Mon Sep 17 00:00:00 2001 From: Kyle Ellrott Date: Thu, 24 Jan 2013 16:49:10 -0800 Subject: [PATCH 002/523] Getting more of the search engine to work --- lib/galaxy/model/search.py | 21 +++++++++++++++++---- lib/galaxy/webapps/galaxy/api/search.py | 4 +++- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/lib/galaxy/model/search.py b/lib/galaxy/model/search.py index b61d7330045..1cc8cdd92b6 100644 --- a/lib/galaxy/model/search.py +++ b/lib/galaxy/model/search.py @@ -51,6 +51,7 @@ class QueryBaseClass(object): print self.query for row in self.query.distinct().all(): out = {} + out['domain'] = self.DOMAIN for col in self.OUTPUT_COLUMNS: out[col] = getattr(row, col) yield out @@ -76,9 +77,11 @@ class SearchBaseClass(object): #Library Searching ################## -class LibraryQuery(QueryBaseClass): +class LibraryDatasetQuery(QueryBaseClass): + DOMAIN = "library_dataset" OUTPUT_COLUMNS = [ 'extended_metadata', 'name', 'id' ] def filter(self, arg): + print "Library", arg if arg.name == 'extended_metadata': self.do_query = True self.query = self.query.join( ExtendedMetadata ) @@ -92,9 +95,16 @@ class LibraryQuery(QueryBaseClass): alias.value == str(ex_meta[f]) ) ) + if arg.name == "name": + self.do_query = True + if arg.mode == "==": + self.query = self.query.filter( LibraryDatasetDatasetAssociation.name == arg.other ) + if arg.mode == "like": + self.query = self.query.filter( LibraryDatasetDatasetAssociation.name.like(arg.other) ) -class LibrarySearch(SearchBaseClass): + +class LibraryDatasetSearch(SearchBaseClass): FIELDS = [ SearchField("name"), SearchField("id"), @@ -104,7 +114,7 @@ class LibrarySearch(SearchBaseClass): @staticmethod def search(trans): query = trans.sa_session.query( LibraryDatasetDatasetAssociation ) - return LibraryQuery(query) + return LibraryDatasetQuery(query) ################## #History Dataset Searching @@ -112,6 +122,7 @@ class LibrarySearch(SearchBaseClass): class HistoryDatasetQuery(QueryBaseClass): + DOMAIN = "history_dataset" OUTPUT_COLUMNS = ['name', 'id'] def filter(self, arg): @@ -139,6 +150,7 @@ class HistoryDatasetSearch(SearchBaseClass): class HistoryQuery(QueryBaseClass): + DOMAIN = "history" OUTPUT_COLUMNS = ['name', 'id'] def filter(self, arg): @@ -197,6 +209,7 @@ class HistorySearch(SearchBaseClass): class WorkflowQuery(QueryBaseClass): + DOMAIN = "workflow" OUTPUT_COLUMNS = ['name', 'id'] def filter(self, arg): @@ -233,7 +246,7 @@ class WorkflowSearch(SearchBaseClass): search_mapping = { - 'library' : LibrarySearch, + 'library_dataset' : LibraryDatasetSearch, 'history_dataset' : HistoryDatasetSearch, 'history' : HistorySearch, 'workflow' : WorkflowSearch diff --git a/lib/galaxy/webapps/galaxy/api/search.py b/lib/galaxy/webapps/galaxy/api/search.py index 66f1836cee8..6ea7ca6ec5a 100644 --- a/lib/galaxy/webapps/galaxy/api/search.py +++ b/lib/galaxy/webapps/galaxy/api/search.py @@ -50,6 +50,7 @@ class SearchController( BaseAPIController ): FIELD_NAME = "name" FIELD_ID = "id" + FIELD_DOMAIN = "domain" FIELD_EXTENDED_METADATA = "extended_metadata" FIELD_ANNOTATION = "annotation" FIELD_TAGS = "tags" @@ -86,8 +87,9 @@ class SearchController( BaseAPIController ): o = {} o[ self.FIELD_ID ] = trans.security.encode_id(row['id']) o[ self.FIELD_NAME ] = row['name'] + o[ self.FIELD_DOMAIN ] = row['domain'] - if self.FIELD_EXTENDED_METADATA in result_fields and 'extended_metadata' in row: + if self.FIELD_EXTENDED_METADATA in result_fields and 'extended_metadata.data' in row: o[ self.FIELD_EXTENDED_METADATA ] = row['extended_metadata.data'] if self.FIELD_ANNOTATION in result_fields: From 6058a25282057347c25f7dab80b1005bd84bd2ab Mon Sep 17 00:00:00 2001 From: Kyle Ellrott Date: Tue, 5 Feb 2013 12:34:07 -0800 Subject: [PATCH 003/523] Using methods from model to produce json api output --- lib/galaxy/model/__init__.py | 29 ++++++++++++++++++++++ lib/galaxy/model/search.py | 7 +----- lib/galaxy/webapps/galaxy/api/search.py | 32 ++++--------------------- 3 files changed, 34 insertions(+), 34 deletions(-) diff --git a/lib/galaxy/model/__init__.py b/lib/galaxy/model/__init__.py index ff316dba983..a170245fb67 100644 --- a/lib/galaxy/model/__init__.py +++ b/lib/galaxy/model/__init__.py @@ -1869,6 +1869,35 @@ class LibraryDatasetDatasetAssociation( DatasetInstance ): if restrict: return None, inherited return self.library_dataset.folder.get_info_association( inherited=True ) + def get_api_value( self, view='collection' ): + # Since this class is a proxy to rather complex attributes we want to + # display in other objects, we can't use the simpler method used by + # other model classes. + ldda = self + try: + file_size = int( ldda.get_size() ) + except OSError: + file_size = 0 + rval = dict( id = ldda.id, + model_class = self.__class__.__name__, + name = ldda.name, + deleted = ldda.deleted, + visible = ldda.visible, + state = ldda.state, + file_size = file_size, + data_type = ldda.ext, + genome_build = ldda.dbkey, + misc_info = ldda.info, + misc_blurb = ldda.blurb ) + for name, spec in ldda.metadata.spec.items(): + val = ldda.metadata.get( name ) + if isinstance( val, MetadataFile ): + val = val.file_name + # If no value for metadata, look in datatype for metadata. + elif val == None and hasattr( ldda.datatype, name ): + val = getattr( ldda.datatype, name ) + rval['metadata_' + name] = val + return rval def get_template_widgets( self, trans, get_contents=True ): # See if we have any associated templatesThe get_contents # param is passed by callers that are inheriting a template - these diff --git a/lib/galaxy/model/search.py b/lib/galaxy/model/search.py index 1cc8cdd92b6..ee61f562856 100644 --- a/lib/galaxy/model/search.py +++ b/lib/galaxy/model/search.py @@ -48,13 +48,8 @@ class QueryBaseClass(object): def get_results(self, force_query=False): if self.query is not None and (force_query or self.do_query): - print self.query for row in self.query.distinct().all(): - out = {} - out['domain'] = self.DOMAIN - for col in self.OUTPUT_COLUMNS: - out[col] = getattr(row, col) - yield out + yield row class SearchBaseClass(object): diff --git a/lib/galaxy/webapps/galaxy/api/search.py b/lib/galaxy/webapps/galaxy/api/search.py index 6ea7ca6ec5a..47ab94e1cde 100644 --- a/lib/galaxy/webapps/galaxy/api/search.py +++ b/lib/galaxy/webapps/galaxy/api/search.py @@ -82,34 +82,10 @@ class SearchController( BaseAPIController ): return self._create_response(trans, out, result_fields) def _create_response(self, trans, rows, result_fields): + current_user_roles = trans.get_current_user_roles() out = [] for row in rows: - o = {} - o[ self.FIELD_ID ] = trans.security.encode_id(row['id']) - o[ self.FIELD_NAME ] = row['name'] - o[ self.FIELD_DOMAIN ] = row['domain'] - - if self.FIELD_EXTENDED_METADATA in result_fields and 'extended_metadata.data' in row: - o[ self.FIELD_EXTENDED_METADATA ] = row['extended_metadata.data'] - - if self.FIELD_ANNOTATION in result_fields: - try: - o[self.FIELD_ANNOTATION] = [] - for a in row['annotations']: - o[self.FIELD_ANNOTATION].append(a.annotation) - except AttributeError: - del o[self.FIELD_ANNOTATION] - - if self.FIELD_TAGS in result_fields: - try: - o[self.FIELD_TAGS] = [] - for t in row['tags']: - s = t.user_tname - if t.user_value is not None: - s += ":" + t.user_value - o[self.FIELD_TAGS].append(s) - except AttributeError: - del o[self.FIELD_TAGS] - - out.append(o) + if trans.app.security_agent.can_access_dataset( current_user_roles, row.dataset ): + r = self.encode_all_ids( trans, row.get_api_value( view='element' ) ) + out.append(r) return out From 1ad1ab963bbc36bc9ea505b1248a530a7da5b7c2 Mon Sep 17 00:00:00 2001 From: Kyle Ellrott Date: Tue, 5 Feb 2013 16:29:23 -0800 Subject: [PATCH 004/523] Working on the HTML output of the search page --- templates/search/index.mako | 50 +++++++++++++++++++++---- templates/search/search.mako | 72 +++++++++++------------------------- 2 files changed, 63 insertions(+), 59 deletions(-) diff --git a/templates/search/index.mako b/templates/search/index.mako index b8926d7387e..ea79922c4fb 100644 --- a/templates/search/index.mako +++ b/templates/search/index.mako @@ -5,17 +5,51 @@ <%def name="init()"> <% - self.has_left_panel = False - self.has_right_panel = False - self.active_view = "profile" + self.has_left_panel=False + self.has_right_panel=False + self.message_box_visible=False + self.active_view="shared" + self.overlay_visible=False %> -<%def name="center_panel()"> - -${search_init()} - -${search_dialog()} +<%def name="stylesheets()"> +${parent.stylesheets()} +${h.css( "embedded_item" )} + +<%def name="center_panel()"> +${search_init()} + + +
+${search_dialog("search_format_output")} +
+ + + diff --git a/templates/search/search.mako b/templates/search/search.mako index b10f1a63c6c..0fd74c76aba 100644 --- a/templates/search/search.mako +++ b/templates/search/search.mako @@ -8,7 +8,7 @@ -<%def name="search_dialog()"> +<%def name="search_dialog(output_format)"> - -
- - - - \ No newline at end of file + From 280b23a6428b1120ff9a9fda1a93d8235dc56fca Mon Sep 17 00:00:00 2001 From: Kyle Ellrott Date: Wed, 6 Feb 2013 10:40:57 -0800 Subject: [PATCH 005/523] Adding peek to search page --- lib/galaxy/model/__init__.py | 3 +++ templates/search/index.mako | 20 +++++++++----------- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/lib/galaxy/model/__init__.py b/lib/galaxy/model/__init__.py index a170245fb67..a68f78fefed 100644 --- a/lib/galaxy/model/__init__.py +++ b/lib/galaxy/model/__init__.py @@ -16,6 +16,7 @@ from galaxy.security import get_permitted_actions from galaxy import util from galaxy.util.bunch import Bunch from galaxy.util.hash_util import new_secure_hash +from galaxy.web.framework.helpers import to_unicode from galaxy.web.form_builder import (AddressField, CheckboxField, PasswordField, SelectField, TextArea, TextField, WorkflowField, WorkflowMappingField, HistoryField) from galaxy.model.item_attrs import UsesAnnotations, APIItem @@ -1525,6 +1526,8 @@ class HistoryDatasetAssociation( DatasetInstance ): genome_build = hda.dbkey, misc_info = hda.info, misc_blurb = hda.blurb ) + + rval[ 'peek' ] = to_unicode( hda.display_peek() ) for name, spec in hda.metadata.spec.items(): val = hda.metadata.get( name ) if isinstance( val, MetadataFile ): diff --git a/templates/search/index.mako b/templates/search/index.mako index ea79922c4fb..6cad2cfbec5 100644 --- a/templates/search/index.mako +++ b/templates/search/index.mako @@ -31,19 +31,17 @@ ${search_init()} From 7b177a63bde6cb52c6a0be548b95ef6208e1a3cb Mon Sep 17 00:00:00 2001 From: Kyle Ellrott Date: Mon, 25 Feb 2013 09:28:19 -0800 Subject: [PATCH 006/523] Changing search query system to an SQL like text query --- lib/galaxy/model/search.py | 102 +++++++++++++++++++++--- lib/galaxy/webapps/galaxy/api/search.py | 30 +++---- 2 files changed, 102 insertions(+), 30 deletions(-) diff --git a/lib/galaxy/model/search.py b/lib/galaxy/model/search.py index ee61f562856..76442b20cee 100644 --- a/lib/galaxy/model/search.py +++ b/lib/galaxy/model/search.py @@ -3,6 +3,9 @@ Classes for implmenting search methods for various data models """ import logging +import parsley +import re + from galaxy.model.orm import * from galaxy.model import * @@ -22,6 +25,7 @@ and presents the 'tag' field as a single concept that can be filtered against. """ + class SearchField(object): def __init__(self, name): @@ -52,7 +56,7 @@ class QueryBaseClass(object): yield row -class SearchBaseClass(object): +class ViewBaseClass(object): def get_field(self, name): for f_obj in self.FIELDS: @@ -99,7 +103,7 @@ class LibraryDatasetQuery(QueryBaseClass): -class LibraryDatasetSearch(SearchBaseClass): +class LibraryDatasetView(ViewBaseClass): FIELDS = [ SearchField("name"), SearchField("id"), @@ -129,7 +133,7 @@ class HistoryDatasetQuery(QueryBaseClass): self.do_query = True self.query= self.query.filter( HistoryDatasetAssociation.name.like(arg.other) ) -class HistoryDatasetSearch(SearchBaseClass): +class HistoryDatasetView(ViewBaseClass): FIELDS = [ SearchField("name") ] @@ -185,7 +189,7 @@ class HistoryQuery(QueryBaseClass): ) -class HistorySearch(SearchBaseClass): +class HistoryView(ViewBaseClass): FIELDS = [ SearchField("name"), SearchField("tag"), @@ -227,7 +231,7 @@ class WorkflowQuery(QueryBaseClass): self.query = self.query.filter( StoredWorkflowTagAssociation.user_value == tmp[1] ) -class WorkflowSearch(SearchBaseClass): +class WorkflowView(ViewBaseClass): FIELDS = [ SearchField("name"), @@ -240,11 +244,89 @@ class WorkflowSearch(SearchBaseClass): return WorkflowQuery(query) -search_mapping = { - 'library_dataset' : LibraryDatasetSearch, - 'history_dataset' : HistoryDatasetSearch, - 'history' : HistorySearch, - 'workflow' : WorkflowSearch +view_mapping = { + 'library_dataset' : LibraryDatasetView, + 'ldda' : LibraryDatasetView, + 'history_dataset' : HistoryDatasetView, + 'hda' : HistoryDatasetView, + 'history' : HistoryView, + 'workflow' : WorkflowView } +sqlGrammar = """ +expr = 'select' bs field_desc:f bs 'from' bs word:t ( + bs 'where' bs conditional:c -> GalaxyQuery(f,t,c) + | -> GalaxyQuery(f, t, None) ) +bs = ' '+ +ws = ' '* +field_desc = ( '*' -> ['*'] + | field_list ) +field_list = word:x ( + ws ',' ws field_list:y -> [x] + y + | -> [x] ) +conditional = ( + logic_statement:x -> x + | conditional:x 'and' conditional:y -> GalaxyQueryAnd(x,y) ) +logic_statement = word:left ws comparison:comp ws word:right -> [left, comp, right] +quote_word = '"' word '"' +word = alphanum+:x -> "".join(x) +comparison = ( '=' -> '=' + | '>' -> '>' + | '<' -> '<' + | '>=' -> '>=' + | '<=' -> '<=' + ) +alphanum = anything:x ?(re.search(r'\w', x) is not None) -> x +""" + +""" +comparison = ( '=' -> '=' + | '>' -> '>' + | '<' -> '<' + | '>=' -> '>=' + | '<=' -> '<=' + ) +""" + + +class GalaxyQuery: + def __init__(self, field_list, table_name, conditional): + self.field_list = field_list + self.table_name = table_name + self.conditional = conditional + +class GalaxyQueryComparison: + def __init__(self, left, operator, right): + self.left = left + self.operator = operator + self.right = right + +class SearchProcess: + def __init__(self, view, query): + self.view = view + self.query = query + + def process(self, trans): + search = self.view.search(trans) + if self.query.conditional is not None: + search.filter( + self.query.conditional.left, + self.query.conditional.operator, + self.query.conditional.right ) + return search.get_results(True) + + + +class GalaxySearchEngine: + def __init__(self): + self.parser = parsley.makeGrammar(sqlGrammar, { 're' : re, 'GalaxyQuery' : GalaxyQuery}) + + def query(self, query_text): + q = self.parser(query_text).expr() + + if q.table_name in view_mapping: + view = view_mapping[q.table_name]() + return SearchProcess(view, q) + return None + diff --git a/lib/galaxy/webapps/galaxy/api/search.py b/lib/galaxy/webapps/galaxy/api/search.py index 47ab94e1cde..91accce0129 100644 --- a/lib/galaxy/webapps/galaxy/api/search.py +++ b/lib/galaxy/webapps/galaxy/api/search.py @@ -61,27 +61,17 @@ class SearchController( BaseAPIController ): POST /api/search Do a search of the various elements of Galaxy. """ - domains = payload.get("domain", []) - result_fields = payload.get("result_fields", [self.FIELD_EXTENDED_METADATA]) - query = payload.get("query", {}) - print payload - out = [] - for domain in domains: - if domain in search_mapping: - search_base = search_mapping[domain]() - search_query = search_base.search(trans) - for field in query: - search_field = search_base.get_field(field) - if search_field is not None: - if isinstance(query[field], dict) and '$like' in query[field]: - search_query.filter( search_field.like(query[field]['$like']) ) - else: - search_query.filter( search_field == query[field] ) - out += list(search_query.get_results()) + query_txt = payload.get("query", None) + if query_txt is not None: + se = GalaxySearchEngine() + query = se.query(query_txt) + out = [] + if query is not None: + for row in query.process(trans): + out.append(row) + return self._create_response(trans, out) - return self._create_response(trans, out, result_fields) - - def _create_response(self, trans, rows, result_fields): + def _create_response(self, trans, rows): current_user_roles = trans.get_current_user_roles() out = [] for row in rows: From cfd2a7b2b8553a29eaa9fa4da48a0d9458ad4bb9 Mon Sep 17 00:00:00 2001 From: Kyle Ellrott Date: Tue, 5 Mar 2013 00:19:49 -0800 Subject: [PATCH 007/523] Fixing security checks for search engine. Adding more info to api output --- lib/galaxy/model/__init__.py | 6 ++- lib/galaxy/model/item_attrs.py | 2 + lib/galaxy/model/search.py | 5 +- lib/galaxy/webapps/galaxy/api/search.py | 70 ++++++++----------------- 4 files changed, 32 insertions(+), 51 deletions(-) diff --git a/lib/galaxy/model/__init__.py b/lib/galaxy/model/__init__.py index b8679c90930..6532532d597 100644 --- a/lib/galaxy/model/__init__.py +++ b/lib/galaxy/model/__init__.py @@ -693,6 +693,7 @@ class History( object, UsesAnnotations ): rval[key] = value_mapper.get( key )( rval[key] ) except AttributeError: rval[key] = None + rval['model_class'] = self.__class__.__name__ return rval @property def get_disk_size_bytes( self ): @@ -2092,8 +2093,8 @@ class UCI( object ): self.user = None class StoredWorkflow( object, APIItem): - api_collection_visible_keys = ( 'id', 'name' ) - api_element_visible_keys = ( 'id', 'name' ) + api_collection_visible_keys = ( 'id', 'name', 'published' ) + api_element_visible_keys = ( 'id', 'name', 'published' ) def __init__( self ): self.id = None self.user = None @@ -2109,6 +2110,7 @@ class StoredWorkflow( object, APIItem): new_swta.user = target_user self.tags.append(new_swta) + class Workflow( object ): def __init__( self ): self.user = None diff --git a/lib/galaxy/model/item_attrs.py b/lib/galaxy/model/item_attrs.py index 354d9c285e6..1598da3499a 100644 --- a/lib/galaxy/model/item_attrs.py +++ b/lib/galaxy/model/item_attrs.py @@ -188,4 +188,6 @@ class APIItem: rval[key] = get_value( key, item ) except AttributeError: rval[key] = None + + rval['model_class'] = self.__class__.__name__ return rval diff --git a/lib/galaxy/model/search.py b/lib/galaxy/model/search.py index 76442b20cee..377ce9c1b66 100644 --- a/lib/galaxy/model/search.py +++ b/lib/galaxy/model/search.py @@ -6,8 +6,9 @@ import logging import parsley import re -from galaxy.model.orm import * -from galaxy.model import * +#from galaxy.model.orm import * +from galaxy.model import HistoryDatasetAssociation, LibraryDatasetDatasetAssociation, History +from galaxy.model import StoredWorkflowTagAssociation, StoredWorkflow from sqlalchemy import or_, and_ diff --git a/lib/galaxy/webapps/galaxy/api/search.py b/lib/galaxy/webapps/galaxy/api/search.py index 91accce0129..30e8a47b86f 100644 --- a/lib/galaxy/webapps/galaxy/api/search.py +++ b/lib/galaxy/webapps/galaxy/api/search.py @@ -4,56 +4,16 @@ API for searching Galaxy Datasets import logging, os, string, shutil, urllib, re, socket from cgi import escape, FieldStorage from galaxy import util, datatypes, jobs, web, util -from galaxy.web.base.controller import * +from galaxy.web.base.controller import SharableItemSecurityMixin, BaseAPIController from galaxy.util.sanitize_html import sanitize_html -from galaxy.model.orm import * -from galaxy.model.search import * +from galaxy.model.search import GalaxySearchEngine - -from galaxy.model import * - -from sqlalchemy import or_, and_ +#from galaxy.model.orm import * +#from galaxy.model import * log = logging.getLogger( __name__ ) -class SearchController( BaseAPIController ): - """ - The search API accepts a POST with a payload of: - - search_type : The type of data to be searched, "library", "history_dataset", "history", "workflow" - - result_fields : The fields in the return structure that should be populated, includes: - "name", "id", "extended_metadata", "annotation", "tags" - - query : The query structure to be used - - Example query data structures: - - Find elements such that name == "test" - { - "name" : "test" - } - - - Find elements such that annotation contains the work 'world' - - { - "annotation" : { "$like" : "%world%" } - } - - Find elements such that the extended metadata field dataSubType/@id == 'geneExp' - - { - "extended_metadata" : { - "dataSubType/@id" : "geneExp" - } - } - - """ - - FIELD_NAME = "name" - FIELD_ID = "id" - FIELD_DOMAIN = "domain" - FIELD_EXTENDED_METADATA = "extended_metadata" - FIELD_ANNOTATION = "annotation" - FIELD_TAGS = "tags" +class SearchController( BaseAPIController, SharableItemSecurityMixin ): @web.expose_api def create( self, trans, payload, **kwd ): @@ -62,10 +22,10 @@ class SearchController( BaseAPIController ): Do a search of the various elements of Galaxy. """ query_txt = payload.get("query", None) + out = [] if query_txt is not None: se = GalaxySearchEngine() query = se.query(query_txt) - out = [] if query is not None: for row in query.process(trans): out.append(row) @@ -75,7 +35,23 @@ class SearchController( BaseAPIController ): current_user_roles = trans.get_current_user_roles() out = [] for row in rows: - if trans.app.security_agent.can_access_dataset( current_user_roles, row.dataset ): + append = False + if trans.user_is_admin(): + append = True + + if not append: + if type( item ) in ( trans.app.model.LibraryFolder, trans.app.model.LibraryDatasetDatasetAssociation, trans.app.model.LibraryDataset ): + if (trans.app.security_agent.can_access_library_item( trans.get_current_user_roles(), item, trans.user ) ): + append = True + + if not append: + if hasattr(row, 'dataset'): + if trans.app.security_agent.can_access_dataset( current_user_roles, row.dataset ): + r = self.encode_all_ids( trans, row.get_api_value( view='element' ) ) + out.append(r) + + + if append: r = self.encode_all_ids( trans, row.get_api_value( view='element' ) ) out.append(r) return out From 4ca631440cb421731c8ab1d3ec3623e33d5af1f2 Mon Sep 17 00:00:00 2001 From: Kyle Ellrott Date: Tue, 5 Mar 2013 00:40:29 -0800 Subject: [PATCH 008/523] Getting field selection in gql to work --- lib/galaxy/model/search.py | 14 ++++++-- lib/galaxy/webapps/galaxy/api/search.py | 43 +++++++++---------------- 2 files changed, 28 insertions(+), 29 deletions(-) diff --git a/lib/galaxy/model/search.py b/lib/galaxy/model/search.py index 377ce9c1b66..93eda95cdc3 100644 --- a/lib/galaxy/model/search.py +++ b/lib/galaxy/model/search.py @@ -255,7 +255,7 @@ view_mapping = { } -sqlGrammar = """ +gqlGrammar = """ expr = 'select' bs field_desc:f bs 'from' bs word:t ( bs 'where' bs conditional:c -> GalaxyQuery(f,t,c) | -> GalaxyQuery(f, t, None) ) @@ -316,12 +316,22 @@ class SearchProcess: self.query.conditional.operator, self.query.conditional.right ) return search.get_results(True) + + def item_to_api_value(self, item): + r = item.get_api_value( view='element' ) + if self.query.field_list.count("*"): + return r + o = {} + for a in r: + if a in self.query.field_list: + o[a] = r[a] + return o class GalaxySearchEngine: def __init__(self): - self.parser = parsley.makeGrammar(sqlGrammar, { 're' : re, 'GalaxyQuery' : GalaxyQuery}) + self.parser = parsley.makeGrammar(gqlGrammar, { 're' : re, 'GalaxyQuery' : GalaxyQuery}) def query(self, query_text): q = self.parser(query_text).expr() diff --git a/lib/galaxy/webapps/galaxy/api/search.py b/lib/galaxy/webapps/galaxy/api/search.py index 30e8a47b86f..76b4aadb858 100644 --- a/lib/galaxy/webapps/galaxy/api/search.py +++ b/lib/galaxy/webapps/galaxy/api/search.py @@ -27,31 +27,20 @@ class SearchController( BaseAPIController, SharableItemSecurityMixin ): se = GalaxySearchEngine() query = se.query(query_txt) if query is not None: - for row in query.process(trans): - out.append(row) - return self._create_response(trans, out) - - def _create_response(self, trans, rows): - current_user_roles = trans.get_current_user_roles() - out = [] - for row in rows: - append = False - if trans.user_is_admin(): - append = True - - if not append: - if type( item ) in ( trans.app.model.LibraryFolder, trans.app.model.LibraryDatasetDatasetAssociation, trans.app.model.LibraryDataset ): - if (trans.app.security_agent.can_access_library_item( trans.get_current_user_roles(), item, trans.user ) ): + current_user_roles = trans.get_current_user_roles() + for item in query.process(trans): + append = False + if trans.user_is_admin(): append = True - - if not append: - if hasattr(row, 'dataset'): - if trans.app.security_agent.can_access_dataset( current_user_roles, row.dataset ): - r = self.encode_all_ids( trans, row.get_api_value( view='element' ) ) - out.append(r) - - - if append: - r = self.encode_all_ids( trans, row.get_api_value( view='element' ) ) - out.append(r) - return out + if not append: + if type( item ) in ( trans.app.model.LibraryFolder, trans.app.model.LibraryDatasetDatasetAssociation, trans.app.model.LibraryDataset ): + if (trans.app.security_agent.can_access_library_item( trans.get_current_user_roles(), item, trans.user ) ): + append = True + if not append: + if hasattr(row, 'dataset'): + if trans.app.security_agent.can_access_dataset( current_user_roles, row.dataset ): + append = True + if append: + row = query.item_to_api_value(item) + out.append( self.encode_all_ids( trans, row) ) + return out From 77a8958bbfaed84a473e501b8fda0b013e3354f9 Mon Sep 17 00:00:00 2001 From: Kyle Ellrott Date: Tue, 5 Mar 2013 01:02:37 -0800 Subject: [PATCH 009/523] Getting simple conditional statements to parse. --- lib/galaxy/model/search.py | 44 +++++++++++++++++++++----------------- 1 file changed, 24 insertions(+), 20 deletions(-) diff --git a/lib/galaxy/model/search.py b/lib/galaxy/model/search.py index 93eda95cdc3..52618e09cc2 100644 --- a/lib/galaxy/model/search.py +++ b/lib/galaxy/model/search.py @@ -80,9 +80,8 @@ class ViewBaseClass(object): class LibraryDatasetQuery(QueryBaseClass): DOMAIN = "library_dataset" OUTPUT_COLUMNS = [ 'extended_metadata', 'name', 'id' ] - def filter(self, arg): - print "Library", arg - if arg.name == 'extended_metadata': + def filter(self, left, operator, right): + if left == 'extended_metadata': self.do_query = True self.query = self.query.join( ExtendedMetadata ) ex_meta = arg.other @@ -125,7 +124,7 @@ class HistoryDatasetQuery(QueryBaseClass): DOMAIN = "history_dataset" OUTPUT_COLUMNS = ['name', 'id'] - def filter(self, arg): + def filter(self, left, operator, right): if arg.name == 'name': if arg.mode == "==": self.do_query = True @@ -153,16 +152,16 @@ class HistoryQuery(QueryBaseClass): DOMAIN = "history" OUTPUT_COLUMNS = ['name', 'id'] - def filter(self, arg): - if arg.name == 'name': - if arg.mode == "==": + def filter(self, left, operator, right): + if left == 'name': + if operator == "==": self.do_query = True - self.query = self.query.filter( History.name == arg.other ) - if arg.mode == "like": + self.query = self.query.filter( History.name == right ) + if operator == "like": self.do_query = True - self.query = self.query.filter( History.name.like(arg.other) ) + self.query = self.query.filter( History.name.like(right) ) - if arg.name == 'tag': + if left == 'tag': self.do_query = True self.query = self.query.filter( History.id == HistoryTagAssociation.history_id @@ -172,20 +171,20 @@ class HistoryQuery(QueryBaseClass): if len(tmp) > 1: self.query = self.query.filter( HistoryTagAssociation.user_value == tmp[1] ) - if arg.name == 'annotation': - if arg.mode == "==": + if left == 'annotation': + if operator == "==": self.do_query = True self.query = self.query.filter( and_( HistoryAnnotationAssociation.history_id == History.id, - HistoryAnnotationAssociation.annotation == arg.other + HistoryAnnotationAssociation.annotation == right ) ) - if arg.mode == "like": + if operator == "like": self.do_query = True self.query = self.query.filter( and_( HistoryAnnotationAssociation.history_id == History.id, - HistoryAnnotationAssociation.annotation.like( arg.other ) + HistoryAnnotationAssociation.annotation.like( right ) ) ) @@ -212,7 +211,7 @@ class WorkflowQuery(QueryBaseClass): DOMAIN = "workflow" OUTPUT_COLUMNS = ['name', 'id'] - def filter(self, arg): + def filter(self, left, operator, right): if arg.name == 'name': self.do_query = True self.query = self.query.filter( StoredWorkflow.name == arg.other ) @@ -269,8 +268,9 @@ field_list = word:x ( conditional = ( logic_statement:x -> x | conditional:x 'and' conditional:y -> GalaxyQueryAnd(x,y) ) -logic_statement = word:left ws comparison:comp ws word:right -> [left, comp, right] -quote_word = '"' word '"' +logic_statement = word:left ws comparison:comp ws word:right -> GalaxyQueryComparison(left, comp, right) +quote_word = ( '"' word '"' + | '\'' word '\'' ) word = alphanum+:x -> "".join(x) comparison = ( '=' -> '=' | '>' -> '>' @@ -331,7 +331,11 @@ class SearchProcess: class GalaxySearchEngine: def __init__(self): - self.parser = parsley.makeGrammar(gqlGrammar, { 're' : re, 'GalaxyQuery' : GalaxyQuery}) + self.parser = parsley.makeGrammar(gqlGrammar, { + 're' : re, + 'GalaxyQuery' : GalaxyQuery, + 'GalaxyQueryComparison' : GalaxyQueryComparison + }) def query(self, query_text): q = self.parser(query_text).expr() From 2f650b88667cb343d0b64339ac48eef8a927995f Mon Sep 17 00:00:00 2001 From: Nicola Soranzo Date: Wed, 6 Mar 2013 12:37:08 +0100 Subject: [PATCH 010/523] Fix file extension determination Extension was determined uncorrectly for filenames with more than 1 dot --- tools/data_source/upload.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/data_source/upload.py b/tools/data_source/upload.py index 60d63a27d9e..db030a537d0 100644 --- a/tools/data_source/upload.py +++ b/tools/data_source/upload.py @@ -247,7 +247,7 @@ def add_file( dataset, registry, json_file, output_path ): #binary_ok = False parts = dataset.name.split( "." ) if len( parts ) > 1: - ext = parts[1].strip().lower() + ext = parts[-1].strip().lower() if not Binary.is_ext_unsniffable(ext): file_err( 'The uploaded binary file contains inappropriate content', dataset, json_file ) return From b6d79019ada0ba01f3536c4f59726877d9c9718e Mon Sep 17 00:00:00 2001 From: Nicola Soranzo Date: Wed, 6 Mar 2013 12:39:04 +0100 Subject: [PATCH 011/523] Add generic ASN.1 text and binary datatypes --- datatypes_conf.xml.sample | 2 ++ lib/galaxy/datatypes/binary.py | 6 ++++++ lib/galaxy/datatypes/data.py | 4 ++++ 3 files changed, 12 insertions(+) diff --git a/datatypes_conf.xml.sample b/datatypes_conf.xml.sample index 57122d0d8c3..9967d3391b9 100644 --- a/datatypes_conf.xml.sample +++ b/datatypes_conf.xml.sample @@ -3,6 +3,8 @@ + + diff --git a/lib/galaxy/datatypes/binary.py b/lib/galaxy/datatypes/binary.py index 8d51135af31..89b8bfecc9c 100644 --- a/lib/galaxy/datatypes/binary.py +++ b/lib/galaxy/datatypes/binary.py @@ -85,6 +85,12 @@ class Ab1( Binary ): Binary.register_unsniffable_binary_ext("ab1") +class GenericAsn1Binary( Binary ): + """Class for generic ASN.1 binary format""" + file_ext = "asn1-binary" + +Binary.register_unsniffable_binary_ext("asn1-binary") + class Bam( Binary ): """Class describing a BAM binary file""" file_ext = "bam" diff --git a/lib/galaxy/datatypes/data.py b/lib/galaxy/datatypes/data.py index 3f9c4d74b09..f6862b23368 100644 --- a/lib/galaxy/datatypes/data.py +++ b/lib/galaxy/datatypes/data.py @@ -739,6 +739,10 @@ class Text( Data ): f.close() split = classmethod(split) +class GenericAsn1( Text ): + """Class for generic ASN.1 text format""" + file_ext = 'asn1' + class LineCount( Text ): """ Dataset contains a single line with a single integer that denotes the From 719f19a9ca2c14f81ade9f0af86f2b192ee4d82b Mon Sep 17 00:00:00 2001 From: Kyle Ellrott Date: Fri, 8 Mar 2013 13:43:42 -0800 Subject: [PATCH 012/523] Refactoring the views to make them much simpler. Getting the parsley GQL syntax to recognize quotations marks. --- lib/galaxy/model/search.py | 373 ++++++++++++++++++------------------- 1 file changed, 177 insertions(+), 196 deletions(-) diff --git a/lib/galaxy/model/search.py b/lib/galaxy/model/search.py index 52618e09cc2..24edd47a1b7 100644 --- a/lib/galaxy/model/search.py +++ b/lib/galaxy/model/search.py @@ -1,5 +1,27 @@ """ -Classes for implmenting search methods for various data models +The GQL (Galaxy Query Language) search engine parsers a simple 'SQL-Like' query +syntax to obtain items from the Galaxy installations. +Rather then allow/force the user to do queries on the Galaxy schema, it uses +a small set of 'Views' which are simple table representations of complex galaxy ideas. +So while a history and it's tags may exist in seperate tables in the real schema, in +GQL they exist in the same view + +Example Queries: + +select name, id, file_size from hda + +select name from hda + +select name, model_class from ldda + +select * from history + +select * from workflow + +select id, name from history where name='Unnamed history' + +select * from history where name='Unnamed history' + """ import logging @@ -15,132 +37,100 @@ from sqlalchemy import or_, and_ log = logging.getLogger( __name__ ) -""" - -The search model is a simplified view of the SQL schema. A search domain -typicaly covers a base data table, but it's fields can be constructs of multiple -tables that are joined togeather. For example, a History dataset may have tags associated with it. -The Search class simplifies the joining process between the history dataset and the tags table, -and presents the 'tag' field as a single concept that can be filtered against. - - -""" - - -class SearchField(object): - - def __init__(self, name): +class ViewField(object): + """ + A ViewField defines a field in a view that filter operations can be applied to + These filter operations are either handled with standard sqlalchemy filter calls, + or passed to specialized handlers (such as when a table join would be needed to + do the filtering) + """ + def __init__(self, name, sqlalchemy_field=None, handler=None): self.name = name - self.other = None - self.mode = None + self.sqlalchemy_field = sqlalchemy_field + self.handler = handler + - def __eq__(self, other ): - self.other = other - self.mode = "==" - return self +class ViewQueryBaseClass(object): + FIELDS = {} + VIEW_NAME = "undefined" - def like(self, other): - self.other = other - self.mode = "like" - return self - -class QueryBaseClass(object): - OUTPUT_COLUMNS = [] - - def __init__(self, base_query): - self.query = base_query + def __init__(self): + self.query = None self.do_query = False + def filter(self, left, operator, right): + if left in self.FIELDS: + self.do_query = True + field = self.FIELDS[left] + if field.sqlalchemy_field is not None: + if operator == "=": + self.query = self.query.filter( field.sqlalchemy_field == right ) + elif operator == "like": + self.query = self.query.filter( field.sqlalchemy_field.like(right) ) + else: + raise GalaxyParseError("Invalid comparison operator: %s" % (operator)) + elif field.handler is not None: + field.handler(self, left, operator, right) + else: + raise GalaxyParseError("Unable to filter on field: %s" % (left)) + + else: + raise GalaxyParseError("Unknown field: %s" % (left)) + + def search(trans): + raise GalaxyParseError("Unable to search view: %s" % (self.VIEW_NAME)) + def get_results(self, force_query=False): if self.query is not None and (force_query or self.do_query): for row in self.query.distinct().all(): yield row -class ViewBaseClass(object): - - def get_field(self, name): - for f_obj in self.FIELDS: - if isinstance(f_obj, SearchField): - if f_obj.name == name: - return f_obj - else: - print "Not SearchField", f_obj - return None - - @staticmethod - def search(trans): - return None - - ################## #Library Searching ################## -class LibraryDatasetQuery(QueryBaseClass): - DOMAIN = "library_dataset" - OUTPUT_COLUMNS = [ 'extended_metadata', 'name', 'id' ] - def filter(self, left, operator, right): - if left == 'extended_metadata': - self.do_query = True - self.query = self.query.join( ExtendedMetadata ) - ex_meta = arg.other - for f in ex_meta: - alias = aliased( ExtendedMetadataIndex ) - self.query = self.query.filter( - and_( - ExtendedMetadata.id == alias.extended_metadata_id, - alias.path == "/" + f, - alias.value == str(ex_meta[f]) - ) - ) - if arg.name == "name": - self.do_query = True - if arg.mode == "==": - self.query = self.query.filter( LibraryDatasetDatasetAssociation.name == arg.other ) - if arg.mode == "like": - self.query = self.query.filter( LibraryDatasetDatasetAssociation.name.like(arg.other) ) +def library_extended_metadata_filter(view, left, operator, right): + view.do_query = True + view.query = view.query.join( ExtendedMetadata ) + ex_meta = arg.other + for f in ex_meta: + alias = aliased( ExtendedMetadataIndex ) + view.query = view.query.filter( + and_( + ExtendedMetadata.id == alias.extended_metadata_id, + alias.path == "/" + f, + alias.value == str(ex_meta[f]) + ) + ) +class LibraryDatasetView(ViewQueryBaseClass): + VIEW_NAME = "library_dataset" + FIELDS = { + 'extended_metadata' : ViewField('extended_metadata', handler=library_extended_metadata_filter), + 'name' : ViewField('name', sqlalchemy_field=LibraryDatasetDatasetAssociation.name ), + 'id' : ViewField('id', sqlalchemy_field=LibraryDatasetDatasetAssociation.id) + } -class LibraryDatasetView(ViewBaseClass): - FIELDS = [ - SearchField("name"), - SearchField("id"), - SearchField("extended_metadata") - ] - - @staticmethod - def search(trans): - query = trans.sa_session.query( LibraryDatasetDatasetAssociation ) - return LibraryDatasetQuery(query) + def search(self, trans): + self.query = trans.sa_session.query( LibraryDatasetDatasetAssociation ) + ################## #History Dataset Searching ################## -class HistoryDatasetQuery(QueryBaseClass): +class HistoryDatasetView(ViewQueryBaseClass): DOMAIN = "history_dataset" - OUTPUT_COLUMNS = ['name', 'id'] + FIELDS = { + 'name' : ViewField('name', sqlalchemy_field=HistoryDatasetAssociation.name), + 'id' : ViewField('id',sqlalchemy_field=HistoryDatasetAssociation.id ) + } - def filter(self, left, operator, right): - if arg.name == 'name': - if arg.mode == "==": - self.do_query = True - self.query= self.query.filter( HistoryDatasetAssociation.name == arg.other ) - if arg.mode == "like": - self.do_query = True - self.query= self.query.filter( HistoryDatasetAssociation.name.like(arg.other) ) - -class HistoryDatasetView(ViewBaseClass): - FIELDS = [ - SearchField("name") - ] - @staticmethod - def search(trans): - query = trans.sa_session.query( HistoryDatasetAssociation ) - return HistoryDatasetQuery(query) + def search(self, trans): + self.query = trans.sa_session.query( HistoryDatasetAssociation ) ################## @@ -148,58 +138,47 @@ class HistoryDatasetView(ViewBaseClass): ################## -class HistoryQuery(QueryBaseClass): - DOMAIN = "history" - OUTPUT_COLUMNS = ['name', 'id'] +def history_handle_tag(view, left, operator, right): + view.do_query = True + view.query = view.query.filter( + History.id == HistoryTagAssociation.history_id + ) + tmp = arg.other.split(":") + view.query = view.query.filter( HistoryTagAssociation.user_tname == tmp[0] ) + if len(tmp) > 1: + view.query = view.query.filter( HistoryTagAssociation.user_value == tmp[1] ) - def filter(self, left, operator, right): - if left == 'name': - if operator == "==": - self.do_query = True - self.query = self.query.filter( History.name == right ) - if operator == "like": - self.do_query = True - self.query = self.query.filter( History.name.like(right) ) - if left == 'tag': - self.do_query = True - self.query = self.query.filter( - History.id == HistoryTagAssociation.history_id +def history_handle_annotation(view, left, operator, right): + if operator == "==": + view.do_query = True + view.query = view.query.filter( and_( + HistoryAnnotationAssociation.history_id == History.id, + HistoryAnnotationAssociation.annotation == right ) - tmp = arg.other.split(":") - self.query = self.query.filter( HistoryTagAssociation.user_tname == tmp[0] ) - if len(tmp) > 1: - self.query = self.query.filter( HistoryTagAssociation.user_value == tmp[1] ) + ) - if left == 'annotation': - if operator == "==": - self.do_query = True - self.query = self.query.filter( and_( - HistoryAnnotationAssociation.history_id == History.id, - HistoryAnnotationAssociation.annotation == right - ) - ) - - if operator == "like": - self.do_query = True - self.query = self.query.filter( and_( - HistoryAnnotationAssociation.history_id == History.id, - HistoryAnnotationAssociation.annotation.like( right ) - ) - ) + if operator == "like": + view.do_query = True + view.query = view.query.filter( and_( + HistoryAnnotationAssociation.history_id == History.id, + HistoryAnnotationAssociation.annotation.like( right ) + ) + ) -class HistoryView(ViewBaseClass): - FIELDS = [ - SearchField("name"), - SearchField("tag"), - SearchField("annotation") - ] +class HistoryView(ViewQueryBaseClass): + DOMAIN = "history" + FIELDS = { + 'name' : ViewField('name', sqlalchemy_field=History.name), + 'id' : ViewField('id', sqlalchemy_field=History.id), + 'tag' : ViewField("tag", handler=history_handle_tag), + 'annotation' : ViewField("annotation", handler=history_handle_annotation) + } + + def search(self, trans): + self.query = trans.sa_session.query( History ) - @staticmethod - def search(trans): - query = trans.sa_session.query( History ) - return HistoryQuery(query) ################## #Workflow Searching @@ -207,43 +186,37 @@ class HistoryView(ViewBaseClass): -class WorkflowQuery(QueryBaseClass): +def workflow_tag_handler(view, left, operator, right): + view.do_query = True + view.query = view.query.filter( and_( + Tag.name == arg.other, + Tag.id == StoredWorkflowTagAssociation.tag_id, + StoredWorkflowTagAssociation.stored_workflow_id == StoredWorkflow.id ) + ) + view.query = view.query.filter( + Workflow.id == StoredWorkflowTagAssociation.workflow_id + ) + tmp = arg.other.split(":") + view.query = view.query.filter( StoredWorkflowTagAssociation.user_tname == tmp[0] ) + if len(tmp) > 1: + view.query = view.query.filter( StoredWorkflowTagAssociation.user_value == tmp[1] ) + + +class WorkflowView(ViewQueryBaseClass): DOMAIN = "workflow" - OUTPUT_COLUMNS = ['name', 'id'] - - def filter(self, left, operator, right): - if arg.name == 'name': - self.do_query = True - self.query = self.query.filter( StoredWorkflow.name == arg.other ) - if arg.name == 'tag': - self.do_query = True - self.query = self.query.filter( and_( - Tag.name == arg.other, - Tag.id == StoredWorkflowTagAssociation.tag_id, - StoredWorkflowTagAssociation.stored_workflow_id == StoredWorkflow.id ) - ) - self.query = self.query.filter( - Workflow.id == StoredWorkflowTagAssociation.workflow_id - ) - tmp = arg.other.split(":") - self.query = self.query.filter( StoredWorkflowTagAssociation.user_tname == tmp[0] ) - if len(tmp) > 1: - self.query = self.query.filter( StoredWorkflowTagAssociation.user_value == tmp[1] ) - - -class WorkflowView(ViewBaseClass): - - FIELDS = [ - SearchField("name"), - SearchField("tag") - ] - - @staticmethod - def search(trans): - query = trans.sa_session.query( StoredWorkflow ) - return WorkflowQuery(query) + FIELDS = { + 'name' : ViewField('name', sqlalchemy_field=StoredWorkflow.name), + 'id' : ViewField('id', sqlalchemy_field=StoredWorkflow.id), + 'tag' : ViewField('tag', handler=workflow_tag_handler) + } + def search(self, trans): + self.query = trans.sa_session.query( StoredWorkflow ) +""" +The view mapping takes a user's name for a table and maps it to a View class that will +handle queries +""" view_mapping = { 'library_dataset' : LibraryDatasetView, 'ldda' : LibraryDatasetView, @@ -253,6 +226,9 @@ view_mapping = { 'workflow' : WorkflowView } +""" +The GQL gramar is defined in Parsley syntax ( http://parsley.readthedocs.org/en/latest/ ) +""" gqlGrammar = """ expr = 'select' bs field_desc:f bs 'from' bs word:t ( @@ -268,54 +244,56 @@ field_list = word:x ( conditional = ( logic_statement:x -> x | conditional:x 'and' conditional:y -> GalaxyQueryAnd(x,y) ) -logic_statement = word:left ws comparison:comp ws word:right -> GalaxyQueryComparison(left, comp, right) -quote_word = ( '"' word '"' - | '\'' word '\'' ) word = alphanum+:x -> "".join(x) -comparison = ( '=' -> '=' - | '>' -> '>' - | '<' -> '<' - | '>=' -> '>=' - | '<=' -> '<=' - ) alphanum = anything:x ?(re.search(r'\w', x) is not None) -> x -""" - -""" +logic_statement = word:left ws comparison:comp ws quotable_word:right -> GalaxyQueryComparison(left, comp, right) +quotable_word = ( word | quote_word ) comparison = ( '=' -> '=' | '>' -> '>' | '<' -> '<' | '>=' -> '>=' | '<=' -> '<=' ) +quote_word = "'" not_quote+:x "'" -> "".join(x) +not_quote = anything:x ?(x != "'") -> x +not_dquote = anything:x ?(x != '"') -> x """ - class GalaxyQuery: + """ + This class represents a data structure of a compiled GQL query + """ def __init__(self, field_list, table_name, conditional): self.field_list = field_list self.table_name = table_name self.conditional = conditional class GalaxyQueryComparison: + """ + This class represents the data structure of the comparison arguments of a + compiled GQL query (ie where name='Untitled History') + """ def __init__(self, left, operator, right): self.left = left self.operator = operator self.right = right +class GalaxyParseError(Exception): + pass + class SearchProcess: def __init__(self, view, query): self.view = view self.query = query def process(self, trans): - search = self.view.search(trans) + self.view.search(trans) if self.query.conditional is not None: - search.filter( + self.view.filter( self.query.conditional.left, self.query.conditional.operator, self.query.conditional.right ) - return search.get_results(True) + return self.view.get_results(True) def item_to_api_value(self, item): r = item.get_api_value( view='element' ) @@ -330,6 +308,9 @@ class SearchProcess: class GalaxySearchEngine: + """ + Primary class for searching. Parses GQL (Galaxy Query Language) queries and returns a 'SearchProcess' class + """ def __init__(self): self.parser = parsley.makeGrammar(gqlGrammar, { 're' : re, From aea2591cb8c27e6aecf1df07d5ab38793c90ab43 Mon Sep 17 00:00:00 2001 From: Kyle Ellrott Date: Fri, 8 Mar 2013 19:33:32 -0800 Subject: [PATCH 013/523] Fixing the tag selection methods. Removing the link to the search page from the main menu (until it works better). Updating the search page to support GQL --- lib/galaxy/model/__init__.py | 22 ++++++++- lib/galaxy/model/search.py | 55 ++++++++++++----------- templates/search/search.mako | 49 +------------------- templates/webapps/galaxy/base_panels.mako | 4 +- 4 files changed, 50 insertions(+), 80 deletions(-) diff --git a/lib/galaxy/model/__init__.py b/lib/galaxy/model/__init__.py index 6532532d597..60bc7eef476 100644 --- a/lib/galaxy/model/__init__.py +++ b/lib/galaxy/model/__init__.py @@ -575,8 +575,8 @@ class UserGroupAssociation( object ): self.group = group class History( object, UsesAnnotations ): - api_collection_visible_keys = ( 'id', 'name' ) - api_element_visible_keys = ( 'id', 'name' ) + api_collection_visible_keys = ( 'id', 'name', 'published', 'deleted' ) + api_element_visible_keys = ( 'id', 'name', 'published', 'deleted' ) def __init__( self, id=None, name=None, user=None ): self.id = id self.name = name or "Unnamed history" @@ -693,6 +693,13 @@ class History( object, UsesAnnotations ): rval[key] = value_mapper.get( key )( rval[key] ) except AttributeError: rval[key] = None + tags_str_list = [] + for tag in self.tags: + tag_str = tag.user_tname + if tag.value is not None: + tag_str += ":" + tag.user_value + tags_str_list.append( tag_str ) + rval['tags'] = tags_str_list rval['model_class'] = self.__class__.__name__ return rval @property @@ -2110,6 +2117,17 @@ class StoredWorkflow( object, APIItem): new_swta.user = target_user self.tags.append(new_swta) + def get_api_value( self, view='collection' ): + rval = APIItem.get_api_value(self, view=view ) + tags_str_list = [] + for tag in self.tags: + tag_str = tag.user_tname + if tag.value is not None: + tag_str += ":" + tag.user_value + tags_str_list.append( tag_str ) + rval['tags'] = tags_str_list + return rval + class Workflow( object ): def __init__( self ): diff --git a/lib/galaxy/model/search.py b/lib/galaxy/model/search.py index 24edd47a1b7..2ddbc6b9dfd 100644 --- a/lib/galaxy/model/search.py +++ b/lib/galaxy/model/search.py @@ -28,9 +28,8 @@ import logging import parsley import re -#from galaxy.model.orm import * from galaxy.model import HistoryDatasetAssociation, LibraryDatasetDatasetAssociation, History -from galaxy.model import StoredWorkflowTagAssociation, StoredWorkflow +from galaxy.model import StoredWorkflowTagAssociation, StoredWorkflow, HistoryTagAssociation from sqlalchemy import or_, and_ @@ -139,32 +138,36 @@ class HistoryDatasetView(ViewQueryBaseClass): def history_handle_tag(view, left, operator, right): - view.do_query = True - view.query = view.query.filter( - History.id == HistoryTagAssociation.history_id - ) - tmp = arg.other.split(":") - view.query = view.query.filter( HistoryTagAssociation.user_tname == tmp[0] ) - if len(tmp) > 1: - view.query = view.query.filter( HistoryTagAssociation.user_value == tmp[1] ) + if operator == "=": + view.do_query = True + view.query = view.query.filter( + History.id == HistoryTagAssociation.history_id + ) + tmp = right.split(":") + view.query = view.query.filter( HistoryTagAssociation.user_tname == tmp[0] ) + if len(tmp) > 1: + view.query = view.query.filter( HistoryTagAssociation.user_value == tmp[1] ) + else: + raise GalaxyParseError("Invalid comparison operator: %s" % (operator)) def history_handle_annotation(view, left, operator, right): - if operator == "==": + if operator == "=": view.do_query = True view.query = view.query.filter( and_( HistoryAnnotationAssociation.history_id == History.id, HistoryAnnotationAssociation.annotation == right ) ) - - if operator == "like": + elif operator == "like": view.do_query = True view.query = view.query.filter( and_( HistoryAnnotationAssociation.history_id == History.id, HistoryAnnotationAssociation.annotation.like( right ) ) ) + else: + raise GalaxyParseError("Invalid comparison operator: %s" % (operator)) class HistoryView(ViewQueryBaseClass): @@ -185,21 +188,18 @@ class HistoryView(ViewQueryBaseClass): ################## - def workflow_tag_handler(view, left, operator, right): - view.do_query = True - view.query = view.query.filter( and_( - Tag.name == arg.other, - Tag.id == StoredWorkflowTagAssociation.tag_id, - StoredWorkflowTagAssociation.stored_workflow_id == StoredWorkflow.id ) - ) - view.query = view.query.filter( - Workflow.id == StoredWorkflowTagAssociation.workflow_id - ) - tmp = arg.other.split(":") - view.query = view.query.filter( StoredWorkflowTagAssociation.user_tname == tmp[0] ) - if len(tmp) > 1: - view.query = view.query.filter( StoredWorkflowTagAssociation.user_value == tmp[1] ) + if operator == "=": + view.do_query = True + view.query = view.query.filter( + StoredWorkflow.id == StoredWorkflowTagAssociation.stored_workflow_id + ) + tmp = right.split(":") + view.query = view.query.filter( StoredWorkflowTagAssociation.user_tname == tmp[0] ) + if len(tmp) > 1: + view.query = view.query.filter( StoredWorkflowTagAssociation.user_value == tmp[1] ) + else: + raise GalaxyParseError("Invalid comparison operator: %s" % (operator)) class WorkflowView(ViewQueryBaseClass): @@ -253,6 +253,7 @@ comparison = ( '=' -> '=' | '<' -> '<' | '>=' -> '>=' | '<=' -> '<=' + | 'like' -> 'like' ) quote_word = "'" not_quote+:x "'" -> "".join(x) not_quote = anything:x ?(x != "'") -> x diff --git a/templates/search/search.mako b/templates/search/search.mako index 0fd74c76aba..58c8ee63edc 100644 --- a/templates/search/search.mako +++ b/templates/search/search.mako @@ -15,27 +15,10 @@ function doSearch(query) { if (query.length > 1) { var url = "/api/search"; - var query_struct = {}; - var domain_array = []; - var field = $("#field_type").val(); - if (field == "name") { - query_struct["name"] = { "$like" : "%" + $("#search_text").val() + "%" }; - } - - if ($("#history_search").val()) { - domain_array[domain_array.length] = "history" - } - if ($("#history_dataset_search").val()) { - domain_array[domain_array.length] = "history_dataset" - } - if ($("#library_dataset_search").val()) { - domain_array[domain_array.length] = "library_dataset" - } - $.ajax({ type : 'POST', url: url, - data: JSON.stringify({ "domain" : domain_array, "query" : query_struct }), + data: JSON.stringify({"query" : query }), contentType : 'application/json', dataType : 'json', success : function(data) { @@ -71,40 +54,10 @@ var queryURL = function (query) { return url; } -var suggestURL = function (query) { - var url = "/api/search" + encodeURIComponent(query); - url = url + "&field=" + $("#searchFields").val(); - if ($("#fileType").val() != "All") { - url = url + "&type=" + $("#fileType").val() - } - return url; -} - -var postClick = function(data) { - $("#name").attr("value", data.data[0]); - $("#type").attr("value", data.data[1]); - $("#uuid").attr("value", data.data[2]); - document.getElementById('galaxy_form').submit(); -} -
diff --git a/templates/webapps/galaxy/base_panels.mako b/templates/webapps/galaxy/base_panels.mako index 24f402ab5de..f77e7dbb1f3 100644 --- a/templates/webapps/galaxy/base_panels.mako +++ b/templates/webapps/galaxy/base_panels.mako @@ -80,9 +80,7 @@ [ _('Published Histories'), h.url_for( controller='/history', action='list_published' ) ], [ _('Published Workflows'), h.url_for( controller='/workflow', action='list_published' ) ], [ _('Published Visualizations'), h.url_for( controller='/visualization', action='list_published' ) ], - [ _('Published Pages'), h.url_for( controller='/page', action='list_published' ) ], - None, - [ _('Search'), h.url_for( controller='/search', action='index' ) ] + [ _('Published Pages'), h.url_for( controller='/page', action='list_published' ) ] ] tab( "shared", _("Shared Data"), h.url_for( controller='/library', action='index'), menu_options=menu_options ) %> From b6ba2b1782bddd5d7bc7687bdd56b964868b2f32 Mon Sep 17 00:00:00 2001 From: Kyle Ellrott Date: Tue, 12 Mar 2013 09:43:59 -0700 Subject: [PATCH 014/523] The 'and' statement can now be used for multiple conditionals in a GQL query. Also, for the LDDA view, the extended_metadata field can be searched. Example extended metadata query: select * from ldda where extended_metadata.'platform'='IlluminaHiSeq_RNASeqV2' note: the extended metadata field name is in quotes (a non-quotes rule could be added to the grammar) --- lib/galaxy/model/__init__.py | 2 + lib/galaxy/model/search.py | 99 +++++++++++++++++++++++------------- 2 files changed, 65 insertions(+), 36 deletions(-) diff --git a/lib/galaxy/model/__init__.py b/lib/galaxy/model/__init__.py index 60bc7eef476..cfd1b1969ab 100644 --- a/lib/galaxy/model/__init__.py +++ b/lib/galaxy/model/__init__.py @@ -1902,6 +1902,8 @@ class LibraryDatasetDatasetAssociation( DatasetInstance ): genome_build = ldda.dbkey, misc_info = ldda.info, misc_blurb = ldda.blurb ) + if ldda.extended_metadata is not None: + rval['extended_metadata'] = ldda.extended_metadata.data for name, spec in ldda.metadata.spec.items(): val = ldda.metadata.get( name ) if isinstance( val, MetadataFile ): diff --git a/lib/galaxy/model/search.py b/lib/galaxy/model/search.py index 2ddbc6b9dfd..0ea01313a8e 100644 --- a/lib/galaxy/model/search.py +++ b/lib/galaxy/model/search.py @@ -29,9 +29,10 @@ import parsley import re from galaxy.model import HistoryDatasetAssociation, LibraryDatasetDatasetAssociation, History -from galaxy.model import StoredWorkflowTagAssociation, StoredWorkflow, HistoryTagAssociation +from galaxy.model import StoredWorkflowTagAssociation, StoredWorkflow, HistoryTagAssociation, ExtendedMetadata, ExtendedMetadataIndex from sqlalchemy import or_, and_ +from sqlalchemy.orm import aliased log = logging.getLogger( __name__ ) @@ -56,25 +57,31 @@ class ViewQueryBaseClass(object): def __init__(self): self.query = None self.do_query = False + self.state = {} def filter(self, left, operator, right): - if left in self.FIELDS: - self.do_query = True - field = self.FIELDS[left] - if field.sqlalchemy_field is not None: - if operator == "=": - self.query = self.query.filter( field.sqlalchemy_field == right ) - elif operator == "like": - self.query = self.query.filter( field.sqlalchemy_field.like(right) ) - else: - raise GalaxyParseError("Invalid comparison operator: %s" % (operator)) - elif field.handler is not None: - field.handler(self, left, operator, right) - else: - raise GalaxyParseError("Unable to filter on field: %s" % (left)) - + if operator == 'and': + self.filter(left.left, left.operator, left.right) + self.filter(right.left, right.operator, right.right) else: - raise GalaxyParseError("Unknown field: %s" % (left)) + left_base = left.split('.')[0] + if left_base in self.FIELDS: + self.do_query = True + field = self.FIELDS[left_base] + if field.sqlalchemy_field is not None: + if operator == "=": + self.query = self.query.filter( field.sqlalchemy_field == right ) + elif operator == "like": + self.query = self.query.filter( field.sqlalchemy_field.like(right) ) + else: + raise GalaxyParseError("Invalid comparison operator: %s" % (operator)) + elif field.handler is not None: + field.handler(self, left, operator, right) + else: + raise GalaxyParseError("Unable to filter on field: %s" % (left)) + + else: + raise GalaxyParseError("Unknown field: %s" % (left)) def search(trans): raise GalaxyParseError("Unable to search view: %s" % (self.VIEW_NAME)) @@ -91,17 +98,19 @@ class ViewQueryBaseClass(object): def library_extended_metadata_filter(view, left, operator, right): view.do_query = True - view.query = view.query.join( ExtendedMetadata ) - ex_meta = arg.other - for f in ex_meta: - alias = aliased( ExtendedMetadataIndex ) - view.query = view.query.filter( - and_( - ExtendedMetadata.id == alias.extended_metadata_id, - alias.path == "/" + f, - alias.value == str(ex_meta[f]) - ) + if 'extended_metadata_joined' not in view.state: + view.query = view.query.join( ExtendedMetadata ) + view.state['extended_metadata_joined'] = True + alias = aliased( ExtendedMetadataIndex ) + field = "/%s" % ("/".join(left.split(".")[1:])) + print "FIELD", field + view.query = view.query.filter( + and_( + ExtendedMetadata.id == alias.extended_metadata_id, + alias.path == field, + alias.value == str(right) ) + ) class LibraryDatasetView(ViewQueryBaseClass): @@ -232,21 +241,27 @@ The GQL gramar is defined in Parsley syntax ( http://parsley.readthedocs.org/en/ gqlGrammar = """ expr = 'select' bs field_desc:f bs 'from' bs word:t ( - bs 'where' bs conditional:c -> GalaxyQuery(f,t,c) - | -> GalaxyQuery(f, t, None) ) + bs 'where' bs conditional:c ws -> GalaxyQuery(f,t,c) + | ws -> GalaxyQuery(f, t, None) ) bs = ' '+ ws = ' '* field_desc = ( '*' -> ['*'] | field_list ) -field_list = word:x ( +field_list = field_name:x ( ws ',' ws field_list:y -> [x] + y - | -> [x] ) -conditional = ( - logic_statement:x -> x - | conditional:x 'and' conditional:y -> GalaxyQueryAnd(x,y) ) + | -> [x] + ) +conditional = logic_statement:x ( + bs 'and' bs conditional:y -> GalaxyQueryAnd(x,y) + | -> x + ) word = alphanum+:x -> "".join(x) +field_name = word:x ( + '.' quote_word:y -> x + "." + y + |-> x + ) alphanum = anything:x ?(re.search(r'\w', x) is not None) -> x -logic_statement = word:left ws comparison:comp ws quotable_word:right -> GalaxyQueryComparison(left, comp, right) +logic_statement = field_name:left ws comparison:comp ws quotable_word:right -> GalaxyQueryComparison(left, comp, right) quotable_word = ( word | quote_word ) comparison = ( '=' -> '=' | '>' -> '>' @@ -279,6 +294,17 @@ class GalaxyQueryComparison: self.operator = operator self.right = right +class GalaxyQueryAnd: + """ + This class represents the data structure of the comparison arguments of a + compiled GQL query (ie where name='Untitled History') + """ + def __init__(self, left, right): + self.left = left + self.operator = 'and' + self.right = right + + class GalaxyParseError(Exception): pass @@ -316,7 +342,8 @@ class GalaxySearchEngine: self.parser = parsley.makeGrammar(gqlGrammar, { 're' : re, 'GalaxyQuery' : GalaxyQuery, - 'GalaxyQueryComparison' : GalaxyQueryComparison + 'GalaxyQueryComparison' : GalaxyQueryComparison, + 'GalaxyQueryAnd' : GalaxyQueryAnd }) def query(self, query_text): From 70567c18366567da6059ddfe589f8058ddb63e1e Mon Sep 17 00:00:00 2001 From: Kyle Ellrott Date: Tue, 12 Mar 2013 09:52:19 -0700 Subject: [PATCH 015/523] Chaning search output to return dictionary with either 'results' or 'error' fields filled in. Also catching parse exceptions and passing them back to the user --- lib/galaxy/webapps/galaxy/api/search.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/galaxy/webapps/galaxy/api/search.py b/lib/galaxy/webapps/galaxy/api/search.py index 76b4aadb858..a4d31be9989 100644 --- a/lib/galaxy/webapps/galaxy/api/search.py +++ b/lib/galaxy/webapps/galaxy/api/search.py @@ -25,7 +25,10 @@ class SearchController( BaseAPIController, SharableItemSecurityMixin ): out = [] if query_txt is not None: se = GalaxySearchEngine() - query = se.query(query_txt) + try: + query = se.query(query_txt) + except Exception, e: + return {'error' : str(e)} if query is not None: current_user_roles = trans.get_current_user_roles() for item in query.process(trans): @@ -43,4 +46,4 @@ class SearchController( BaseAPIController, SharableItemSecurityMixin ): if append: row = query.item_to_api_value(item) out.append( self.encode_all_ids( trans, row) ) - return out + return { 'results' : out } From 074ddd8b2af5ae3c409d0dcd30f80092377ee9de Mon Sep 17 00:00:00 2001 From: Kyle Ellrott Date: Tue, 12 Mar 2013 22:31:40 -0700 Subject: [PATCH 016/523] Adding library/library folder views to the search API. --- lib/galaxy/model/__init__.py | 4 +-- lib/galaxy/model/search.py | 64 ++++++++++++++++++++++++++++++++---- 2 files changed, 59 insertions(+), 9 deletions(-) diff --git a/lib/galaxy/model/__init__.py b/lib/galaxy/model/__init__.py index 64683d270c2..b2def24bfec 100644 --- a/lib/galaxy/model/__init__.py +++ b/lib/galaxy/model/__init__.py @@ -1563,7 +1563,7 @@ class HistoryDatasetAssociationSubset( object ): class Library( object, APIItem ): permitted_actions = get_permitted_actions( filter='LIBRARY' ) api_collection_visible_keys = ( 'id', 'name' ) - api_element_visible_keys = ( 'name', 'description', 'synopsis' ) + api_element_visible_keys = ( 'id', 'deleted', 'name', 'description', 'synopsis' ) def __init__( self, name=None, description=None, synopsis=None, root_folder=None ): self.name = name or "Unnamed library" self.description = description @@ -1630,7 +1630,7 @@ class Library( object, APIItem ): return name class LibraryFolder( object, APIItem ): - api_element_visible_keys = ( 'id', 'name', 'description', 'item_count', 'genome_build' ) + api_element_visible_keys = ( 'id', 'parent_id', 'name', 'description', 'item_count', 'genome_build' ) def __init__( self, name=None, description=None, item_count=0, order_id=None ): self.name = name or "Unnamed folder" self.description = description diff --git a/lib/galaxy/model/search.py b/lib/galaxy/model/search.py index 0ea01313a8e..e7d97cf39df 100644 --- a/lib/galaxy/model/search.py +++ b/lib/galaxy/model/search.py @@ -28,7 +28,7 @@ import logging import parsley import re -from galaxy.model import HistoryDatasetAssociation, LibraryDatasetDatasetAssociation, History +from galaxy.model import HistoryDatasetAssociation, LibraryDatasetDatasetAssociation, History, Library, LibraryFolder, LibraryDataset from galaxy.model import StoredWorkflowTagAssociation, StoredWorkflow, HistoryTagAssociation, ExtendedMetadata, ExtendedMetadataIndex from sqlalchemy import or_, and_ @@ -93,7 +93,7 @@ class ViewQueryBaseClass(object): ################## -#Library Searching +#Library Dataset Searching ################## def library_extended_metadata_filter(view, left, operator, right): @@ -113,8 +113,8 @@ def library_extended_metadata_filter(view, left, operator, right): ) -class LibraryDatasetView(ViewQueryBaseClass): - VIEW_NAME = "library_dataset" +class LibraryDatasetDatasetView(ViewQueryBaseClass): + VIEW_NAME = "library_dataset_dataset" FIELDS = { 'extended_metadata' : ViewField('extended_metadata', handler=library_extended_metadata_filter), 'name' : ViewField('name', sqlalchemy_field=LibraryDatasetDatasetAssociation.name ), @@ -123,7 +123,53 @@ class LibraryDatasetView(ViewQueryBaseClass): def search(self, trans): self.query = trans.sa_session.query( LibraryDatasetDatasetAssociation ) - + + +################## +#Library Searching +################## + +class LibraryView(ViewQueryBaseClass): + VIEW_NAME = "library" + FIELDS = { + 'name' : ViewField('name', sqlalchemy_field=Library.name ), + 'id' : ViewField('id', sqlalchemy_field=Library.id) + } + + def search(self, trans): + self.query = trans.sa_session.query( Library ) + + + +################## +#Library Folder Searching +################## + +class LibraryFolderView(ViewQueryBaseClass): + VIEW_NAME = "library_folder" + FIELDS = { + 'name' : ViewField('name', sqlalchemy_field=LibraryFolder.name ), + 'id' : ViewField('id', sqlalchemy_field=LibraryFolder.id) + } + + def search(self, trans): + self.query = trans.sa_session.query( LibraryFolder ) + + + +################## +#Library Dataset Searching +################## + +class LibraryDatasetView(ViewQueryBaseClass): + VIEW_NAME = "library_dataset" + FIELDS = { + 'name' : ViewField('name', sqlalchemy_field=LibraryDataset.name ), + 'id' : ViewField('id', sqlalchemy_field=LibraryDataset.id) + } + + def search(self, trans): + self.query = trans.sa_session.query( LibraryDataset ) ################## #History Dataset Searching @@ -227,8 +273,12 @@ The view mapping takes a user's name for a table and maps it to a View class tha handle queries """ view_mapping = { + 'library' : LibraryView, + 'library_folder' : LibraryFolderView, + 'library_dataset_dataset' : LibraryDatasetDatasetView, 'library_dataset' : LibraryDatasetView, - 'ldda' : LibraryDatasetView, + 'lda' : LibraryDatasetView, + 'ldda' : LibraryDatasetDatasetView, 'history_dataset' : HistoryDatasetView, 'hda' : HistoryDatasetView, 'history' : HistoryView, @@ -352,5 +402,5 @@ class GalaxySearchEngine: if q.table_name in view_mapping: view = view_mapping[q.table_name]() return SearchProcess(view, q) - return None + raise GalaxyParseError("No such table %s" % (q.table_name)) From f130d83f39fbacf16b7bb5770384756a86efa7eb Mon Sep 17 00:00:00 2001 From: Kyle Ellrott Date: Wed, 13 Mar 2013 00:49:04 -0700 Subject: [PATCH 017/523] Adding ToolVersion view to query system --- lib/galaxy/model/__init__.py | 12 +++++++++++- lib/galaxy/model/mapping.py | 9 ++++++++- lib/galaxy/model/search.py | 20 +++++++++++++++++++- 3 files changed, 38 insertions(+), 3 deletions(-) diff --git a/lib/galaxy/model/__init__.py b/lib/galaxy/model/__init__.py index b2def24bfec..5dce86e5f1e 100644 --- a/lib/galaxy/model/__init__.py +++ b/lib/galaxy/model/__init__.py @@ -3454,7 +3454,8 @@ class ToolDependency( object ): self.tool_shed_repository.name, self.tool_shed_repository.installed_changeset_revision ) -class ToolVersion( object ): +class ToolVersion( object, APIItem ): + api_element_visible_keys = ( 'id', 'tool_shed_repository' ) def __init__( self, id=None, create_time=None, tool_id=None, tool_shed_repository=None ): self.id = id self.create_time = create_time @@ -3511,6 +3512,15 @@ class ToolVersion( object ): return version_ids return [ tool_version.tool_id for tool_version in self.get_versions( app ) ] + def get_api_value( self, view='element' ): + rval = APIItem.get_api_value(self, view) + rval['tool_name'] = self.tool_id + for a in self.parent_tool_association: + rval['parent_tool_id'] = a.parent_id + for a in self.child_tool_association: + rval['child_tool_id'] = a.tool_id + return rval + class ToolVersionAssociation( object ): def __init__( self, id=None, tool_id=None, parent_id=None ): self.id = id diff --git a/lib/galaxy/model/mapping.py b/lib/galaxy/model/mapping.py index a845dcbaf5d..113feeb346c 100644 --- a/lib/galaxy/model/mapping.py +++ b/lib/galaxy/model/mapping.py @@ -1793,7 +1793,14 @@ assign_mapper( context, RepositoryDependency, RepositoryDependency.table, assign_mapper( context, ToolDependency, ToolDependency.table ) -assign_mapper( context, ToolVersion, ToolVersion.table ) +assign_mapper( context, ToolVersion, ToolVersion.table, + properties=dict( + parent_tool_association=relation( ToolVersionAssociation, + primaryjoin=( ToolVersion.table.c.id == ToolVersionAssociation.table.c.tool_id ) ), + child_tool_association=relation( ToolVersionAssociation, + primaryjoin=( ToolVersion.table.c.id == ToolVersionAssociation.table.c.parent_id ) ) + ) + ) assign_mapper( context, ToolVersionAssociation, ToolVersionAssociation.table ) diff --git a/lib/galaxy/model/search.py b/lib/galaxy/model/search.py index e7d97cf39df..3254e51ccc3 100644 --- a/lib/galaxy/model/search.py +++ b/lib/galaxy/model/search.py @@ -30,6 +30,7 @@ import re from galaxy.model import HistoryDatasetAssociation, LibraryDatasetDatasetAssociation, History, Library, LibraryFolder, LibraryDataset from galaxy.model import StoredWorkflowTagAssociation, StoredWorkflow, HistoryTagAssociation, ExtendedMetadata, ExtendedMetadataIndex +from galaxy.model import ToolVersion from sqlalchemy import or_, and_ from sqlalchemy.orm import aliased @@ -171,6 +172,22 @@ class LibraryDatasetView(ViewQueryBaseClass): def search(self, trans): self.query = trans.sa_session.query( LibraryDataset ) + + +################## +#Tool Searching +################## + +class ToolView(ViewQueryBaseClass): + VIEW_NAME = "tool" + FIELDS = { + 'tool_id' : ViewField('name', sqlalchemy_field=ToolVersion.tool_id ), + 'id' : ViewField('id', sqlalchemy_field=ToolVersion.id) + } + + def search(self, trans): + self.query = trans.sa_session.query( ToolVersion ) + ################## #History Dataset Searching ################## @@ -282,7 +299,8 @@ view_mapping = { 'history_dataset' : HistoryDatasetView, 'hda' : HistoryDatasetView, 'history' : HistoryView, - 'workflow' : WorkflowView + 'workflow' : WorkflowView, + 'tool' : ToolView } """ From b3f7b2d0cca2980e06961bd77904b58c910ce3d9 Mon Sep 17 00:00:00 2001 From: Kyle Ellrott Date: Thu, 14 Mar 2013 17:52:24 -0700 Subject: [PATCH 018/523] Enabling select of ldda based on 'deleted' status --- lib/galaxy/model/__init__.py | 10 +++++++++- lib/galaxy/model/search.py | 12 +++++++++--- lib/galaxy/webapps/galaxy/api/search.py | 7 ++++++- 3 files changed, 24 insertions(+), 5 deletions(-) diff --git a/lib/galaxy/model/__init__.py b/lib/galaxy/model/__init__.py index b2def24bfec..f57a8d8045b 100644 --- a/lib/galaxy/model/__init__.py +++ b/lib/galaxy/model/__init__.py @@ -929,7 +929,10 @@ class Dataset( object ): return self.object_store.get_filename( self, dir_only=True, extra_dir=self._extra_files_path or "dataset_%d_files" % self.id ) def _calculate_size( self ): if self.external_filename: - return os.path.getsize(self.external_filename) + try: + return os.path.getsize(self.external_filename) + except OSError: + return 0 else: return self.object_store.size(self) def get_size( self, nice_size=False ): @@ -1902,10 +1905,15 @@ class LibraryDatasetDatasetAssociation( DatasetInstance ): visible = ldda.visible, state = ldda.state, file_size = file_size, + file_name = ldda.file_name, data_type = ldda.ext, genome_build = ldda.dbkey, misc_info = ldda.info, misc_blurb = ldda.blurb ) + if ldda.dataset.uuid is None: + rval['uuid'] = None + else: + rval['uuid'] = str(ldda.dataset.uuid) if ldda.extended_metadata is not None: rval['extended_metadata'] = ldda.extended_metadata.data for name, spec in ldda.metadata.spec.items(): diff --git a/lib/galaxy/model/search.py b/lib/galaxy/model/search.py index e7d97cf39df..008e322e6b2 100644 --- a/lib/galaxy/model/search.py +++ b/lib/galaxy/model/search.py @@ -118,7 +118,8 @@ class LibraryDatasetDatasetView(ViewQueryBaseClass): FIELDS = { 'extended_metadata' : ViewField('extended_metadata', handler=library_extended_metadata_filter), 'name' : ViewField('name', sqlalchemy_field=LibraryDatasetDatasetAssociation.name ), - 'id' : ViewField('id', sqlalchemy_field=LibraryDatasetDatasetAssociation.id) + 'id' : ViewField('id', sqlalchemy_field=LibraryDatasetDatasetAssociation.id), + 'deleted' : ViewField('deleted', sqlalchemy_field=LibraryDatasetDatasetAssociation.deleted), } def search(self, trans): @@ -311,8 +312,13 @@ field_name = word:x ( |-> x ) alphanum = anything:x ?(re.search(r'\w', x) is not None) -> x -logic_statement = field_name:left ws comparison:comp ws quotable_word:right -> GalaxyQueryComparison(left, comp, right) -quotable_word = ( word | quote_word ) +logic_statement = field_name:left ws comparison:comp ws value_word:right -> GalaxyQueryComparison(left, comp, right) +value_word = ( + 'false' -> False + | 'False' -> False + | 'true' -> True + | 'True' -> True + | quote_word ) comparison = ( '=' -> '=' | '>' -> '>' | '<' -> '<' diff --git a/lib/galaxy/webapps/galaxy/api/search.py b/lib/galaxy/webapps/galaxy/api/search.py index a4d31be9989..df9188878a1 100644 --- a/lib/galaxy/webapps/galaxy/api/search.py +++ b/lib/galaxy/webapps/galaxy/api/search.py @@ -31,7 +31,12 @@ class SearchController( BaseAPIController, SharableItemSecurityMixin ): return {'error' : str(e)} if query is not None: current_user_roles = trans.get_current_user_roles() - for item in query.process(trans): + try: + results = query.process(trans) + except Exception, e: + return {'error' : str(e)} + + for item in results: append = False if trans.user_is_admin(): append = True From f971ecacefb437aa6e648d69b8a100f8810c9bfc Mon Sep 17 00:00:00 2001 From: Kyle Ellrott Date: Sun, 17 Mar 2013 11:12:08 -0700 Subject: [PATCH 019/523] Giving search system the ability to deal with encoded ids and allowing for more complicated filter mechanisms that would require loaded objects to do comparisions. --- lib/galaxy/model/__init__.py | 8 +++ lib/galaxy/model/search.py | 75 ++++++++++++++++++++----- lib/galaxy/web/base/controller.py | 2 +- lib/galaxy/webapps/galaxy/api/search.py | 1 + 4 files changed, 72 insertions(+), 14 deletions(-) diff --git a/lib/galaxy/model/__init__.py b/lib/galaxy/model/__init__.py index f57a8d8045b..7d707cc6200 100644 --- a/lib/galaxy/model/__init__.py +++ b/lib/galaxy/model/__init__.py @@ -1713,6 +1713,14 @@ class LibraryFolder( object, APIItem ): else: template = info_association.template rval['data_template'] = template.name + + l_path = [] + f = self + while f.parent: + l_path.insert(0, f.name) + f = f.parent + rval['library_path'] = l_path + rval['parent_library_id'] = self.parent_library.id return rval @property def parent_library( self ): diff --git a/lib/galaxy/model/search.py b/lib/galaxy/model/search.py index 008e322e6b2..55f19bf782c 100644 --- a/lib/galaxy/model/search.py +++ b/lib/galaxy/model/search.py @@ -43,12 +43,27 @@ class ViewField(object): These filter operations are either handled with standard sqlalchemy filter calls, or passed to specialized handlers (such as when a table join would be needed to do the filtering) + + Parameters: + + sqlalchemy_field - Simple filtering using existing table columns, the argument is an sqlalchemy column + that the right hand value will be compared against + + handler - Requires more specialized code to do filtering, usually requires a table join in order to + process the conditional + + post_filter - Unable to do simple sqlalchemy based table filtering, filter is applied to loaded object + Thus methods avalible to the object can be used for filtering. example: a library folder must climb + its chain of parents to find out which library it belongs to + """ - def __init__(self, name, sqlalchemy_field=None, handler=None): + def __init__(self, name, sqlalchemy_field=None, handler=None, post_filter=None, id_decode=False): self.name = name self.sqlalchemy_field = sqlalchemy_field self.handler = handler - + self.post_filter = post_filter + self.id_decode = id_decode + class ViewQueryBaseClass(object): FIELDS = {} @@ -58,6 +73,19 @@ class ViewQueryBaseClass(object): self.query = None self.do_query = False self.state = {} + self.post_filter = [] + + def decode_query_ids(self, trans, conditional): + if conditional.operator == 'and': + self.decode_query_ids(trans, conditional.left) + self.decode_query_ids(trans, conditional.right) + else: + left_base = conditional.left.split('.')[0] + if left_base in self.FIELDS: + field = self.FIELDS[left_base] + if field.id_decode: + conditional.right = trans.security.decode_id( conditional.right ) + def filter(self, left, operator, right): if operator == 'and': @@ -77,6 +105,8 @@ class ViewQueryBaseClass(object): raise GalaxyParseError("Invalid comparison operator: %s" % (operator)) elif field.handler is not None: field.handler(self, left, operator, right) + elif field.post_filter is not None: + self.post_filter.append( [field.post_filter, left, operator, right] ) else: raise GalaxyParseError("Unable to filter on field: %s" % (left)) @@ -89,7 +119,12 @@ class ViewQueryBaseClass(object): def get_results(self, force_query=False): if self.query is not None and (force_query or self.do_query): for row in self.query.distinct().all(): - yield row + selected = True + for f in self.post_filter: + if not f[0](row, f[1],f[2],f[3]): + selected = False + if selected: + yield row ################## @@ -118,7 +153,7 @@ class LibraryDatasetDatasetView(ViewQueryBaseClass): FIELDS = { 'extended_metadata' : ViewField('extended_metadata', handler=library_extended_metadata_filter), 'name' : ViewField('name', sqlalchemy_field=LibraryDatasetDatasetAssociation.name ), - 'id' : ViewField('id', sqlalchemy_field=LibraryDatasetDatasetAssociation.id), + 'id' : ViewField('id', sqlalchemy_field=LibraryDatasetDatasetAssociation.id, id_decode=True), 'deleted' : ViewField('deleted', sqlalchemy_field=LibraryDatasetDatasetAssociation.deleted), } @@ -134,7 +169,7 @@ class LibraryView(ViewQueryBaseClass): VIEW_NAME = "library" FIELDS = { 'name' : ViewField('name', sqlalchemy_field=Library.name ), - 'id' : ViewField('id', sqlalchemy_field=Library.id) + 'id' : ViewField('id', sqlalchemy_field=Library.id, id_decode=True) } def search(self, trans): @@ -146,11 +181,20 @@ class LibraryView(ViewQueryBaseClass): #Library Folder Searching ################## +def library_folder_parent_library_id_filter(item, left, operator, right): + if operator == '=': + return item.parent_library.id == right + if operator == '!=': + return item.parent_library.id != right + raise GalaxyParseError("Invalid comparison operator: %s" % (operator)) + class LibraryFolderView(ViewQueryBaseClass): VIEW_NAME = "library_folder" FIELDS = { 'name' : ViewField('name', sqlalchemy_field=LibraryFolder.name ), - 'id' : ViewField('id', sqlalchemy_field=LibraryFolder.id) + 'id' : ViewField('id', sqlalchemy_field=LibraryFolder.id, id_decode=True), + 'parent_id' : ViewField('parent_id', sqlalchemy_field=LibraryFolder.parent_id, id_decode=True ), + 'parent_library_id' : ViewField('parent_library_id', post_filter=library_folder_parent_library_id_filter, id_decode=True) } def search(self, trans): @@ -166,7 +210,7 @@ class LibraryDatasetView(ViewQueryBaseClass): VIEW_NAME = "library_dataset" FIELDS = { 'name' : ViewField('name', sqlalchemy_field=LibraryDataset.name ), - 'id' : ViewField('id', sqlalchemy_field=LibraryDataset.id) + 'id' : ViewField('id', sqlalchemy_field=LibraryDataset.id, id_decode=True) } def search(self, trans): @@ -181,7 +225,7 @@ class HistoryDatasetView(ViewQueryBaseClass): DOMAIN = "history_dataset" FIELDS = { 'name' : ViewField('name', sqlalchemy_field=HistoryDatasetAssociation.name), - 'id' : ViewField('id',sqlalchemy_field=HistoryDatasetAssociation.id ) + 'id' : ViewField('id',sqlalchemy_field=HistoryDatasetAssociation.id, id_decode=True) } def search(self, trans): @@ -230,7 +274,7 @@ class HistoryView(ViewQueryBaseClass): DOMAIN = "history" FIELDS = { 'name' : ViewField('name', sqlalchemy_field=History.name), - 'id' : ViewField('id', sqlalchemy_field=History.id), + 'id' : ViewField('id', sqlalchemy_field=History.id, id_decode=True), 'tag' : ViewField("tag", handler=history_handle_tag), 'annotation' : ViewField("annotation", handler=history_handle_annotation) } @@ -262,7 +306,7 @@ class WorkflowView(ViewQueryBaseClass): DOMAIN = "workflow" FIELDS = { 'name' : ViewField('name', sqlalchemy_field=StoredWorkflow.name), - 'id' : ViewField('id', sqlalchemy_field=StoredWorkflow.id), + 'id' : ViewField('id', sqlalchemy_field=StoredWorkflow.id, id_decode=True), 'tag' : ViewField('tag', handler=workflow_tag_handler) } @@ -318,6 +362,7 @@ value_word = ( | 'False' -> False | 'true' -> True | 'True' -> True + | 'None' -> None | quote_word ) comparison = ( '=' -> '=' | '>' -> '>' @@ -364,11 +409,15 @@ class GalaxyQueryAnd: class GalaxyParseError(Exception): pass -class SearchProcess: +class SearchQuery: def __init__(self, view, query): self.view = view self.query = query + def decode_query_ids(self, trans): + if self.query.conditional is not None: + self.view.decode_query_ids(trans, self.query.conditional) + def process(self, trans): self.view.search(trans) if self.query.conditional is not None: @@ -392,7 +441,7 @@ class SearchProcess: class GalaxySearchEngine: """ - Primary class for searching. Parses GQL (Galaxy Query Language) queries and returns a 'SearchProcess' class + Primary class for searching. Parses GQL (Galaxy Query Language) queries and returns a 'SearchQuery' class """ def __init__(self): self.parser = parsley.makeGrammar(gqlGrammar, { @@ -407,6 +456,6 @@ class GalaxySearchEngine: if q.table_name in view_mapping: view = view_mapping[q.table_name]() - return SearchProcess(view, q) + return SearchQuery(view, q) raise GalaxyParseError("No such table %s" % (q.table_name)) diff --git a/lib/galaxy/web/base/controller.py b/lib/galaxy/web/base/controller.py index 82f33f3704b..055a11f6b5f 100644 --- a/lib/galaxy/web/base/controller.py +++ b/lib/galaxy/web/base/controller.py @@ -131,7 +131,7 @@ class BaseController( object ): if type( rval ) != dict: return rval for k, v in rval.items(): - if k == 'id' or k.endswith( '_id' ): + if (k == 'id' or k.endswith( '_id' )) and v is not None: try: rval[k] = trans.security.encode_id( v ) except: diff --git a/lib/galaxy/webapps/galaxy/api/search.py b/lib/galaxy/webapps/galaxy/api/search.py index df9188878a1..65334ab320c 100644 --- a/lib/galaxy/webapps/galaxy/api/search.py +++ b/lib/galaxy/webapps/galaxy/api/search.py @@ -30,6 +30,7 @@ class SearchController( BaseAPIController, SharableItemSecurityMixin ): except Exception, e: return {'error' : str(e)} if query is not None: + query.decode_query_ids(trans) current_user_roles = trans.get_current_user_roles() try: results = query.process(trans) From 1d302307f9e7d959682a8c85d15cbd1ddc6cf9ed Mon Sep 17 00:00:00 2001 From: Kyle Ellrott Date: Sun, 17 Mar 2013 14:10:22 -0700 Subject: [PATCH 020/523] Adding library_path to Library Folder view. --- lib/galaxy/model/__init__.py | 12 +++++++----- lib/galaxy/model/search.py | 12 +++++++++++- 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/lib/galaxy/model/__init__.py b/lib/galaxy/model/__init__.py index 7d707cc6200..075ed798b9e 100644 --- a/lib/galaxy/model/__init__.py +++ b/lib/galaxy/model/__init__.py @@ -1713,15 +1713,17 @@ class LibraryFolder( object, APIItem ): else: template = info_association.template rval['data_template'] = template.name - + rval['library_path'] = self.library_path + rval['parent_library_id'] = self.parent_library.id + return rval + @property + def library_path(self): l_path = [] f = self while f.parent: l_path.insert(0, f.name) - f = f.parent - rval['library_path'] = l_path - rval['parent_library_id'] = self.parent_library.id - return rval + f = f.parent + return l_path @property def parent_library( self ): f = self diff --git a/lib/galaxy/model/search.py b/lib/galaxy/model/search.py index 55f19bf782c..001ff7feb5c 100644 --- a/lib/galaxy/model/search.py +++ b/lib/galaxy/model/search.py @@ -188,13 +188,23 @@ def library_folder_parent_library_id_filter(item, left, operator, right): return item.parent_library.id != right raise GalaxyParseError("Invalid comparison operator: %s" % (operator)) +def library_path_filter(item, left, operator, right): + lpath = "/" + "/".join(item.library_path) + if operator == '=': + return lpath == right + if operator == '!=': + return lpath != right + raise GalaxyParseError("Invalid comparison operator: %s" % (operator)) + + class LibraryFolderView(ViewQueryBaseClass): VIEW_NAME = "library_folder" FIELDS = { 'name' : ViewField('name', sqlalchemy_field=LibraryFolder.name ), 'id' : ViewField('id', sqlalchemy_field=LibraryFolder.id, id_decode=True), 'parent_id' : ViewField('parent_id', sqlalchemy_field=LibraryFolder.parent_id, id_decode=True ), - 'parent_library_id' : ViewField('parent_library_id', post_filter=library_folder_parent_library_id_filter, id_decode=True) + 'parent_library_id' : ViewField('parent_library_id', post_filter=library_folder_parent_library_id_filter, id_decode=True), + 'library_path' : ViewField('library_path', post_filter=library_path_filter) } def search(self, trans): From f4b6f745f2916c39ae8dae3477f14eb28d353eac Mon Sep 17 00:00:00 2001 From: Kyle Ellrott Date: Sun, 17 Mar 2013 15:24:20 -0700 Subject: [PATCH 021/523] Adding ability to search filter Library Dataset by name or library folder_id. --- lib/galaxy/model/__init__.py | 1 + lib/galaxy/model/search.py | 14 ++++++++++++-- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/lib/galaxy/model/__init__.py b/lib/galaxy/model/__init__.py index 075ed798b9e..d211da3e412 100644 --- a/lib/galaxy/model/__init__.py +++ b/lib/galaxy/model/__init__.py @@ -1786,6 +1786,7 @@ class LibraryDataset( object ): rval = dict( id = self.id, ldda_id = ldda.id, + folder_id = self.folder_id, model_class = self.__class__.__name__, name = ldda.name, file_name = ldda.file_name, diff --git a/lib/galaxy/model/search.py b/lib/galaxy/model/search.py index 001ff7feb5c..52fb1b9b4ad 100644 --- a/lib/galaxy/model/search.py +++ b/lib/galaxy/model/search.py @@ -98,6 +98,7 @@ class ViewQueryBaseClass(object): field = self.FIELDS[left_base] if field.sqlalchemy_field is not None: if operator == "=": + print field.sqlalchemy_field == right, field.sqlalchemy_field, right self.query = self.query.filter( field.sqlalchemy_field == right ) elif operator == "like": self.query = self.query.filter( field.sqlalchemy_field.like(right) ) @@ -216,11 +217,20 @@ class LibraryFolderView(ViewQueryBaseClass): #Library Dataset Searching ################## +def library_dataset_name_filter(item, left, operator, right): + if operator == '=': + return item.name == right + if operator == '!=': + return item.name != right + raise GalaxyParseError("Invalid comparison operator: %s" % (operator)) + + class LibraryDatasetView(ViewQueryBaseClass): VIEW_NAME = "library_dataset" FIELDS = { - 'name' : ViewField('name', sqlalchemy_field=LibraryDataset.name ), - 'id' : ViewField('id', sqlalchemy_field=LibraryDataset.id, id_decode=True) + 'name' : ViewField('name', post_filter=library_dataset_name_filter), + 'id' : ViewField('id', sqlalchemy_field=LibraryDataset.id, id_decode=True), + 'folder_id' : ViewField('folder_id', sqlalchemy_field=LibraryDataset.folder_id, id_decode=True) } def search(self, trans): From 10df11fbac67e3e952b5c7ace38da2bea6ff5ebf Mon Sep 17 00:00:00 2001 From: John Chilton Date: Fri, 22 Mar 2013 00:41:08 -0500 Subject: [PATCH 022/523] Refactor tool loading and macro preprocessing stuff into its own module, implement unit tests for basic functionality. --- lib/galaxy/tools/__init__.py | 100 +--------------- lib/galaxy/tools/loader.py | 223 +++++++++++++++++++++++++++++++++++ 2 files changed, 225 insertions(+), 98 deletions(-) create mode 100644 lib/galaxy/tools/loader.py diff --git a/lib/galaxy/tools/__init__.py b/lib/galaxy/tools/__init__.py index 12e44d9e9a5..84c2e04ad7e 100755 --- a/lib/galaxy/tools/__init__.py +++ b/lib/galaxy/tools/__init__.py @@ -29,7 +29,6 @@ from elementtree import ElementTree from mako.template import Template from paste import httpexceptions from sqlalchemy import and_ -from copy import deepcopy from galaxy import jobs, model from galaxy.datatypes.metadata import JobExternalOutputMetadataWrapper @@ -58,6 +57,7 @@ from galaxy.visualization.genome.visual_analytics import TracksterConfig from galaxy.web import url_for from galaxy.web.form_builder import SelectField from tool_shed.util import shed_util_common +from .loader import load_tool log = logging.getLogger( __name__ ) @@ -577,7 +577,7 @@ class ToolBox( object ): def load_tool( self, config_file, guid=None, **kwds ): """Load a single tool from the file named by `config_file` and return an instance of `Tool`.""" # Parse XML configuration file and get the root element - tree = self._load_and_preprocess_tool_xml( config_file ) + tree = load_tool( config_file ) root = tree.getroot() # Allow specifying a different tool subclass to instantiate if root.find( "type" ) is not None: @@ -762,102 +762,6 @@ class ToolBox( object ): return rval - def _load_and_preprocess_tool_xml(self, config_file): - tree = parse_xml(config_file) - root = tree.getroot() - macros_el = root.find('macros') - if not macros_el: - return tree - tool_dir = os.path.dirname(config_file) - macros = self._load_macros(macros_el, tool_dir) - - self._expand_macros([root], macros) - return tree - - def _expand_macros(self, elements, macros): - for element in elements: - # HACK for elementtree, newer implementations (etree/lxml) won't - # require this parent_map data structure but elementtree does not - # track parents or recongnize .find('..'). - parent_map = dict((c, p) for p in element.getiterator() for c in p) - for expand_el in element.findall('.//expand'): - macro_name = expand_el.get('macro') - macro_def = deepcopy(macros[macro_name]) # deepcopy needed? - - yield_els = [yield_el for macro_def_el in macro_def for yield_el in macro_def_el.findall('.//yield')] - - expand_el_children = expand_el.getchildren() - macro_def_parent_map = \ - dict((c, p) for macro_def_el in macro_def for p in macro_def_el.getiterator() for c in p) - - for yield_el in yield_els: - self._xml_replace(yield_el, expand_el_children, macro_def_parent_map) - - # Recursively expand contained macros. - self._expand_macros(macro_def, macros) - self._xml_replace(expand_el, macro_def, parent_map) - - def _load_macros(self, macros_el, tool_dir): - macros = {} - # Import macros from external files. - macros.update(self._load_imported_macros(macros_el, tool_dir)) - # Load all directly defined macros. - macros.update(self._load_embedded_macros(macros_el, tool_dir)) - return macros - - def _load_embedded_macros(self, macros_el, tool_dir): - macros = {} - - macro_els = [] - if macros_el: - macro_els = macros_el.findall("macro") - for macro in macro_els: - macro_name = macro.get("name") - macros[macro_name] = self._load_macro_def(macro) - - return macros - - def _load_imported_macros(self, macros_el, tool_dir): - macros = {} - - macro_import_els = [] - if macros_el: - macro_import_els = macros_el.findall("import") - for macro_import_el in macro_import_els: - raw_import_path = macro_import_el.text - tool_relative_import_path = \ - os.path.basename(raw_import_path) # Sanitize this - import_path = \ - os.path.join(tool_dir, tool_relative_import_path) - file_macros = self._load_macro_file(import_path, tool_dir) - macros.update(file_macros) - - return macros - - def _load_macro_file(self, path, tool_dir): - tree = parse_xml(path) - root = tree.getroot() - return self._load_macros(root, tool_dir) - - def _load_macro_def(self, macro): - return list(macro.getchildren()) - - def _xml_replace(self, query, targets, parent_map): - #parent_el = query.find('..') ## Something like this would be better with newer xml library - parent_el = parent_map[query] - matching_index = -1 - #for index, el in enumerate(parent_el.iter('.')): ## Something like this for newer implementation - for index, el in enumerate(parent_el.getchildren()): - if el == query: - matching_index = index - break - assert matching_index >= 0 - current_index = matching_index - for target in targets: - current_index += 1 - parent_el.insert(current_index, deepcopy(target)) - parent_el.remove(query) - class ToolSection( object ): """ diff --git a/lib/galaxy/tools/loader.py b/lib/galaxy/tools/loader.py new file mode 100644 index 00000000000..ea6c2d26811 --- /dev/null +++ b/lib/galaxy/tools/loader.py @@ -0,0 +1,223 @@ +from __future__ import with_statement + +from copy import deepcopy +import os + +from galaxy.util import parse_xml + + +def load_tool(path): + """ + Loads tool from file system and preprocesses tool macros. + """ + tree = parse_xml(path) + root = tree.getroot() + macros_el = root.find('macros') + if not macros_el: + return tree + tool_dir = os.path.dirname(path) + macros = _load_macros(macros_el, tool_dir) + + _expand_macros([root], macros) + return tree + + +def _expand_macros(elements, macros): + for element in elements: + # HACK for elementtree, newer implementations (etree/lxml) won't + # require this parent_map data structure but elementtree does not + # track parents or recongnize .find('..'). + parent_map = dict((c, p) for p in element.getiterator() for c in p) + for expand_el in element.findall('.//expand'): + macro_name = expand_el.get('macro') + macro_def = deepcopy(macros[macro_name]) # deepcopy needed? + + yield_els = [yield_el for macro_def_el in macro_def for yield_el in macro_def_el.findall('.//yield')] + + expand_el_children = expand_el.getchildren() + macro_def_parent_map = \ + dict((c, p) for macro_def_el in macro_def for p in macro_def_el.getiterator() for c in p) + + for yield_el in yield_els: + _xml_replace(yield_el, expand_el_children, macro_def_parent_map) + + # Recursively expand contained macros. + _expand_macros(macro_def, macros) + _xml_replace(expand_el, macro_def, parent_map) + + +def _load_macros(macros_el, tool_dir): + macros = {} + # Import macros from external files. + macros.update(_load_imported_macros(macros_el, tool_dir)) + # Load all directly defined macros. + macros.update(_load_embedded_macros(macros_el, tool_dir)) + return macros + + +def _load_embedded_macros(macros_el, tool_dir): + macros = {} + + macro_els = [] + if macros_el: + macro_els = macros_el.findall("macro") + for macro in macro_els: + macro_name = macro.get("name") + macros[macro_name] = _load_macro_def(macro) + + return macros + + +def _load_imported_macros(macros_el, tool_dir): + macros = {} + + macro_import_els = [] + if macros_el: + macro_import_els = macros_el.findall("import") + for macro_import_el in macro_import_els: + raw_import_path = macro_import_el.text + tool_relative_import_path = \ + os.path.basename(raw_import_path) # Sanitize this + import_path = \ + os.path.join(tool_dir, tool_relative_import_path) + file_macros = _load_macro_file(import_path, tool_dir) + macros.update(file_macros) + + return macros + + +def _load_macro_file(path, tool_dir): + tree = parse_xml(path) + root = tree.getroot() + return _load_macros(root, tool_dir) + + +def _load_macro_def(macro): + return list(macro.getchildren()) + + +def _xml_replace(query, targets, parent_map): + #parent_el = query.find('..') ## Something like this would be better with newer xml library + parent_el = parent_map[query] + matching_index = -1 + #for index, el in enumerate(parent_el.iter('.')): ## Something like this for newer implementation + for index, el in enumerate(parent_el.getchildren()): + if el == query: + matching_index = index + break + assert matching_index >= 0 + current_index = matching_index + for target in targets: + current_index += 1 + parent_el.insert(current_index, deepcopy(target)) + parent_el.remove(query) + + +def test_loader(): + """ + Function to test this module. Galaxy doesn't seem to have a + place to put unit tests that are not doctests. These tests can + be run with nosetests via the following command: + + % nosetests --with-doctest lib/galaxy/tools/loader.py + + """ + from tempfile import mkdtemp + from shutil import rmtree + + class TestToolDirectory(object): + def __init__(self): + self.temp_directory = mkdtemp() + + def __enter__(self): + return self + + def __exit__(self, type, value, tb): + rmtree(self.temp_directory) + + def write(self, contents, name="tool.xml"): + open(os.path.join(self.temp_directory, name), "w").write(contents) + + def load(self, name="tool.xml", preprocess=True): + if preprocess: + loader = load_tool + else: + loader = parse_xml + return loader(os.path.join(self.temp_directory, name)) + + ## Test simple macro replacement. + with TestToolDirectory() as tool_dir: + tool_dir.write(''' + + + + + + + +''') + xml = tool_dir.load(preprocess=False) + assert xml.find("inputs") is None + xml = tool_dir.load(preprocess=True) + assert xml.find("inputs") is not None + + # Test importing macros from external files + with TestToolDirectory() as tool_dir: + tool_dir.write(''' + + + + external.xml + +''') + + tool_dir.write(''' + + + + +''', name="external.xml") + xml = tool_dir.load(preprocess=False) + assert xml.find("inputs") is None + xml = tool_dir.load(preprocess=True) + assert xml.find("inputs") is not None + + # Test macros with unnamed yield statements. + with TestToolDirectory() as tool_dir: + tool_dir.write(''' + + + + + + + + + + + +''') + xml = tool_dir.load() + assert xml.find("inputs").find("input").get("name") == "first_input" + + # Test recursive macro applications. + with TestToolDirectory() as tool_dir: + tool_dir.write(''' + + + + + + + + + + + + + + + +''') + xml = tool_dir.load() + assert xml.find("inputs").findall("input")[1].get("name") == "second_input" From 32119ab203f27c3bc04b700b86bb278785865a7e Mon Sep 17 00:00:00 2001 From: John Chilton Date: Fri, 22 Mar 2013 00:41:08 -0500 Subject: [PATCH 023/523] Refactor macro stuff to allow multiple macro types (default type is xml, will be short for is a shortcut for . + typed_tag = ['xml'] + for tag in typed_tag: + macro_els = [] + if macros_el: + macro_els = macros_el.findall(tag) + for macro_el in macro_els: + macro_el.attrib['type'] = tag + macro_el.tag = 'macro' + macros.append(macro_el) return macros def _load_imported_macros(macros_el, tool_dir): - macros = {} + macros = [] macro_import_els = [] if macros_el: @@ -81,7 +101,7 @@ def _load_imported_macros(macros_el, tool_dir): import_path = \ os.path.join(tool_dir, tool_relative_import_path) file_macros = _load_macro_file(import_path, tool_dir) - macros.update(file_macros) + macros.extend(file_macros) return macros @@ -96,6 +116,13 @@ def _load_macro_def(macro): return list(macro.getchildren()) +def _xml_set_children(element, new_children): + for old_child in element.getchildren(): + element.remove(old_child) + for i, new_child in enumerate(new_children): + element.insert(i, new_child) + + def _xml_replace(query, targets, parent_map): #parent_el = query.find('..') ## Something like this would be better with newer xml library parent_el = parent_map[query] @@ -119,7 +146,7 @@ def test_loader(): place to put unit tests that are not doctests. These tests can be run with nosetests via the following command: - % nosetests --with-doctest lib/galaxy/tools/loader.py + % nosetests lib/galaxy/tools/loader.py """ from tempfile import mkdtemp @@ -221,3 +248,17 @@ def test_loader(): ''') xml = tool_dir.load() assert xml.find("inputs").findall("input")[1].get("name") == "second_input" + + # Test is shortcut for macro type="xml" + with TestToolDirectory() as tool_dir: + tool_dir.write(''' + + + + + + + +''') + xml = tool_dir.load(preprocess=True) + assert xml.find("inputs") is not None From 8237ff43436e2605f702842bf00f87c0d909aa04 Mon Sep 17 00:00:00 2001 From: John Chilton Date: Fri, 22 Mar 2013 00:41:08 -0500 Subject: [PATCH 024/523] Implement 'template' macros that define variables which are made available to cheetah templates. --- lib/galaxy/tools/__init__.py | 5 +-- lib/galaxy/tools/loader.py | 67 +++++++++++++++++++++++++++++------- 2 files changed, 58 insertions(+), 14 deletions(-) diff --git a/lib/galaxy/tools/__init__.py b/lib/galaxy/tools/__init__.py index 84c2e04ad7e..09af06ac629 100755 --- a/lib/galaxy/tools/__init__.py +++ b/lib/galaxy/tools/__init__.py @@ -57,7 +57,7 @@ from galaxy.visualization.genome.visual_analytics import TracksterConfig from galaxy.web import url_for from galaxy.web.form_builder import SelectField from tool_shed.util import shed_util_common -from .loader import load_tool +from .loader import load_tool, template_macro_params log = logging.getLogger( __name__ ) @@ -1246,6 +1246,7 @@ class Tool( object ): # thus hardcoded) FIXME: hidden parameters aren't # parameters at all really, and should be passed in a different # way, making this check easier. + self.template_macro_params = template_macro_params(root) for param in self.inputs.values(): if not isinstance( param, ( HiddenToolParameter, BaseURLToolParameter ) ): self.input_required = True @@ -2367,7 +2368,7 @@ class Tool( object ): `to_param_dict_string` method of the associated input. """ param_dict = dict() - + param_dict.update(self.template_macro_params) # All parameters go into the param_dict param_dict.update( incoming ) diff --git a/lib/galaxy/tools/loader.py b/lib/galaxy/tools/loader.py index 03d0881f4b8..becb1db5b81 100644 --- a/lib/galaxy/tools/loader.py +++ b/lib/galaxy/tools/loader.py @@ -12,22 +12,51 @@ def load_tool(path): """ tree = parse_xml(path) root = tree.getroot() - macros_el = root.find('macros') - tool_dir = os.path.dirname(path) - if macros_el: - macro_els = _load_macros(macros_el, tool_dir) - _xml_set_children(macros_el, macro_els) + _import_macros(root, path) - macro_dict = dict([(macro_el.get("name"), list(macro_el.getchildren())) \ - for macro_el in macro_els \ - if macro_el.get('type') == 'xml']) - _expand_macros([root], macro_dict) + # Expand xml macros + macro_dict = _macros_of_type(root, 'xml', lambda el: list(el.getchildren())) + _expand_macros([root], macro_dict) return tree +def template_macro_params(root): + """ + Look for template macros and populate param_dict (for cheetah) + with these. + """ + param_dict = {} + macro_dict = _macros_of_type(root, 'template', lambda el: el.text) + for key, value in macro_dict.iteritems(): + param_dict[key] = value + return param_dict + + +def _import_macros(root, path): + tool_dir = os.path.dirname(path) + macros_el = root.find('macros') + if macros_el: + macro_els = _load_macros(macros_el, tool_dir) + _xml_set_children(macros_el, macro_els) + + +def _macros_of_type(root, type, el_func): + macros_el = root.find('macros') + macro_dict = {} + if macros_el: + macro_els = macros_el.findall('macro') + macro_dict = dict([(macro_el.get("name"), el_func(macro_el)) \ + for macro_el in macro_els \ + if macro_el.get('type') == type]) + return macro_dict + + def _expand_macros(elements, macros): + if not macros: + return + for element in elements: # HACK for elementtree, newer implementations (etree/lxml) won't # require this parent_map data structure but elementtree does not @@ -35,7 +64,6 @@ def _expand_macros(elements, macros): parent_map = dict((c, p) for p in element.getiterator() for c in p) for expand_el in element.findall('.//expand'): macro_name = expand_el.get('macro') - print macros.keys() macro_def = deepcopy(macros[macro_name]) # deepcopy needed? yield_els = [yield_el for macro_def_el in macro_def for yield_el in macro_def_el.findall('.//yield')] @@ -75,7 +103,7 @@ def _load_embedded_macros(macros_el, tool_dir): # type shortcuts ( is a shortcut for . - typed_tag = ['xml'] + typed_tag = ['template', 'xml'] for tag in typed_tag: macro_els = [] if macros_el: @@ -260,5 +288,20 @@ def test_loader(): ''') - xml = tool_dir.load(preprocess=True) + xml = tool_dir.load() assert xml.find("inputs") is not None + + with TestToolDirectory() as tool_dir: + tool_dir.write(''' + + tool_wrapper.py + #include source=$tool_params + + + + + +''') + xml = tool_dir.load() + params_dict = template_macro_params(xml.getroot()) + assert params_dict['tool_params'] == "-a 1 -b 2" From 47c5f3bcc1b9ee729c5ff58d6bd4157f9f649e91 Mon Sep 17 00:00:00 2001 From: John Chilton Date: Fri, 22 Mar 2013 00:41:08 -0500 Subject: [PATCH 025/523] Add GATK macros file and import in each wrapper. --- tools/gatk/analyze_covariates.xml | 3 +++ tools/gatk/count_covariates.xml | 3 +++ tools/gatk/depth_of_coverage.xml | 3 +++ tools/gatk/gatk_macros.xml | 2 ++ tools/gatk/indel_realigner.xml | 3 +++ tools/gatk/print_reads.xml | 3 +++ tools/gatk/realigner_target_creator.xml | 3 +++ tools/gatk/table_recalibration.xml | 3 +++ tools/gatk/unified_genotyper.xml | 3 +++ tools/gatk/variant_annotator.xml | 3 +++ tools/gatk/variant_apply_recalibration.xml | 3 +++ tools/gatk/variant_combine.xml | 3 +++ tools/gatk/variant_eval.xml | 3 +++ tools/gatk/variant_filtration.xml | 3 +++ tools/gatk/variant_recalibrator.xml | 3 +++ tools/gatk/variant_select.xml | 3 +++ tools/gatk/variants_validate.xml | 3 +++ 17 files changed, 50 insertions(+) create mode 100644 tools/gatk/gatk_macros.xml diff --git a/tools/gatk/analyze_covariates.xml b/tools/gatk/analyze_covariates.xml index b0447df086f..a5b8c9a53f4 100644 --- a/tools/gatk/analyze_covariates.xml +++ b/tools/gatk/analyze_covariates.xml @@ -3,6 +3,9 @@ gatk + + gatk_macros.xml + gatk_wrapper.py --max_jvm_heap_fraction "1" --stdout "${output_log}" diff --git a/tools/gatk/count_covariates.xml b/tools/gatk/count_covariates.xml index 975e8a09860..df58ca0517b 100644 --- a/tools/gatk/count_covariates.xml +++ b/tools/gatk/count_covariates.xml @@ -4,6 +4,9 @@ gatk samtools + + gatk_macros.xml + gatk_wrapper.py --max_jvm_heap_fraction "1" --stdout "${output_log}" diff --git a/tools/gatk/depth_of_coverage.xml b/tools/gatk/depth_of_coverage.xml index f4f0be827b3..1aeeec7c150 100644 --- a/tools/gatk/depth_of_coverage.xml +++ b/tools/gatk/depth_of_coverage.xml @@ -4,6 +4,9 @@ gatk samtools + + gatk_macros.xml + gatk_wrapper.py --max_jvm_heap_fraction "1" --stdout "${output_log}" diff --git a/tools/gatk/gatk_macros.xml b/tools/gatk/gatk_macros.xml new file mode 100644 index 00000000000..a861d0cefb9 --- /dev/null +++ b/tools/gatk/gatk_macros.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/tools/gatk/indel_realigner.xml b/tools/gatk/indel_realigner.xml index dba7dd68bb1..5f7d3b57778 100644 --- a/tools/gatk/indel_realigner.xml +++ b/tools/gatk/indel_realigner.xml @@ -4,6 +4,9 @@ gatk samtools + + gatk_macros.xml + gatk_wrapper.py --max_jvm_heap_fraction "1" --stdout "${output_log}" diff --git a/tools/gatk/print_reads.xml b/tools/gatk/print_reads.xml index 998c0e3fba4..913450dc8c6 100644 --- a/tools/gatk/print_reads.xml +++ b/tools/gatk/print_reads.xml @@ -4,6 +4,9 @@ gatk samtools + + gatk_macros.xml + gatk_wrapper.py --max_jvm_heap_fraction "1" --stdout "${output_log}" diff --git a/tools/gatk/realigner_target_creator.xml b/tools/gatk/realigner_target_creator.xml index c56e057d519..e5acb43d97e 100644 --- a/tools/gatk/realigner_target_creator.xml +++ b/tools/gatk/realigner_target_creator.xml @@ -4,6 +4,9 @@ gatk samtools + + gatk_macros.xml + gatk_wrapper.py --max_jvm_heap_fraction "1" --stdout "${output_log}" diff --git a/tools/gatk/table_recalibration.xml b/tools/gatk/table_recalibration.xml index ad0e965bffe..ea7f1f65108 100644 --- a/tools/gatk/table_recalibration.xml +++ b/tools/gatk/table_recalibration.xml @@ -4,6 +4,9 @@ gatk samtools + + gatk_macros.xml + gatk_wrapper.py --max_jvm_heap_fraction "1" --stdout "${output_log}" diff --git a/tools/gatk/unified_genotyper.xml b/tools/gatk/unified_genotyper.xml index 3481dd184bc..a73f073aed8 100644 --- a/tools/gatk/unified_genotyper.xml +++ b/tools/gatk/unified_genotyper.xml @@ -4,6 +4,9 @@ gatk samtools + + gatk_macros.xml + gatk_wrapper.py --max_jvm_heap_fraction "1" --stdout "${output_log}" diff --git a/tools/gatk/variant_annotator.xml b/tools/gatk/variant_annotator.xml index b93aa5508ed..68f54ce6b4e 100644 --- a/tools/gatk/variant_annotator.xml +++ b/tools/gatk/variant_annotator.xml @@ -4,6 +4,9 @@ gatk samtools + + gatk_macros.xml + gatk_wrapper.py --max_jvm_heap_fraction "1" --stdout "${output_log}" diff --git a/tools/gatk/variant_apply_recalibration.xml b/tools/gatk/variant_apply_recalibration.xml index 87b5a9a4241..f5e3a968f9e 100644 --- a/tools/gatk/variant_apply_recalibration.xml +++ b/tools/gatk/variant_apply_recalibration.xml @@ -3,6 +3,9 @@ gatk + + gatk_macros.xml + gatk_wrapper.py --max_jvm_heap_fraction "1" --stdout "${output_log}" diff --git a/tools/gatk/variant_combine.xml b/tools/gatk/variant_combine.xml index 90d3cc8c429..ce3bf6c0044 100644 --- a/tools/gatk/variant_combine.xml +++ b/tools/gatk/variant_combine.xml @@ -3,6 +3,9 @@ gatk + + gatk_macros.xml + gatk_wrapper.py --max_jvm_heap_fraction "1" --stdout "${output_log}" diff --git a/tools/gatk/variant_eval.xml b/tools/gatk/variant_eval.xml index d14ea023df7..1bedb5336e7 100644 --- a/tools/gatk/variant_eval.xml +++ b/tools/gatk/variant_eval.xml @@ -3,6 +3,9 @@ gatk + + gatk_macros.xml + gatk_wrapper.py #from binascii import hexlify --max_jvm_heap_fraction "1" diff --git a/tools/gatk/variant_filtration.xml b/tools/gatk/variant_filtration.xml index 99bb6059f71..942380d5f30 100644 --- a/tools/gatk/variant_filtration.xml +++ b/tools/gatk/variant_filtration.xml @@ -3,6 +3,9 @@ gatk + + gatk_macros.xml + gatk_wrapper.py #from binascii import hexlify --max_jvm_heap_fraction "1" diff --git a/tools/gatk/variant_recalibrator.xml b/tools/gatk/variant_recalibrator.xml index da8c4be8221..39f4b55767e 100644 --- a/tools/gatk/variant_recalibrator.xml +++ b/tools/gatk/variant_recalibrator.xml @@ -3,6 +3,9 @@ gatk + + gatk_macros.xml + gatk_wrapper.py --max_jvm_heap_fraction "1" --stdout "${output_log}" diff --git a/tools/gatk/variant_select.xml b/tools/gatk/variant_select.xml index 1eeeaeecd49..f01a9dfc6dd 100644 --- a/tools/gatk/variant_select.xml +++ b/tools/gatk/variant_select.xml @@ -3,6 +3,9 @@ gatk + + gatk_macros.xml + gatk_wrapper.py #from binascii import hexlify --max_jvm_heap_fraction "1" diff --git a/tools/gatk/variants_validate.xml b/tools/gatk/variants_validate.xml index 8c8919a8766..11f13e5f1ae 100644 --- a/tools/gatk/variants_validate.xml +++ b/tools/gatk/variants_validate.xml @@ -3,6 +3,9 @@ gatk + + gatk_macros.xml + gatk_wrapper.py --max_jvm_heap_fraction "1" --stdout "${output_log}" From a503f07fc236a58bac6f6332cff98c2b23dac00b Mon Sep 17 00:00:00 2001 From: John Chilton Date: Fri, 22 Mar 2013 00:41:08 -0500 Subject: [PATCH 026/523] Create shared cheetah template variable for gatk standard options. --- tools/gatk/count_covariates.xml | 61 +-------------------- tools/gatk/depth_of_coverage.xml | 61 +-------------------- tools/gatk/gatk_macros.xml | 62 ++++++++++++++++++++++ tools/gatk/indel_realigner.xml | 60 +-------------------- tools/gatk/print_reads.xml | 61 +-------------------- tools/gatk/realigner_target_creator.xml | 61 +-------------------- tools/gatk/table_recalibration.xml | 61 +-------------------- tools/gatk/unified_genotyper.xml | 61 +-------------------- tools/gatk/variant_annotator.xml | 60 +-------------------- tools/gatk/variant_apply_recalibration.xml | 61 +-------------------- tools/gatk/variant_combine.xml | 61 +-------------------- tools/gatk/variant_eval.xml | 61 +-------------------- tools/gatk/variant_filtration.xml | 61 +-------------------- tools/gatk/variant_recalibrator.xml | 61 +-------------------- tools/gatk/variant_select.xml | 61 +-------------------- tools/gatk/variants_validate.xml | 62 +--------------------- 16 files changed, 77 insertions(+), 899 deletions(-) diff --git a/tools/gatk/count_covariates.xml b/tools/gatk/count_covariates.xml index df58ca0517b..8937a6caddb 100644 --- a/tools/gatk/count_covariates.xml +++ b/tools/gatk/count_covariates.xml @@ -47,66 +47,7 @@ -d "--knownSites:${rod_bind_name},%(file_type)s" "${rod_binding.rod_bind_type.input_rod}" "${rod_binding.rod_bind_type.input_rod.ext}" "input_${rod_bind_name}_${rod_binding_names[$rod_bind_name]}" #end for - ##start standard gatk options - #if $gatk_param_type.gatk_param_type_selector == "advanced": - #for $pedigree in $gatk_param_type.pedigree: - -p '--pedigree "${pedigree.pedigree_file}"' - #end for - #for $pedigree_string in $gatk_param_type.pedigree_string_repeat: - -p '--pedigreeString "${pedigree_string.pedigree_string}"' - #end for - -p '--pedigreeValidationType "${gatk_param_type.pedigree_validation_type}"' - #for $read_filter in $gatk_param_type.read_filter: - -p '--read_filter "${read_filter.read_filter_type.read_filter_type_selector}" - ###raise Exception( str( dir( $read_filter ) ) ) - #for $name, $param in $read_filter.read_filter_type.iteritems(): - #if $name not in [ "__current_case__", "read_filter_type_selector" ]: - #if hasattr( $param.input, 'truevalue' ): - ${param} - #else: - --${name} "${param}" - #end if - #end if - #end for - ' - #end for - #for $interval_count, $input_intervals in enumerate( $gatk_param_type.input_interval_repeat ): - -d "--intervals" "${input_intervals.input_intervals}" "${input_intervals.input_intervals.ext}" "input_intervals_${interval_count}" - #end for - - #for $interval_count, $input_intervals in enumerate( $gatk_param_type.input_exclude_interval_repeat ): - -d "--excludeIntervals" "${input_intervals.input_exclude_intervals}" "${input_intervals.input_exclude_intervals.ext}" "input_exlude_intervals_${interval_count}" - #end for - - -p '--interval_set_rule "${gatk_param_type.interval_set_rule}"' - - -p '--downsampling_type "${gatk_param_type.downsampling_type.downsampling_type_selector}"' - #if str( $gatk_param_type.downsampling_type.downsampling_type_selector ) != "NONE": - -p '--${gatk_param_type.downsampling_type.downsample_to_type.downsample_to_type_selector} "${gatk_param_type.downsampling_type.downsample_to_type.downsample_to_value}"' - #end if - -p ' - --baq "${gatk_param_type.baq}" - --baqGapOpenPenalty "${gatk_param_type.baq_gap_open_penalty}" - ${gatk_param_type.use_original_qualities} - --defaultBaseQualities "${gatk_param_type.default_base_qualities}" - --validation_strictness "${gatk_param_type.validation_strictness}" - --interval_merging "${gatk_param_type.interval_merging}" - ${gatk_param_type.disable_experimental_low_memory_sharding} - ${gatk_param_type.non_deterministic_random_seed} - ' - #for $rg_black_list_count, $rg_black_list in enumerate( $gatk_param_type.read_group_black_list_repeat ): - #if $rg_black_list.read_group_black_list_type.read_group_black_list_type_selector == "file": - -d "--read_group_black_list" "${rg_black_list.read_group_black_list_type.read_group_black_list}" "txt" "input_read_group_black_list_${rg_black_list_count}" - #else - -p '--read_group_black_list "${rg_black_list.read_group_black_list_type.read_group_black_list}"' - #end if - #end for - #end if - - #if str( $reference_source.reference_source_selector ) == "history": - -d "-R" "${reference_source.ref_file}" "${reference_source.ref_file.ext}" "gatk_input" - #end if - ##end standard gatk options + #include source=$standard_gatk_options# ##start analysis specific options #if $analysis_param_type.analysis_param_type_selector == "advanced": diff --git a/tools/gatk/depth_of_coverage.xml b/tools/gatk/depth_of_coverage.xml index 1aeeec7c150..73acf44673b 100644 --- a/tools/gatk/depth_of_coverage.xml +++ b/tools/gatk/depth_of_coverage.xml @@ -41,66 +41,7 @@ --outputFormat "${output_format}" ' - ##start standard gatk options - #if $gatk_param_type.gatk_param_type_selector == "advanced": - #for $pedigree in $gatk_param_type.pedigree: - -p '--pedigree "${pedigree.pedigree_file}"' - #end for - #for $pedigree_string in $gatk_param_type.pedigree_string_repeat: - -p '--pedigreeString "${pedigree_string.pedigree_string}"' - #end for - -p '--pedigreeValidationType "${gatk_param_type.pedigree_validation_type}"' - #for $read_filter in $gatk_param_type.read_filter: - -p '--read_filter "${read_filter.read_filter_type.read_filter_type_selector}" - ###raise Exception( str( dir( $read_filter ) ) ) - #for $name, $param in $read_filter.read_filter_type.iteritems(): - #if $name not in [ "__current_case__", "read_filter_type_selector" ]: - #if hasattr( $param.input, 'truevalue' ): - ${param} - #else: - --${name} "${param}" - #end if - #end if - #end for - ' - #end for - #for $interval_count, $input_intervals in enumerate( $gatk_param_type.input_interval_repeat ): - -d "--intervals" "${input_intervals.input_intervals}" "${input_intervals.input_intervals.ext}" "input_intervals_${interval_count}" - #end for - - #for $interval_count, $input_intervals in enumerate( $gatk_param_type.input_exclude_interval_repeat ): - -d "--excludeIntervals" "${input_intervals.input_exclude_intervals}" "${input_intervals.input_exclude_intervals.ext}" "input_exlude_intervals_${interval_count}" - #end for - - -p '--interval_set_rule "${gatk_param_type.interval_set_rule}"' - - -p '--downsampling_type "${gatk_param_type.downsampling_type.downsampling_type_selector}"' - #if str( $gatk_param_type.downsampling_type.downsampling_type_selector ) != "NONE": - -p '--${gatk_param_type.downsampling_type.downsample_to_type.downsample_to_type_selector} "${gatk_param_type.downsampling_type.downsample_to_type.downsample_to_value}"' - #end if - -p ' - --baq "${gatk_param_type.baq}" - --baqGapOpenPenalty "${gatk_param_type.baq_gap_open_penalty}" - ${gatk_param_type.use_original_qualities} - --defaultBaseQualities "${gatk_param_type.default_base_qualities}" - --validation_strictness "${gatk_param_type.validation_strictness}" - --interval_merging "${gatk_param_type.interval_merging}" - ${gatk_param_type.disable_experimental_low_memory_sharding} - ${gatk_param_type.non_deterministic_random_seed} - ' - #for $rg_black_list_count, $rg_black_list in enumerate( $gatk_param_type.read_group_black_list_repeat ): - #if $rg_black_list.read_group_black_list_type.read_group_black_list_type_selector == "file": - -d "--read_group_black_list" "${rg_black_list.read_group_black_list_type.read_group_black_list}" "txt" "input_read_group_black_list_${rg_black_list_count}" - #else - -p '--read_group_black_list "${rg_black_list.read_group_black_list_type.read_group_black_list}"' - #end if - #end for - #end if - - #if $reference_source.reference_source_selector == "history": - -d "-R" "${reference_source.ref_file}" "${reference_source.ref_file.ext}" "gatk_input" - #end if - ##end standard gatk options + #include source=$standard_gatk_options# ##start analysis specific options #if $analysis_param_type.analysis_param_type_selector == "advanced": -p ' diff --git a/tools/gatk/gatk_macros.xml b/tools/gatk/gatk_macros.xml index a861d0cefb9..da71ba6df74 100644 --- a/tools/gatk/gatk_macros.xml +++ b/tools/gatk/gatk_macros.xml @@ -1,2 +1,64 @@ + \ No newline at end of file diff --git a/tools/gatk/indel_realigner.xml b/tools/gatk/indel_realigner.xml index 5f7d3b57778..d6dd66d5351 100644 --- a/tools/gatk/indel_realigner.xml +++ b/tools/gatk/indel_realigner.xml @@ -39,65 +39,7 @@ -d "-known:${rod_bind_name},%(file_type)s" "${rod_binding.rod_bind_type.input_rod}" "${rod_binding.rod_bind_type.input_rod.ext}" "input_${rod_bind_name}_${rod_binding_names[$rod_bind_name]}" #end for - ##start standard gatk options - #if $gatk_param_type.gatk_param_type_selector == "advanced": - #for $pedigree in $gatk_param_type.pedigree: - -p '--pedigree "${pedigree.pedigree_file}"' - #end for - #for $pedigree_string in $gatk_param_type.pedigree_string_repeat: - -p '--pedigreeString "${pedigree_string.pedigree_string}"' - #end for - -p '--pedigreeValidationType "${gatk_param_type.pedigree_validation_type}"' - #for $read_filter in $gatk_param_type.read_filter: - -p '--read_filter "${read_filter.read_filter_type.read_filter_type_selector}" - ###raise Exception( str( dir( $read_filter ) ) ) - #for $name, $param in $read_filter.read_filter_type.iteritems(): - #if $name not in [ "__current_case__", "read_filter_type_selector" ]: - #if hasattr( $param.input, 'truevalue' ): - ${param} - #else: - --${name} "${param}" - #end if - #end if - #end for - ' - #end for - #for $interval_count, $input_intervals in enumerate( $gatk_param_type.input_interval_repeat ): - -d "--intervals" "${input_intervals.input_intervals}" "${input_intervals.input_intervals.ext}" "input_intervals_${interval_count}" - #end for - - #for $interval_count, $input_intervals in enumerate( $gatk_param_type.input_exclude_interval_repeat ): - -d "--excludeIntervals" "${input_intervals.input_exclude_intervals}" "${input_intervals.input_exclude_intervals.ext}" "input_exlude_intervals_${interval_count}" - #end for - - -p '--interval_set_rule "${gatk_param_type.interval_set_rule}"' - - -p '--downsampling_type "${gatk_param_type.downsampling_type.downsampling_type_selector}"' - #if str( $gatk_param_type.downsampling_type.downsampling_type_selector ) != "NONE": - -p '--${gatk_param_type.downsampling_type.downsample_to_type.downsample_to_type_selector} "${gatk_param_type.downsampling_type.downsample_to_type.downsample_to_value}"' - #end if - -p ' - --baq "${gatk_param_type.baq}" - --baqGapOpenPenalty "${gatk_param_type.baq_gap_open_penalty}" - ${gatk_param_type.use_original_qualities} - --defaultBaseQualities "${gatk_param_type.default_base_qualities}" - --validation_strictness "${gatk_param_type.validation_strictness}" - --interval_merging "${gatk_param_type.interval_merging}" - ${gatk_param_type.disable_experimental_low_memory_sharding} - ${gatk_param_type.non_deterministic_random_seed} - ' - #for $rg_black_list_count, $rg_black_list in enumerate( $gatk_param_type.read_group_black_list_repeat ): - #if $rg_black_list.read_group_black_list_type.read_group_black_list_type_selector == "file": - -d "--read_group_black_list" "${rg_black_list.read_group_black_list_type.read_group_black_list}" "txt" "input_read_group_black_list_${rg_black_list_count}" - #else - -p '--read_group_black_list "${rg_black_list.read_group_black_list_type.read_group_black_list}"' - #end if - #end for - #end if - #if $reference_source.reference_source_selector == "history": - -d "-R" "${reference_source.ref_file}" "${reference_source.ref_file.ext}" "gatk_input" - #end if - ##end standard gatk options + #include source=$standard_gatk_options# ##start analysis specific options -d "-targetIntervals" "${target_intervals}" "${target_intervals.ext}" "gatk_target_intervals" -p ' diff --git a/tools/gatk/print_reads.xml b/tools/gatk/print_reads.xml index 913450dc8c6..f6474cc5a18 100644 --- a/tools/gatk/print_reads.xml +++ b/tools/gatk/print_reads.xml @@ -40,66 +40,7 @@ #end for ' - ##start standard gatk options - #if $gatk_param_type.gatk_param_type_selector == "advanced": - #for $pedigree in $gatk_param_type.pedigree: - -p '--pedigree "${pedigree.pedigree_file}"' - #end for - #for $pedigree_string in $gatk_param_type.pedigree_string_repeat: - -p '--pedigreeString "${pedigree_string.pedigree_string}"' - #end for - -p '--pedigreeValidationType "${gatk_param_type.pedigree_validation_type}"' - #for $read_filter in $gatk_param_type.read_filter: - -p '--read_filter "${read_filter.read_filter_type.read_filter_type_selector}" - ###raise Exception( str( dir( $read_filter ) ) ) - #for $name, $param in $read_filter.read_filter_type.iteritems(): - #if $name not in [ "__current_case__", "read_filter_type_selector" ]: - #if hasattr( $param.input, 'truevalue' ): - ${param} - #else: - --${name} "${param}" - #end if - #end if - #end for - ' - #end for - #for $interval_count, $input_intervals in enumerate( $gatk_param_type.input_interval_repeat ): - -d "--intervals" "${input_intervals.input_intervals}" "${input_intervals.input_intervals.ext}" "input_intervals_${interval_count}" - #end for - - #for $interval_count, $input_intervals in enumerate( $gatk_param_type.input_exclude_interval_repeat ): - -d "--excludeIntervals" "${input_intervals.input_exclude_intervals}" "${input_intervals.input_exclude_intervals.ext}" "input_exlude_intervals_${interval_count}" - #end for - - -p '--interval_set_rule "${gatk_param_type.interval_set_rule}"' - - -p '--downsampling_type "${gatk_param_type.downsampling_type.downsampling_type_selector}"' - #if str( $gatk_param_type.downsampling_type.downsampling_type_selector ) != "NONE": - -p '--${gatk_param_type.downsampling_type.downsample_to_type.downsample_to_type_selector} "${gatk_param_type.downsampling_type.downsample_to_type.downsample_to_value}"' - #end if - -p ' - --baq "${gatk_param_type.baq}" - --baqGapOpenPenalty "${gatk_param_type.baq_gap_open_penalty}" - ${gatk_param_type.use_original_qualities} - --defaultBaseQualities "${gatk_param_type.default_base_qualities}" - --validation_strictness "${gatk_param_type.validation_strictness}" - --interval_merging "${gatk_param_type.interval_merging}" - ${gatk_param_type.disable_experimental_low_memory_sharding} - ${gatk_param_type.non_deterministic_random_seed} - ' - #for $rg_black_list_count, $rg_black_list in enumerate( $gatk_param_type.read_group_black_list_repeat ): - #if $rg_black_list.read_group_black_list_type.read_group_black_list_type_selector == "file": - -d "--read_group_black_list" "${rg_black_list.read_group_black_list_type.read_group_black_list}" "txt" "input_read_group_black_list_${rg_black_list_count}" - #else - -p '--read_group_black_list "${rg_black_list.read_group_black_list_type.read_group_black_list}"' - #end if - #end for - #end if - - #if $reference_source.reference_source_selector == "history": - -d "-R" "${reference_source.ref_file}" "${reference_source.ref_file.ext}" "gatk_input" - #end if - ##end standard gatk options + #include source=$standard_gatk_options# diff --git a/tools/gatk/realigner_target_creator.xml b/tools/gatk/realigner_target_creator.xml index e5acb43d97e..9b61e655122 100644 --- a/tools/gatk/realigner_target_creator.xml +++ b/tools/gatk/realigner_target_creator.xml @@ -36,66 +36,7 @@ -d "-known:${rod_bind_name},%(file_type)s" "${rod_binding.rod_bind_type.input_rod}" "${rod_binding.rod_bind_type.input_rod.ext}" "input_${rod_bind_name}_${rod_binding_names[$rod_bind_name]}" #end for - ##start standard gatk options - #if $gatk_param_type.gatk_param_type_selector == "advanced": - #for $pedigree in $gatk_param_type.pedigree: - -p '--pedigree "${pedigree.pedigree_file}"' - #end for - #for $pedigree_string in $gatk_param_type.pedigree_string_repeat: - -p '--pedigreeString "${pedigree_string.pedigree_string}"' - #end for - -p '--pedigreeValidationType "${gatk_param_type.pedigree_validation_type}"' - #for $read_filter in $gatk_param_type.read_filter: - -p '--read_filter "${read_filter.read_filter_type.read_filter_type_selector}" - ###raise Exception( str( dir( $read_filter ) ) ) - #for $name, $param in $read_filter.read_filter_type.iteritems(): - #if $name not in [ "__current_case__", "read_filter_type_selector" ]: - #if hasattr( $param.input, 'truevalue' ): - ${param} - #else: - --${name} "${param}" - #end if - #end if - #end for - ' - #end for - #for $interval_count, $input_intervals in enumerate( $gatk_param_type.input_interval_repeat ): - -d "--intervals" "${input_intervals.input_intervals}" "${input_intervals.input_intervals.ext}" "input_intervals_${interval_count}" - #end for - - #for $interval_count, $input_intervals in enumerate( $gatk_param_type.input_exclude_interval_repeat ): - -d "--excludeIntervals" "${input_intervals.input_exclude_intervals}" "${input_intervals.input_exclude_intervals.ext}" "input_exlude_intervals_${interval_count}" - #end for - - -p '--interval_set_rule "${gatk_param_type.interval_set_rule}"' - - -p '--downsampling_type "${gatk_param_type.downsampling_type.downsampling_type_selector}"' - #if str( $gatk_param_type.downsampling_type.downsampling_type_selector ) != "NONE": - -p '--${gatk_param_type.downsampling_type.downsample_to_type.downsample_to_type_selector} "${gatk_param_type.downsampling_type.downsample_to_type.downsample_to_value}"' - #end if - -p ' - --baq "${gatk_param_type.baq}" - --baqGapOpenPenalty "${gatk_param_type.baq_gap_open_penalty}" - ${gatk_param_type.use_original_qualities} - --defaultBaseQualities "${gatk_param_type.default_base_qualities}" - --validation_strictness "${gatk_param_type.validation_strictness}" - --interval_merging "${gatk_param_type.interval_merging}" - ${gatk_param_type.disable_experimental_low_memory_sharding} - ${gatk_param_type.non_deterministic_random_seed} - ' - #for $rg_black_list_count, $rg_black_list in enumerate( $gatk_param_type.read_group_black_list_repeat ): - #if $rg_black_list.read_group_black_list_type.read_group_black_list_type_selector == "file": - -d "--read_group_black_list" "${rg_black_list.read_group_black_list_type.read_group_black_list}" "txt" "input_read_group_black_list_${rg_black_list_count}" - #else - -p '--read_group_black_list "${rg_black_list.read_group_black_list_type.read_group_black_list}"' - #end if - #end for - #end if - - #if $reference_source.reference_source_selector == "history": - -d "-R" "${reference_source.ref_file}" "${reference_source.ref_file.ext}" "gatk_input" - #end if - ##end standard gatk options + #include source=$standard_gatk_options# ##start analysis specific options #if $analysis_param_type.analysis_param_type_selector == "advanced": -p ' diff --git a/tools/gatk/table_recalibration.xml b/tools/gatk/table_recalibration.xml index ea7f1f65108..5e26849c2b5 100644 --- a/tools/gatk/table_recalibration.xml +++ b/tools/gatk/table_recalibration.xml @@ -27,66 +27,7 @@ --recal_file "${input_recal}" --disable_bam_indexing ' - ##start standard gatk options - #if $gatk_param_type.gatk_param_type_selector == "advanced": - #for $pedigree in $gatk_param_type.pedigree: - -p '--pedigree "${pedigree.pedigree_file}"' - #end for - #for $pedigree_string in $gatk_param_type.pedigree_string_repeat: - -p '--pedigreeString "${pedigree_string.pedigree_string}"' - #end for - -p '--pedigreeValidationType "${gatk_param_type.pedigree_validation_type}"' - #for $read_filter in $gatk_param_type.read_filter: - -p '--read_filter "${read_filter.read_filter_type.read_filter_type_selector}" - ###raise Exception( str( dir( $read_filter ) ) ) - #for $name, $param in $read_filter.read_filter_type.iteritems(): - #if $name not in [ "__current_case__", "read_filter_type_selector" ]: - #if hasattr( $param.input, 'truevalue' ): - ${param} - #else: - --${name} "${param}" - #end if - #end if - #end for - ' - #end for - #for $interval_count, $input_intervals in enumerate( $gatk_param_type.input_interval_repeat ): - -d "--intervals" "${input_intervals.input_intervals}" "${input_intervals.input_intervals.ext}" "input_intervals_${interval_count}" - #end for - - #for $interval_count, $input_intervals in enumerate( $gatk_param_type.input_exclude_interval_repeat ): - -d "--excludeIntervals" "${input_intervals.input_exclude_intervals}" "${input_intervals.input_exclude_intervals.ext}" "input_exlude_intervals_${interval_count}" - #end for - - -p '--interval_set_rule "${gatk_param_type.interval_set_rule}"' - - -p '--downsampling_type "${gatk_param_type.downsampling_type.downsampling_type_selector}"' - #if str( $gatk_param_type.downsampling_type.downsampling_type_selector ) != "NONE": - -p '--${gatk_param_type.downsampling_type.downsample_to_type.downsample_to_type_selector} "${gatk_param_type.downsampling_type.downsample_to_type.downsample_to_value}"' - #end if - -p ' - --baq "${gatk_param_type.baq}" - --baqGapOpenPenalty "${gatk_param_type.baq_gap_open_penalty}" - ${gatk_param_type.use_original_qualities} - --defaultBaseQualities "${gatk_param_type.default_base_qualities}" - --validation_strictness "${gatk_param_type.validation_strictness}" - --interval_merging "${gatk_param_type.interval_merging}" - ${gatk_param_type.disable_experimental_low_memory_sharding} - ${gatk_param_type.non_deterministic_random_seed} - ' - #for $rg_black_list_count, $rg_black_list in enumerate( $gatk_param_type.read_group_black_list_repeat ): - #if $rg_black_list.read_group_black_list_type.read_group_black_list_type_selector == "file": - -d "--read_group_black_list" "${rg_black_list.read_group_black_list_type.read_group_black_list}" "txt" "input_read_group_black_list_${rg_black_list_count}" - #else - -p '--read_group_black_list "${rg_black_list.read_group_black_list_type.read_group_black_list}"' - #end if - #end for - #end if - - #if str( $reference_source.reference_source_selector ) == "history": - -d "-R" "${reference_source.ref_file}" "${reference_source.ref_file.ext}" "gatk_input" - #end if - ##end standard gatk options + #include source=$standard_gatk_options# ##start analysis specific options #if $analysis_param_type.analysis_param_type_selector == "advanced": diff --git a/tools/gatk/unified_genotyper.xml b/tools/gatk/unified_genotyper.xml index a73f073aed8..392417cc2f9 100644 --- a/tools/gatk/unified_genotyper.xml +++ b/tools/gatk/unified_genotyper.xml @@ -42,66 +42,7 @@ -d "--dbsnp:${rod_bind_name},%(file_type)s" "${rod_binding.rod_bind_type.input_rod}" "${rod_binding.rod_bind_type.input_rod.ext}" "input_${rod_bind_name}_${rod_binding_names[$rod_bind_name]}" #end for - ##start standard gatk options - #if $gatk_param_type.gatk_param_type_selector == "advanced": - #for $pedigree in $gatk_param_type.pedigree: - -p '--pedigree "${pedigree.pedigree_file}"' - #end for - #for $pedigree_string in $gatk_param_type.pedigree_string_repeat: - -p '--pedigreeString "${pedigree_string.pedigree_string}"' - #end for - -p '--pedigreeValidationType "${gatk_param_type.pedigree_validation_type}"' - #for $read_filter in $gatk_param_type.read_filter: - -p '--read_filter "${read_filter.read_filter_type.read_filter_type_selector}" - ###raise Exception( str( dir( $read_filter ) ) ) - #for $name, $param in $read_filter.read_filter_type.iteritems(): - #if $name not in [ "__current_case__", "read_filter_type_selector" ]: - #if hasattr( $param.input, 'truevalue' ): - ${param} - #else: - --${name} "${param}" - #end if - #end if - #end for - ' - #end for - #for $interval_count, $input_intervals in enumerate( $gatk_param_type.input_interval_repeat ): - -d "--intervals" "${input_intervals.input_intervals}" "${input_intervals.input_intervals.ext}" "input_intervals_${interval_count}" - #end for - - #for $interval_count, $input_intervals in enumerate( $gatk_param_type.input_exclude_interval_repeat ): - -d "--excludeIntervals" "${input_intervals.input_exclude_intervals}" "${input_intervals.input_exclude_intervals.ext}" "input_exlude_intervals_${interval_count}" - #end for - - -p '--interval_set_rule "${gatk_param_type.interval_set_rule}"' - - -p '--downsampling_type "${gatk_param_type.downsampling_type.downsampling_type_selector}"' - #if str( $gatk_param_type.downsampling_type.downsampling_type_selector ) != "NONE": - -p '--${gatk_param_type.downsampling_type.downsample_to_type.downsample_to_type_selector} "${gatk_param_type.downsampling_type.downsample_to_type.downsample_to_value}"' - #end if - -p ' - --baq "${gatk_param_type.baq}" - --baqGapOpenPenalty "${gatk_param_type.baq_gap_open_penalty}" - ${gatk_param_type.use_original_qualities} - --defaultBaseQualities "${gatk_param_type.default_base_qualities}" - --validation_strictness "${gatk_param_type.validation_strictness}" - --interval_merging "${gatk_param_type.interval_merging}" - ${gatk_param_type.disable_experimental_low_memory_sharding} - ${gatk_param_type.non_deterministic_random_seed} - ' - #for $rg_black_list_count, $rg_black_list in enumerate( $gatk_param_type.read_group_black_list_repeat ): - #if $rg_black_list.read_group_black_list_type.read_group_black_list_type_selector == "file": - -d "--read_group_black_list" "${rg_black_list.read_group_black_list_type.read_group_black_list}" "txt" "input_read_group_black_list_${rg_black_list_count}" - #else - -p '--read_group_black_list "${rg_black_list.read_group_black_list_type.read_group_black_list}"' - #end if - #end for - #end if - - #if $reference_source.reference_source_selector == "history": - -d "-R" "${reference_source.ref_file}" "${reference_source.ref_file.ext}" "gatk_input" - #end if - ##end standard gatk options + #include source=$standard_gatk_options# ##start analysis specific options #if $analysis_param_type.analysis_param_type_selector == "advanced": -p ' diff --git a/tools/gatk/variant_annotator.xml b/tools/gatk/variant_annotator.xml index 68f54ce6b4e..079082adf6d 100644 --- a/tools/gatk/variant_annotator.xml +++ b/tools/gatk/variant_annotator.xml @@ -74,65 +74,7 @@ -p '--expression "${expression.expression}"' #end for - ##start standard gatk options - #if $gatk_param_type.gatk_param_type_selector == "advanced": - #for $pedigree in $gatk_param_type.pedigree: - -p '--pedigree "${pedigree.pedigree_file}"' - #end for - #for $pedigree_string in $gatk_param_type.pedigree_string_repeat: - -p '--pedigreeString "${pedigree_string.pedigree_string}"' - #end for - -p '--pedigreeValidationType "${gatk_param_type.pedigree_validation_type}"' - #for $read_filter in $gatk_param_type.read_filter: - -p '--read_filter "${read_filter.read_filter_type.read_filter_type_selector}" - ###raise Exception( str( dir( $read_filter ) ) ) - #for $name, $param in $read_filter.read_filter_type.iteritems(): - #if $name not in [ "__current_case__", "read_filter_type_selector" ]: - #if hasattr( $param.input, 'truevalue' ): - ${param} - #else: - --${name} "${param}" - #end if - #end if - #end for - ' - #end for - #for $interval_count, $input_intervals in enumerate( $gatk_param_type.input_interval_repeat ): - -d "--intervals" "${input_intervals.input_intervals}" "${input_intervals.input_intervals.ext}" "input_intervals_${interval_count}" - #end for - - #for $interval_count, $input_intervals in enumerate( $gatk_param_type.input_exclude_interval_repeat ): - -d "--excludeIntervals" "${input_intervals.input_exclude_intervals}" "${input_intervals.input_exclude_intervals.ext}" "input_exlude_intervals_${interval_count}" - #end for - - -p '--interval_set_rule "${gatk_param_type.interval_set_rule}"' - - -p '--downsampling_type "${gatk_param_type.downsampling_type.downsampling_type_selector}"' - #if str( $gatk_param_type.downsampling_type.downsampling_type_selector ) != "NONE": - -p '--${gatk_param_type.downsampling_type.downsample_to_type.downsample_to_type_selector} "${gatk_param_type.downsampling_type.downsample_to_type.downsample_to_value}"' - #end if - -p ' - --baq "${gatk_param_type.baq}" - --baqGapOpenPenalty "${gatk_param_type.baq_gap_open_penalty}" - ${gatk_param_type.use_original_qualities} - --defaultBaseQualities "${gatk_param_type.default_base_qualities}" - --validation_strictness "${gatk_param_type.validation_strictness}" - --interval_merging "${gatk_param_type.interval_merging}" - ${gatk_param_type.disable_experimental_low_memory_sharding} - ${gatk_param_type.non_deterministic_random_seed} - ' - #for $rg_black_list_count, $rg_black_list in enumerate( $gatk_param_type.read_group_black_list_repeat ): - #if $rg_black_list.read_group_black_list_type.read_group_black_list_type_selector == "file": - -d "--read_group_black_list" "${rg_black_list.read_group_black_list_type.read_group_black_list}" "txt" "input_read_group_black_list_${rg_black_list_count}" - #else - -p '--read_group_black_list "${rg_black_list.read_group_black_list_type.read_group_black_list}"' - #end if - #end for - #end if - #if str( $reference_source.reference_source_selector ) == "history": - -d "-R" "${reference_source.ref_file}" "${reference_source.ref_file.ext}" "gatk_input" - #end if - ##end standard gatk options + #include source=$standard_gatk_options# -p ' #if str( $annotation_group ) != "None": diff --git a/tools/gatk/variant_apply_recalibration.xml b/tools/gatk/variant_apply_recalibration.xml index f5e3a968f9e..731c37d8d3a 100644 --- a/tools/gatk/variant_apply_recalibration.xml +++ b/tools/gatk/variant_apply_recalibration.xml @@ -25,66 +25,7 @@ --out "${output_variants}" ' - ##start standard gatk options - #if $gatk_param_type.gatk_param_type_selector == "advanced": - #for $pedigree in $gatk_param_type.pedigree: - -p '--pedigree "${pedigree.pedigree_file}"' - #end for - #for $pedigree_string in $gatk_param_type.pedigree_string_repeat: - -p '--pedigreeString "${pedigree_string.pedigree_string}"' - #end for - -p '--pedigreeValidationType "${gatk_param_type.pedigree_validation_type}"' - #for $read_filter in $gatk_param_type.read_filter: - -p '--read_filter "${read_filter.read_filter_type.read_filter_type_selector}" - ###raise Exception( str( dir( $read_filter ) ) ) - #for $name, $param in $read_filter.read_filter_type.iteritems(): - #if $name not in [ "__current_case__", "read_filter_type_selector" ]: - #if hasattr( $param.input, 'truevalue' ): - ${param} - #else: - --${name} "${param}" - #end if - #end if - #end for - ' - #end for - #for $interval_count, $input_intervals in enumerate( $gatk_param_type.input_interval_repeat ): - -d "--intervals" "${input_intervals.input_intervals}" "${input_intervals.input_intervals.ext}" "input_intervals_${interval_count}" - #end for - - #for $interval_count, $input_intervals in enumerate( $gatk_param_type.input_exclude_interval_repeat ): - -d "--excludeIntervals" "${input_intervals.input_exclude_intervals}" "${input_intervals.input_exclude_intervals.ext}" "input_exlude_intervals_${interval_count}" - #end for - - -p '--interval_set_rule "${gatk_param_type.interval_set_rule}"' - - -p '--downsampling_type "${gatk_param_type.downsampling_type.downsampling_type_selector}"' - #if str( $gatk_param_type.downsampling_type.downsampling_type_selector ) != "NONE": - -p '--${gatk_param_type.downsampling_type.downsample_to_type.downsample_to_type_selector} "${gatk_param_type.downsampling_type.downsample_to_type.downsample_to_value}"' - #end if - -p ' - --baq "${gatk_param_type.baq}" - --baqGapOpenPenalty "${gatk_param_type.baq_gap_open_penalty}" - ${gatk_param_type.use_original_qualities} - --defaultBaseQualities "${gatk_param_type.default_base_qualities}" - --validation_strictness "${gatk_param_type.validation_strictness}" - --interval_merging "${gatk_param_type.interval_merging}" - ${gatk_param_type.disable_experimental_low_memory_sharding} - ${gatk_param_type.non_deterministic_random_seed} - ' - #for $rg_black_list_count, $rg_black_list in enumerate( $gatk_param_type.read_group_black_list_repeat ): - #if $rg_black_list.read_group_black_list_type.read_group_black_list_type_selector == "file": - -d "--read_group_black_list" "${rg_black_list.read_group_black_list_type.read_group_black_list}" "txt" "input_read_group_black_list_${rg_black_list_count}" - #else - -p '--read_group_black_list "${rg_black_list.read_group_black_list_type.read_group_black_list}"' - #end if - #end for - #end if - - #if str( $reference_source.reference_source_selector ) == "history": - -d "-R" "${reference_source.ref_file}" "${reference_source.ref_file.ext}" "gatk_input" - #end if - ##end standard gatk options + #include source=$standard_gatk_options# ##start analysis specific options -p ' diff --git a/tools/gatk/variant_combine.xml b/tools/gatk/variant_combine.xml index ce3bf6c0044..b3df4be5abb 100644 --- a/tools/gatk/variant_combine.xml +++ b/tools/gatk/variant_combine.xml @@ -31,66 +31,7 @@ --rod_priority_list "${ ','.join( $priority_order ) }" ' - ##start standard gatk options - #if $gatk_param_type.gatk_param_type_selector == "advanced": - #for $pedigree in $gatk_param_type.pedigree: - -p '--pedigree "${pedigree.pedigree_file}"' - #end for - #for $pedigree_string in $gatk_param_type.pedigree_string_repeat: - -p '--pedigreeString "${pedigree_string.pedigree_string}"' - #end for - -p '--pedigreeValidationType "${gatk_param_type.pedigree_validation_type}"' - #for $read_filter in $gatk_param_type.read_filter: - -p '--read_filter "${read_filter.read_filter_type.read_filter_type_selector}" - ###raise Exception( str( dir( $read_filter ) ) ) - #for $name, $param in $read_filter.read_filter_type.iteritems(): - #if $name not in [ "__current_case__", "read_filter_type_selector" ]: - #if hasattr( $param.input, 'truevalue' ): - ${param} - #else: - --${name} "${param}" - #end if - #end if - #end for - ' - #end for - #for $interval_count, $input_intervals in enumerate( $gatk_param_type.input_interval_repeat ): - -d "--intervals" "${input_intervals.input_intervals}" "${input_intervals.input_intervals.ext}" "input_intervals_${interval_count}" - #end for - - #for $interval_count, $input_intervals in enumerate( $gatk_param_type.input_exclude_interval_repeat ): - -d "--excludeIntervals" "${input_intervals.input_exclude_intervals}" "${input_intervals.input_exclude_intervals.ext}" "input_exlude_intervals_${interval_count}" - #end for - - -p '--interval_set_rule "${gatk_param_type.interval_set_rule}"' - - -p '--downsampling_type "${gatk_param_type.downsampling_type.downsampling_type_selector}"' - #if str( $gatk_param_type.downsampling_type.downsampling_type_selector ) != "NONE": - -p '--${gatk_param_type.downsampling_type.downsample_to_type.downsample_to_type_selector} "${gatk_param_type.downsampling_type.downsample_to_type.downsample_to_value}"' - #end if - -p ' - --baq "${gatk_param_type.baq}" - --baqGapOpenPenalty "${gatk_param_type.baq_gap_open_penalty}" - ${gatk_param_type.use_original_qualities} - --defaultBaseQualities "${gatk_param_type.default_base_qualities}" - --validation_strictness "${gatk_param_type.validation_strictness}" - --interval_merging "${gatk_param_type.interval_merging}" - ${gatk_param_type.disable_experimental_low_memory_sharding} - ${gatk_param_type.non_deterministic_random_seed} - ' - #for $rg_black_list_count, $rg_black_list in enumerate( $gatk_param_type.read_group_black_list_repeat ): - #if $rg_black_list.read_group_black_list_type.read_group_black_list_type_selector == "file": - -d "--read_group_black_list" "${rg_black_list.read_group_black_list_type.read_group_black_list}" "txt" "input_read_group_black_list_${rg_black_list_count}" - #else - -p '--read_group_black_list "${rg_black_list.read_group_black_list_type.read_group_black_list}"' - #end if - #end for - #end if - - #if $reference_source.reference_source_selector == "history": - -d "-R" "${reference_source.ref_file}" "${reference_source.ref_file.ext}" "gatk_input" - #end if - ##end standard gatk options + #include source=$standard_gatk_options# ##start analysis specific options diff --git a/tools/gatk/variant_eval.xml b/tools/gatk/variant_eval.xml index 1bedb5336e7..a1114fa02df 100644 --- a/tools/gatk/variant_eval.xml +++ b/tools/gatk/variant_eval.xml @@ -39,66 +39,7 @@ #end if #end if - ##start standard gatk options - #if $gatk_param_type.gatk_param_type_selector == "advanced": - #for $pedigree in $gatk_param_type.pedigree: - -p '--pedigree "${pedigree.pedigree_file}"' - #end for - #for $pedigree_string in $gatk_param_type.pedigree_string_repeat: - -p '--pedigreeString "${pedigree_string.pedigree_string}"' - #end for - -p '--pedigreeValidationType "${gatk_param_type.pedigree_validation_type}"' - #for $read_filter in $gatk_param_type.read_filter: - -p '--read_filter "${read_filter.read_filter_type.read_filter_type_selector}" - ###raise Exception( str( dir( $read_filter ) ) ) - #for $name, $param in $read_filter.read_filter_type.iteritems(): - #if $name not in [ "__current_case__", "read_filter_type_selector" ]: - #if hasattr( $param.input, 'truevalue' ): - ${param} - #else: - --${name} "${param}" - #end if - #end if - #end for - ' - #end for - #for $interval_count, $input_intervals in enumerate( $gatk_param_type.input_interval_repeat ): - -d "--intervals" "${input_intervals.input_intervals}" "${input_intervals.input_intervals.ext}" "input_intervals_${interval_count}" - #end for - - #for $interval_count, $input_intervals in enumerate( $gatk_param_type.input_exclude_interval_repeat ): - -d "--excludeIntervals" "${input_intervals.input_exclude_intervals}" "${input_intervals.input_exclude_intervals.ext}" "input_exlude_intervals_${interval_count}" - #end for - - -p '--interval_set_rule "${gatk_param_type.interval_set_rule}"' - - -p '--downsampling_type "${gatk_param_type.downsampling_type.downsampling_type_selector}"' - #if str( $gatk_param_type.downsampling_type.downsampling_type_selector ) != "NONE": - -p '--${gatk_param_type.downsampling_type.downsample_to_type.downsample_to_type_selector} "${gatk_param_type.downsampling_type.downsample_to_type.downsample_to_value}"' - #end if - -p ' - --baq "${gatk_param_type.baq}" - --baqGapOpenPenalty "${gatk_param_type.baq_gap_open_penalty}" - ${gatk_param_type.use_original_qualities} - --defaultBaseQualities "${gatk_param_type.default_base_qualities}" - --validation_strictness "${gatk_param_type.validation_strictness}" - --interval_merging "${gatk_param_type.interval_merging}" - ${gatk_param_type.disable_experimental_low_memory_sharding} - ${gatk_param_type.non_deterministic_random_seed} - ' - #for $rg_black_list_count, $rg_black_list in enumerate( $gatk_param_type.read_group_black_list_repeat ): - #if $rg_black_list.read_group_black_list_type.read_group_black_list_type_selector == "file": - -d "--read_group_black_list" "${rg_black_list.read_group_black_list_type.read_group_black_list}" "txt" "input_read_group_black_list_${rg_black_list_count}" - #else - -p '--read_group_black_list "${rg_black_list.read_group_black_list_type.read_group_black_list}"' - #end if - #end for - #end if - - #if $reference_source.reference_source_selector == "history": - -d "-R" "${reference_source.ref_file}" "${reference_source.ref_file.ext}" "gatk_input" - #end if - ##end standard gatk options + #include source=$standard_gatk_options# ##start analysis specific options diff --git a/tools/gatk/variant_filtration.xml b/tools/gatk/variant_filtration.xml index 942380d5f30..49750f1154f 100644 --- a/tools/gatk/variant_filtration.xml +++ b/tools/gatk/variant_filtration.xml @@ -35,66 +35,7 @@ ' #end if - ##start standard gatk options - #if $gatk_param_type.gatk_param_type_selector == "advanced": - #for $pedigree in $gatk_param_type.pedigree: - -p '--pedigree "${pedigree.pedigree_file}"' - #end for - #for $pedigree_string in $gatk_param_type.pedigree_string_repeat: - -p '--pedigreeString "${pedigree_string.pedigree_string}"' - #end for - -p '--pedigreeValidationType "${gatk_param_type.pedigree_validation_type}"' - #for $read_filter in $gatk_param_type.read_filter: - -p '--read_filter "${read_filter.read_filter_type.read_filter_type_selector}" - ###raise Exception( str( dir( $read_filter ) ) ) - #for $name, $param in $read_filter.read_filter_type.iteritems(): - #if $name not in [ "__current_case__", "read_filter_type_selector" ]: - #if hasattr( $param.input, 'truevalue' ): - ${param} - #else: - --${name} "${param}" - #end if - #end if - #end for - ' - #end for - #for $interval_count, $input_intervals in enumerate( $gatk_param_type.input_interval_repeat ): - -d "--intervals" "${input_intervals.input_intervals}" "${input_intervals.input_intervals.ext}" "input_intervals_${interval_count}" - #end for - - #for $interval_count, $input_intervals in enumerate( $gatk_param_type.input_exclude_interval_repeat ): - -d "--excludeIntervals" "${input_intervals.input_exclude_intervals}" "${input_intervals.input_exclude_intervals.ext}" "input_exlude_intervals_${interval_count}" - #end for - - -p '--interval_set_rule "${gatk_param_type.interval_set_rule}"' - - -p '--downsampling_type "${gatk_param_type.downsampling_type.downsampling_type_selector}"' - #if str( $gatk_param_type.downsampling_type.downsampling_type_selector ) != "NONE": - -p '--${gatk_param_type.downsampling_type.downsample_to_type.downsample_to_type_selector} "${gatk_param_type.downsampling_type.downsample_to_type.downsample_to_value}"' - #end if - -p ' - --baq "${gatk_param_type.baq}" - --baqGapOpenPenalty "${gatk_param_type.baq_gap_open_penalty}" - ${gatk_param_type.use_original_qualities} - --defaultBaseQualities "${gatk_param_type.default_base_qualities}" - --validation_strictness "${gatk_param_type.validation_strictness}" - --interval_merging "${gatk_param_type.interval_merging}" - ${gatk_param_type.disable_experimental_low_memory_sharding} - ${gatk_param_type.non_deterministic_random_seed} - ' - #for $rg_black_list_count, $rg_black_list in enumerate( $gatk_param_type.read_group_black_list_repeat ): - #if $rg_black_list.read_group_black_list_type.read_group_black_list_type_selector == "file": - -d "--read_group_black_list" "${rg_black_list.read_group_black_list_type.read_group_black_list}" "txt" "input_read_group_black_list_${rg_black_list_count}" - #else - -p '--read_group_black_list "${rg_black_list.read_group_black_list_type.read_group_black_list}"' - #end if - #end for - #end if - - #if str( $reference_source.reference_source_selector ) == "history": - -d "-R" "${reference_source.ref_file}" "${reference_source.ref_file.ext}" "gatk_input" - #end if - ##end standard gatk options + #include source=$standard_gatk_options# ##start analysis specific options #if $cluster_snp_type.cluster_snp_type_selector == "cluster_snp": diff --git a/tools/gatk/variant_recalibrator.xml b/tools/gatk/variant_recalibrator.xml index 39f4b55767e..769a48823db 100644 --- a/tools/gatk/variant_recalibrator.xml +++ b/tools/gatk/variant_recalibrator.xml @@ -43,66 +43,7 @@ #end if #end for - ##start standard gatk options - #if $gatk_param_type.gatk_param_type_selector == "advanced": - #for $pedigree in $gatk_param_type.pedigree: - -p '--pedigree "${pedigree.pedigree_file}"' - #end for - #for $pedigree_string in $gatk_param_type.pedigree_string_repeat: - -p '--pedigreeString "${pedigree_string.pedigree_string}"' - #end for - -p '--pedigreeValidationType "${gatk_param_type.pedigree_validation_type}"' - #for $read_filter in $gatk_param_type.read_filter: - -p '--read_filter "${read_filter.read_filter_type.read_filter_type_selector}" - ###raise Exception( str( dir( $read_filter ) ) ) - #for $name, $param in $read_filter.read_filter_type.iteritems(): - #if $name not in [ "__current_case__", "read_filter_type_selector" ]: - #if hasattr( $param.input, 'truevalue' ): - ${param} - #else: - --${name} "${param}" - #end if - #end if - #end for - ' - #end for - #for $interval_count, $input_intervals in enumerate( $gatk_param_type.input_interval_repeat ): - -d "--intervals" "${input_intervals.input_intervals}" "${input_intervals.input_intervals.ext}" "input_intervals_${interval_count}" - #end for - - #for $interval_count, $input_intervals in enumerate( $gatk_param_type.input_exclude_interval_repeat ): - -d "--excludeIntervals" "${input_intervals.input_exclude_intervals}" "${input_intervals.input_exclude_intervals.ext}" "input_exlude_intervals_${interval_count}" - #end for - - -p '--interval_set_rule "${gatk_param_type.interval_set_rule}"' - - -p '--downsampling_type "${gatk_param_type.downsampling_type.downsampling_type_selector}"' - #if str( $gatk_param_type.downsampling_type.downsampling_type_selector ) != "NONE": - -p '--${gatk_param_type.downsampling_type.downsample_to_type.downsample_to_type_selector} "${gatk_param_type.downsampling_type.downsample_to_type.downsample_to_value}"' - #end if - -p ' - --baq "${gatk_param_type.baq}" - --baqGapOpenPenalty "${gatk_param_type.baq_gap_open_penalty}" - ${gatk_param_type.use_original_qualities} - --defaultBaseQualities "${gatk_param_type.default_base_qualities}" - --validation_strictness "${gatk_param_type.validation_strictness}" - --interval_merging "${gatk_param_type.interval_merging}" - ${gatk_param_type.disable_experimental_low_memory_sharding} - ${gatk_param_type.non_deterministic_random_seed} - ' - #for $rg_black_list_count, $rg_black_list in enumerate( $gatk_param_type.read_group_black_list_repeat ): - #if $rg_black_list.read_group_black_list_type.read_group_black_list_type_selector == "file": - -d "--read_group_black_list" "${rg_black_list.read_group_black_list_type.read_group_black_list}" "txt" "input_read_group_black_list_${rg_black_list_count}" - #else - -p '--read_group_black_list "${rg_black_list.read_group_black_list_type.read_group_black_list}"' - #end if - #end for - #end if - - #if str( $reference_source.reference_source_selector ) == "history": - -d "-R" "${reference_source.ref_file}" "${reference_source.ref_file.ext}" "gatk_input" - #end if - ##end standard gatk options + #include source=$standard_gatk_options# ##start analysis specific options -p ' diff --git a/tools/gatk/variant_select.xml b/tools/gatk/variant_select.xml index f01a9dfc6dd..d4ba3b09fcd 100644 --- a/tools/gatk/variant_select.xml +++ b/tools/gatk/variant_select.xml @@ -95,66 +95,7 @@ #end if ##end tool specific options - ##start standard gatk options - #if $gatk_param_type.gatk_param_type_selector == "advanced": - #for $pedigree in $gatk_param_type.pedigree: - -p '--pedigree "${pedigree.pedigree_file}"' - #end for - #for $pedigree_string in $gatk_param_type.pedigree_string_repeat: - -p '--pedigreeString "${pedigree_string.pedigree_string}"' - #end for - -p '--pedigreeValidationType "${gatk_param_type.pedigree_validation_type}"' - #for $read_filter in $gatk_param_type.read_filter: - -p '--read_filter "${read_filter.read_filter_type.read_filter_type_selector}" - ###raise Exception( str( dir( $read_filter ) ) ) - #for $name, $param in $read_filter.read_filter_type.iteritems(): - #if $name not in [ "__current_case__", "read_filter_type_selector" ]: - #if hasattr( $param.input, 'truevalue' ): - ${param} - #else: - --${name} "${param}" - #end if - #end if - #end for - ' - #end for - #for $interval_count, $input_intervals in enumerate( $gatk_param_type.input_interval_repeat ): - -d "--intervals" "${input_intervals.input_intervals}" "${input_intervals.input_intervals.ext}" "input_intervals_${interval_count}" - #end for - - #for $interval_count, $input_intervals in enumerate( $gatk_param_type.input_exclude_interval_repeat ): - -d "--excludeIntervals" "${input_intervals.input_exclude_intervals}" "${input_intervals.input_exclude_intervals.ext}" "input_exlude_intervals_${interval_count}" - #end for - - -p '--interval_set_rule "${gatk_param_type.interval_set_rule}"' - - -p '--downsampling_type "${gatk_param_type.downsampling_type.downsampling_type_selector}"' - #if str( $gatk_param_type.downsampling_type.downsampling_type_selector ) != "NONE": - -p '--${gatk_param_type.downsampling_type.downsample_to_type.downsample_to_type_selector} "${gatk_param_type.downsampling_type.downsample_to_type.downsample_to_value}"' - #end if - -p ' - --baq "${gatk_param_type.baq}" - --baqGapOpenPenalty "${gatk_param_type.baq_gap_open_penalty}" - ${gatk_param_type.use_original_qualities} - --defaultBaseQualities "${gatk_param_type.default_base_qualities}" - --validation_strictness "${gatk_param_type.validation_strictness}" - --interval_merging "${gatk_param_type.interval_merging}" - ${gatk_param_type.disable_experimental_low_memory_sharding} - ${gatk_param_type.non_deterministic_random_seed} - ' - #for $rg_black_list_count, $rg_black_list in enumerate( $gatk_param_type.read_group_black_list_repeat ): - #if $rg_black_list.read_group_black_list_type.read_group_black_list_type_selector == "file": - -d "--read_group_black_list" "${rg_black_list.read_group_black_list_type.read_group_black_list}" "txt" "input_read_group_black_list_${rg_black_list_count}" - #else - -p '--read_group_black_list "${rg_black_list.read_group_black_list_type.read_group_black_list}"' - #end if - #end for - #end if - - #if str( $reference_source.reference_source_selector ) == "history": - -d "-R" "${reference_source.ref_file}" "${reference_source.ref_file.ext}" "gatk_input" - #end if - ##end standard gatk options + #include source=$standard_gatk_options# diff --git a/tools/gatk/variants_validate.xml b/tools/gatk/variants_validate.xml index 11f13e5f1ae..920ece9b8c8 100644 --- a/tools/gatk/variants_validate.xml +++ b/tools/gatk/variants_validate.xml @@ -28,67 +28,7 @@ -d "--dbsnp:${dbsnp_rod_bind_type.dbsnp_rod_name},%(file_type)s" "${dbsnp_rod_bind_type.dbsnp_input_rod}" "${dbsnp_rod_bind_type.dbsnp_input_rod.ext}" "input_dbsnp_${dbsnp_rod_bind_type.dbsnp_rod_name}" #end if - ##start standard gatk options - #if $gatk_param_type.gatk_param_type_selector == "advanced": - #for $pedigree in $gatk_param_type.pedigree: - -p '--pedigree "${pedigree.pedigree_file}"' - #end for - #for $pedigree_string in $gatk_param_type.pedigree_string_repeat: - -p '--pedigreeString "${pedigree_string.pedigree_string}"' - #end for - -p '--pedigreeValidationType "${gatk_param_type.pedigree_validation_type}"' - #for $read_filter in $gatk_param_type.read_filter: - -p '--read_filter "${read_filter.read_filter_type.read_filter_type_selector}" - ###raise Exception( str( dir( $read_filter ) ) ) - #for $name, $param in $read_filter.read_filter_type.iteritems(): - #if $name not in [ "__current_case__", "read_filter_type_selector" ]: - #if hasattr( $param.input, 'truevalue' ): - ${param} - #else: - --${name} "${param}" - #end if - #end if - #end for - ' - #end for - #for $interval_count, $input_intervals in enumerate( $gatk_param_type.input_interval_repeat ): - -d "--intervals" "${input_intervals.input_intervals}" "${input_intervals.input_intervals.ext}" "input_intervals_${interval_count}" - #end for - - #for $interval_count, $input_intervals in enumerate( $gatk_param_type.input_exclude_interval_repeat ): - -d "--excludeIntervals" "${input_intervals.input_exclude_intervals}" "${input_intervals.input_exclude_intervals.ext}" "input_exlude_intervals_${interval_count}" - #end for - - -p '--interval_set_rule "${gatk_param_type.interval_set_rule}"' - - -p '--downsampling_type "${gatk_param_type.downsampling_type.downsampling_type_selector}"' - #if str( $gatk_param_type.downsampling_type.downsampling_type_selector ) != "NONE": - -p '--${gatk_param_type.downsampling_type.downsample_to_type.downsample_to_type_selector} "${gatk_param_type.downsampling_type.downsample_to_type.downsample_to_value}"' - #end if - -p ' - --baq "${gatk_param_type.baq}" - --baqGapOpenPenalty "${gatk_param_type.baq_gap_open_penalty}" - ${gatk_param_type.use_original_qualities} - --defaultBaseQualities "${gatk_param_type.default_base_qualities}" - --validation_strictness "${gatk_param_type.validation_strictness}" - --interval_merging "${gatk_param_type.interval_merging}" - ${gatk_param_type.disable_experimental_low_memory_sharding} - ${gatk_param_type.non_deterministic_random_seed} - ' - #for $rg_black_list_count, $rg_black_list in enumerate( $gatk_param_type.read_group_black_list_repeat ): - #if $rg_black_list.read_group_black_list_type.read_group_black_list_type_selector == "file": - -d "--read_group_black_list" "${rg_black_list.read_group_black_list_type.read_group_black_list}" "txt" "input_read_group_black_list_${rg_black_list_count}" - #else - -p '--read_group_black_list "${rg_black_list.read_group_black_list_type.read_group_black_list}"' - #end if - #end for - #end if - - #if $reference_source.reference_source_selector == "history": - -d "-R" "${reference_source.ref_file}" "${reference_source.ref_file.ext}" "gatk_input" - #end if - ##end standard gatk options - + #include source=$standard_gatk_options# From d56ca2c58b9bbe3b32a1fcd6622334320500f0be Mon Sep 17 00:00:00 2001 From: John Chilton Date: Fri, 22 Mar 2013 00:41:08 -0500 Subject: [PATCH 027/523] Refactor big GATK parameter type conditional into shared macro for 15 GATK tools. --- tools/gatk/count_covariates.xml | 211 +------------------- tools/gatk/depth_of_coverage.xml | 211 +------------------- tools/gatk/gatk_macros.xml | 212 +++++++++++++++++++++ tools/gatk/indel_realigner.xml | 211 +------------------- tools/gatk/print_reads.xml | 211 +------------------- tools/gatk/realigner_target_creator.xml | 211 +------------------- tools/gatk/table_recalibration.xml | 211 +------------------- tools/gatk/unified_genotyper.xml | 211 +------------------- tools/gatk/variant_annotator.xml | 211 +------------------- tools/gatk/variant_apply_recalibration.xml | 211 +------------------- tools/gatk/variant_combine.xml | 211 +------------------- tools/gatk/variant_eval.xml | 211 +------------------- tools/gatk/variant_filtration.xml | 211 +------------------- tools/gatk/variant_recalibrator.xml | 211 +------------------- tools/gatk/variant_select.xml | 211 +------------------- tools/gatk/variants_validate.xml | 211 +------------------- 16 files changed, 227 insertions(+), 3150 deletions(-) diff --git a/tools/gatk/count_covariates.xml b/tools/gatk/count_covariates.xml index 8937a6caddb..dd03ab46ab5 100644 --- a/tools/gatk/count_covariates.xml +++ b/tools/gatk/count_covariates.xml @@ -153,216 +153,7 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + diff --git a/tools/gatk/depth_of_coverage.xml b/tools/gatk/depth_of_coverage.xml index 73acf44673b..3f0d80da5ea 100644 --- a/tools/gatk/depth_of_coverage.xml +++ b/tools/gatk/depth_of_coverage.xml @@ -173,216 +173,7 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + diff --git a/tools/gatk/gatk_macros.xml b/tools/gatk/gatk_macros.xml index da71ba6df74..831292e4297 100644 --- a/tools/gatk/gatk_macros.xml +++ b/tools/gatk/gatk_macros.xml @@ -61,4 +61,216 @@ #end if ##end standard gatk options + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/tools/gatk/indel_realigner.xml b/tools/gatk/indel_realigner.xml index d6dd66d5351..89521877bbf 100644 --- a/tools/gatk/indel_realigner.xml +++ b/tools/gatk/indel_realigner.xml @@ -114,216 +114,7 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + diff --git a/tools/gatk/print_reads.xml b/tools/gatk/print_reads.xml index f6474cc5a18..a8888c866e6 100644 --- a/tools/gatk/print_reads.xml +++ b/tools/gatk/print_reads.xml @@ -82,216 +82,7 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + diff --git a/tools/gatk/realigner_target_creator.xml b/tools/gatk/realigner_target_creator.xml index 9b61e655122..c9620246042 100644 --- a/tools/gatk/realigner_target_creator.xml +++ b/tools/gatk/realigner_target_creator.xml @@ -99,216 +99,7 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + diff --git a/tools/gatk/table_recalibration.xml b/tools/gatk/table_recalibration.xml index 5e26849c2b5..510e71bb444 100644 --- a/tools/gatk/table_recalibration.xml +++ b/tools/gatk/table_recalibration.xml @@ -92,216 +92,7 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + diff --git a/tools/gatk/unified_genotyper.xml b/tools/gatk/unified_genotyper.xml index 392417cc2f9..8ecf4adf80f 100644 --- a/tools/gatk/unified_genotyper.xml +++ b/tools/gatk/unified_genotyper.xml @@ -155,216 +155,7 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + diff --git a/tools/gatk/variant_annotator.xml b/tools/gatk/variant_annotator.xml index 079082adf6d..c570435781e 100644 --- a/tools/gatk/variant_annotator.xml +++ b/tools/gatk/variant_annotator.xml @@ -181,216 +181,7 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + diff --git a/tools/gatk/variant_apply_recalibration.xml b/tools/gatk/variant_apply_recalibration.xml index 731c37d8d3a..8cf5385a51f 100644 --- a/tools/gatk/variant_apply_recalibration.xml +++ b/tools/gatk/variant_apply_recalibration.xml @@ -70,216 +70,7 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + diff --git a/tools/gatk/variant_combine.xml b/tools/gatk/variant_combine.xml index b3df4be5abb..486f5b3efa0 100644 --- a/tools/gatk/variant_combine.xml +++ b/tools/gatk/variant_combine.xml @@ -90,216 +90,7 @@ + ------ + +**Citation** + +For the underlying tool, please cite `DePristo MA, Banks E, Poplin R, Garimella KV, Maguire JR, Hartl C, Philippakis AA, del Angel G, Rivas MA, Hanna M, McKenna A, Fennell TJ, Kernytsky AM, Sivachenko AY, Cibulskis K, Gabriel SB, Altshuler D, Daly MJ. A framework for variation discovery and genotyping using next-generation DNA sequencing data. Nat Genet. 2011 May;43(5):491-8. <http://www.ncbi.nlm.nih.gov/pubmed/21478889>`_ + +If you use this tool in Galaxy, please cite Blankenberg D, et al. *In preparation.* + + \ No newline at end of file diff --git a/tools/gatk/indel_realigner.xml b/tools/gatk/indel_realigner.xml index 89521877bbf..c094d51b03f 100644 --- a/tools/gatk/indel_realigner.xml +++ b/tools/gatk/indel_realigner.xml @@ -216,13 +216,6 @@ Go `here <http://www.broadinstitute.org/gsa/wiki/index.php/Input_files_for_th maxReadsForRealignment max reads allowed at an interval for realignment; if this value is exceeded, realignment is not attempted and the reads are passed to the output file(s) as-is noOriginalAlignmentTags Don't output the original cigar or alignment start tags for each realigned read in the output bam. ------- - -**Citation** - -For the underlying tool, please cite `DePristo MA, Banks E, Poplin R, Garimella KV, Maguire JR, Hartl C, Philippakis AA, del Angel G, Rivas MA, Hanna M, McKenna A, Fennell TJ, Kernytsky AM, Sivachenko AY, Cibulskis K, Gabriel SB, Altshuler D, Daly MJ. A framework for variation discovery and genotyping using next-generation DNA sequencing data. Nat Genet. 2011 May;43(5):491-8. <http://www.ncbi.nlm.nih.gov/pubmed/21478889>`_ - -If you use this tool in Galaxy, please cite Blankenberg D, et al. *In preparation.* - +@CITATION_SECTION@ diff --git a/tools/gatk/print_reads.xml b/tools/gatk/print_reads.xml index a8888c866e6..0ad5b1b6dff 100644 --- a/tools/gatk/print_reads.xml +++ b/tools/gatk/print_reads.xml @@ -148,13 +148,6 @@ Go `here <http://www.broadinstitute.org/gsa/wiki/index.php/Input_files_for_th sample_file Set[File] [] File containing a list of samples (one per line). Can be specified multiple times sample_name Set[String] [] Sample name to be included in the analysis. Can be specified multiple times. ------- - -**Citation** - -For the underlying tool, please cite `DePristo MA, Banks E, Poplin R, Garimella KV, Maguire JR, Hartl C, Philippakis AA, del Angel G, Rivas MA, Hanna M, McKenna A, Fennell TJ, Kernytsky AM, Sivachenko AY, Cibulskis K, Gabriel SB, Altshuler D, Daly MJ. A framework for variation discovery and genotyping using next-generation DNA sequencing data. Nat Genet. 2011 May;43(5):491-8. <http://www.ncbi.nlm.nih.gov/pubmed/21478889>`_ - -If you use this tool in Galaxy, please cite Blankenberg D, et al. *In preparation.* - +@CITATION_SECTION@ diff --git a/tools/gatk/realigner_target_creator.xml b/tools/gatk/realigner_target_creator.xml index c9620246042..4e97174e825 100644 --- a/tools/gatk/realigner_target_creator.xml +++ b/tools/gatk/realigner_target_creator.xml @@ -172,13 +172,6 @@ Go `here <http://www.broadinstitute.org/gsa/wiki/index.php/Input_files_for_th minReadsAtLocus minimum reads at a locus to enable using the entropy calculation maxIntervalSize maximum interval size ------- - -**Citation** - -For the underlying tool, please cite `DePristo MA, Banks E, Poplin R, Garimella KV, Maguire JR, Hartl C, Philippakis AA, del Angel G, Rivas MA, Hanna M, McKenna A, Fennell TJ, Kernytsky AM, Sivachenko AY, Cibulskis K, Gabriel SB, Altshuler D, Daly MJ. A framework for variation discovery and genotyping using next-generation DNA sequencing data. Nat Genet. 2011 May;43(5):491-8. <http://www.ncbi.nlm.nih.gov/pubmed/21478889>`_ - -If you use this tool in Galaxy, please cite Blankenberg D, et al. *In preparation.* - +@CITATION_SECTION@ diff --git a/tools/gatk/table_recalibration.xml b/tools/gatk/table_recalibration.xml index 510e71bb444..2be1cc0af67 100644 --- a/tools/gatk/table_recalibration.xml +++ b/tools/gatk/table_recalibration.xml @@ -239,13 +239,6 @@ Go `here <http://www.broadinstitute.org/gsa/wiki/index.php/Input_files_for_th max_quality_score The integer value at which to cap the quality scores, default=50 doNotWriteOriginalQuals If true, we will not write the original quality (OQ) tag for each read ------- - -**Citation** - -For the underlying tool, please cite `DePristo MA, Banks E, Poplin R, Garimella KV, Maguire JR, Hartl C, Philippakis AA, del Angel G, Rivas MA, Hanna M, McKenna A, Fennell TJ, Kernytsky AM, Sivachenko AY, Cibulskis K, Gabriel SB, Altshuler D, Daly MJ. A framework for variation discovery and genotyping using next-generation DNA sequencing data. Nat Genet. 2011 May;43(5):491-8. <http://www.ncbi.nlm.nih.gov/pubmed/21478889>`_ - -If you use this tool in Galaxy, please cite Blankenberg D, et al. *In preparation.* - +@CITATION_SECTION@ diff --git a/tools/gatk/unified_genotyper.xml b/tools/gatk/unified_genotyper.xml index 8ecf4adf80f..9cd5ad4c659 100644 --- a/tools/gatk/unified_genotyper.xml +++ b/tools/gatk/unified_genotyper.xml @@ -334,13 +334,6 @@ Go `here <http://www.broadinstitute.org/gsa/wiki/index.php/Input_files_for_th annotation One or more specific annotations to apply to variant calls group One or more classes/groups of annotations to apply to variant calls ------- - -**Citation** - -For the underlying tool, please cite `DePristo MA, Banks E, Poplin R, Garimella KV, Maguire JR, Hartl C, Philippakis AA, del Angel G, Rivas MA, Hanna M, McKenna A, Fennell TJ, Kernytsky AM, Sivachenko AY, Cibulskis K, Gabriel SB, Altshuler D, Daly MJ. A framework for variation discovery and genotyping using next-generation DNA sequencing data. Nat Genet. 2011 May;43(5):491-8. <http://www.ncbi.nlm.nih.gov/pubmed/21478889>`_ - -If you use this tool in Galaxy, please cite Blankenberg D, et al. *In preparation.* - +@CITATION_SECTION@ diff --git a/tools/gatk/variant_annotator.xml b/tools/gatk/variant_annotator.xml index c570435781e..b9c2166f876 100644 --- a/tools/gatk/variant_annotator.xml +++ b/tools/gatk/variant_annotator.xml @@ -264,13 +264,6 @@ Go `here <http://www.broadinstitute.org/gsa/wiki/index.php/Input_files_for_th expression One or more specific expressions to apply to variant calls; see documentation for more details useAllAnnotations Use all possible annotations (not for the faint of heart) ------- - -**Citation** - -For the underlying tool, please cite `DePristo MA, Banks E, Poplin R, Garimella KV, Maguire JR, Hartl C, Philippakis AA, del Angel G, Rivas MA, Hanna M, McKenna A, Fennell TJ, Kernytsky AM, Sivachenko AY, Cibulskis K, Gabriel SB, Altshuler D, Daly MJ. A framework for variation discovery and genotyping using next-generation DNA sequencing data. Nat Genet. 2011 May;43(5):491-8. <http://www.ncbi.nlm.nih.gov/pubmed/21478889>`_ - -If you use this tool in Galaxy, please cite Blankenberg D, et al. *In preparation.* - +@CITATION_SECTION@ diff --git a/tools/gatk/variant_apply_recalibration.xml b/tools/gatk/variant_apply_recalibration.xml index 8cf5385a51f..043c450950e 100644 --- a/tools/gatk/variant_apply_recalibration.xml +++ b/tools/gatk/variant_apply_recalibration.xml @@ -137,13 +137,6 @@ Go `here <http://www.broadinstitute.org/gsa/wiki/index.php/Input_files_for_th ignore_filter If specified the optimizer will use variants even if the specified filter name is marked in the input VCF file mode Recalibration mode to employ: 1.) SNP for recalibrating only SNPs (emitting indels untouched in the output VCF); 2.) INDEL for indels; and 3.) BOTH for recalibrating both SNPs and indels simultaneously. (SNP|INDEL|BOTH) ------- - -**Citation** - -For the underlying tool, please cite `DePristo MA, Banks E, Poplin R, Garimella KV, Maguire JR, Hartl C, Philippakis AA, del Angel G, Rivas MA, Hanna M, McKenna A, Fennell TJ, Kernytsky AM, Sivachenko AY, Cibulskis K, Gabriel SB, Altshuler D, Daly MJ. A framework for variation discovery and genotyping using next-generation DNA sequencing data. Nat Genet. 2011 May;43(5):491-8. <http://www.ncbi.nlm.nih.gov/pubmed/21478889>`_ - -If you use this tool in Galaxy, please cite Blankenberg D, et al. *In preparation.* - +@CITATION_SECTION@ diff --git a/tools/gatk/variant_combine.xml b/tools/gatk/variant_combine.xml index 486f5b3efa0..1d1b1f102d6 100644 --- a/tools/gatk/variant_combine.xml +++ b/tools/gatk/variant_combine.xml @@ -178,13 +178,6 @@ Go `here <http://www.broadinstitute.org/gsa/wiki/index.php/Input_files_for_th assumeIdenticalSamples If true, assume input VCFs have identical sample sets and disjoint calls so that one can simply perform a merge sort to combine the VCFs into one, drastically reducing the runtime. minimumN Combine variants and output site only if variant is present in at least N input files. ------- - -**Citation** - -For the underlying tool, please cite `DePristo MA, Banks E, Poplin R, Garimella KV, Maguire JR, Hartl C, Philippakis AA, del Angel G, Rivas MA, Hanna M, McKenna A, Fennell TJ, Kernytsky AM, Sivachenko AY, Cibulskis K, Gabriel SB, Altshuler D, Daly MJ. A framework for variation discovery and genotyping using next-generation DNA sequencing data. Nat Genet. 2011 May;43(5):491-8. <http://www.ncbi.nlm.nih.gov/pubmed/21478889>`_ - -If you use this tool in Galaxy, please cite Blankenberg D, et al. *In preparation.* - +@CITATION_SECTION@ diff --git a/tools/gatk/variant_eval.xml b/tools/gatk/variant_eval.xml index 2837044b55c..62cc50795ac 100644 --- a/tools/gatk/variant_eval.xml +++ b/tools/gatk/variant_eval.xml @@ -295,13 +295,6 @@ Go `here <http://www.broadinstitute.org/gsa/wiki/index.php/Input_files_for_th mendelianViolationQualThreshold Minimum genotype QUAL score for each trio member required to accept a site as a violation ancestralAlignments Fasta file with ancestral alleles ------- - -**Citation** - -For the underlying tool, please cite `DePristo MA, Banks E, Poplin R, Garimella KV, Maguire JR, Hartl C, Philippakis AA, del Angel G, Rivas MA, Hanna M, McKenna A, Fennell TJ, Kernytsky AM, Sivachenko AY, Cibulskis K, Gabriel SB, Altshuler D, Daly MJ. A framework for variation discovery and genotyping using next-generation DNA sequencing data. Nat Genet. 2011 May;43(5):491-8. <http://www.ncbi.nlm.nih.gov/pubmed/21478889>`_ - -If you use this tool in Galaxy, please cite Blankenberg D, et al. *In preparation.* - +@CITATION_SECTION@ diff --git a/tools/gatk/variant_filtration.xml b/tools/gatk/variant_filtration.xml index 2c7118a3335..6c0d4412837 100644 --- a/tools/gatk/variant_filtration.xml +++ b/tools/gatk/variant_filtration.xml @@ -180,13 +180,6 @@ Go `here <http://www.broadinstitute.org/gsa/wiki/index.php/Input_files_for_th maskName The text to put in the FILTER field if a 'mask' rod is provided and overlaps with a variant call; [default:'Mask'] missingValuesInExpressionsShouldEvaluateAsFailing When evaluating the JEXL expressions, should missing values be considered failing the expression (by default they are considered passing)? ------- - -**Citation** - -For the underlying tool, please cite `DePristo MA, Banks E, Poplin R, Garimella KV, Maguire JR, Hartl C, Philippakis AA, del Angel G, Rivas MA, Hanna M, McKenna A, Fennell TJ, Kernytsky AM, Sivachenko AY, Cibulskis K, Gabriel SB, Altshuler D, Daly MJ. A framework for variation discovery and genotyping using next-generation DNA sequencing data. Nat Genet. 2011 May;43(5):491-8. <http://www.ncbi.nlm.nih.gov/pubmed/21478889>`_ - -If you use this tool in Galaxy, please cite Blankenberg D, et al. *In preparation.* - +@CITATION_SECTION@ diff --git a/tools/gatk/variant_recalibrator.xml b/tools/gatk/variant_recalibrator.xml index 11dd833a064..917f3d835ed 100644 --- a/tools/gatk/variant_recalibrator.xml +++ b/tools/gatk/variant_recalibrator.xml @@ -438,13 +438,6 @@ Go `here <http://www.broadinstitute.org/gsa/wiki/index.php/Input_files_for_th path_to_resources Path to resources folder holding the Sting R scripts. ts_filter_level The truth sensitivity level at which to start filtering, used here to indicate filtered variants in plots ------- - -**Citation** - -For the underlying tool, please cite `DePristo MA, Banks E, Poplin R, Garimella KV, Maguire JR, Hartl C, Philippakis AA, del Angel G, Rivas MA, Hanna M, McKenna A, Fennell TJ, Kernytsky AM, Sivachenko AY, Cibulskis K, Gabriel SB, Altshuler D, Daly MJ. A framework for variation discovery and genotyping using next-generation DNA sequencing data. Nat Genet. 2011 May;43(5):491-8. <http://www.ncbi.nlm.nih.gov/pubmed/21478889>`_ - -If you use this tool in Galaxy, please cite Blankenberg D, et al. *In preparation.* - +@CITATION_SECTION@ diff --git a/tools/gatk/variant_select.xml b/tools/gatk/variant_select.xml index f2e265267f7..34407b1f68f 100644 --- a/tools/gatk/variant_select.xml +++ b/tools/gatk/variant_select.xml @@ -297,13 +297,6 @@ Go `here <http://www.broadinstitute.org/gsa/wiki/index.php/Input_files_for_th select_random_number int 0 Selects a number of variants at random from the variant track selectTypeToInclude List[Type] [] Select only a certain type of variants from the input file. Valid types are INDEL, SNP, MIXED, MNP, SYMBOLIC, NO_VARIATION. Can be specified multiple times ------- - -**Citation** - -For the underlying tool, please cite `DePristo MA, Banks E, Poplin R, Garimella KV, Maguire JR, Hartl C, Philippakis AA, del Angel G, Rivas MA, Hanna M, McKenna A, Fennell TJ, Kernytsky AM, Sivachenko AY, Cibulskis K, Gabriel SB, Altshuler D, Daly MJ. A framework for variation discovery and genotyping using next-generation DNA sequencing data. Nat Genet. 2011 May;43(5):491-8. <http://www.ncbi.nlm.nih.gov/pubmed/21478889>`_ - -If you use this tool in Galaxy, please cite Blankenberg D, et al. *In preparation.* - +@CITATION_SECTION@ diff --git a/tools/gatk/variants_validate.xml b/tools/gatk/variants_validate.xml index b58c977b7ec..eedca6bece6 100644 --- a/tools/gatk/variants_validate.xml +++ b/tools/gatk/variants_validate.xml @@ -120,13 +120,6 @@ Go `here <http://www.broadinstitute.org/gsa/wiki/index.php/Input_files_for_th doNotValidateFilteredRecords should we skip validation on filtered records? warnOnErrors should we just emit warnings on errors instead of terminating the run? ------- - -**Citation** - -For the underlying tool, please cite `DePristo MA, Banks E, Poplin R, Garimella KV, Maguire JR, Hartl C, Philippakis AA, del Angel G, Rivas MA, Hanna M, McKenna A, Fennell TJ, Kernytsky AM, Sivachenko AY, Cibulskis K, Gabriel SB, Altshuler D, Daly MJ. A framework for variation discovery and genotyping using next-generation DNA sequencing data. Nat Genet. 2011 May;43(5):491-8. <http://www.ncbi.nlm.nih.gov/pubmed/21478889>`_ - -If you use this tool in Galaxy, please cite Blankenberg D, et al. *In preparation.* - +@CITATION_SECTION@ From 5d6f8c835b779408c2d3c85c1671cd25d39ca4be Mon Sep 17 00:00:00 2001 From: John Chilton Date: Fri, 22 Mar 2013 00:41:08 -0500 Subject: [PATCH 031/523] Covert more duplicated GATK wrapper code to macros. --- tools/gatk/count_covariates.xml | 18 +++--------------- tools/gatk/depth_of_coverage.xml | 18 +++--------------- tools/gatk/gatk_macros.xml | 20 ++++++++++++++++++++ tools/gatk/indel_realigner.xml | 18 +++--------------- tools/gatk/print_reads.xml | 5 +---- tools/gatk/realigner_target_creator.xml | 18 +++--------------- tools/gatk/table_recalibration.xml | 18 +++--------------- tools/gatk/unified_genotyper.xml | 18 +++--------------- tools/gatk/variant_annotator.xml | 5 +---- tools/gatk/variant_apply_recalibration.xml | 5 +---- tools/gatk/variant_combine.xml | 18 +++--------------- tools/gatk/variant_eval.xml | 18 +++--------------- tools/gatk/variant_filtration.xml | 5 +---- tools/gatk/variant_recalibrator.xml | 18 +++--------------- tools/gatk/variant_select.xml | 19 +++---------------- tools/gatk/variants_validate.xml | 5 +---- 16 files changed, 55 insertions(+), 171 deletions(-) diff --git a/tools/gatk/count_covariates.xml b/tools/gatk/count_covariates.xml index 59a42d850fe..c955ce36531 100644 --- a/tools/gatk/count_covariates.xml +++ b/tools/gatk/count_covariates.xml @@ -83,10 +83,7 @@ - - - - + @@ -155,15 +152,7 @@ - - - - - - - - - + @@ -227,8 +216,7 @@ - - + diff --git a/tools/gatk/depth_of_coverage.xml b/tools/gatk/depth_of_coverage.xml index 7055ed40ff1..20b1800d6d7 100644 --- a/tools/gatk/depth_of_coverage.xml +++ b/tools/gatk/depth_of_coverage.xml @@ -129,10 +129,7 @@ - - - - + @@ -175,15 +172,7 @@ - - - - - - - - - + @@ -199,8 +188,7 @@ - - + diff --git a/tools/gatk/gatk_macros.xml b/tools/gatk/gatk_macros.xml index 7fe2393002a..7bd2bc07010 100644 --- a/tools/gatk/gatk_macros.xml +++ b/tools/gatk/gatk_macros.xml @@ -273,6 +273,26 @@ + + + + + + + + + + + + + + + + + + + + ------ **Citation** diff --git a/tools/gatk/indel_realigner.xml b/tools/gatk/indel_realigner.xml index c094d51b03f..88bdb878305 100644 --- a/tools/gatk/indel_realigner.xml +++ b/tools/gatk/indel_realigner.xml @@ -62,10 +62,7 @@ - - - - + @@ -116,15 +113,7 @@ - - - - - - - - - + @@ -139,8 +128,7 @@ - - + diff --git a/tools/gatk/print_reads.xml b/tools/gatk/print_reads.xml index 0ad5b1b6dff..68667b1434b 100644 --- a/tools/gatk/print_reads.xml +++ b/tools/gatk/print_reads.xml @@ -45,10 +45,7 @@ - - - - + diff --git a/tools/gatk/realigner_target_creator.xml b/tools/gatk/realigner_target_creator.xml index 4e97174e825..853ab8f24cb 100644 --- a/tools/gatk/realigner_target_creator.xml +++ b/tools/gatk/realigner_target_creator.xml @@ -49,10 +49,7 @@ - - - - + @@ -101,21 +98,12 @@ - - - - - - - - - + - - + diff --git a/tools/gatk/table_recalibration.xml b/tools/gatk/table_recalibration.xml index 2be1cc0af67..caf2480cee4 100644 --- a/tools/gatk/table_recalibration.xml +++ b/tools/gatk/table_recalibration.xml @@ -66,10 +66,7 @@ - - - - + @@ -95,15 +92,7 @@ - - - - - - - - - + @@ -172,8 +161,7 @@ - - + diff --git a/tools/gatk/unified_genotyper.xml b/tools/gatk/unified_genotyper.xml index 9cd5ad4c659..39d095cca97 100644 --- a/tools/gatk/unified_genotyper.xml +++ b/tools/gatk/unified_genotyper.xml @@ -94,10 +94,7 @@ - - - - + @@ -157,15 +154,7 @@ - - - - - - - - - + @@ -240,8 +229,7 @@ - - + diff --git a/tools/gatk/variant_annotator.xml b/tools/gatk/variant_annotator.xml index b9c2166f876..cb41296ac0d 100644 --- a/tools/gatk/variant_annotator.xml +++ b/tools/gatk/variant_annotator.xml @@ -90,10 +90,7 @@ - - - - + diff --git a/tools/gatk/variant_apply_recalibration.xml b/tools/gatk/variant_apply_recalibration.xml index 043c450950e..0fb4d82e1a6 100644 --- a/tools/gatk/variant_apply_recalibration.xml +++ b/tools/gatk/variant_apply_recalibration.xml @@ -43,10 +43,7 @@ - - - - + diff --git a/tools/gatk/variant_combine.xml b/tools/gatk/variant_combine.xml index 1d1b1f102d6..a18e5023c47 100644 --- a/tools/gatk/variant_combine.xml +++ b/tools/gatk/variant_combine.xml @@ -54,10 +54,7 @@ - - - - + @@ -93,15 +90,7 @@ - - - - - - - - - + - + diff --git a/tools/gatk/variant_eval.xml b/tools/gatk/variant_eval.xml index 62cc50795ac..5055eed3fca 100644 --- a/tools/gatk/variant_eval.xml +++ b/tools/gatk/variant_eval.xml @@ -102,10 +102,7 @@ - - - - + @@ -149,15 +146,7 @@ - - - - - - - - - + @@ -224,8 +213,7 @@ - - + diff --git a/tools/gatk/variant_filtration.xml b/tools/gatk/variant_filtration.xml index 6c0d4412837..ae63e762145 100644 --- a/tools/gatk/variant_filtration.xml +++ b/tools/gatk/variant_filtration.xml @@ -48,10 +48,7 @@ - - - - + diff --git a/tools/gatk/variant_recalibrator.xml b/tools/gatk/variant_recalibrator.xml index 917f3d835ed..7aa3c94b293 100644 --- a/tools/gatk/variant_recalibrator.xml +++ b/tools/gatk/variant_recalibrator.xml @@ -95,10 +95,7 @@ - - - - + @@ -327,15 +324,7 @@ - - - - - - - - - + @@ -373,8 +362,7 @@ - - + diff --git a/tools/gatk/variant_select.xml b/tools/gatk/variant_select.xml index 34407b1f68f..973469f503c 100644 --- a/tools/gatk/variant_select.xml +++ b/tools/gatk/variant_select.xml @@ -101,10 +101,7 @@ - - - - + @@ -147,15 +144,7 @@ - - - - - - - - - + @@ -219,9 +208,7 @@ - - - + diff --git a/tools/gatk/variants_validate.xml b/tools/gatk/variants_validate.xml index eedca6bece6..89520274099 100644 --- a/tools/gatk/variants_validate.xml +++ b/tools/gatk/variants_validate.xml @@ -33,10 +33,7 @@ - - - - + From 0f5ed549f33bf146aaa2f5a82a2bf7cc3b1dd970 Mon Sep 17 00:00:00 2001 From: John Chilton Date: Fri, 22 Mar 2013 00:41:08 -0500 Subject: [PATCH 032/523] Remove unused code in lib/galaxy/tools/loader.py --- lib/galaxy/tools/loader.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/lib/galaxy/tools/loader.py b/lib/galaxy/tools/loader.py index 418c91485e8..b735fe591ed 100644 --- a/lib/galaxy/tools/loader.py +++ b/lib/galaxy/tools/loader.py @@ -168,10 +168,6 @@ def _load_macro_file(path, tool_dir): return _load_macros(root, tool_dir) -def _load_macro_def(macro): - return list(macro.getchildren()) - - def _xml_set_children(element, new_children): for old_child in element.getchildren(): element.remove(old_child) From 3394af83eb9b742685fdcfefab9cae851d2a83b0 Mon Sep 17 00:00:00 2001 From: Bjoern Gruening Date: Fri, 22 Mar 2013 17:05:51 +0100 Subject: [PATCH 033/523] Change file-merging to use copy.copyfileobj() rather than cat and mv. --- lib/galaxy/datatypes/data.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/lib/galaxy/datatypes/data.py b/lib/galaxy/datatypes/data.py index 3f9c4d74b09..4fa308acfb5 100644 --- a/lib/galaxy/datatypes/data.py +++ b/lib/galaxy/datatypes/data.py @@ -544,21 +544,21 @@ class Data( object ): return False - def merge( split_files, output_file): """ - TODO: Do we need to merge gzip files using gzjoin? cat seems to work, - but might be brittle. Need to revisit this. + Merge files with copy.copyfileobj() will not hit the + max argument limitation of cat. gz and bz2 files are also working. """ if not split_files: raise ValueError('Asked to merge zero files as %s' % output_file) elif len(split_files) == 1: - cmd = 'mv -f %s %s' % ( split_files[0], output_file ) + shutil.copyfileobj(open(split_files[0], 'rb'), open(output_file, 'wb')) else: - cmd = 'cat %s > %s' % ( ' '.join(split_files), output_file ) - result = os.system(cmd) - if result != 0: - raise Exception('Result %s from %s' % (result, cmd)) + fdst = open(output_file, 'wb') + for fsrc in split_files: + shutil.copyfileobj(open(fsrc, 'rb'), fdst) + fdst.close() + merge = staticmethod(merge) def get_visualizations( self, dataset ): From 7510b91c13a66dbb540aa940b91acce2e71a65e4 Mon Sep 17 00:00:00 2001 From: John Chilton Date: Sun, 24 Mar 2013 11:36:16 -0500 Subject: [PATCH 034/523] For task parallelism if no shared_inputs attribute is defined just assume all variables that are not being split are being shared. I cannot think of a good reason not to do this. --- lib/galaxy/jobs/splitters/multi.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/galaxy/jobs/splitters/multi.py b/lib/galaxy/jobs/splitters/multi.py index 8cb8dc9a79a..4949fef3acd 100644 --- a/lib/galaxy/jobs/splitters/multi.py +++ b/lib/galaxy/jobs/splitters/multi.py @@ -19,8 +19,10 @@ def do_split (job_wrapper): split_inputs = [x.strip() for x in split_inputs.split(",")] shared_inputs=parallel_settings.get("shared_inputs") + auto_shared_inputs = False if shared_inputs is None: shared_inputs = [] + auto_shared_inputs = True else: shared_inputs = [x.strip() for x in shared_inputs.split(",")] illegal_inputs = [x for x in shared_inputs if x in split_inputs] @@ -45,6 +47,8 @@ def do_split (job_wrapper): type_to_input_map.setdefault(input.dataset.datatype, []).append(input.name) elif input.name in shared_inputs: pass # pass original file name + elif auto_shared_inputs: + shared_inputs.append(input.name) else: log_error = "The input '%s' does not define a method for implementing parallelism" % str(input.name) log.exception(log_error) From 6b822b6d21f79285d6356155b75e9a77cc21d5b8 Mon Sep 17 00:00:00 2001 From: Kyle Ellrott Date: Mon, 25 Mar 2013 21:46:26 -0700 Subject: [PATCH 035/523] Adding 'deleted' field to Library select --- lib/galaxy/model/search.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/galaxy/model/search.py b/lib/galaxy/model/search.py index 8b72a570e14..db289e3c879 100644 --- a/lib/galaxy/model/search.py +++ b/lib/galaxy/model/search.py @@ -171,7 +171,8 @@ class LibraryView(ViewQueryBaseClass): VIEW_NAME = "library" FIELDS = { 'name' : ViewField('name', sqlalchemy_field=Library.name ), - 'id' : ViewField('id', sqlalchemy_field=Library.id, id_decode=True) + 'id' : ViewField('id', sqlalchemy_field=Library.id, id_decode=True), + 'deleted' : ViewField('deleted', sqlalchemy_field=Library.deleted) } def search(self, trans): From f06eaa91e1a4fabdbc433cfb139bc8ba7328b3a9 Mon Sep 17 00:00:00 2001 From: John Chilton Date: Tue, 26 Mar 2013 10:30:46 -0500 Subject: [PATCH 036/523] Fix typo related to lwr in job_conf.xml.sample_advanced. --- job_conf.xml.sample_advanced | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/job_conf.xml.sample_advanced b/job_conf.xml.sample_advanced index 6cb16404cba..bcc62b587c4 100644 --- a/job_conf.xml.sample_advanced +++ b/job_conf.xml.sample_advanced @@ -7,7 +7,7 @@ - + From 542dd66f96427a02540ceb36944f42e15857c648 Mon Sep 17 00:00:00 2001 From: John Chilton Date: Tue, 26 Mar 2013 11:16:30 -0500 Subject: [PATCH 037/523] Fix returning destination id from dynamic job runners, JobMapper was using self.app.job_config but it didn't have access to app, now JobWrapper is passing job_config into JobMapper which in turn is storing it. --- lib/galaxy/jobs/__init__.py | 2 +- lib/galaxy/jobs/mapper.py | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/galaxy/jobs/__init__.py b/lib/galaxy/jobs/__init__.py index eb2d7cfd2c7..95b698e94e2 100644 --- a/lib/galaxy/jobs/__init__.py +++ b/lib/galaxy/jobs/__init__.py @@ -580,7 +580,7 @@ class JobWrapper( object ): self.tool_provided_job_metadata = None # Wrapper holding the info required to restore and clean up from files used for setting metadata externally self.external_output_metadata = metadata.JobExternalOutputMetadataWrapper( job ) - self.job_runner_mapper = JobRunnerMapper( self, queue.dispatcher.url_to_destination ) + self.job_runner_mapper = JobRunnerMapper( self, queue.dispatcher.url_to_destination, self.app.job_config ) self.params = None if job.params: self.params = from_json_string( job.params ) diff --git a/lib/galaxy/jobs/mapper.py b/lib/galaxy/jobs/mapper.py index 511dd7853a0..cc170e3dc3f 100644 --- a/lib/galaxy/jobs/mapper.py +++ b/lib/galaxy/jobs/mapper.py @@ -21,9 +21,10 @@ class JobRunnerMapper( object ): (in the form of job_wrappers) to job runner url strings. """ - def __init__( self, job_wrapper, url_to_destination ): + def __init__( self, job_wrapper, url_to_destination, job_config ): self.job_wrapper = job_wrapper self.url_to_destination = url_to_destination + self.job_config = job_config self.rule_modules = self.__get_rule_modules( ) def __get_rule_modules( self ): @@ -135,7 +136,7 @@ class JobRunnerMapper( object ): if '://' in rval: return self.__convert_url_to_destination(rval) else: - return self.app.job_config.get_destination(rval) + return self.job_config.get_destination(rval) elif isinstance(rval, galaxy.jobs.JobDestination): # If the function generated a JobDestination, we'll use that # destination directly. However, for advanced job limiting, a From 5260cd5f261839bd5b97a6b46325da901b9d22bb Mon Sep 17 00:00:00 2001 From: John Chilton Date: Tue, 26 Mar 2013 11:18:36 -0500 Subject: [PATCH 038/523] Slight optimization in JobMapper, no need to prefetch rules modules and hit the disk for every job mapper if 99.9% of them are never going to use rules. --- lib/galaxy/jobs/mapper.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/galaxy/jobs/mapper.py b/lib/galaxy/jobs/mapper.py index cc170e3dc3f..08e540d2d28 100644 --- a/lib/galaxy/jobs/mapper.py +++ b/lib/galaxy/jobs/mapper.py @@ -25,7 +25,6 @@ class JobRunnerMapper( object ): self.job_wrapper = job_wrapper self.url_to_destination = url_to_destination self.job_config = job_config - self.rule_modules = self.__get_rule_modules( ) def __get_rule_modules( self ): unsorted_module_names = self.__get_rule_module_names( ) @@ -119,7 +118,7 @@ class JobRunnerMapper( object ): def __last_rule_module_with_function( self, function_name ): # self.rule_modules is sorted in reverse order, so find first # wiht function - for rule_module in self.rule_modules: + for rule_module in self.__get_rule_modules( ): if hasattr( rule_module, function_name ): return rule_module return None From 2694d2ff8f5c4664511b36afc6d3c54393124242 Mon Sep 17 00:00:00 2001 From: John Chilton Date: Tue, 26 Mar 2013 11:29:23 -0500 Subject: [PATCH 039/523] Cleanup/reworking of logic in JobMapper for handling multiple possible output types coming from dynamic rules. This is a little more pythonic in that it is making fewer assumptions about types. This is also slightly less code, reduces the number of return statements, etc.... Also removed the the #TODO: Test extensively, this has now been done. --- lib/galaxy/jobs/mapper.py | 27 +++++++-------------------- 1 file changed, 7 insertions(+), 20 deletions(-) diff --git a/lib/galaxy/jobs/mapper.py b/lib/galaxy/jobs/mapper.py index 08e540d2d28..abfb801d1bb 100644 --- a/lib/galaxy/jobs/mapper.py +++ b/lib/galaxy/jobs/mapper.py @@ -128,27 +128,14 @@ class JobRunnerMapper( object ): if expand_type == "python": expand_function_name = self.__determine_expand_function_name( destination ) expand_function = self.__get_expand_function( expand_function_name ) - rval = self.__invoke_expand_function( expand_function ) - # TODO: test me extensively - if isinstance(rval, basestring): - # If the function returned a string, check if it's a URL, convert if necessary - if '://' in rval: - return self.__convert_url_to_destination(rval) + job_destination = self.__invoke_expand_function( expand_function ) + if not isinstance(job_destination, galaxy.jobs.JobDestination): + job_destination_rep = str(job_destination) # Should be either id or url + if '://' in job_destination_rep: + job_destination = self.__convert_url_to_destination(job_destination_rep) else: - return self.job_config.get_destination(rval) - elif isinstance(rval, galaxy.jobs.JobDestination): - # If the function generated a JobDestination, we'll use that - # destination directly. However, for advanced job limiting, a - # function may want to set the JobDestination's 'tags' - # attribute so that limiting can be done on a destination tag. - #id_or_tag = rval.get('id') - #if rval.get('tags', None): - # # functions that are generating destinations should only define one tag - # id_or_tag = rval.get('tags')[0] - #return id_or_tag, rval - return rval - else: - raise Exception( 'Dynamic function returned a value that could not be understood: %s' % rval ) + job_destination = self.job_config.get_destination(job_destination_rep) + return job_destination elif expand_type is None: raise Exception( 'Dynamic function type not specified (hint: add python to your )' ) else: From b9a1b561c4a124b11fffdf74d26ab1454070f525 Mon Sep 17 00:00:00 2001 From: John Chilton Date: Tue, 26 Mar 2013 13:14:50 -0500 Subject: [PATCH 040/523] Dynamic job runner will take id of the tool as the python function if one is not specified. This extends that logic to handle newer tools (i.e. tool shed tools) that may have multiple ids. The most specific id with a corresponding function name will be used. --- lib/galaxy/jobs/mapper.py | 9 +++++++-- lib/galaxy/tools/__init__.py | 1 + 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/lib/galaxy/jobs/mapper.py b/lib/galaxy/jobs/mapper.py index abfb801d1bb..afe4c2f2009 100644 --- a/lib/galaxy/jobs/mapper.py +++ b/lib/galaxy/jobs/mapper.py @@ -103,8 +103,13 @@ class JobRunnerMapper( object ): return dest def __determine_expand_function_name( self, destination ): - # default look for function with same name as tool, unless one specified - expand_function_name = destination.params.get('function', self.job_wrapper.tool.id) + # default look for function with name matching an id of tool, unless one specified + expand_function_name = destination.params.get('function', None) + if not expand_function_name: + for tool_id in self.job_wrapper.tool.all_ids: + if self.__last_rule_module_with_function( tool_id ): + expand_function_name = tool_id + break return expand_function_name def __get_expand_function( self, expand_function_name ): diff --git a/lib/galaxy/tools/__init__.py b/lib/galaxy/tools/__init__.py index 12e44d9e9a5..a4e62cf828b 100755 --- a/lib/galaxy/tools/__init__.py +++ b/lib/galaxy/tools/__init__.py @@ -1207,6 +1207,7 @@ class Tool( object ): if self.old_id != self.id: # Handle toolshed guids self_ids = [ self.id.lower(), self.id.lower().rsplit('/',1)[0], self.old_id.lower() ] + self.all_ids = self_ids # In the toolshed context, there is no job config. if 'job_config' in dir(self.app): self.job_tool_configurations = self.app.job_config.get_job_tool_configurations(self_ids) From 97de64723a0114db01c47cc265688c61a23682bd Mon Sep 17 00:00:00 2001 From: Kyle Ellrott Date: Tue, 26 Mar 2013 11:46:27 -0700 Subject: [PATCH 041/523] Fixing bug related to StoredWorkflow get_api_value --- lib/galaxy/model/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/galaxy/model/__init__.py b/lib/galaxy/model/__init__.py index cc0c0ed158e..b89322568f8 100644 --- a/lib/galaxy/model/__init__.py +++ b/lib/galaxy/model/__init__.py @@ -2142,8 +2142,8 @@ class StoredWorkflow( object, APIItem): new_swta.user = target_user self.tags.append(new_swta) - def get_api_value( self, view='collection' ): - rval = APIItem.get_api_value(self, view=view ) + def get_api_value( self, view='collection', value_mapper = None ): + rval = APIItem.get_api_value(self, view=view, value_mapper = value_mapper) tags_str_list = [] for tag in self.tags: tag_str = tag.user_tname From ad61464b9170e1e347917d364f4e1a6d97f44e59 Mon Sep 17 00:00:00 2001 From: John Chilton Date: Tue, 26 Mar 2013 15:24:25 -0500 Subject: [PATCH 042/523] Specify `python` as default dynamic job runner type - simplifies configuration and documentation and other types cheetah, XML, etc... do not seem to be on the roadmap. --- lib/galaxy/jobs/mapper.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/galaxy/jobs/mapper.py b/lib/galaxy/jobs/mapper.py index afe4c2f2009..885fd6b21bf 100644 --- a/lib/galaxy/jobs/mapper.py +++ b/lib/galaxy/jobs/mapper.py @@ -129,7 +129,7 @@ class JobRunnerMapper( object ): return None def __handle_dynamic_job_destination( self, destination ): - expand_type = destination.params.get('type', None) + expand_type = destination.params.get('type', "python") if expand_type == "python": expand_function_name = self.__determine_expand_function_name( destination ) expand_function = self.__get_expand_function( expand_function_name ) @@ -141,8 +141,6 @@ class JobRunnerMapper( object ): else: job_destination = self.job_config.get_destination(job_destination_rep) return job_destination - elif expand_type is None: - raise Exception( 'Dynamic function type not specified (hint: add python to your )' ) else: raise Exception( "Unhandled dynamic job runner type specified - %s" % expand_type ) From 9293457479384ef15e2f29f379b49605cae4c544 Mon Sep 17 00:00:00 2001 From: Galaxy Date: Wed, 27 Mar 2013 12:29:49 +1300 Subject: [PATCH 043/523] hg flow init, add .hgflow file --- .hgflow | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 .hgflow diff --git a/.hgflow b/.hgflow new file mode 100644 index 00000000000..030d8041563 --- /dev/null +++ b/.hgflow @@ -0,0 +1,8 @@ +[Basic] +develop = next-stable +feature = feature/ +version_tag = +publish = default +release = release/ +hotfix = hotfix/ + From 6aaac4a08cc7c8eec37f065f5dc27becd70033e0 Mon Sep 17 00:00:00 2001 From: John Chilton Date: Wed, 27 Mar 2013 15:19:57 -0500 Subject: [PATCH 044/523] Bug fix for recent changes to the LWR server's handling of config files. --- lib/galaxy/jobs/runners/lwr_client/stager.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/galaxy/jobs/runners/lwr_client/stager.py b/lib/galaxy/jobs/runners/lwr_client/stager.py index dfe27aa05ff..51a93e8d303 100644 --- a/lib/galaxy/jobs/runners/lwr_client/stager.py +++ b/lib/galaxy/jobs/runners/lwr_client/stager.py @@ -155,6 +155,9 @@ class FileStager(object): self.new_working_directory = job_config['working_directory'] self.new_outputs_directory = job_config['outputs_directory'] + # Default configs_directory to match remote working_directory to mimic + # behavior of older LWR servers. + self.new_configs_drectory = job_config.get('configs_directory', self.new_working_directory) self.remote_path_separator = job_config['path_separator'] # If remote LWR server assigned job id, use that otherwise # just use local job_id assigned. @@ -218,7 +221,7 @@ class FileStager(object): def __initialize_config_file_renames(self): for config_file in self.config_files: - self.file_renames[config_file] = r'%s%s%s' % (self.new_working_directory, + self.file_renames[config_file] = r'%s%s%s' % (self.new_configs_drectory, self.remote_path_separator, os.path.basename(config_file)) From 06d018087567aab8af64a4d58e824da94bd4fd98 Mon Sep 17 00:00:00 2001 From: John Chilton Date: Wed, 27 Mar 2013 15:27:38 -0500 Subject: [PATCH 045/523] Small documentation update to reflect changes in 14a5aec. --- job_conf.xml.sample_advanced | 1 - 1 file changed, 1 deletion(-) diff --git a/job_conf.xml.sample_advanced b/job_conf.xml.sample_advanced index bcc62b587c4..7beff833373 100644 --- a/job_conf.xml.sample_advanced +++ b/job_conf.xml.sample_advanced @@ -40,7 +40,6 @@ - python foo From e42c7a657d69b7e986904f5b88182ffb551be3af Mon Sep 17 00:00:00 2001 From: Galaxy Date: Thu, 28 Mar 2013 09:43:08 +1300 Subject: [PATCH 046/523] remove .hgflow --- .hgflow | 8 -------- 1 file changed, 8 deletions(-) delete mode 100644 .hgflow diff --git a/.hgflow b/.hgflow deleted file mode 100644 index 030d8041563..00000000000 --- a/.hgflow +++ /dev/null @@ -1,8 +0,0 @@ -[Basic] -develop = next-stable -feature = feature/ -version_tag = -publish = default -release = release/ -hotfix = hotfix/ - From ef218b31ed705bd8e990d6862c2d06d62c818173 Mon Sep 17 00:00:00 2001 From: Galaxy Date: Thu, 28 Mar 2013 09:44:19 +1300 Subject: [PATCH 047/523] hg flow, add branch `feature/transact-with-ts`. From 25df1b355cf7d6c1e9002f77285c4927dac0bb9d Mon Sep 17 00:00:00 2001 From: Dannon Baker Date: Thu, 28 Mar 2013 18:44:10 -0400 Subject: [PATCH 048/523] Add default controller for browse_library --> now 'library'. --- lib/galaxy/webapps/galaxy/controllers/library_common.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/galaxy/webapps/galaxy/controllers/library_common.py b/lib/galaxy/webapps/galaxy/controllers/library_common.py index 69568067301..b2ee502fb78 100644 --- a/lib/galaxy/webapps/galaxy/controllers/library_common.py +++ b/lib/galaxy/webapps/galaxy/controllers/library_common.py @@ -94,7 +94,7 @@ class LibraryCommon( BaseUIController, UsesFormDefinitionsMixin ): return rval @web.expose - def browse_library( self, trans, cntrller, **kwd ): + def browse_library( self, trans, cntrller='library', **kwd ): params = util.Params( kwd ) message = util.restore_text( params.get( 'message', '' ) ) status = params.get( 'status', 'done' ) From 1b43c9895efce4ebebc9ad1acbbfb1f45d737e54 Mon Sep 17 00:00:00 2001 From: Jeremy Goecks Date: Thu, 28 Mar 2013 18:44:41 -0400 Subject: [PATCH 049/523] Fix import *s in genome data providers. --- lib/galaxy/visualization/data_providers/genome.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/galaxy/visualization/data_providers/genome.py b/lib/galaxy/visualization/data_providers/genome.py index 4494e1a8e3b..ba0d87c3007 100644 --- a/lib/galaxy/visualization/data_providers/genome.py +++ b/lib/galaxy/visualization/data_providers/genome.py @@ -11,12 +11,12 @@ if sys.version_info[:2] == (2, 4): pkg_resources.require( "pysam" ) pkg_resources.require( "numpy" ) import numpy -from galaxy.datatypes.util.gff_util import * +from galaxy.datatypes.util.gff_util import GFFReaderWrapper, GFFInterval, GFFFeature, convert_gff_coords_to_bed from galaxy.util.json import from_json_string from bx.interval_index_file import Indexes from bx.bbi.bigwig_file import BigWigFile from galaxy.util.lrucache import LRUCache -from galaxy.visualization.tracks.summary import * +from galaxy.visualization.tracks.summary import summary_tree_from_file from galaxy.visualization.data_providers.basic import BaseDataProvider import galaxy_utils.sequence.vcf from galaxy.datatypes.tabular import Tabular, Vcf From d9a65483847c016f015849c3ba027906f5f47e83 Mon Sep 17 00:00:00 2001 From: Jeremy Goecks Date: Thu, 28 Mar 2013 18:58:30 -0400 Subject: [PATCH 050/523] More import cleanup. --- lib/galaxy/visualization/data_providers/genome.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/galaxy/visualization/data_providers/genome.py b/lib/galaxy/visualization/data_providers/genome.py index ba0d87c3007..dde36eecb70 100644 --- a/lib/galaxy/visualization/data_providers/genome.py +++ b/lib/galaxy/visualization/data_providers/genome.py @@ -18,9 +18,7 @@ from bx.bbi.bigwig_file import BigWigFile from galaxy.util.lrucache import LRUCache from galaxy.visualization.tracks.summary import summary_tree_from_file from galaxy.visualization.data_providers.basic import BaseDataProvider -import galaxy_utils.sequence.vcf -from galaxy.datatypes.tabular import Tabular, Vcf -from galaxy.datatypes.interval import Interval, Bed, Gff, Gtf, ENCODEPeak, ChromatinInteractions +from galaxy.datatypes.interval import Bed, Gff, Gtf from pysam import csamtools, ctabix From 73b83b1de17b34df07fa23296a299a157dc20943 Mon Sep 17 00:00:00 2001 From: Greg Von Kuster Date: Fri, 29 Mar 2013 11:22:10 -0400 Subject: [PATCH 051/523] Minor fixes to the questionable install_repository_revisions method in Galaxy's tool_shed_repositories API controller. --- .../galaxy/api/tool_shed_repositories.py | 21 ++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/lib/galaxy/webapps/galaxy/api/tool_shed_repositories.py b/lib/galaxy/webapps/galaxy/api/tool_shed_repositories.py index 9541045c5cf..7a648cfc009 100644 --- a/lib/galaxy/webapps/galaxy/api/tool_shed_repositories.py +++ b/lib/galaxy/webapps/galaxy/api/tool_shed_repositories.py @@ -271,11 +271,13 @@ class ToolShedRepositoriesController( BaseAPIController ): It's questionable whether this method is needed as the above method for installing a single repository can probably cover all desired scenarios. We'll keep this one around just in case... + :param key: the current Galaxy admin user's API key + + The following parameters are included in the payload. :param tool_shed_urls: the base URLs of the Tool Sheds from which to install a specified Repository :param names: the names of the Repositories to be installed :param owners: the owners of the Repositories to be installed :param changset_revisions: the changset_revisions of each RepositoryMetadata object associated with each Repository to be installed - :param key: the current Galaxy admin user's API key :param new_tool_panel_section_label: optional label of a new section to be added to the Galaxy tool panel in which to load tools contained in the Repository. Either this parameter must be an empty string or the tool_panel_section_id parameter must be an empty string, as both cannot be used. @@ -283,6 +285,16 @@ class ToolShedRepositoriesController( BaseAPIController ): If not set, tools will be loaded outside of any sections in the tool panel. Either this parameter must be an empty string or the tool_panel_section_id parameter must be an empty string, as both cannot be used. + :param install_repository_dependencies (optional): Set to True if you want to install repository dependencies defined for the specified + repository being installed. The default setting is False. + :param install_tool_dependencies (optional): Set to True if you want to install tool dependencies defined for the specified repository being + installed. The default setting is False. + :param shed_tool_conf (optional): The shed-related tool panel configuration file configured in the "tool_config_file" setting in the Galaxy config file + (e.g., universe_wsgi.ini). At least one shed-related tool panel config file is required to be configured. Setting + this parameter to a specific file enables you to choose where the specified repository will be installed because + the tool_path attribute of the from the specified file is used as the installation location + (e.g., ). If this parameter is not set, a shed-related tool panel configuration + file will be selected automatically. """ if not suc.have_shed_tool_conf_for_install( trans ): # This Galaxy instance is not configured with a shed-related tool panel configuration file. @@ -312,7 +324,7 @@ class ToolShedRepositoriesController( BaseAPIController ): # forcing all repositories to use the same settings. install_repository_dependencies = payload.get( 'install_repository_dependencies', False ) install_tool_dependencies = payload.get( 'install_tool_dependencies', False ) - new_tool_panel_section = payload.get( 'new_tool_panel_section_label', '' ) + new_tool_panel_section_label = payload.get( 'new_tool_panel_section_label', '' ) shed_tool_conf = payload.get( 'shed_tool_conf', None ) tool_path = payload.get( 'tool_path', None ) tool_panel_section_id = payload.get( 'tool_panel_section_id', '' ) @@ -323,12 +335,11 @@ class ToolShedRepositoriesController( BaseAPIController ): current_payload[ 'name' ] = names[ index ] current_payload[ 'owner' ] = owners[ index ] current_payload[ 'changeset_revision' ] = changeset_revisions[ index ] + current_payload[ 'new_tool_panel_section_label' ] = new_tool_panel_section_label + current_payload[ 'tool_panel_section_id' ] = tool_panel_section_id current_payload[ 'install_repository_dependencies' ] = install_repository_dependencies current_payload[ 'install_tool_dependencies' ] = install_tool_dependencies - current_payload[ 'new_tool_panel_section' ] = new_tool_panel_section current_payload[ 'shed_tool_conf' ] = shed_tool_conf - current_payload[ 'tool_path' ] = tool_path - current_payload[ 'tool_panel_section_id' ] = tool_panel_section_id installed_tool_shed_repositories = self.install_repository_revision( trans, **current_payload ) if isinstance( installed_tool_shed_repositories, dict ): # We encountered an error. From 348994314c482bfb3fd9bcf41196e49ab6117ecc Mon Sep 17 00:00:00 2001 From: John Chilton Date: Sun, 31 Mar 2013 12:39:20 -0500 Subject: [PATCH 052/523] Tool Macros: Added test case for doubly recursive macros. Implement bug fix and refactor to simplify things and make problem/fix more obvious. --- lib/galaxy/tools/loader.py | 69 +++++++++++++++++++++++++++++--------- 1 file changed, 53 insertions(+), 16 deletions(-) diff --git a/lib/galaxy/tools/loader.py b/lib/galaxy/tools/loader.py index b735fe591ed..a3d245920ce 100644 --- a/lib/galaxy/tools/loader.py +++ b/lib/galaxy/tools/loader.py @@ -86,26 +86,38 @@ def _expand_macros(elements, macros): return for element in elements: - # HACK for elementtree, newer implementations (etree/lxml) won't - # require this parent_map data structure but elementtree does not - # track parents or recongnize .find('..'). - parent_map = dict((c, p) for p in element.getiterator() for c in p) - for expand_el in element.findall('.//expand'): - macro_name = expand_el.get('macro') - macro_def = deepcopy(macros[macro_name]) # deepcopy needed? + while True: + expand_el = element.find('.//expand') + if expand_el is None: + break + _expand_macro(element, expand_el, macros) - yield_els = [yield_el for macro_def_el in macro_def for yield_el in macro_def_el.findall('.//yield')] - expand_el_children = expand_el.getchildren() - macro_def_parent_map = \ - dict((c, p) for macro_def_el in macro_def for p in macro_def_el.getiterator() for c in p) +def _expand_macro(element, expand_el, macros): + macro_name = expand_el.get('macro') + macro_def = deepcopy(macros[macro_name]) - for yield_el in yield_els: - _xml_replace(yield_el, expand_el_children, macro_def_parent_map) + _expand_yield_statements(macro_def, expand_el) - # Recursively expand contained macros. - _expand_macros(macro_def, macros) - _xml_replace(expand_el, macro_def, parent_map) + # Recursively expand contained macros. + _expand_macros(macro_def, macros) + + # HACK for elementtree, newer implementations (etree/lxml) won't + # require this parent_map data structure but elementtree does not + # track parents or recongnize .find('..'). + parent_map = dict((c, p) for p in element.getiterator() for c in p) + _xml_replace(expand_el, macro_def, parent_map) + + +def _expand_yield_statements(macro_def, expand_el): + yield_els = [yield_el for macro_def_el in macro_def for yield_el in macro_def_el.findall('.//yield')] + + expand_el_children = expand_el.getchildren() + macro_def_parent_map = \ + dict((c, p) for macro_def_el in macro_def for p in macro_def_el.getiterator() for c in p) + + for yield_el in yield_els: + _xml_replace(yield_el, expand_el_children, macro_def_parent_map) def _load_macros(macros_el, tool_dir): @@ -301,6 +313,31 @@ def test_loader(): xml = tool_dir.load() assert xml.find("inputs").findall("input")[1].get("name") == "second_input" + # Test recursive macro applications. + with TestToolDirectory() as tool_dir: + tool_dir.write(''' + + + + + + + + + + + + + + + + + + +''') + xml = tool_dir.load() + assert xml.find("inputs").findall("input")[1].get("name") == "second_input" + # Test is shortcut for macro type="xml" with TestToolDirectory() as tool_dir: tool_dir.write(''' From 7549fea5d26c0a21c819fd95d9dff01ba243851c Mon Sep 17 00:00:00 2001 From: Carl Eberhard Date: Mon, 1 Apr 2013 11:10:08 -0400 Subject: [PATCH 053/523] history panel: load display applications via ajax, update on force_history_refresh instead of reloading frame --- lib/galaxy/web/base/controller.py | 6 +- .../webapps/galaxy/api/history_contents.py | 6 +- .../webapps/galaxy/controllers/history.py | 67 ++++++++++++++++++- lib/galaxy/webapps/galaxy/controllers/root.py | 5 +- static/scripts/mvc/dataset/hda-base.js | 61 ++++++++++++----- static/scripts/mvc/dataset/hda-edit.js | 4 +- static/scripts/mvc/dataset/hda-model.js | 23 ++++++- static/scripts/mvc/history/history-model.js | 43 ++++++++++++ static/scripts/mvc/history/history-panel.js | 8 --- .../galaxy/root/alternate_history.mako | 3 + 10 files changed, 188 insertions(+), 38 deletions(-) diff --git a/lib/galaxy/web/base/controller.py b/lib/galaxy/web/base/controller.py index 0e3883e2734..84f1e04737d 100644 --- a/lib/galaxy/web/base/controller.py +++ b/lib/galaxy/web/base/controller.py @@ -364,8 +364,8 @@ class UsesHistoryDatasetAssociationMixin: if meta_files: hda_dict[ 'meta_files' ] = meta_files - hda_dict[ 'display_types' ] = self.get_old_display_applications( trans, hda ) - hda_dict[ 'display_apps' ] = self.get_display_apps( trans, hda ) + #hda_dict[ 'display_types' ] = self.get_old_display_applications( trans, hda ) + #hda_dict[ 'display_apps' ] = self.get_display_apps( trans, hda ) hda_dict[ 'visualizations' ] = hda.get_visualizations() # ---- return here if deleted @@ -899,8 +899,6 @@ class UsesHistoryMixin( SharableItemSecurityMixin ): hda_dicts = [] for hda_tuple in query.all(): hda_dict = dict( zip( column_keys, hda_tuple ) ) - #if hda_dict[ 'job_state' ] not in [ None, 'ok' ]: - # print hda_dict[ 'hid' ], hda_dict[ 'name' ], hda_dict[ 'job_state' ] hda_dict[ 'history_id' ] = history.id trans.security.encode_dict_ids( hda_dict ) hda_dicts.append( hda_dict ) diff --git a/lib/galaxy/webapps/galaxy/api/history_contents.py b/lib/galaxy/webapps/galaxy/api/history_contents.py index d6be5510355..cce39d6a9e6 100644 --- a/lib/galaxy/webapps/galaxy/api/history_contents.py +++ b/lib/galaxy/webapps/galaxy/api/history_contents.py @@ -55,7 +55,11 @@ class HistoryContentsController( BaseAPIController, UsesHistoryDatasetAssociatio if encoded_hda_id in ids: #TODO: share code with show try: - rval.append( self.get_hda_dict( trans, hda ) ) + hda_dict = self.get_hda_dict( trans, hda ) + hda_dict[ 'display_types' ] = self.get_old_display_applications( trans, hda ) + hda_dict[ 'display_apps' ] = self.get_display_apps( trans, hda ) + #rval.append( self.get_hda_dict( trans, hda ) ) + rval.append( hda_dict ) except Exception, exc: # don't fail entire list if hda err's, record and move on diff --git a/lib/galaxy/webapps/galaxy/controllers/history.py b/lib/galaxy/webapps/galaxy/controllers/history.py index f1c9b294100..e728dbcefba 100644 --- a/lib/galaxy/webapps/galaxy/controllers/history.py +++ b/lib/galaxy/webapps/galaxy/controllers/history.py @@ -24,6 +24,7 @@ class NameColumn( grids.TextColumn ): return history.get_display_name() class HistoryListGrid( grids.Grid ): + # Custom column types class DatasetsByStateColumn( grids.GridColumn, UsesHistoryMixin ): def get_value( self, trans, grid, history ): @@ -56,6 +57,7 @@ class HistoryListGrid( grids.Grid ): elif history.deleted: return "deleted" return "" + def sort( self, trans, query, ascending, column_name=None ): if ascending: query = query.order_by( self.model_class.table.c.purged.asc(), self.model_class.table.c.update_time.desc() ) @@ -63,7 +65,6 @@ class HistoryListGrid( grids.Grid ): query = query.order_by( self.model_class.table.c.purged.desc(), self.model_class.table.c.update_time.desc() ) return query - # Grid definition title = "Saved Histories" model_class = model.History @@ -105,12 +106,15 @@ class HistoryListGrid( grids.Grid ): preserve_state = False use_async = True use_paging = True + def get_current_item( self, trans, **kwargs ): return trans.get_history() + def apply_query_filter( self, trans, query, **kwargs ): return query.filter_by( user=trans.user, importing=False ) class SharedHistoryListGrid( grids.Grid ): + # Custom column types class DatasetsByStateColumn( grids.GridColumn ): def get_value( self, trans, grid, history ): @@ -122,9 +126,11 @@ class SharedHistoryListGrid( grids.Grid ): else: rval.append( '' ) return rval + class SharedByColumn( grids.GridColumn ): def get_value( self, trans, grid, history ): return history.user.email + # Grid definition title = "Histories shared with you by others" model_class = model.History @@ -143,8 +149,10 @@ class SharedHistoryListGrid( grids.Grid ): grids.GridOperation( "Unshare" ) ] standard_filters = [] + def build_initial_query( self, trans, **kwargs ): return trans.sa_session.query( self.model_class ).join( 'users_shared_with' ) + def apply_query_filter( self, trans, query, **kwargs ): return query.filter( model.HistoryUserShareAssociation.user == trans.user ) @@ -172,17 +180,21 @@ class HistoryAllPublishedGrid( grids.Grid ): key="free-text-search", visible=False, filterable="standard" ) ) operations = [] + def build_initial_query( self, trans, **kwargs ): # Join so that searching history.user makes sense. return trans.sa_session.query( self.model_class ).join( model.User.table ) + def apply_query_filter( self, trans, query, **kwargs ): # A public history is published, has a slug, and is not deleted. return query.filter( self.model_class.published == True ).filter( self.model_class.slug != None ).filter( self.model_class.deleted == False ) -class HistoryController( BaseUIController, SharableMixin, UsesAnnotations, UsesItemRatings, UsesHistoryMixin ): +class HistoryController( BaseUIController, SharableMixin, UsesAnnotations, UsesItemRatings, + UsesHistoryMixin, UsesHistoryDatasetAssociationMixin ): @web.expose def index( self, trans ): return "" + @web.expose def list_as_xml( self, trans ): """XML history list for functional tests""" @@ -282,6 +294,7 @@ class HistoryController( BaseUIController, SharableMixin, UsesAnnotations, UsesI trans.sa_session.flush() # Render the list view return self.stored_list_grid( trans, status=status, message=message, **kwargs ) + def _list_delete( self, trans, histories, purge=False ): """Delete histories""" n_deleted = 0 @@ -331,6 +344,7 @@ class HistoryController( BaseUIController, SharableMixin, UsesAnnotations, UsesI message_parts.append( "Your active history was deleted, a new empty history is now active. " ) status = INFO return ( status, " ".join( message_parts ) ) + def _list_undelete( self, trans, histories ): """Undelete histories""" n_undeleted = 0 @@ -359,6 +373,7 @@ class HistoryController( BaseUIController, SharableMixin, UsesAnnotations, UsesI message_parts.append( "%d histories have already been purged and cannot be undeleted." % n_already_purged ) status = WARNING return status, "".join( message_parts ) + def _list_switch( self, trans, histories ): """Switch to a new different history""" new_history = histories[0] @@ -1062,6 +1077,7 @@ class HistoryController( BaseUIController, SharableMixin, UsesAnnotations, UsesI elif history not in histories_for_sharing[ send_to_user ]: histories_for_sharing[ send_to_user ].append( history ) return self._share_histories( trans, user, send_to_err, histories=histories_for_sharing ) + def _get_histories_and_users( self, trans, user, id, email ): if not id: # Default to the current history @@ -1087,6 +1103,7 @@ class HistoryController( BaseUIController, SharableMixin, UsesAnnotations, UsesI else: send_to_err += "%s is not a valid Galaxy user. " % email_address return histories, send_to_users, send_to_err + def _populate( self, trans, histories_for_sharing, other, send_to_err ): # This method will populate the histories_for_sharing dictionary with the users and # histories in other, eliminating histories that have already been shared with the @@ -1110,6 +1127,7 @@ class HistoryController( BaseUIController, SharableMixin, UsesAnnotations, UsesI elif history not in histories_for_sharing[ send_to_user ]: histories_for_sharing[ send_to_user ].append( history ) return histories_for_sharing, send_to_err + def _populate_restricted( self, trans, user, histories, send_to_users, action, send_to_err, unique=False ): # The user may be attempting to share histories whose datasets cannot all be accessed by other users. # If this is the case, the user sharing the histories can: @@ -1196,6 +1214,7 @@ class HistoryController( BaseUIController, SharableMixin, UsesAnnotations, UsesI else: cannot_change[ send_to_user ][ history ].append( hda ) return can_change, cannot_change, no_change_needed, unique_no_change_needed, send_to_err + def _share_histories( self, trans, user, send_to_err, histories={} ): # histories looks like: { userA: [ historyX, historyY ], userB: [ historyY ] } msg = "" @@ -1314,3 +1333,47 @@ class HistoryController( BaseUIController, SharableMixin, UsesAnnotations, UsesI def get_item( self, trans, id ): return self.get_history( trans, id ) + + @web.json + def get_display_application_links( self, trans, hda_ids=None ): + """Returns external display application JSON data for all/given + HDAs within the current history. + """ + try: + history = trans.get_history() + #TODO: allow id for more flexibility? (the following doesn't work if anonymous...) + #history = self.get_history( trans, id, check_ownership=True, check_accessible=True, deleted=None ) + if hda_ids: + unencoded_hda_ids = [ trans.security.decode_id( hda_id ) for hda_id in util.listify( hda_ids ) ] + #TODO: this gets all - should narrow query by using hda_ids here - no way with this current method + hdas = self.get_history_datasets( trans, history, show_deleted=False, show_hidden=True, show_purged=False ) + + except Exception, exc: + log.error( 'Error get_display_application_links (%s): %s', hda_ids, str( exc ) ) + trans.response.status = 500 + return str( exc ) + + hda_display_links = [] + for hda in hdas: + # only get requested hdas + if hda_ids and hda.id not in unencoded_hda_ids: + continue + + hda_link_data = { 'id': trans.security.encode_id( hda.id ) } + # don't bail on entire list if one hda has an error; record and move on + try: + # 'old style': must be enabled in config (see universe_wsgi.ini) + if trans.app.config.enable_old_display_applications: + hda_link_data[ 'display_types' ] = self.get_old_display_applications( trans, hda ) + + # 'new style' + hda_link_data[ 'display_apps' ] = self.get_display_apps( trans, hda ) + + except Exception, exc: + log.error( 'Error getting display applications, hda (%s): %s', hda_link_data[ 'id' ], str( exc ) ) + hda_link_data[ 'error' ] = str( exc ) + + hda_display_links.append( hda_link_data ) + + trans.response.set_content_type( 'application/json' ) + return hda_display_links diff --git a/lib/galaxy/webapps/galaxy/controllers/root.py b/lib/galaxy/webapps/galaxy/controllers/root.py index 592d647e5c1..494403d6f06 100644 --- a/lib/galaxy/webapps/galaxy/controllers/root.py +++ b/lib/galaxy/webapps/galaxy/controllers/root.py @@ -98,8 +98,8 @@ class RootController( BaseUIController, UsesHistoryMixin, UsesHistoryDatasetAsso @web.expose def history( self, trans, as_xml=False, show_deleted=None, show_hidden=None, hda_id=None, **kwd ): - """ - Display the current history, creating a new history if necessary. + """Display the current history, creating a new history if necessary. + NOTE: No longer accepts "id" or "template" options for security reasons. """ params = util.Params( kwd ) @@ -165,6 +165,7 @@ class RootController( BaseUIController, UsesHistoryMixin, UsesHistoryDatasetAsso datasets = hda_dictionaries return trans.stream_template_mako( history_panel_template, + #return trans.fill_template_mako( history_panel_template, history = history, annotation = self.get_item_annotation_str( trans.sa_session, trans.user, history ), datasets = datasets, diff --git a/static/scripts/mvc/dataset/hda-base.js b/static/scripts/mvc/dataset/hda-base.js index 07ce2d39b50..ff0499b9341 100644 --- a/static/scripts/mvc/dataset/hda-base.js +++ b/static/scripts/mvc/dataset/hda-base.js @@ -1,6 +1,8 @@ //define([ // "../mvc/base-mvc" //], function(){ + +/* global BaseView, LoggableMixin, HistoryDatasetAssociation, HDABaseView */ //============================================================================== /** @class Read only view for HistoryDatasetAssociation. * @name HDABaseView @@ -44,7 +46,21 @@ var HDABaseView = BaseView.extend( LoggableMixin ).extend( this.expanded = attributes.expanded || false; // re-render the entire view on any model change - this.model.bind( 'change', this.render , this ); + //this.model.bind( 'change', this.render , this ); + this.model.bind( 'change', function( event, changed ){ + //var withoutDisplayApps = _.omit( changed, [ 'display_apps', 'display_types' ] ); + var nonDisplayAppChanges = _.without( _.keys( changed.changes ), [ 'display_apps', 'display_types' ] ); + + if( nonDisplayAppChanges.length ){ + this.render(); + + //TODO: need a better handler for these sorts of rendering exceptions + } else { + if( this.expanded ){ + this._render_displayApps(); + } + } + }, this ); //this.bind( 'all', function( event ){ // this.log( event ); @@ -291,28 +307,38 @@ var HDABaseView = BaseView.extend( LoggableMixin ).extend( }, // ......................................................................... other elements - /** Render links to external genome display applications (igb, gbrowse, etc.). + /** Render the area for display application links. * @returns {jQuery} rendered DOM */ + _render_displayAppArea : function(){ + return $( '
' ).addClass( 'display-apps' ); + }, + + /** Render links to external genome display applications (igb, gbrowse, etc.). + * @param {jQuery} $parent the jq node to search for .display-apps and render into to (defaults to this.$el) + */ //TODO: not a fan of the style on these - _render_displayApps : function(){ - if( !this.model.hasData() ){ return null; } - - var displayAppsDiv = $( '
' ).addClass( 'display-apps' ); - if( !_.isEmpty( this.model.get( 'display_types' ) ) ){ + _render_displayApps : function( $parent ){ + $parent = $parent || this.$el; + var $displayAppsDiv = $parent.find( 'div.display-apps' ), + display_types = this.model.get( 'display_types' ), + display_apps = this.model.get( 'display_apps' ); + + if( ( !this.model.hasData() ) + || ( !$parent || !$parent.length ) + || ( !$displayAppsDiv.length ) ){ + return; + } + + $displayAppsDiv.html( null ); + if( !_.isEmpty( display_types ) ){ //this.log( this + 'display_types:', this.model.get( 'urls' ).display_types ); - //TODO:?? does this ever get used? - displayAppsDiv.append( - HDABaseView.templates.displayApps({ displayApps : this.model.get( 'display_types' ) }) - ); + $displayAppsDiv.append( HDABaseView.templates.displayApps({ displayApps : display_types }) ); } - if( !_.isEmpty( this.model.get( 'display_apps' ) ) ){ + if( !_.isEmpty( display_apps ) ){ //this.log( this + 'display_apps:', this.model.get( 'urls' ).display_apps ); - displayAppsDiv.append( - HDABaseView.templates.displayApps({ displayApps : this.model.get( 'display_apps' ) }) - ); + $displayAppsDiv.append( HDABaseView.templates.displayApps({ displayApps : display_apps }) ); } - return displayAppsDiv; }, /** Render the data peek. @@ -526,7 +552,8 @@ var HDABaseView = BaseView.extend( LoggableMixin ).extend( ])); parent.append( '
' ); - parent.append( this._render_displayApps() ); + parent.append( this._render_displayAppArea() ); + this._render_displayApps( parent ); parent.append( this._render_peek() ); }, diff --git a/static/scripts/mvc/dataset/hda-edit.js b/static/scripts/mvc/dataset/hda-edit.js index f28ef57d71c..94eef870776 100644 --- a/static/scripts/mvc/dataset/hda-edit.js +++ b/static/scripts/mvc/dataset/hda-edit.js @@ -404,6 +404,7 @@ var HDAEditView = HDABaseView.extend( LoggableMixin ).extend( * @see HDABaseView#_render_body_ok */ _render_body_ok : function( parent ){ + //TODO: should call super somehow and insert the needed... // most common state renderer and the most complicated parent.append( this._render_hdaSummary() ); @@ -434,7 +435,8 @@ var HDAEditView = HDABaseView.extend( LoggableMixin ).extend( parent.append( this._render_tagArea() ); parent.append( this._render_annotationArea() ); - parent.append( this._render_displayApps() ); + parent.append( this._render_displayAppArea() ); + this._render_displayApps( parent ); parent.append( this._render_peek() ); }, diff --git a/static/scripts/mvc/dataset/hda-model.js b/static/scripts/mvc/dataset/hda-model.js index 0ae41681ae9..72b74e32392 100644 --- a/static/scripts/mvc/dataset/hda-model.js +++ b/static/scripts/mvc/dataset/hda-model.js @@ -152,9 +152,9 @@ var HistoryDatasetAssociation = BaseModel.extend( LoggableMixin ).extend( toString : function(){ var nameAndId = this.get( 'id' ) || ''; if( this.get( 'name' ) ){ - nameAndId += ':"' + this.get( 'name' ) + '"'; + nameAndId = this.get( 'hid' ) + ' :"' + this.get( 'name' ) + '",' + nameAndId; } - return 'HistoryDatasetAssociation(' + nameAndId + ')'; + return 'HDA(' + nameAndId + ')'; } }); @@ -300,8 +300,25 @@ var HDACollection = Backbone.Collection.extend( LoggableMixin ).extend( return idList; }, + /** Set ea + * Precondition: each data_array object must have an id + * @param {Object[]} data_array an array of attribute objects to update the models with + * @see HistoryDatasetAssociation#set + */ + set : function( data_array ){ + var collection = this; + if( !data_array || !_.isArray( data_array ) ){ return; } + + //TODO: remove when updated backbone >= 1.0 + data_array.forEach( function( hda_data ){ + var model = collection.get( hda_data.id ); + if( model ){ + model.set( hda_data ); + } + }); + }, + /** Update (fetch) the data of the hdas with the given ids. - * @param {String} historyId the id of the history containing this collection * @param {String[]} ids an array of hda ids to update * @returns {HistoryDatasetAssociation[]} hda models that were updated * @see HistoryDatasetAssociation#fetch diff --git a/static/scripts/mvc/history/history-model.js b/static/scripts/mvc/history/history-model.js index aefc89ad28a..576391d6899 100644 --- a/static/scripts/mvc/history/history-model.js +++ b/static/scripts/mvc/history/history-model.js @@ -59,6 +59,8 @@ var History = BaseModel.extend( LoggableMixin ).extend( if( _.isArray( initialHdas ) ){ this.hdas.reset( initialHdas ); this.checkForUpdates(); + //TODO: don't call if force_history_refresh + this.updateDisplayApplications(); // handle errors in initialHdas //TODO: errors from the api shouldn't be plain strings... @@ -68,6 +70,18 @@ var History = BaseModel.extend( LoggableMixin ).extend( } } + // if an hda moves into the ready state and has the force_history_refresh flag (often via tool.xml) + // then: refresh the panel + this.hdas.bind( 'state:ready', function( hda, newState, oldState ){ + if( hda.get( 'force_history_refresh' ) ){ + //TODO: could poll jobs here... + var history = this; + setTimeout( function(){ + history.stateUpdater(); + }, History.UPDATE_DELAY ); + } + }, this ); + // events //this.on( 'change', function( currModel, changedList ){ // this.log( this + ' has changed:', currModel, changedList ); @@ -290,6 +304,35 @@ var History = BaseModel.extend( LoggableMixin ).extend( history.hdas.trigger( 'add', hdaDataList ); }, + /** Update (from controller) the display application link data of the hdas with the given ids. + * @param {String[]} ids an array of hda ids to update (optional, defaults to all hdas) + * @returns {HistoryDatasetAssociation[]} hda models that were updated + */ + updateDisplayApplications : function( ids ){ + this.log( this + 'updateDisplayApplications:', ids ); + var history = this, + // data = { id: this.get( 'id' ) }; + //if( ids && _.isArray( ids ) ){ data.hda_ids = ids.join( ',' ); } + data = ( ids && _.isArray( ids ) )?({ hda_ids : ids.join( ',' ) }):({}); + + //TODO: hardcoded + history.log( this + ': fetching display application data' ); + jQuery.ajax( 'history/get_display_application_links', { + data : data, + success : function( data, status, xhr ){ + history.hdas.set( data ); + }, + error : function( xhr, status, error ){ + if( !( ( xhr.readyState === 0 ) && ( xhr.status === 0 ) ) ){ + var msg = 'Error fetching display applications, ' + ids + ':' + ( xhr.responseText || error ); + Galaxy.show_modal( 'History panel error', + msg, { 'Ok': function(){ Galaxy.hide_modal(); } }); + this.log( msg ); + } + } + }); + }, + toString : function(){ var nameString = ( this.get( 'name' ) )? ( ',' + this.get( 'name' ) ) : ( '' ); diff --git a/static/scripts/mvc/history/history-panel.js b/static/scripts/mvc/history/history-panel.js index 4a75f84c558..b48fe1b7bf5 100644 --- a/static/scripts/mvc/history/history-panel.js +++ b/static/scripts/mvc/history/history-panel.js @@ -126,14 +126,6 @@ var HistoryPanel = BaseView.extend( LoggableMixin ).extend( } }, this ); - // if an hda moves into the ready state and has the force_history_refresh flag (often via tool.xml) - // then: refresh the panel - this.model.hdas.bind( 'state:ready', function( hda, newState, oldState ){ - if( hda.get( 'force_history_refresh' ) ){ - window.location.reload(); - } - }, this ); - //this.bind( 'all', function(){ // this.log( arguments ); //}, this ); diff --git a/templates/webapps/galaxy/root/alternate_history.mako b/templates/webapps/galaxy/root/alternate_history.mako index 6af706444e3..8807c72b6f0 100644 --- a/templates/webapps/galaxy/root/alternate_history.mako +++ b/templates/webapps/galaxy/root/alternate_history.mako @@ -285,6 +285,9 @@ function galaxyPageSetUp(){ top.Galaxy.$rightPanel = top.Galaxy.$rightPanel || $( top.document ).find( 'div#right' ); //modals + top.Galaxy.show_modal = top.show_modal; + top.Galaxy.hide_modal = top.hide_modal; + // other base functions // global backbone models From 422281a2520315c8c646693b6f4944afce656ccc Mon Sep 17 00:00:00 2001 From: Carl Eberhard Date: Mon, 1 Apr 2013 11:20:18 -0400 Subject: [PATCH 054/523] pack scripts --- static/scripts/packed/galaxy.base.js | 2 +- static/scripts/packed/mvc/dataset/hda-base.js | 2 +- static/scripts/packed/mvc/dataset/hda-edit.js | 2 +- static/scripts/packed/mvc/dataset/hda-model.js | 2 +- static/scripts/packed/mvc/history/history-model.js | 2 +- static/scripts/packed/mvc/history/history-panel.js | 2 +- static/scripts/packed/viz/trackster/tracks.js | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/static/scripts/packed/galaxy.base.js b/static/scripts/packed/galaxy.base.js index 9343d371083..be17f05b7d7 100644 --- a/static/scripts/packed/galaxy.base.js +++ b/static/scripts/packed/galaxy.base.js @@ -1 +1 @@ -(function(){var b=0;var c=["ms","moz","webkit","o"];for(var a=0;a");var f=b.data("menu_options");if(obj_length(f)<=0){$("
  • No Options.
  • ").appendTo(g)}$.each(f,function(j,i){if(i){g.append($("
  • ").append($("").html(j).click(i)))}else{g.append($("
  • ").addClass("head").append($("").html(j)))}});var h=$("
    ").append(g).appendTo("body");var e=d.pageX-h.width()/2;e=Math.min(e,$(document).scrollLeft()+$(window).width()-$(h).width()-5);e=Math.max(e,$(document).scrollLeft()+5);h.css({top:d.pageY,left:e})},10);setTimeout(function(){var f=function(h){$(h).bind("click.close_popup",function(){$(".popmenu-wrapper").remove();h.unbind("click.close_popup")})};f($(window.document));f($(window.top.document));for(var e=window.top.frames.length;e--;){var g=$(window.top.frames[e].document);f(g)}},50);return false})}function make_popup_menus(a){a=a||document;$(a).find("div[popupmenu]").each(function(){var b={};var d=$(this);d.find("a").each(function(){var g=$(this),i=g.get(0),e=i.getAttribute("confirm"),f=i.getAttribute("href"),h=i.getAttribute("target");if(!f){b[g.text()]=null}else{b[g.text()]=function(){if(!e||confirm(e)){var j;if(h=="_parent"){window.parent.location=f}else{if(h=="_top"){window.top.location=f}else{if(h=="demo"){if(j===undefined||j.closed){j=window.open(f,h);j.creator=self}}else{window.location=f}}}}}}});var c=$(a).find("#"+d.attr("popupmenu"));c.find("a").bind("click",function(f){f.stopPropagation();return true});make_popupmenu(c,b);c.addClass("popup");d.remove()})}function naturalSort(j,h){var p=/(-?[0-9\.]+)/g,k=j.toString().toLowerCase()||"",g=h.toString().toLowerCase()||"",l=String.fromCharCode(0),n=k.replace(p,l+"$1"+l).split(l),e=g.replace(p,l+"$1"+l).split(l),d=(new Date(k)).getTime(),o=d?(new Date(g)).getTime():null;if(o){if(do){return 1}}}var m,f;for(var i=0,c=Math.max(n.length,e.length);if){return 1}}}return 0}function replace_big_select_inputs(a,c,b){if(!jQuery.fn.select2){return}if(a===undefined){a=20}if(c===undefined){c=3000}var b=b||$("select");b.each(function(){var e=$(this);var d=e.find("option").length;if((dc)){return}if(e.hasClass("no-autocomplete")){return}e.select2({width:"resolve"})})}$.fn.make_text_editable=function(g){var d=("num_cols" in g?g.num_cols:30),c=("num_rows" in g?g.num_rows:4),e=("use_textarea" in g?g.use_textarea:false),b=("on_finish" in g?g.on_finish:null),f=("help_text" in g?g.help_text:null);var a=$(this);a.addClass("editable-text").click(function(l){if($(this).children(":input").length>0){return}a.removeClass("editable-text");var i=function(m){a.find(":input").remove();if(m!==""){a.text(m)}else{a.html("
    ")}a.addClass("editable-text");if(b){b(m)}};var h=a.text(),k,j;if(e){k=$("").attr({rows:i,cols:c}).text($.trim(k))}else{j=$("").attr({value:$.trim(k),size:c})}j.attr("id","renaming-active");j.blur(function(){$(this).remove();l.show();if(b){b(j)}});j.keyup(function(n){if(n.keyCode===27){$(this).trigger("blur")}else{if(n.keyCode===13){var m={};m[a]=$(this).val();$(this).trigger("blur");$.ajax({url:e,data:m,error:function(){alert("Text editing for elt "+f+" failed")},success:function(o){if(o!==""){l.text(o)}else{l.html("None")}if(b){b(j)}}})}}});if(g){g(j)}l.hide();j.insertAfter(l);j.focus();j.select();return})}function init_history_items(d,a,c){var b=function(){try{var e=$.jStorage.get("history_expand_state");if(e){for(var g in e){$("#"+g+" div.historyItemBody").show()}}}catch(f){$.jStorage.deleteKey("history_expand_state")}if($.browser.mozilla){$("div.historyItemBody").each(function(){if(!$(this).is(":visible")){$(this).find("pre.peek").css("overflow","hidden")}})}d.each(function(){var j=this.id,h=$(this).children("div.historyItemBody"),i=h.find("pre.peek");$(this).find(".historyItemTitleBar > .historyItemTitle").wrap("").click(function(){var k;if(h.is(":visible")){if($.browser.mozilla){i.css("overflow","hidden")}h.slideUp("fast");if(!c){k=$.jStorage.get("history_expand_state");if(k){delete k[j];$.jStorage.set("history_expand_state",k)}}}else{h.slideDown("fast",function(){if($.browser.mozilla){i.css("overflow","auto")}});if(!c){k=$.jStorage.get("history_expand_state");if(!k){k={}}k[j]=true;$.jStorage.set("history_expand_state",k)}}return false})});$("#top-links > a.toggle").click(function(){var h=$.jStorage.get("history_expand_state");if(!h){h={}}$("div.historyItemBody:visible").each(function(){if($.browser.mozilla){$(this).find("pre.peek").css("overflow","hidden")}$(this).slideUp("fast");if(h){delete h[$(this).parent().attr("id")]}});$.jStorage.set("history_expand_state",h)}).show()};b()}function commatize(b){b+="";var a=/(\d+)(\d{3})/;while(a.test(b)){b=b.replace(a,"$1,$2")}return b}function reset_tool_search(a){var c=$("#galaxy_tools").contents();if(c.length===0){c=$(document)}$(this).removeClass("search_active");c.find(".toolTitle").removeClass("search_match");c.find(".toolSectionBody").hide();c.find(".toolTitle").show();c.find(".toolPanelLabel").show();c.find(".toolSectionWrapper").each(function(){if($(this).attr("id")!="recently_used_wrapper"){$(this).show()}else{if($(this).hasClass("user_pref_visible")){$(this).show()}}});c.find("#search-no-results").hide();c.find("#search-spinner").hide();if(a){var b=c.find("#tool-search-query");b.val("search tools")}}var GalaxyAsync=function(a){this.url_dict={};this.log_action=(a===undefined?false:a)};GalaxyAsync.prototype.set_func_url=function(a,b){this.url_dict[a]=b};GalaxyAsync.prototype.set_user_pref=function(a,b){var c=this.url_dict[arguments.callee];if(c===undefined){return false}$.ajax({url:c,data:{pref_name:a,pref_value:b},error:function(){return false},success:function(){return true}})};GalaxyAsync.prototype.log_user_action=function(c,b,d){if(!this.log_action){return}var a=this.url_dict[arguments.callee];if(a===undefined){return false}$.ajax({url:a,data:{action:c,context:b,params:d},error:function(){return false},success:function(){return true}})};$(document).ready(function(){$("select[refresh_on_change='true']").change(function(){var a=$(this),e=a.val(),d=false,c=a.attr("refresh_on_change_values");if(c){c=c.split(",");var b=a.attr("last_selected_value");if($.inArray(e,c)===-1&&$.inArray(b,c)===-1){return}}$(window).trigger("refresh_on_change");$(document).trigger("convert_to_values");a.get(0).form.submit()});$(":checkbox[refresh_on_change='true']").click(function(){var a=$(this),e=a.val(),d=false,c=a.attr("refresh_on_change_values");if(c){c=c.split(",");var b=a.attr("last_selected_value");if($.inArray(e,c)===-1&&$.inArray(b,c)===-1){return}}$(window).trigger("refresh_on_change");a.get(0).form.submit()});$("a[confirm]").click(function(){return confirm($(this).attr("confirm"))});if($.fn.tooltip){$(".tooltip").tooltip({placement:"top"})}make_popup_menus();replace_big_select_inputs(20,1500);$("a").click(function(){var b=$(this);var c=(parent.frames&&parent.frames.galaxy_main);if((b.attr("target")=="galaxy_main")&&(!c)){var a=b.attr("href");if(a.indexOf("?")==-1){a+="?"}else{a+="&"}a+="use_panels=True";b.attr("href",a);b.attr("target","_self")}return b})}); \ No newline at end of file +(function(){var b=0;var c=["ms","moz","webkit","o"];for(var a=0;a");var f=b.data("menu_options");if(obj_length(f)<=0){$("
  • No Options.
  • ").appendTo(g)}$.each(f,function(j,i){if(i){g.append($("
  • ").append($("").html(j).click(i)))}else{g.append($("
  • ").addClass("head").append($("").html(j)))}});var h=$("
    ").append(g).appendTo("body");var e=d.pageX-h.width()/2;e=Math.min(e,$(document).scrollLeft()+$(window).width()-$(h).width()-5);e=Math.max(e,$(document).scrollLeft()+5);h.css({top:d.pageY,left:e})},10);setTimeout(function(){var f=function(h){$(h).bind("click.close_popup",function(){$(".popmenu-wrapper").remove();h.unbind("click.close_popup")})};f($(window.document));f($(window.top.document));for(var e=window.top.frames.length;e--;){var g=$(window.top.frames[e].document);f(g)}},50);return false})}function make_popup_menus(a){a=a||document;$(a).find("div[popupmenu]").each(function(){var b={};var d=$(this);d.find("a").each(function(){var g=$(this),i=g.get(0),e=i.getAttribute("confirm"),f=i.getAttribute("href"),h=i.getAttribute("target");if(!f){b[g.text()]=null}else{b[g.text()]=function(){if(!e||confirm(e)){var j;if(h=="_parent"){window.parent.location=f}else{if(h=="_top"){window.top.location=f}else{if(h=="demo"){if(j===undefined||j.closed){j=window.open(f,h);j.creator=self}}else{window.location=f}}}}}}});var c=$(a).find("#"+d.attr("popupmenu"));c.find("a").bind("click",function(f){f.stopPropagation();return true});make_popupmenu(c,b);c.addClass("popup");d.remove()})}function naturalSort(j,h){var p=/(-?[0-9\.]+)/g,k=j.toString().toLowerCase()||"",g=h.toString().toLowerCase()||"",l=String.fromCharCode(0),n=k.replace(p,l+"$1"+l).split(l),e=g.replace(p,l+"$1"+l).split(l),d=(new Date(k)).getTime(),o=d?(new Date(g)).getTime():null;if(o){if(do){return 1}}}var m,f;for(var i=0,c=Math.max(n.length,e.length);if){return 1}}}return 0}$.fn.refresh_select2=function(){var b=$(this);var a={width:"resolve",closeOnSelect:!b.is("[MULTIPLE]"),};return b.select2(a)};function replace_big_select_inputs(a,c,b){if(!jQuery.fn.select2){return}if(a===undefined){a=20}if(c===undefined){c=3000}var b=b||$("select");b.each(function(){var e=$(this);var d=e.find("option").length;if((dc)){return}if(e.hasClass("no-autocomplete")){return}e.refresh_select2()})}$.fn.make_text_editable=function(g){var d=("num_cols" in g?g.num_cols:30),c=("num_rows" in g?g.num_rows:4),e=("use_textarea" in g?g.use_textarea:false),b=("on_finish" in g?g.on_finish:null),f=("help_text" in g?g.help_text:null);var a=$(this);a.addClass("editable-text").click(function(l){if($(this).children(":input").length>0){return}a.removeClass("editable-text");var i=function(m){a.find(":input").remove();if(m!==""){a.text(m)}else{a.html("
    ")}a.addClass("editable-text");if(b){b(m)}};var h=a.text(),k,j;if(e){k=$("").attr({rows:i,cols:c}).text($.trim(k))}else{j=$("").attr({value:$.trim(k),size:c})}j.attr("id","renaming-active");j.blur(function(){$(this).remove();l.show();if(b){b(j)}});j.keyup(function(n){if(n.keyCode===27){$(this).trigger("blur")}else{if(n.keyCode===13){var m={};m[a]=$(this).val();$(this).trigger("blur");$.ajax({url:e,data:m,error:function(){alert("Text editing for elt "+f+" failed")},success:function(o){if(o!==""){l.text(o)}else{l.html("None")}if(b){b(j)}}})}}});if(g){g(j)}l.hide();j.insertAfter(l);j.focus();j.select();return})}function init_history_items(d,a,c){var b=function(){try{var e=$.jStorage.get("history_expand_state");if(e){for(var g in e){$("#"+g+" div.historyItemBody").show()}}}catch(f){$.jStorage.deleteKey("history_expand_state")}if($.browser.mozilla){$("div.historyItemBody").each(function(){if(!$(this).is(":visible")){$(this).find("pre.peek").css("overflow","hidden")}})}d.each(function(){var j=this.id,h=$(this).children("div.historyItemBody"),i=h.find("pre.peek");$(this).find(".historyItemTitleBar > .historyItemTitle").wrap("").click(function(){var k;if(h.is(":visible")){if($.browser.mozilla){i.css("overflow","hidden")}h.slideUp("fast");if(!c){k=$.jStorage.get("history_expand_state");if(k){delete k[j];$.jStorage.set("history_expand_state",k)}}}else{h.slideDown("fast",function(){if($.browser.mozilla){i.css("overflow","auto")}});if(!c){k=$.jStorage.get("history_expand_state");if(!k){k={}}k[j]=true;$.jStorage.set("history_expand_state",k)}}return false})});$("#top-links > a.toggle").click(function(){var h=$.jStorage.get("history_expand_state");if(!h){h={}}$("div.historyItemBody:visible").each(function(){if($.browser.mozilla){$(this).find("pre.peek").css("overflow","hidden")}$(this).slideUp("fast");if(h){delete h[$(this).parent().attr("id")]}});$.jStorage.set("history_expand_state",h)}).show()};b()}function commatize(b){b+="";var a=/(\d+)(\d{3})/;while(a.test(b)){b=b.replace(a,"$1,$2")}return b}function reset_tool_search(a){var c=$("#galaxy_tools").contents();if(c.length===0){c=$(document)}$(this).removeClass("search_active");c.find(".toolTitle").removeClass("search_match");c.find(".toolSectionBody").hide();c.find(".toolTitle").show();c.find(".toolPanelLabel").show();c.find(".toolSectionWrapper").each(function(){if($(this).attr("id")!="recently_used_wrapper"){$(this).show()}else{if($(this).hasClass("user_pref_visible")){$(this).show()}}});c.find("#search-no-results").hide();c.find("#search-spinner").hide();if(a){var b=c.find("#tool-search-query");b.val("search tools")}}var GalaxyAsync=function(a){this.url_dict={};this.log_action=(a===undefined?false:a)};GalaxyAsync.prototype.set_func_url=function(a,b){this.url_dict[a]=b};GalaxyAsync.prototype.set_user_pref=function(a,b){var c=this.url_dict[arguments.callee];if(c===undefined){return false}$.ajax({url:c,data:{pref_name:a,pref_value:b},error:function(){return false},success:function(){return true}})};GalaxyAsync.prototype.log_user_action=function(c,b,d){if(!this.log_action){return}var a=this.url_dict[arguments.callee];if(a===undefined){return false}$.ajax({url:a,data:{action:c,context:b,params:d},error:function(){return false},success:function(){return true}})};$(document).ready(function(){$("select[refresh_on_change='true']").change(function(){var a=$(this),e=a.val(),d=false,c=a.attr("refresh_on_change_values");if(c){c=c.split(",");var b=a.attr("last_selected_value");if($.inArray(e,c)===-1&&$.inArray(b,c)===-1){return}}$(window).trigger("refresh_on_change");$(document).trigger("convert_to_values");a.get(0).form.submit()});$(":checkbox[refresh_on_change='true']").click(function(){var a=$(this),e=a.val(),d=false,c=a.attr("refresh_on_change_values");if(c){c=c.split(",");var b=a.attr("last_selected_value");if($.inArray(e,c)===-1&&$.inArray(b,c)===-1){return}}$(window).trigger("refresh_on_change");a.get(0).form.submit()});$("a[confirm]").click(function(){return confirm($(this).attr("confirm"))});if($.fn.tooltip){$(".tooltip").tooltip({placement:"top"})}make_popup_menus();replace_big_select_inputs(20,1500);$("a").click(function(){var b=$(this);var c=(parent.frames&&parent.frames.galaxy_main);if((b.attr("target")=="galaxy_main")&&(!c)){var a=b.attr("href");if(a.indexOf("?")==-1){a+="?"}else{a+="&"}a+="use_panels=True";b.attr("href",a);b.attr("target","_self")}return b})}); \ No newline at end of file diff --git a/static/scripts/packed/mvc/dataset/hda-base.js b/static/scripts/packed/mvc/dataset/hda-base.js index 8361fb8a279..3eb5386ce7a 100644 --- a/static/scripts/packed/mvc/dataset/hda-base.js +++ b/static/scripts/packed/mvc/dataset/hda-base.js @@ -1 +1 @@ -var HDABaseView=BaseView.extend(LoggableMixin).extend({tagName:"div",className:"historyItemContainer",initialize:function(a){if(a.logger){this.logger=this.model.logger=a.logger}this.log(this+".initialize:",a);this.defaultPrimaryActionButtonRenderers=[this._render_showParamsButton];if(!a.urlTemplates){throw ("HDAView needs urlTemplates on initialize")}this.urlTemplates=a.urlTemplates;this.expanded=a.expanded||false;this.model.bind("change",this.render,this)},render:function(){var b=this,e=this.model.get("id"),c=this.model.get("state"),a=$("
    ").attr("id","historyItem-"+e),d=(this.$el.children().size()===0);this.$el.attr("id","historyItemContainer-"+e);this.urls=this._renderUrls(this.urlTemplates,this.model.toJSON());a.addClass("historyItemWrapper").addClass("historyItem").addClass("historyItem-"+c);a.append(this._render_warnings());a.append(this._render_titleBar());this._setUpBehaviors(a);this.body=$(this._render_body());a.append(this.body);this.$el.fadeOut("fast",function(){b.$el.children().remove();b.$el.append(a).fadeIn("fast",function(){b.log(b+" rendered:",b.$el);var f="rendered";if(d){f+=":initial"}else{if(b.model.inReadyState()){f+=":ready"}}b.trigger(f)})});return this},_renderUrls:function(d,a){var b=this,c={};_.each(d,function(e,f){if(_.isObject(e)){c[f]=b._renderUrls(e,a)}else{if(f==="meta_download"){c[f]=b._renderMetaDownloadUrls(e,a)}else{try{c[f]=_.template(e,a)}catch(g){throw (b+"._renderUrls error: "+g+"\n rendering:"+e+"\n with "+JSON.stringify(a))}}}});return c},_renderMetaDownloadUrls:function(b,a){return _.map(a.meta_files,function(c){return{url:_.template(b,{id:a.id,file_type:c.file_type}),file_type:c.file_type}})},_setUpBehaviors:function(a){a=a||this.$el;make_popup_menus(a);a.find(".tooltip").tooltip({placement:"bottom"})},_render_warnings:function(){return $(jQuery.trim(HDABaseView.templates.messages(this.model.toJSON())))},_render_titleBar:function(){var a=$('
    ');a.append(this._render_titleButtons());a.append('');a.append(this._render_titleLink());return a},_render_titleButtons:function(){var a=$('
    ');a.append(this._render_displayButton());return a},_render_displayButton:function(){if((this.model.get("state")===HistoryDatasetAssociation.STATES.NOT_VIEWABLE)||(this.model.get("state")===HistoryDatasetAssociation.STATES.NEW)||(!this.model.get("accessible"))){this.displayButton=null;return null}var a={icon_class:"display",target:"galaxy_main"};if(this.model.get("purged")){a.enabled=false;a.title=_l("Cannot display datasets removed from disk")}else{a.title=_l("Display data in browser");a.href=this.urls.display}this.displayButton=new IconButtonView({model:new IconButton(a)});return this.displayButton.render().$el},_render_titleLink:function(){return $(jQuery.trim(HDABaseView.templates.titleLink(_.extend(this.model.toJSON(),{urls:this.urls}))))},_render_hdaSummary:function(){var a=_.extend(this.model.toJSON(),{urls:this.urls});return HDABaseView.templates.hdaSummary(a)},_render_primaryActionButtons:function(c){var a=this,b=$("
    ").attr("id","primary-actions-"+this.model.get("id"));_.each(c,function(d){b.append(d.call(a))});return b},_render_downloadButton:function(){if(this.model.get("purged")||!this.model.hasData()){return null}var a=HDABaseView.templates.downloadLinks(_.extend(this.model.toJSON(),{urls:this.urls}));return $(a)},_render_showParamsButton:function(){this.showParamsButton=new IconButtonView({model:new IconButton({title:_l("View details"),href:this.urls.show_params,target:"galaxy_main",icon_class:"information"})});return this.showParamsButton.render().$el},_render_displayApps:function(){if(!this.model.hasData()){return null}var a=$("
    ").addClass("display-apps");if(!_.isEmpty(this.model.get("display_types"))){a.append(HDABaseView.templates.displayApps({displayApps:this.model.get("display_types")}))}if(!_.isEmpty(this.model.get("display_apps"))){a.append(HDABaseView.templates.displayApps({displayApps:this.model.get("display_apps")}))}return a},_render_peek:function(){var a=this.model.get("peek");if(!a){return null}return $("
    ").append($("
    ").attr("id","peek"+this.model.get("id")).addClass("peek").append(a))},_render_body:function(){var a=$("
    ").attr("id","info-"+this.model.get("id")).addClass("historyItemBody").attr("style","display: none");if(this.expanded){this._render_body_html(a);a.show()}return a},_render_body_html:function(a){a.html("");switch(this.model.get("state")){case HistoryDatasetAssociation.STATES.NEW:this._render_body_new(a);break;case HistoryDatasetAssociation.STATES.NOT_VIEWABLE:this._render_body_not_viewable(a);break;case HistoryDatasetAssociation.STATES.UPLOAD:this._render_body_uploading(a);break;case HistoryDatasetAssociation.STATES.PAUSED:this._render_body_paused(a);break;case HistoryDatasetAssociation.STATES.QUEUED:this._render_body_queued(a);break;case HistoryDatasetAssociation.STATES.RUNNING:this._render_body_running(a);break;case HistoryDatasetAssociation.STATES.ERROR:this._render_body_error(a);break;case HistoryDatasetAssociation.STATES.DISCARDED:this._render_body_discarded(a);break;case HistoryDatasetAssociation.STATES.SETTING_METADATA:this._render_body_setting_metadata(a);break;case HistoryDatasetAssociation.STATES.EMPTY:this._render_body_empty(a);break;case HistoryDatasetAssociation.STATES.FAILED_METADATA:this._render_body_failed_metadata(a);break;case HistoryDatasetAssociation.STATES.OK:this._render_body_ok(a);break;default:a.append($('
    Error: unknown dataset state "'+this.model.get("state")+'".
    '))}a.append('
    ');this._setUpBehaviors(a)},_render_body_new:function(b){var a="This is a new dataset and not all of its data are available yet";b.append($("
    "+_l(a)+"
    "))},_render_body_not_viewable:function(a){a.append($("
    "+_l("You do not have permission to view dataset")+"
    "))},_render_body_uploading:function(a){a.append($("
    "+_l("Dataset is uploading")+"
    "))},_render_body_queued:function(a){a.append($("
    "+_l("Job is waiting to run")+"
    "));a.append(this._render_primaryActionButtons(this.defaultPrimaryActionButtonRenderers))},_render_body_paused:function(a){a.append($("
    "+_l("Job is paused. Use the history menu to resume")+"
    "));a.append(this._render_primaryActionButtons(this.defaultPrimaryActionButtonRenderers))},_render_body_running:function(a){a.append("
    "+_l("Job is currently running")+"
    ");a.append(this._render_primaryActionButtons(this.defaultPrimaryActionButtonRenderers))},_render_body_error:function(a){if(!this.model.get("purged")){a.append($("
    "+this.model.get("misc_blurb")+"
    "))}a.append((_l("An error occurred with this dataset")+": "+$.trim(this.model.get("misc_info"))+""));a.append(this._render_primaryActionButtons(this.defaultPrimaryActionButtonRenderers.concat([this._render_downloadButton])))},_render_body_discarded:function(a){a.append("
    "+_l("The job creating this dataset was cancelled before completion")+".
    ");a.append(this._render_primaryActionButtons(this.defaultPrimaryActionButtonRenderers))},_render_body_setting_metadata:function(a){a.append($("
    "+_l("Metadata is being auto-detected")+".
    "))},_render_body_empty:function(a){a.append($("
    "+_l("No data")+": "+this.model.get("misc_blurb")+"
    "));a.append(this._render_primaryActionButtons(this.defaultPrimaryActionButtonRenderers))},_render_body_failed_metadata:function(a){a.append($(HDABaseView.templates.failedMetadata(_.extend(this.model.toJSON(),{urls:this.urls}))));this._render_body_ok(a)},_render_body_ok:function(a){a.append(this._render_hdaSummary());if(this.model.isDeletedOrPurged()){a.append(this._render_primaryActionButtons([this._render_downloadButton,this._render_showParamsButton]));return}a.append(this._render_primaryActionButtons([this._render_downloadButton,this._render_showParamsButton]));a.append('
    ');a.append(this._render_displayApps());a.append(this._render_peek())},events:{"click .historyItemTitle":"toggleBodyVisibility"},toggleBodyVisibility:function(c,a){var b=this;this.expanded=(a===undefined)?(!this.body.is(":visible")):(a);if(this.expanded){b._render_body_html(b.body);this.body.slideDown("fast",function(){b.trigger("body-expanded",b.model.get("id"))})}else{this.body.slideUp("fast",function(){b.trigger("body-collapsed",b.model.get("id"))})}},remove:function(b){var a=this;this.$el.fadeOut("fast",function(){a.$el.remove();if(b){b()}})},toString:function(){var a=(this.model)?(this.model+""):("(no model)");return"HDABaseView("+a+")"}});HDABaseView.templates={warningMsg:Handlebars.templates["template-warningmessagesmall"],messages:Handlebars.templates["template-hda-warning-messages"],titleLink:Handlebars.templates["template-hda-titleLink"],hdaSummary:Handlebars.templates["template-hda-hdaSummary"],downloadLinks:Handlebars.templates["template-hda-downloadLinks"],failedMetadata:Handlebars.templates["template-hda-failedMetadata"],displayApps:Handlebars.templates["template-hda-displayApps"]}; \ No newline at end of file +var HDABaseView=BaseView.extend(LoggableMixin).extend({tagName:"div",className:"historyItemContainer",initialize:function(a){if(a.logger){this.logger=this.model.logger=a.logger}this.log(this+".initialize:",a);this.defaultPrimaryActionButtonRenderers=[this._render_showParamsButton];if(!a.urlTemplates){throw ("HDAView needs urlTemplates on initialize")}this.urlTemplates=a.urlTemplates;this.expanded=a.expanded||false;this.model.bind("change",function(c,d){var b=_.without(_.keys(d.changes),["display_apps","display_types"]);if(b.length){this.render()}else{if(this.expanded){this._render_displayApps()}}},this)},render:function(){var b=this,e=this.model.get("id"),c=this.model.get("state"),a=$("
    ").attr("id","historyItem-"+e),d=(this.$el.children().size()===0);this.$el.attr("id","historyItemContainer-"+e);this.urls=this._renderUrls(this.urlTemplates,this.model.toJSON());a.addClass("historyItemWrapper").addClass("historyItem").addClass("historyItem-"+c);a.append(this._render_warnings());a.append(this._render_titleBar());this._setUpBehaviors(a);this.body=$(this._render_body());a.append(this.body);this.$el.fadeOut("fast",function(){b.$el.children().remove();b.$el.append(a).fadeIn("fast",function(){b.log(b+" rendered:",b.$el);var f="rendered";if(d){f+=":initial"}else{if(b.model.inReadyState()){f+=":ready"}}b.trigger(f)})});return this},_renderUrls:function(d,a){var b=this,c={};_.each(d,function(e,f){if(_.isObject(e)){c[f]=b._renderUrls(e,a)}else{if(f==="meta_download"){c[f]=b._renderMetaDownloadUrls(e,a)}else{try{c[f]=_.template(e,a)}catch(g){throw (b+"._renderUrls error: "+g+"\n rendering:"+e+"\n with "+JSON.stringify(a))}}}});return c},_renderMetaDownloadUrls:function(b,a){return _.map(a.meta_files,function(c){return{url:_.template(b,{id:a.id,file_type:c.file_type}),file_type:c.file_type}})},_setUpBehaviors:function(a){a=a||this.$el;make_popup_menus(a);a.find(".tooltip").tooltip({placement:"bottom"})},_render_warnings:function(){return $(jQuery.trim(HDABaseView.templates.messages(this.model.toJSON())))},_render_titleBar:function(){var a=$('
    ');a.append(this._render_titleButtons());a.append('');a.append(this._render_titleLink());return a},_render_titleButtons:function(){var a=$('
    ');a.append(this._render_displayButton());return a},_render_displayButton:function(){if((this.model.get("state")===HistoryDatasetAssociation.STATES.NOT_VIEWABLE)||(this.model.get("state")===HistoryDatasetAssociation.STATES.NEW)||(!this.model.get("accessible"))){this.displayButton=null;return null}var a={icon_class:"display",target:"galaxy_main"};if(this.model.get("purged")){a.enabled=false;a.title=_l("Cannot display datasets removed from disk")}else{a.title=_l("View data");a.href=this.urls.display}this.displayButton=new IconButtonView({model:new IconButton(a)});return this.displayButton.render().$el},_render_titleLink:function(){return $(jQuery.trim(HDABaseView.templates.titleLink(_.extend(this.model.toJSON(),{urls:this.urls}))))},_render_hdaSummary:function(){var a=_.extend(this.model.toJSON(),{urls:this.urls});return HDABaseView.templates.hdaSummary(a)},_render_primaryActionButtons:function(c){var a=this,b=$("
    ").attr("id","primary-actions-"+this.model.get("id"));_.each(c,function(d){b.append(d.call(a))});return b},_render_downloadButton:function(){if(this.model.get("purged")||!this.model.hasData()){return null}var a=HDABaseView.templates.downloadLinks(_.extend(this.model.toJSON(),{urls:this.urls}));return $(a)},_render_showParamsButton:function(){this.showParamsButton=new IconButtonView({model:new IconButton({title:_l("View details"),href:this.urls.show_params,target:"galaxy_main",icon_class:"information"})});return this.showParamsButton.render().$el},_render_displayAppArea:function(){return $("
    ").addClass("display-apps")},_render_displayApps:function(c){c=c||this.$el;var d=c.find("div.display-apps"),a=this.model.get("display_types"),b=this.model.get("display_apps");if((!this.model.hasData())||(!c||!c.length)||(!d.length)){return}d.html(null);if(!_.isEmpty(a)){d.append(HDABaseView.templates.displayApps({displayApps:a}))}if(!_.isEmpty(b)){d.append(HDABaseView.templates.displayApps({displayApps:b}))}},_render_peek:function(){var a=this.model.get("peek");if(!a){return null}return $("
    ").append($("
    ").attr("id","peek"+this.model.get("id")).addClass("peek").append(a))},_render_body:function(){var a=$("
    ").attr("id","info-"+this.model.get("id")).addClass("historyItemBody").attr("style","display: none");if(this.expanded){this._render_body_html(a);a.show()}return a},_render_body_html:function(a){a.html("");switch(this.model.get("state")){case HistoryDatasetAssociation.STATES.NEW:this._render_body_new(a);break;case HistoryDatasetAssociation.STATES.NOT_VIEWABLE:this._render_body_not_viewable(a);break;case HistoryDatasetAssociation.STATES.UPLOAD:this._render_body_uploading(a);break;case HistoryDatasetAssociation.STATES.PAUSED:this._render_body_paused(a);break;case HistoryDatasetAssociation.STATES.QUEUED:this._render_body_queued(a);break;case HistoryDatasetAssociation.STATES.RUNNING:this._render_body_running(a);break;case HistoryDatasetAssociation.STATES.ERROR:this._render_body_error(a);break;case HistoryDatasetAssociation.STATES.DISCARDED:this._render_body_discarded(a);break;case HistoryDatasetAssociation.STATES.SETTING_METADATA:this._render_body_setting_metadata(a);break;case HistoryDatasetAssociation.STATES.EMPTY:this._render_body_empty(a);break;case HistoryDatasetAssociation.STATES.FAILED_METADATA:this._render_body_failed_metadata(a);break;case HistoryDatasetAssociation.STATES.OK:this._render_body_ok(a);break;default:a.append($('
    Error: unknown dataset state "'+this.model.get("state")+'".
    '))}a.append('
    ');this._setUpBehaviors(a)},_render_body_new:function(b){var a="This is a new dataset and not all of its data are available yet";b.append($("
    "+_l(a)+"
    "))},_render_body_not_viewable:function(a){a.append($("
    "+_l("You do not have permission to view dataset")+"
    "))},_render_body_uploading:function(a){a.append($("
    "+_l("Dataset is uploading")+"
    "))},_render_body_queued:function(a){a.append($("
    "+_l("Job is waiting to run")+"
    "));a.append(this._render_primaryActionButtons(this.defaultPrimaryActionButtonRenderers))},_render_body_paused:function(a){a.append($("
    "+_l("Job is paused. Use the history menu to resume")+"
    "));a.append(this._render_primaryActionButtons(this.defaultPrimaryActionButtonRenderers))},_render_body_running:function(a){a.append("
    "+_l("Job is currently running")+"
    ");a.append(this._render_primaryActionButtons(this.defaultPrimaryActionButtonRenderers))},_render_body_error:function(a){if(!this.model.get("purged")){a.append($("
    "+this.model.get("misc_blurb")+"
    "))}a.append((_l("An error occurred with this dataset")+": "+$.trim(this.model.get("misc_info"))+""));a.append(this._render_primaryActionButtons(this.defaultPrimaryActionButtonRenderers.concat([this._render_downloadButton])))},_render_body_discarded:function(a){a.append("
    "+_l("The job creating this dataset was cancelled before completion")+".
    ");a.append(this._render_primaryActionButtons(this.defaultPrimaryActionButtonRenderers))},_render_body_setting_metadata:function(a){a.append($("
    "+_l("Metadata is being auto-detected")+".
    "))},_render_body_empty:function(a){a.append($("
    "+_l("No data")+": "+this.model.get("misc_blurb")+"
    "));a.append(this._render_primaryActionButtons(this.defaultPrimaryActionButtonRenderers))},_render_body_failed_metadata:function(a){a.append($(HDABaseView.templates.failedMetadata(_.extend(this.model.toJSON(),{urls:this.urls}))));this._render_body_ok(a)},_render_body_ok:function(a){a.append(this._render_hdaSummary());if(this.model.isDeletedOrPurged()){a.append(this._render_primaryActionButtons([this._render_downloadButton,this._render_showParamsButton]));return}a.append(this._render_primaryActionButtons([this._render_downloadButton,this._render_showParamsButton]));a.append('
    ');a.append(this._render_displayAppArea());this._render_displayApps(a);a.append(this._render_peek())},events:{"click .historyItemTitle":"toggleBodyVisibility"},toggleBodyVisibility:function(c,a){var b=this;this.expanded=(a===undefined)?(!this.body.is(":visible")):(a);if(this.expanded){b._render_body_html(b.body);this.body.slideDown("fast",function(){b.trigger("body-expanded",b.model.get("id"))})}else{this.body.slideUp("fast",function(){b.trigger("body-collapsed",b.model.get("id"))})}},remove:function(b){var a=this;this.$el.fadeOut("fast",function(){a.$el.remove();if(b){b()}})},toString:function(){var a=(this.model)?(this.model+""):("(no model)");return"HDABaseView("+a+")"}});HDABaseView.templates={warningMsg:Handlebars.templates["template-warningmessagesmall"],messages:Handlebars.templates["template-hda-warning-messages"],titleLink:Handlebars.templates["template-hda-titleLink"],hdaSummary:Handlebars.templates["template-hda-hdaSummary"],downloadLinks:Handlebars.templates["template-hda-downloadLinks"],failedMetadata:Handlebars.templates["template-hda-failedMetadata"],displayApps:Handlebars.templates["template-hda-displayApps"]}; \ No newline at end of file diff --git a/static/scripts/packed/mvc/dataset/hda-edit.js b/static/scripts/packed/mvc/dataset/hda-edit.js index 9a9b1b9523c..729cc7617a8 100644 --- a/static/scripts/packed/mvc/dataset/hda-edit.js +++ b/static/scripts/packed/mvc/dataset/hda-edit.js @@ -1 +1 @@ -var HDAEditView=HDABaseView.extend(LoggableMixin).extend({initialize:function(a){HDABaseView.prototype.initialize.call(this,a);this.defaultPrimaryActionButtonRenderers=[this._render_showParamsButton,this._render_rerunButton]},_setUpBehaviors:function(c){HDABaseView.prototype._setUpBehaviors.call(this,c);var a=this,b=this.urls.purge,d=c.find("#historyItemPurger-"+this.model.get("id"));if(d){d.attr("href",["javascript","void(0)"].join(":"));d.click(function(e){var f=jQuery.ajax(b);f.success(function(i,g,h){a.model.set("purged",true)});f.error(function(h,g,i){alert("("+h.status+") Unable to purge this dataset:\n"+i)})})}},_render_warnings:function(){return $(jQuery.trim(HDABaseView.templates.messages(_.extend(this.model.toJSON(),{urls:this.urls}))))},_render_titleButtons:function(){var a=$('
    ');a.append(this._render_displayButton());a.append(this._render_editButton());a.append(this._render_deleteButton());return a},_render_editButton:function(){if((this.model.get("state")===HistoryDatasetAssociation.STATES.NEW)||(this.model.get("state")===HistoryDatasetAssociation.STATES.UPLOAD)||(this.model.get("state")===HistoryDatasetAssociation.STATES.NOT_VIEWABLE)||(!this.model.get("accessible"))){this.editButton=null;return null}var c=this.model.get("purged"),a=this.model.get("deleted"),b={title:_l("Edit Attributes"),href:this.urls.edit,target:"galaxy_main",icon_class:"edit"};if(a||c){b.enabled=false;if(c){b.title=_l("Cannot edit attributes of datasets removed from disk")}else{if(a){b.title=_l("Undelete dataset to edit attributes")}}}this.editButton=new IconButtonView({model:new IconButton(b)});return this.editButton.render().$el},_render_deleteButton:function(){if((this.model.get("state")===HistoryDatasetAssociation.STATES.NEW)||(this.model.get("state")===HistoryDatasetAssociation.STATES.NOT_VIEWABLE)||(!this.model.get("accessible"))){this.deleteButton=null;return null}var a=this,b=a.urls["delete"],c={title:_l("Delete"),href:b,id:"historyItemDeleter-"+this.model.get("id"),icon_class:"delete",on_click:function(){$.ajax({url:b,type:"POST",error:function(){a.$el.show()},success:function(){a.model.set({deleted:true})}})}};if(this.model.get("deleted")||this.model.get("purged")){c={title:_l("Dataset is already deleted"),icon_class:"delete",enabled:false}}this.deleteButton=new IconButtonView({model:new IconButton(c)});return this.deleteButton.render().$el},_render_hdaSummary:function(){var a=_.extend(this.model.toJSON(),{urls:this.urls});if(this.model.get("metadata_dbkey")==="?"&&!this.model.isDeletedOrPurged()){_.extend(a,{dbkey_unknown_and_editable:true})}return HDABaseView.templates.hdaSummary(a)},_render_errButton:function(){if(this.model.get("state")!==HistoryDatasetAssociation.STATES.ERROR){this.errButton=null;return null}this.errButton=new IconButtonView({model:new IconButton({title:_l("View or report this error"),href:this.urls.report_error,target:"galaxy_main",icon_class:"bug"})});return this.errButton.render().$el},_render_rerunButton:function(){this.rerunButton=new IconButtonView({model:new IconButton({title:_l("Run this job again"),href:this.urls.rerun,target:"galaxy_main",icon_class:"arrow-circle"})});return this.rerunButton.render().$el},_render_visualizationsButton:function(){var c=this.model.get("dbkey"),a=this.model.get("visualizations"),f=this.urls.visualization,d={},g={dataset_id:this.model.get("id"),hda_ldda:"hda"};if(c){g.dbkey=c}if(!(this.model.hasData())||!(a&&a.length)||!(f)){this.visualizationsButton=null;return null}this.visualizationsButton=new IconButtonView({model:new IconButton({title:_l("Visualize"),href:f,icon_class:"chart_curve"})});var b=this.visualizationsButton.render().$el;b.addClass("visualize-icon");function e(h){switch(h){case"trackster":return create_trackster_action_fn(f,g,c);case"scatterplot":return create_scatterplot_action_fn(f,g);default:return function(){window.parent.location=f+"/"+h+"?"+$.param(g)}}}if(a.length===1){b.attr("title",a[0]);b.click(e(a[0]))}else{_.each(a,function(i){var h=i.charAt(0).toUpperCase()+i.slice(1);d[_l(h)]=e(i)});make_popupmenu(b,d)}return b},_render_secondaryActionButtons:function(b){var c=$("
    "),a=this;c.attr("style","float: right;").attr("id","secondary-actions-"+this.model.get("id"));_.each(b,function(d){c.append(d.call(a))});return c},_render_tagButton:function(){if(!(this.model.hasData())||(!this.urls.tags.get)){this.tagButton=null;return null}this.tagButton=new IconButtonView({model:new IconButton({title:_l("Edit dataset tags"),target:"galaxy_main",href:this.urls.tags.get,icon_class:"tags"})});return this.tagButton.render().$el},_render_annotateButton:function(){if(!(this.model.hasData())||(!this.urls.annotation.get)){this.annotateButton=null;return null}this.annotateButton=new IconButtonView({model:new IconButton({title:_l("Edit dataset annotation"),target:"galaxy_main",icon_class:"annotate"})});return this.annotateButton.render().$el},_render_tagArea:function(){if(!this.urls.tags.set){return null}return $(HDAEditView.templates.tagArea(_.extend(this.model.toJSON(),{urls:this.urls})))},_render_annotationArea:function(){if(!this.urls.annotation.get){return null}return $(HDAEditView.templates.annotationArea(_.extend(this.model.toJSON(),{urls:this.urls})))},_render_body_error:function(a){HDABaseView.prototype._render_body_error.call(this,a);var b=a.find("#primary-actions-"+this.model.get("id"));b.prepend(this._render_errButton())},_render_body_ok:function(a){a.append(this._render_hdaSummary());if(this.model.isDeletedOrPurged()){a.append(this._render_primaryActionButtons([this._render_downloadButton,this._render_showParamsButton,this._render_rerunButton]));return}a.append(this._render_primaryActionButtons([this._render_downloadButton,this._render_showParamsButton,this._render_rerunButton,this._render_visualizationsButton]));a.append(this._render_secondaryActionButtons([this._render_tagButton,this._render_annotateButton]));a.append('
    ');a.append(this._render_tagArea());a.append(this._render_annotationArea());a.append(this._render_displayApps());a.append(this._render_peek())},events:{"click .historyItemTitle":"toggleBodyVisibility","click a.icon-button.tags":"loadAndDisplayTags","click a.icon-button.annotate":"loadAndDisplayAnnotation"},loadAndDisplayTags:function(b){this.log(this+".loadAndDisplayTags",b);var c=this.$el.find(".tag-area"),a=c.find(".tag-elt");if(c.is(":hidden")){if(!jQuery.trim(a.html())){$.ajax({url:this.urls.tags.get,error:function(){alert(_l("Tagging failed"))},success:function(d){a.html(d);a.find(".tooltip").tooltip();c.slideDown("fast")}})}else{c.slideDown("fast")}}else{c.slideUp("fast")}return false},loadAndDisplayAnnotation:function(b){this.log(this+".loadAndDisplayAnnotation",b);var d=this.$el.find(".annotation-area"),c=d.find(".annotation-elt"),a=this.urls.annotation.set;if(d.is(":hidden")){if(!jQuery.trim(c.html())){$.ajax({url:this.urls.annotation.get,error:function(){alert(_l("Annotations failed"))},success:function(e){if(e===""){e=""+_l("Describe or add notes to dataset")+""}c.html(e);d.find(".tooltip").tooltip();async_save_text(c.attr("id"),c.attr("id"),a,"new_annotation",18,true,4);d.slideDown("fast")}})}else{d.slideDown("fast")}}else{d.slideUp("fast")}return false},toString:function(){var a=(this.model)?(this.model+""):("(no model)");return"HDAView("+a+")"}});HDAEditView.templates={tagArea:Handlebars.templates["template-hda-tagArea"],annotationArea:Handlebars.templates["template-hda-annotationArea"]};function create_scatterplot_action_fn(a,b){action=function(){var d=$(window.parent.document).find("iframe#galaxy_main"),c=a+"/scatterplot?"+$.param(b);d.attr("src",c);$("div.popmenu-wrapper").remove();return false};return action}function create_trackster_action_fn(a,c,b){return function(){var d={};if(b){d["f-dbkey"]=b}$.ajax({url:a+"/list_tracks?"+$.param(d),dataType:"html",error:function(){alert(_l("Could not add this dataset to browser")+".")},success:function(e){var f=window.parent;f.show_modal(_l("View Data in a New or Saved Visualization"),"",{Cancel:function(){f.hide_modal()},"View in saved visualization":function(){f.show_modal(_l("Add Data to Saved Visualization"),e,{Cancel:function(){f.hide_modal()},"Add to visualization":function(){$(f.document).find("input[name=id]:checked").each(function(){var g=$(this).val();c.id=g;f.location=a+"/trackster?"+$.param(c)})}})},"View in new visualization":function(){f.location=a+"/trackster?"+$.param(c)}})}});return false}}; \ No newline at end of file +var HDAEditView=HDABaseView.extend(LoggableMixin).extend({initialize:function(a){HDABaseView.prototype.initialize.call(this,a);this.defaultPrimaryActionButtonRenderers=[this._render_showParamsButton,this._render_rerunButton]},_setUpBehaviors:function(c){HDABaseView.prototype._setUpBehaviors.call(this,c);var a=this,b=this.urls.purge,d=c.find("#historyItemPurger-"+this.model.get("id"));if(d){d.attr("href",["javascript","void(0)"].join(":"));d.click(function(e){var f=jQuery.ajax(b);f.success(function(i,g,h){a.model.set("purged",true)});f.error(function(h,g,i){alert("("+h.status+") Unable to purge this dataset:\n"+i)})})}},_render_warnings:function(){return $(jQuery.trim(HDABaseView.templates.messages(_.extend(this.model.toJSON(),{urls:this.urls}))))},_render_titleButtons:function(){var a=$('
    ');a.append(this._render_displayButton());a.append(this._render_editButton());a.append(this._render_deleteButton());return a},_render_editButton:function(){if((this.model.get("state")===HistoryDatasetAssociation.STATES.NEW)||(this.model.get("state")===HistoryDatasetAssociation.STATES.UPLOAD)||(this.model.get("state")===HistoryDatasetAssociation.STATES.NOT_VIEWABLE)||(!this.model.get("accessible"))){this.editButton=null;return null}var c=this.model.get("purged"),a=this.model.get("deleted"),b={title:_l("Edit Attributes"),href:this.urls.edit,target:"galaxy_main",icon_class:"edit"};if(a||c){b.enabled=false;if(c){b.title=_l("Cannot edit attributes of datasets removed from disk")}else{if(a){b.title=_l("Undelete dataset to edit attributes")}}}this.editButton=new IconButtonView({model:new IconButton(b)});return this.editButton.render().$el},_render_deleteButton:function(){if((this.model.get("state")===HistoryDatasetAssociation.STATES.NEW)||(this.model.get("state")===HistoryDatasetAssociation.STATES.NOT_VIEWABLE)||(!this.model.get("accessible"))){this.deleteButton=null;return null}var a=this,b=a.urls["delete"],c={title:_l("Delete"),href:b,id:"historyItemDeleter-"+this.model.get("id"),icon_class:"delete",on_click:function(){$.ajax({url:b,type:"POST",error:function(){a.$el.show()},success:function(){a.model.set({deleted:true})}})}};if(this.model.get("deleted")||this.model.get("purged")){c={title:_l("Dataset is already deleted"),icon_class:"delete",enabled:false}}this.deleteButton=new IconButtonView({model:new IconButton(c)});return this.deleteButton.render().$el},_render_hdaSummary:function(){var a=_.extend(this.model.toJSON(),{urls:this.urls});if(this.model.get("metadata_dbkey")==="?"&&!this.model.isDeletedOrPurged()){_.extend(a,{dbkey_unknown_and_editable:true})}return HDABaseView.templates.hdaSummary(a)},_render_errButton:function(){if(this.model.get("state")!==HistoryDatasetAssociation.STATES.ERROR){this.errButton=null;return null}this.errButton=new IconButtonView({model:new IconButton({title:_l("View or report this error"),href:this.urls.report_error,target:"galaxy_main",icon_class:"bug"})});return this.errButton.render().$el},_render_rerunButton:function(){this.rerunButton=new IconButtonView({model:new IconButton({title:_l("Run this job again"),href:this.urls.rerun,target:"galaxy_main",icon_class:"arrow-circle"})});return this.rerunButton.render().$el},_render_visualizationsButton:function(){var c=this.model.get("dbkey"),a=this.model.get("visualizations"),f=this.urls.visualization,d={},g={dataset_id:this.model.get("id"),hda_ldda:"hda"};if(c){g.dbkey=c}if(!(this.model.hasData())||!(a&&a.length)||!(f)){this.visualizationsButton=null;return null}this.visualizationsButton=new IconButtonView({model:new IconButton({title:_l("Visualize"),href:f,icon_class:"chart_curve"})});var b=this.visualizationsButton.render().$el;b.addClass("visualize-icon");function e(h){switch(h){case"trackster":return create_trackster_action_fn(f,g,c);case"scatterplot":return create_scatterplot_action_fn(f,g);default:return function(){window.parent.location=f+"/"+h+"?"+$.param(g)}}}if(a.length===1){b.attr("title",a[0]);b.click(e(a[0]))}else{_.each(a,function(i){var h=i.charAt(0).toUpperCase()+i.slice(1);d[_l(h)]=e(i)});make_popupmenu(b,d)}return b},_render_secondaryActionButtons:function(b){var c=$("
    "),a=this;c.attr("style","float: right;").attr("id","secondary-actions-"+this.model.get("id"));_.each(b,function(d){c.append(d.call(a))});return c},_render_tagButton:function(){if(!(this.model.hasData())||(!this.urls.tags.get)){this.tagButton=null;return null}this.tagButton=new IconButtonView({model:new IconButton({title:_l("Edit dataset tags"),target:"galaxy_main",href:this.urls.tags.get,icon_class:"tags"})});return this.tagButton.render().$el},_render_annotateButton:function(){if(!(this.model.hasData())||(!this.urls.annotation.get)){this.annotateButton=null;return null}this.annotateButton=new IconButtonView({model:new IconButton({title:_l("Edit dataset annotation"),target:"galaxy_main",icon_class:"annotate"})});return this.annotateButton.render().$el},_render_tagArea:function(){if(!this.urls.tags.set){return null}return $(HDAEditView.templates.tagArea(_.extend(this.model.toJSON(),{urls:this.urls})))},_render_annotationArea:function(){if(!this.urls.annotation.get){return null}return $(HDAEditView.templates.annotationArea(_.extend(this.model.toJSON(),{urls:this.urls})))},_render_body_error:function(a){HDABaseView.prototype._render_body_error.call(this,a);var b=a.find("#primary-actions-"+this.model.get("id"));b.prepend(this._render_errButton())},_render_body_ok:function(a){a.append(this._render_hdaSummary());if(this.model.isDeletedOrPurged()){a.append(this._render_primaryActionButtons([this._render_downloadButton,this._render_showParamsButton,this._render_rerunButton]));return}a.append(this._render_primaryActionButtons([this._render_downloadButton,this._render_showParamsButton,this._render_rerunButton,this._render_visualizationsButton]));a.append(this._render_secondaryActionButtons([this._render_tagButton,this._render_annotateButton]));a.append('
    ');a.append(this._render_tagArea());a.append(this._render_annotationArea());a.append(this._render_displayAppArea());this._render_displayApps(a);a.append(this._render_peek())},events:{"click .historyItemTitle":"toggleBodyVisibility","click a.icon-button.tags":"loadAndDisplayTags","click a.icon-button.annotate":"loadAndDisplayAnnotation"},loadAndDisplayTags:function(b){this.log(this+".loadAndDisplayTags",b);var c=this.$el.find(".tag-area"),a=c.find(".tag-elt");if(c.is(":hidden")){if(!jQuery.trim(a.html())){$.ajax({url:this.urls.tags.get,error:function(){alert(_l("Tagging failed"))},success:function(d){a.html(d);a.find(".tooltip").tooltip();c.slideDown("fast")}})}else{c.slideDown("fast")}}else{c.slideUp("fast")}return false},loadAndDisplayAnnotation:function(b){this.log(this+".loadAndDisplayAnnotation",b);var d=this.$el.find(".annotation-area"),c=d.find(".annotation-elt"),a=this.urls.annotation.set;if(d.is(":hidden")){if(!jQuery.trim(c.html())){$.ajax({url:this.urls.annotation.get,error:function(){alert(_l("Annotations failed"))},success:function(e){if(e===""){e=""+_l("Describe or add notes to dataset")+""}c.html(e);d.find(".tooltip").tooltip();async_save_text(c.attr("id"),c.attr("id"),a,"new_annotation",18,true,4);d.slideDown("fast")}})}else{d.slideDown("fast")}}else{d.slideUp("fast")}return false},toString:function(){var a=(this.model)?(this.model+""):("(no model)");return"HDAView("+a+")"}});HDAEditView.templates={tagArea:Handlebars.templates["template-hda-tagArea"],annotationArea:Handlebars.templates["template-hda-annotationArea"]};function create_scatterplot_action_fn(a,b){action=function(){var d=$(window.parent.document).find("iframe#galaxy_main"),c=a+"/scatterplot?"+$.param(b);d.attr("src",c);$("div.popmenu-wrapper").remove();return false};return action}function create_trackster_action_fn(a,c,b){return function(){var d={};if(b){d["f-dbkey"]=b}$.ajax({url:a+"/list_tracks?"+$.param(d),dataType:"html",error:function(){alert(_l("Could not add this dataset to browser")+".")},success:function(e){var f=window.parent;f.show_modal(_l("View Data in a New or Saved Visualization"),"",{Cancel:function(){f.hide_modal()},"View in saved visualization":function(){f.show_modal(_l("Add Data to Saved Visualization"),e,{Cancel:function(){f.hide_modal()},"Add to visualization":function(){$(f.document).find("input[name=id]:checked").each(function(){var g=$(this).val();c.id=g;f.location=a+"/trackster?"+$.param(c)})}})},"View in new visualization":function(){f.location=a+"/trackster?"+$.param(c)}})}});return false}}; \ No newline at end of file diff --git a/static/scripts/packed/mvc/dataset/hda-model.js b/static/scripts/packed/mvc/dataset/hda-model.js index a3306064c40..58bb142e774 100644 --- a/static/scripts/packed/mvc/dataset/hda-model.js +++ b/static/scripts/packed/mvc/dataset/hda-model.js @@ -1 +1 @@ -var HistoryDatasetAssociation=BaseModel.extend(LoggableMixin).extend({defaults:{history_id:null,model_class:"HistoryDatasetAssociation",hid:0,id:null,name:"(unnamed dataset)",state:"ok",data_type:null,file_size:0,file_ext:"",meta_files:[],misc_blurb:"",misc_info:"",deleted:false,purged:false,visible:false,accessible:true},urlRoot:"api/histories/",url:function(){return"api/histories/"+this.get("history_id")+"/contents/"+this.get("id")},initialize:function(){this.log(this+".initialize",this.attributes);this.log("\tparent history_id: "+this.get("history_id"));if(!this.get("accessible")){this.set("state",HistoryDatasetAssociation.STATES.NOT_VIEWABLE)}this.on("change:state",function(b,a){this.log(this+" has changed state:",b,a);if(this.inReadyState()){this.trigger("state:ready",b,a,this.previous("state"))}})},isDeletedOrPurged:function(){return(this.get("deleted")||this.get("purged"))},isVisible:function(b,c){var a=true;if((!b)&&(this.get("deleted")||this.get("purged"))){a=false}if((!c)&&(!this.get("visible"))){a=false}return a},inReadyState:function(){var a=this.get("state");return(this.isDeletedOrPurged()||(a===HistoryDatasetAssociation.STATES.OK)||(a===HistoryDatasetAssociation.STATES.EMPTY)||(a===HistoryDatasetAssociation.STATES.FAILED_METADATA)||(a===HistoryDatasetAssociation.STATES.NOT_VIEWABLE)||(a===HistoryDatasetAssociation.STATES.DISCARDED)||(a===HistoryDatasetAssociation.STATES.ERROR))},hasData:function(){return(this.get("file_size")>0)},toString:function(){var a=this.get("id")||"";if(this.get("name")){a+=':"'+this.get("name")+'"'}return"HistoryDatasetAssociation("+a+")"}});HistoryDatasetAssociation.STATES={UPLOAD:"upload",QUEUED:"queued",PAUSED:"paused",RUNNING:"running",SETTING_METADATA:"setting_metadata",NEW:"new",EMPTY:"empty",OK:"ok",FAILED_METADATA:"failed_metadata",NOT_VIEWABLE:"noPermission",DISCARDED:"discarded",ERROR:"error"};var HDACollection=Backbone.Collection.extend(LoggableMixin).extend({model:HistoryDatasetAssociation,initialize:function(){},ids:function(){return this.map(function(a){return a.id})},getByHid:function(a){return _.first(this.filter(function(b){return b.get("hid")===a}))},hidToCollectionIndex:function(a){if(!a){return this.models.length}var d=this.models.length-1;for(var b=d;b>=0;b--){var c=this.at(b).get("hid");if(c==a){return b}if(c0)},toString:function(){var a=this.get("id")||"";if(this.get("name")){a=this.get("hid")+' :"'+this.get("name")+'",'+a}return"HDA("+a+")"}});HistoryDatasetAssociation.STATES={UPLOAD:"upload",QUEUED:"queued",PAUSED:"paused",RUNNING:"running",SETTING_METADATA:"setting_metadata",NEW:"new",EMPTY:"empty",OK:"ok",FAILED_METADATA:"failed_metadata",NOT_VIEWABLE:"noPermission",DISCARDED:"discarded",ERROR:"error"};var HDACollection=Backbone.Collection.extend(LoggableMixin).extend({model:HistoryDatasetAssociation,initialize:function(){},ids:function(){return this.map(function(a){return a.id})},getByHid:function(a){return _.first(this.filter(function(b){return b.get("hid")===a}))},hidToCollectionIndex:function(a){if(!a){return this.models.length}var d=this.models.length-1;for(var b=d;b>=0;b--){var c=this.at(b).get("hid");if(c==a){return b}if(c"),a=this.model.toJSON(),e=(this.$el.children().size()===0);a.urls=this._renderUrls(a);c.append(HistoryPanel.templates.historyPanel(a));c.find(".tooltip").tooltip({placement:"bottom"});if(!this.model.hdas.length||!this.renderItems(c.find("#"+this.model.get("id")+"-datasets"))){c.find("#emptyHistoryMessage").show()}$(b).queue(d,function(f){b.$el.fadeOut("fast",function(){f()})});$(b).queue(d,function(f){b.$el.html("");b.$el.append(c.children());b.$el.fadeIn("fast",function(){f()})});$(b).queue(d,function(f){this.log(b+" rendered:",b.$el);b._setUpBehaviours();if(e){b.trigger("rendered:initial")}else{b.trigger("rendered")}f()});$(b).dequeue(d);return this},_renderUrls:function(a){var b=this;b.urls={};_.each(this.urlTemplates,function(d,c){b.urls[c]=_.template(d,a)});return b.urls},renderItems:function(b){this.hdaViews={};var a=this,c=this.model.hdas.getVisible(this.storage.get("show_deleted"),this.storage.get("show_hidden"));_.each(c,function(f){var e=f.get("id"),d=a.storage.get("expandedHdas").get(e);a.hdaViews[e]=new a.HDAView({model:f,expanded:d,urlTemplates:a.hdaUrlTemplates,logger:a.logger});a._setUpHdaListeners(a.hdaViews[e]);b.prepend(a.hdaViews[e].render().$el)});return c.length},_setUpHdaListeners:function(b){var a=this;b.bind("body-expanded",function(c){a.storage.get("expandedHdas").set(c,true)});b.bind("body-collapsed",function(c){a.storage.get("expandedHdas").deleteKey(c)})},_setUpBehaviours:function(){if(!(this.model.get("user")&&this.model.get("user").email)){return}var a=this.$("#history-annotation-area");this.$("#history-annotate").click(function(){if(a.is(":hidden")){a.slideDown("fast")}else{a.slideUp("fast")}return false});async_save_text("history-name-container","history-name",this.urls.rename,"new_name",18);async_save_text("history-annotation-container","history-annotation",this.urls.annotate,"new_annotation",18,true,4)},updateHistoryDiskSize:function(){this.$el.find("#history-size").text(this.model.get("nice_size"))},showQuotaMessage:function(){var a=this.$el.find("#quota-message-container");if(a.is(":hidden")){a.slideDown("fast")}},hideQuotaMessage:function(){var a=this.$el.find("#quota-message-container");if(!a.is(":hidden")){a.slideUp("fast")}},toggleShowDeleted:function(){this.storage.set("show_deleted",!this.storage.get("show_deleted"));this.render();return this.storage.get("show_deleted")},toggleShowHidden:function(){this.storage.set("show_hidden",!this.storage.get("show_hidden"));this.render();return this.storage.get("show_hidden")},collapseAllHdaBodies:function(){_.each(this.hdaViews,function(a){a.toggleBodyVisibility(null,false)});this.storage.set("expandedHdas",{})},loadAndDisplayTags:function(c){this.log(this+".loadAndDisplayTags",c);var d=this.$el.find("#history-tag-area"),b=d.find(".tag-elt");this.log("\t tagArea",d," tagElt",b);if(d.is(":hidden")){if(!jQuery.trim(b.html())){var a=this;$.ajax({url:a.urls.tag,error:function(){alert(_l("Tagging failed"))},success:function(e){b.html(e);b.find(".tooltip").tooltip();d.slideDown("fast")}})}else{d.slideDown("fast")}}else{d.slideUp("fast")}return false},toString:function(){var a=this.model.get("name")||"";return"HistoryPanel("+a+")"}});HistoryPanel.templates={historyPanel:Handlebars.templates["template-history-historyPanel"]}; \ No newline at end of file +var HistoryPanel=BaseView.extend(LoggableMixin).extend({el:"body.historyPage",HDAView:HDAEditView,events:{"click #history-tag":"loadAndDisplayTags"},initialize:function(a){if(a.logger){this.logger=this.model.logger=a.logger}this.log(this+".initialize:",a);if(!a.urlTemplates){throw (this+" needs urlTemplates on initialize")}if(!a.urlTemplates.history){throw (this+" needs urlTemplates.history on initialize")}if(!a.urlTemplates.hda){throw (this+" needs urlTemplates.hda on initialize")}this.urlTemplates=a.urlTemplates.history;this.hdaUrlTemplates=a.urlTemplates.hda;this._setUpWebStorage(a.initiallyExpanded,a.show_deleted,a.show_hidden);this.model.bind("change:nice_size",this.updateHistoryDiskSize,this);this.model.hdas.bind("add",this.add,this);this.model.hdas.bind("reset",this.addAll,this);this.model.hdas.bind("change:deleted",this.handleHdaDeletionChange,this);this.model.hdas.bind("state:ready",function(c,d,b){if((!c.get("visible"))&&(!this.storage.get("show_hidden"))){this.removeHdaView(c.get("id"))}},this);this.hdaViews={};this.urls={}},_setUpWebStorage:function(b,a,c){this.storage=new PersistantStorage("HistoryView."+this.model.get("id"),{expandedHdas:{},show_deleted:false,show_hidden:false});this.log(this+" (prev) storage:",JSON.stringify(this.storage.get(),null,2));if(b){this.storage.set("exandedHdas",b)}if((a===true)||(a===false)){this.storage.set("show_deleted",a)}if((c===true)||(c===false)){this.storage.set("show_hidden",c)}this.show_deleted=this.storage.get("show_deleted");this.show_hidden=this.storage.get("show_hidden");this.log(this+" (init'd) storage:",this.storage.get())},add:function(a){this.render()},addAll:function(){this.render()},handleHdaDeletionChange:function(a){if(a.get("deleted")&&!this.storage.get("show_deleted")){this.removeHdaView(a.get("id"))}},removeHdaView:function(c,b){var a=this.hdaViews[c];if(!a){return}a.remove(b);delete this.hdaViews[c];if(_.isEmpty(this.hdaViews)){this.render()}},render:function(){var b=this,d=b.toString()+".set-up",c=$("
    "),a=this.model.toJSON(),e=(this.$el.children().size()===0);a.urls=this._renderUrls(a);c.append(HistoryPanel.templates.historyPanel(a));c.find(".tooltip").tooltip({placement:"bottom"});if(!this.model.hdas.length||!this.renderItems(c.find("#"+this.model.get("id")+"-datasets"))){c.find("#emptyHistoryMessage").show()}$(b).queue(d,function(f){b.$el.fadeOut("fast",function(){f()})});$(b).queue(d,function(f){b.$el.html("");b.$el.append(c.children());b.$el.fadeIn("fast",function(){f()})});$(b).queue(d,function(f){this.log(b+" rendered:",b.$el);b._setUpBehaviours();if(e){b.trigger("rendered:initial")}else{b.trigger("rendered")}f()});$(b).dequeue(d);return this},_renderUrls:function(a){var b=this;b.urls={};_.each(this.urlTemplates,function(d,c){b.urls[c]=_.template(d,a)});return b.urls},renderItems:function(b){this.hdaViews={};var a=this,c=this.model.hdas.getVisible(this.storage.get("show_deleted"),this.storage.get("show_hidden"));_.each(c,function(f){var e=f.get("id"),d=a.storage.get("expandedHdas").get(e);a.hdaViews[e]=new a.HDAView({model:f,expanded:d,urlTemplates:a.hdaUrlTemplates,logger:a.logger});a._setUpHdaListeners(a.hdaViews[e]);b.prepend(a.hdaViews[e].render().$el)});return c.length},_setUpHdaListeners:function(b){var a=this;b.bind("body-expanded",function(c){a.storage.get("expandedHdas").set(c,true)});b.bind("body-collapsed",function(c){a.storage.get("expandedHdas").deleteKey(c)})},_setUpBehaviours:function(){if(!(this.model.get("user")&&this.model.get("user").email)){return}var a=this.$("#history-annotation-area");this.$("#history-annotate").click(function(){if(a.is(":hidden")){a.slideDown("fast")}else{a.slideUp("fast")}return false});async_save_text("history-name-container","history-name",this.urls.rename,"new_name",18);async_save_text("history-annotation-container","history-annotation",this.urls.annotate,"new_annotation",18,true,4)},updateHistoryDiskSize:function(){this.$el.find("#history-size").text(this.model.get("nice_size"))},showQuotaMessage:function(){var a=this.$el.find("#quota-message-container");if(a.is(":hidden")){a.slideDown("fast")}},hideQuotaMessage:function(){var a=this.$el.find("#quota-message-container");if(!a.is(":hidden")){a.slideUp("fast")}},toggleShowDeleted:function(){this.storage.set("show_deleted",!this.storage.get("show_deleted"));this.render();return this.storage.get("show_deleted")},toggleShowHidden:function(){this.storage.set("show_hidden",!this.storage.get("show_hidden"));this.render();return this.storage.get("show_hidden")},collapseAllHdaBodies:function(){_.each(this.hdaViews,function(a){a.toggleBodyVisibility(null,false)});this.storage.set("expandedHdas",{})},loadAndDisplayTags:function(c){this.log(this+".loadAndDisplayTags",c);var d=this.$el.find("#history-tag-area"),b=d.find(".tag-elt");this.log("\t tagArea",d," tagElt",b);if(d.is(":hidden")){if(!jQuery.trim(b.html())){var a=this;$.ajax({url:a.urls.tag,error:function(){alert(_l("Tagging failed"))},success:function(e){b.html(e);b.find(".tooltip").tooltip();d.slideDown("fast")}})}else{d.slideDown("fast")}}else{d.slideUp("fast")}return false},toString:function(){var a=this.model.get("name")||"";return"HistoryPanel("+a+")"}});HistoryPanel.templates={historyPanel:Handlebars.templates["template-history-historyPanel"]}; \ No newline at end of file diff --git a/static/scripts/packed/viz/trackster/tracks.js b/static/scripts/packed/viz/trackster/tracks.js index c3b2d643553..e1e8d4c943a 100644 --- a/static/scripts/packed/viz/trackster/tracks.js +++ b/static/scripts/packed/viz/trackster/tracks.js @@ -1 +1 @@ -define(["libs/underscore","viz/visualization","viz/trackster/util","viz/trackster/slotting","viz/trackster/painters","mvc/data","viz/trackster/filters"],function(ab,x,l,u,L,Y,i){var q=ab.extend;var V=function(ac){return("isResolved" in ac)};var n={};var k=function(ac,ad){n[ac.attr("id")]=ad};var m=function(ac,ae,ag,af){ag=".group";var ad={};n[ac.attr("id")]=af;ac.bind("drag",{handle:"."+ae,relative:true},function(ao,ap){var an=$(this),at=$(this).parent(),ak=at.children(),am=n[$(this).attr("id")],aj,ai,aq,ah,al;ai=$(this).parents(ag);if(ai.length!==0){aq=ai.position().top;ah=aq+ai.outerHeight();if(ap.offsetYah){$(this).insertAfter(ai);var ar=n[ai.attr("id")];ar.remove_drawable(am);ar.container.add_drawable(am);return}}}ai=null;for(al=0;al=aq&&ap.offsetY<=ah){if(ap.offsetY-aq0?al-1:al))}}}).bind("dragstart",function(){ad["border-top"]=ac.css("border-top");ad["border-bottom"]=ac.css("border-bottom");$(this).css({"border-top":"1px solid blue","border-bottom":"1px solid blue"})}).bind("dragend",function(){$(this).css(ad)})};var aa=16,G=9,D=20,A=100,I=12000,S=400,K=5000,w=100,o="Cannot display dataset due to an error. ",J="A converter for this dataset is not installed. Please check your datatypes_conf.xml file.",E="No data for this chrom/contig.",v="Preparing data. This can take a while for a large dataset. If the visualization is saved and closed, preparation will continue in the background.",y="Tool cannot be rerun: ",a="Loading data...",U="Ready for display",Q=10,H=20;function W(ad,ac){if(!ac){ac=0}var ae=Math.pow(10,ac);return Math.round(ad*ae)/ae}var r=function(ad,ac,af){if(!r.id_counter){r.id_counter=0}this.id=r.id_counter++;this.name=af.name;this.view=ad;this.container=ac;this.config=new F({track:this,params:[{key:"name",label:"Name",type:"text",default_value:this.name}],saved_values:af.prefs,onchange:function(){this.track.set_name(this.track.config.values.name)}});this.prefs=this.config.values;this.drag_handle_class=af.drag_handle_class;this.is_overview=false;this.action_icons={};this.content_visible=true;this.container_div=this.build_container_div();this.header_div=this.build_header_div();if(this.header_div){this.container_div.append(this.header_div);this.icons_div=$("
    ").css("float","left").hide().appendTo(this.header_div);this.build_action_icons(this.action_icons_def);this.header_div.append($("
    "));this.header_div.dblclick(function(ag){ag.stopPropagation()});var ae=this;this.container_div.hover(function(){ae.icons_div.show()},function(){ae.icons_div.hide()});$("
    ").appendTo(this.container_div)}};r.prototype.action_icons_def=[{name:"toggle_icon",title:"Hide/show content",css_class:"toggle",on_click_fn:function(ac){if(ac.content_visible){ac.action_icons.toggle_icon.addClass("toggle-expand").removeClass("toggle");ac.hide_contents();ac.content_visible=false}else{ac.action_icons.toggle_icon.addClass("toggle").removeClass("toggle-expand");ac.content_visible=true;ac.show_contents()}}},{name:"settings_icon",title:"Edit settings",css_class:"settings-icon",on_click_fn:function(ad){var af=function(){hide_modal();$(window).unbind("keypress.check_enter_esc")},ac=function(){ad.config.update_from_form($(".dialog-box"));hide_modal();$(window).unbind("keypress.check_enter_esc")},ae=function(ag){if((ag.keyCode||ag.which)===27){af()}else{if((ag.keyCode||ag.which)===13){ac()}}};$(window).bind("keypress.check_enter_esc",ae);show_modal("Configure",ad.config.build_form(),{Cancel:af,OK:ac})}},{name:"remove_icon",title:"Remove",css_class:"remove-icon",on_click_fn:function(ac){$(".bs-tooltip").remove();ac.remove()}}];q(r.prototype,{init:function(){},changed:function(){this.view.changed()},can_draw:function(){if(this.enabled&&this.content_visible){return true}return false},request_draw:function(){},_draw:function(){},to_dict:function(){},set_name:function(ac){this.old_name=this.name;this.name=ac;this.name_div.text(this.name)},revert_name:function(){if(this.old_name){this.name=this.old_name;this.name_div.text(this.name)}},remove:function(){this.changed();this.container.remove_drawable(this);var ac=this.view;this.container_div.hide(0,function(){$(this).remove();ac.update_intro_div()})},build_container_div:function(){},build_header_div:function(){},add_action_icon:function(ad,ai,ah,ag,ac,af){var ae=this;this.action_icons[ad]=$("").attr("href","javascript:void(0);").attr("title",ai).addClass("icon-button").addClass(ah).tooltip().click(function(){ag(ae)}).appendTo(this.icons_div);if(af){this.action_icons[ad].hide()}},build_action_icons:function(ac){var ae;for(var ad=0;ad").addClass("content-div").attr("id","group_"+this.id+"_content_div").appendTo(this.container_div);k(this.container_div,this);k(this.content_div,this);m(this.container_div,this.drag_handle_class,".group",this);this.filters_manager=new i.FiltersManager(this);this.header_div.after(this.filters_manager.parent_div);this.saved_filters_managers=[];if("drawables" in af){this.unpack_drawables(af.drawables)}if("filters" in af){var ae=this.filters_manager;this.filters_manager=new i.FiltersManager(this,af.filters);ae.parent_div.replaceWith(this.filters_manager.parent_div);if(af.filters.visible){this.setup_multitrack_filtering()}}};q(P.prototype,r.prototype,z.prototype,{action_icons_def:[r.prototype.action_icons_def[0],r.prototype.action_icons_def[1],{name:"composite_icon",title:"Show composite track",css_class:"layers-stack",on_click_fn:function(ac){$(".bs-tooltip").remove();ac.show_composite_track()}},{name:"filters_icon",title:"Filters",css_class:"filters-icon",on_click_fn:function(ac){if(ac.filters_manager.visible()){ac.filters_manager.clear_filters();ac._restore_filter_managers()}else{ac.setup_multitrack_filtering();ac.request_draw(true)}ac.filters_manager.toggle()}},r.prototype.action_icons_def[2]],build_container_div:function(){var ac=$("
    ").addClass("group").attr("id","group_"+this.id);if(this.container){this.container.content_div.append(ac)}return ac},build_header_div:function(){var ac=$("
    ").addClass("track-header");ac.append($("
    ").addClass(this.drag_handle_class));this.name_div=$("
    ").addClass("track-name").text(this.name).appendTo(ac);return ac},hide_contents:function(){this.tiles_div.hide()},show_contents:function(){this.tiles_div.show();this.request_draw()},update_icons:function(){var ae=this.drawables.length;if(ae===0){this.action_icons.composite_icon.hide();this.action_icons.filters_icon.hide()}else{if(ae===1){if(this.drawables[0] instanceof f){this.action_icons.composite_icon.show()}this.action_icons.filters_icon.hide()}else{var al,ak,ai,ao=true,ag=this.drawables[0].get_type(),ac=0;for(al=0;al1&&ac===this.drawables.length){var ap={},ad;ai=this.drawables[0];for(ak=0;ak0){this.action_icons.filters_icon.show()}else{this.action_icons.filters_icon.hide()}}else{this.action_icons.filters_icon.hide()}}}},_restore_filter_managers:function(){for(var ac=0;ac0){this.saved_filters_managers=[];for(var ac=0;ac").addClass("top-container").appendTo(ae);this.browser_content_div=$("
    ").addClass("content").css("position","relative").appendTo(ae);this.bottom_container=$("
    ").addClass("bottom-container").appendTo(ae);this.top_labeltrack=$("
    ").addClass("top-labeltrack").appendTo(this.top_container);this.viewport_container=$("
    ").addClass("viewport-container").attr("id","viewport-container").appendTo(this.browser_content_div);this.content_div=this.viewport_container;k(this.viewport_container,ac);this.intro_div=$("
    ").addClass("intro").appendTo(this.viewport_container).hide();var af=$("
    ").text("Add Datasets to Visualization").addClass("action-button").appendTo(this.intro_div).click(function(){x.select_datasets(select_datasets_url,add_track_async_url,{"f-dbkey":ac.dbkey},function(ag){ab.each(ag,function(ah){ac.add_drawable(p(ah,ac,ac))})})});this.nav_labeltrack=$("
    ").addClass("nav-labeltrack").appendTo(this.bottom_container);this.nav_container=$("
    ").addClass("trackster-nav-container").prependTo(this.top_container);this.nav=$("
    ").addClass("trackster-nav").appendTo(this.nav_container);this.overview=$("
    ").addClass("overview").appendTo(this.bottom_container);this.overview_viewport=$("
    ").addClass("overview-viewport").appendTo(this.overview);this.overview_close=$("").attr("href","javascript:void(0);").attr("title","Close overview").addClass("icon-button overview-close tooltip").hide().appendTo(this.overview_viewport);this.overview_highlight=$("
    ").addClass("overview-highlight").hide().appendTo(this.overview_viewport);this.overview_box_background=$("
    ").addClass("overview-boxback").appendTo(this.overview_viewport);this.overview_box=$("
    ").addClass("overview-box").appendTo(this.overview_viewport);this.default_overview_height=this.overview_box.height();this.nav_controls=$("
    ").addClass("nav-controls").appendTo(this.nav);this.chrom_select=$("").addClass("nav-input").hide().bind("keyup focusout",ad).appendTo(this.nav_controls);this.location_span=$("").addClass("location").attr("original-title","Click to change location").tooltip({placement:"bottom"}).appendTo(this.nav_controls);this.location_span.click(function(){ac.location_span.hide();ac.chrom_select.hide();ac.nav_input.val(ac.chrom+":"+ac.low+"-"+ac.high);ac.nav_input.css("display","inline-block");ac.nav_input.select();ac.nav_input.focus();ac.nav_input.autocomplete({source:function(ai,ag){var aj=[],ah=$.map(ac.get_drawables(),function(ak){return ak.data_manager.search_features(ai.term).success(function(al){aj=aj.concat(al)})});$.when.apply($,ah).done(function(){ag($.map(aj,function(ak){return{label:ak[0],value:ak[1]}}))})}})});if(this.vis_id!==undefined){this.hidden_input=$("").attr("type","hidden").val(this.vis_id).appendTo(this.nav_controls)}this.zo_link=$("").attr("id","zoom-out").attr("title","Zoom out").tooltip({placement:"bottom"}).click(function(){ac.zoom_out();ac.request_redraw()}).appendTo(this.nav_controls);this.zi_link=$("").attr("id","zoom-in").attr("title","Zoom in").tooltip({placement:"bottom"}).click(function(){ac.zoom_in();ac.request_redraw()}).appendTo(this.nav_controls);this.load_chroms_deferred=this.load_chroms({low:0});this.chrom_select.bind("change",function(){ac.change_chrom(ac.chrom_select.val())});this.browser_content_div.click(function(ag){$(this).find("input").trigger("blur")});this.browser_content_div.bind("dblclick",function(ag){ac.zoom_in(ag.pageX,this.viewport_container)});this.overview_box.bind("dragstart",function(ag,ah){this.current_x=ah.offsetX}).bind("drag",function(ag,ai){var aj=ai.offsetX-this.current_x;this.current_x=ai.offsetX;var ah=Math.round(aj/ac.viewport_container.width()*(ac.max_high-ac.max_low));ac.move_delta(-ah)});this.overview_close.click(function(){ac.reset_overview()});this.viewport_container.bind("draginit",function(ag,ah){if(ag.clientX>ac.viewport_container.width()-16){return false}}).bind("dragstart",function(ag,ah){ah.original_low=ac.low;ah.current_height=ag.clientY;ah.current_x=ah.offsetX}).bind("drag",function(ai,ak){var ag=$(this);var al=ak.offsetX-ak.current_x;var ah=ag.scrollTop()-(ai.clientY-ak.current_height);ag.scrollTop(ah);ak.current_height=ai.clientY;ak.current_x=ak.offsetX;var aj=Math.round(al/ac.viewport_container.width()*(ac.high-ac.low));ac.move_delta(aj)}).bind("mousewheel",function(ai,ak,ah,ag){if(ah){ah*=50;var aj=Math.round(-ah/ac.viewport_container.width()*(ac.high-ac.low));ac.move_delta(aj)}});this.top_labeltrack.bind("dragstart",function(ag,ah){return $("
    ").css({height:ac.browser_content_div.height()+ac.top_labeltrack.height()+ac.nav_labeltrack.height()+1,top:"0px",position:"absolute","background-color":"#ccf",opacity:0.5,"z-index":1000}).appendTo($(this))}).bind("drag",function(ak,al){$(al.proxy).css({left:Math.min(ak.pageX,al.startX)-ac.container.offset().left,width:Math.abs(ak.pageX-al.startX)});var ah=Math.min(ak.pageX,al.startX)-ac.container.offset().left,ag=Math.max(ak.pageX,al.startX)-ac.container.offset().left,aj=(ac.high-ac.low),ai=ac.viewport_container.width();ac.update_location(Math.round(ah/ai*aj)+ac.low,Math.round(ag/ai*aj)+ac.low)}).bind("dragend",function(al,am){var ah=Math.min(al.pageX,am.startX),ag=Math.max(al.pageX,am.startX),aj=(ac.high-ac.low),ai=ac.viewport_container.width(),ak=ac.low;ac.low=Math.round(ah/ai*aj)+ak;ac.high=Math.round(ag/ai*aj)+ak;$(am.proxy).remove();ac.request_redraw()});this.add_label_track(new X(this,{content_div:this.top_labeltrack}));this.add_label_track(new X(this,{content_div:this.nav_labeltrack}));$(window).bind("resize",function(){if(this.resize_timer){clearTimeout(this.resize_timer)}this.resize_timer=setTimeout(function(){ac.resize_window()},500)});$(document).bind("redraw",function(){ac.redraw()});this.reset();$(window).trigger("resize")},get_base_color:function(ac){return this.config.values[ac.toLowerCase()+"_color"]}});q(Z.prototype,z.prototype,{changed:function(){this.has_changes=true},update_intro_div:function(){if(this.drawables.length===0){this.intro_div.show()}else{this.intro_div.hide()}},trigger_navigate:function(ad,af,ac,ag){if(this.timer){clearTimeout(this.timer)}if(ag){var ae=this;this.timer=setTimeout(function(){ae.trigger("navigate",ad+":"+af+"-"+ac)},500)}else{view.trigger("navigate",ad+":"+af+"-"+ac)}},update_location:function(ac,ae){this.location_span.text(commatize(ac)+" - "+commatize(ae));this.nav_input.val(this.chrom+":"+commatize(ac)+"-"+commatize(ae));var ad=view.chrom_select.val();if(ad!==""){this.trigger_navigate(ad,view.low,view.high,true)}},load_chroms:function(ae){ae.num=w;var ac=this,ad=$.Deferred();$.ajax({url:chrom_url+"/"+this.dbkey,data:ae,dataType:"json",success:function(ag){if(ag.chrom_info.length===0){return}if(ag.reference){ac.add_label_track(new B(ac))}ac.chrom_data=ag.chrom_info;var aj='';for(var ai=0,af=ac.chrom_data.length;ai'+ah+""}if(ag.prev_chroms){aj+='"}if(ag.next_chroms){aj+='"}ac.chrom_select.html(aj);ac.chrom_start_index=ag.start_index;ad.resolve(ag.chrom_info)},error:function(){alert("Could not load chroms for this dbkey:",ac.dbkey)}});return ad},change_chrom:function(ah,ad,aj){var ae=this;if(!ae.chrom_data){ae.load_chroms_deferred.then(function(){ae.change_chrom(ah,ad,aj)});return}if(!ah||ah==="None"){return}if(ah==="previous"){ae.load_chroms({low:this.chrom_start_index-w});return}if(ah==="next"){ae.load_chroms({low:this.chrom_start_index+w});return}var ai=$.grep(ae.chrom_data,function(ak,al){return ak.chrom===ah})[0];if(ai===undefined){ae.load_chroms({chrom:ah},function(){ae.change_chrom(ah,ad,aj)});return}else{if(ah!==ae.chrom){ae.chrom=ah;ae.chrom_select.val(ae.chrom);ae.max_high=ai.len-1;ae.reset();ae.request_redraw(true);for(var ag=0,ac=ae.drawables.length;agac.max_high){ac.high=ac.max_high;ac.low=ac.max_high-ae}else{ac.high-=af;ac.low-=af}}ac.request_redraw();var ad=ac.chrom_select.val();this.trigger_navigate(ad,ac.low,ac.high,true)},add_drawable:function(ac){z.prototype.add_drawable.call(this,ac);ac.init();this.changed();this.update_intro_div()},add_label_track:function(ac){ac.view=this;ac.init();this.label_tracks.push(ac)},remove_drawable:function(ae,ad){z.prototype.remove_drawable.call(this,ae);if(ad){var ac=this;ae.container_div.hide(0,function(){$(this).remove();ac.update_intro_div()})}},reset:function(){this.low=this.max_low;this.high=this.max_high;this.viewport_container.find(".yaxislabel").remove()},request_redraw:function(ak,ac,aj,al){var ai=this,ah=(al?[al]:ai.drawables),ae;var ad;for(var ag=0;agthis.max_high){af=this.max_high}var al=this.high-this.low;if(this.high!==0&&al").addClass("dynamic-tool").hide();this.parent_div.bind("drag",function(au){au.stopPropagation()}).click(function(au){au.stopPropagation()}).bind("dblclick",function(au){au.stopPropagation()});var ao=$("
    ").appendTo(this.parent_div).text(this.name);var am=this.params;var ak=this;$.each(this.params,function(av,ay){var ax=$("
    ").addClass("param-row").appendTo(ak.parent_div);var au=$("
    ").addClass("param-label").text(ay.label).appendTo(ax);var aw=$("
    ").addClass("param-input").html(ay.html).appendTo(ax);aw.find(":input").val(ay.value);$("
    ").appendTo(ax)});this.parent_div.find("input").click(function(){$(this).select()});var at=$("
    ").addClass("param-row").appendTo(this.parent_div);var ai=$("").attr("value","Run on complete dataset").appendTo(at);var ac=$("").attr("value","Run on visible region").css("margin-left","3em").appendTo(at);ac.click(function(){ak.run_on_region()});ai.click(function(){ak.run_on_dataset()});if("visible" in af&&af.visible){this.parent_div.show()}};q(s.prototype,{update_params:function(){for(var ac=0;ac");var ae;function ad(ak,ag){for(var ao=0;ao").appendTo(ag);av.append($("").addClass("icon-button arrow-circle").appendTo(au).attr("title","Set new random color").tooltip(),ar=$("
    ").appendTo(au).hide(),aj=$("
    ").appendTo(ar),ah=$("
    ").appendTo(ar),al=$.farbtastic(aj,{width:100,height:100,callback:ap,color:at});au.append($("
    ").css("clear","both"));(function(aw){an.click(function(){aw.setColor(l.get_random_color())})})(al)}else{av.append($("").attr("id",ai).attr("name",ai).val(at))}}}}if(ae.help){av.append($("
    ").text(ae.help))}}}ad(this.params,ac);return ac},update_from_form:function(ac){var ae=this;var ad=false;$.each(this.params,function(af,ah){if(!ah.hidden){var ai="param_"+af;var ag=ac.find("#"+ai).val();if(ah.type==="float"){ag=parseFloat(ag)}else{if(ah.type==="int"){ag=parseInt(ag,10)}else{if(ah.type==="bool"){ag=ac.find("#"+ai).is(":checked")}}}if(ag!==ae.values[ah.key]){ae.values[ah.key]=ag;ad=true}}});if(ad){this.onchange();this.track.changed()}}});var b=function(ac,ag,ae,ad,af){this.track=ac;this.region=ag;this.low=ag.get("start");this.high=ag.get("end");this.resolution=ae;this.html_elt=$("
    ").append(ad).height($(ad).attr("height"));this.data=af;this.stale=false};b.prototype.predisplay_actions=function(){};var j=function(ac,ah,ae,ad,af,ag){b.call(this,ac,ah,ae,ad,af);this.max_val=ag};q(j.prototype,b.prototype);var O=function(af,an,ag,ae,ai,ap,aj,aq,ad,am){b.call(this,af,an,ag,ae,ai);this.mode=aj;this.all_slotted=ad;this.feature_mapper=am;this.has_icons=false;if(aq){this.has_icons=true;var ak=this;ae=this.html_elt.children()[0],message_div=$("
    ").addClass("tile-message").css({height:D-1,width:ae.width}).prependTo(this.html_elt);var al=new x.GenomeRegion({chrom:af.view.chrom,start:this.low,end:this.high}),ao=ai.length,ah=$("").addClass("icon more-down").attr("title","For speed, only the first "+ao+" features in this region were obtained from server. Click to get more data including depth").tooltip().appendTo(message_div),ac=$("").addClass("icon more-across").attr("title","For speed, only the first "+ao+" features in this region were obtained from server. Click to get more data excluding depth").tooltip().appendTo(message_div);ah.click(function(){ak.stale=true;af.data_manager.get_more_data(al,af.mode,ak.resolution,{},af.data_manager.DEEP_DATA_REQ);$(".bs-tooltip").hide();af.request_draw(true)}).dblclick(function(ar){ar.stopPropagation()});ac.click(function(){ak.stale=true;af.data_manager.get_more_data(al,af.mode,ak.resolution,{},af.data_manager.BROAD_DATA_REQ);$(".bs-tooltip").hide();af.request_draw(true)}).dblclick(function(ar){ar.stopPropagation()})}};q(O.prototype,b.prototype);O.prototype.predisplay_actions=function(){var ad=this,ac={};if(ad.mode!=="Pack"){return}$(this.html_elt).hover(function(){this.hovered=true;$(this).mousemove()},function(){this.hovered=false;$(this).parents(".track-content").children(".overlay").children(".feature-popup").remove()}).mousemove(function(ao){if(!this.hovered){return}var aj=$(this).offset(),an=ao.pageX-aj.left,am=ao.pageY-aj.top,at=ad.feature_mapper.get_feature_data(an,am),ak=(at?at[0]:null);$(this).parents(".track-content").children(".overlay").children(".feature-popup").each(function(){if(!ak||$(this).attr("id")!==ak.toString()){$(this).remove()}});if(at){var af=ac[ak];if(!af){var ak=at[0],ap={name:at[3],start:at[1],end:at[2],strand:at[4]},ai=ad.track.filters_manager.filters,ah;for(var al=0;al").attr("id",ak).addClass("feature-popup"),au=$(""),ar,aq,av;for(ar in ap){aq=ap[ar];av=$("").appendTo(au);$(" -
    ").appendTo(av).text(ar);$("").attr("align","left").appendTo(av).text(typeof(aq)==="number"?W(aq,2):aq)}af.append($("
    ").append(au));ac[ak]=af}af.appendTo($(this).parents(".track-content").children(".overlay"));var ag=an+parseInt(ad.html_elt.css("left"))-af.width()/2,ae=am+parseInt(ad.html_elt.css("top"))+7;af.css("left",ag+"px").css("top",ae+"px")}else{if(!ao.isPropagationStopped()){ao.stopPropagation();$(this).siblings().each(function(){$(this).trigger(ao)})}}}).mouseleave(function(){$(this).parents(".track-content").children(".overlay").children(".feature-popup").remove()})};var g=function(ad,ac,ae){q(ae,{drag_handle_class:"draghandle"});r.call(this,ad,ac,ae);this.dataset=new Y.Dataset({id:ae.dataset_id,hda_ldda:ae.hda_ldda});this.dataset_check_type="converted_datasets_state";this.data_url_extra_params={};this.data_query_wait=("data_query_wait" in ae?ae.data_query_wait:K);this.data_manager=("data_manager" in ae?ae.data_manager:new x.GenomeDataManager({dataset:this.dataset,genome:new x.Genome({key:ad.dbkey,chroms_info:{chrom_info:ad.chrom_data}}),data_mode_compatible:this.data_and_mode_compatible,can_subset:this.can_subset}));this.min_height_px=16;this.max_height_px=800;this.visible_height_px=0;this.content_div=$("
    ").appendTo(this.container_div);if(this.container){this.container.content_div.append(this.container_div);if(!("resize" in ae)||ae.resize){this.add_resize_handle()}}};q(g.prototype,r.prototype,{action_icons_def:[{name:"mode_icon",title:"Set display mode",css_class:"chevron-expand",on_click_fn:function(){}},r.prototype.action_icons_def[0],{name:"overview_icon",title:"Set as overview",css_class:"overview-icon",on_click_fn:function(ac){ac.view.set_overview(ac)}},r.prototype.action_icons_def[1],{name:"filters_icon",title:"Filters",css_class:"filters-icon",on_click_fn:function(ac){if(ac.filters_manager.visible()){ac.filters_manager.clear_filters()}else{ac.filters_manager.init_filters()}ac.filters_manager.toggle()}},{name:"tools_icon",title:"Tool",css_class:"hammer",on_click_fn:function(ac){ac.dynamic_tool_div.toggle();if(ac.dynamic_tool_div.is(":visible")){ac.set_name(ac.name+ac.tool_region_and_parameters_str())}else{ac.revert_name()}$(".bs-tooltip").remove()}},{name:"param_space_viz_icon",title:"Tool parameter space visualization",css_class:"arrow-split",on_click_fn:function(ac){var af='Tool: <%= track.tool.name %>
    Dataset: <%= track.name %>
    Region(s): ',ae=ab.template(af,{track:ac});var ah=function(){hide_modal();$(window).unbind("keypress.check_enter_esc")},ad=function(){var aj=$('select[name="regions"] option:selected').val(),al,ai=new x.GenomeRegion({chrom:view.chrom,start:view.low,end:view.high}),ak=ab.map($(".bookmark"),function(am){return new x.GenomeRegion({from_str:$(am).children(".position").text()})});if(aj==="cur"){al=[ai]}else{if(aj==="bookmarks"){al=ak}else{al=[ai].concat(ak)}}hide_modal();window.location.href=galaxy_paths.get("sweepster_url")+"?"+$.param({dataset_id:ac.dataset_id,hda_ldda:ac.hda_ldda,regions:JSON.stringify(new Backbone.Collection(al).toJSON())})},ag=function(ai){if((ai.keyCode||ai.which)===27){ah()}else{if((ai.keyCode||ai.which)===13){ad()}}};show_modal("Visualize tool parameter space and output from different parameter settings?",ae,{No:ah,Yes:ad})}},r.prototype.action_icons_def[2]],can_draw:function(){if(this.dataset_id&&r.prototype.can_draw.call(this)){return true}return false},build_container_div:function(){return $("
    ").addClass("track").attr("id","track_"+this.id).css("position","relative")},build_header_div:function(){var ac=$("
    ");if(this.view.editor){this.drag_div=$("
    ").addClass(this.drag_handle_class).appendTo(ac)}this.name_div=$("
    ").addClass("track-name").appendTo(ac).text(this.name).attr("id",this.name.replace(/\s+/g,"-").replace(/[^a-zA-Z0-9\-]/g,"").toLowerCase());return ac},on_resize:function(){},add_resize_handle:function(){var ac=this;var af=false;var ae=false;var ad=$("
    ");$(ac.container_div).hover(function(){if(ac.content_visible){af=true;ad.show()}},function(){af=false;if(!ae){ad.hide()}});ad.hide().bind("dragstart",function(ag,ah){ae=true;ah.original_height=$(ac.content_div).height()}).bind("drag",function(ah,ai){var ag=Math.min(Math.max(ai.original_height+ai.deltaY,ac.min_height_px),ac.max_height_px);$(ac.tiles_div).css("height",ag);ac.visible_height_px=(ac.max_height_px===ag?0:ag);ac.on_resize()}).bind("dragend",function(ag,ah){ac.tile_cache.clear();ae=false;if(!af){ad.hide()}ac.config.values.height=ac.visible_height_px;ac.changed()}).appendTo(ac.container_div)},set_display_modes:function(af,ai){this.display_modes=af;this.mode=(ai?ai:(this.config&&this.config.values.mode?this.config.values.mode:this.display_modes[0]));this.action_icons.mode_icon.attr("title","Set display mode (now: "+this.mode+")");var ad=this,ag={};for(var ae=0,ac=ad.display_modes.length;ae").text("View error").click(function(){show_modal("Trackster Error","
    "+ag.message+"
    ",{Close:hide_modal})}));ad.tiles_div.append($("").text(" "));ad.tiles_div.append($("").text("Try again").click(function(){ad.init(true)}))}}else{if(ag==="no converter"){ad.container_div.addClass("error");ad.tiles_div.text(J)}else{if(ag==="no data"||(ag.data!==undefined&&(ag.data===null||ag.data.length===0))){ad.container_div.addClass("nodata");ad.tiles_div.text(E)}else{if(ag==="pending"){ad.container_div.addClass("pending");ad.tiles_div.html(v);setTimeout(function(){ad.init()},ad.data_query_wait)}else{if(ag==="data"||ag.status==="data"){if(ag.valid_chroms){ad.valid_chroms=ag.valid_chroms;ad.update_icons()}ad.tiles_div.text(U);if(ad.view.chrom){ad.tiles_div.text("");ad.tiles_div.css("height",ad.visible_height_px+"px");ad.enabled=true;$.when(ad.predraw_init()).done(function(){ac.resolve();ad.container_div.removeClass("nodata error pending");ad.request_draw()})}else{ac.resolve()}}}}}}});this.update_icons();return ac},predraw_init:function(){},get_drawables:function(){return this}});var M=function(ae,ad,af){g.call(this,ae,ad,af);var ac=this;m(ac.container_div,ac.drag_handle_class,".group",ac);this.filters_manager=new i.FiltersManager(this,("filters" in af?af.filters:null));this.data_manager.set("filters_manager",this.filters_manager);this.filters_available=false;this.tool=("tool" in af&&af.tool?new s(this,af.tool,af.tool_state):null);this.tile_cache=new x.Cache(Q);if(this.header_div){this.set_filters_manager(this.filters_manager);if(this.tool){this.dynamic_tool_div=this.tool.parent_div;this.header_div.after(this.dynamic_tool_div)}}this.tiles_div=$("
    ").addClass("tiles").appendTo(this.content_div);this.overlay_div=$("
    ").addClass("overlay").appendTo(this.content_div);if(af.mode){this.change_mode(af.mode)}};q(M.prototype,r.prototype,g.prototype,{action_icons_def:g.prototype.action_icons_def.concat([{name:"show_more_rows_icon",title:"To minimize track height, not all feature rows are displayed. Click to display more rows.",css_class:"exclamation",on_click_fn:function(ac){$(".bs-tooltip").remove();ac.slotters[ac.view.resolution_px_b].max_rows*=2;ac.request_draw(true)},hide:true}]),copy:function(ac){var ad=this.to_dict();q(ad,{data_manager:this.data_manager});var ae=new this.constructor(this.view,ac,ad);ae.change_mode(this.mode);ae.enabled=this.enabled;return ae},set_filters_manager:function(ac){this.filters_manager=ac;this.header_div.after(this.filters_manager.parent_div)},to_dict:function(){return{track_type:this.get_type(),name:this.name,hda_ldda:this.hda_ldda,dataset_id:this.dataset_id,prefs:this.prefs,mode:this.mode,filters:this.filters_manager.to_dict(),tool_state:(this.tool?this.tool.state_dict():{})}},change_mode:function(ad){var ac=this;ac.mode=ad;ac.config.values.mode=ad;ac.tile_cache.clear();ac.request_draw();this.action_icons.mode_icon.attr("title","Set display mode (now: "+ac.mode+")");return ac},update_icons:function(){var ac=this;if(ac.filters_available){ac.action_icons.filters_icon.show()}else{ac.action_icons.filters_icon.hide()}if(ac.tool){ac.action_icons.tools_icon.show();ac.action_icons.param_space_viz_icon.show()}else{ac.action_icons.tools_icon.hide();ac.action_icons.param_space_viz_icon.hide()}},_gen_tile_cache_key:function(ad,ac){return ad+"_"+ac},request_draw:function(ad,ac){this.view.request_redraw(false,ad,ac,this)},before_draw:function(){},_draw:function(ad,ao){if(!this.can_draw()){return}var al=this.view.low,ah=this.view.high,aj=ah-al,ae=this.view.container.width(),aq=this.view.resolution_px_b,ag=this.view.resolution_b_px;if(this.is_overview){al=this.view.max_low;ah=this.view.max_high;ag=(view.max_high-view.max_low)/ae;aq=1/ag}this.before_draw();this.tiles_div.children().addClass("remove");var ac=Math.floor(al/(ag*S)),ak=true,ap=[],ai=function(ar){return(ar&&"track" in ar)};while((ac*S*ag)
    ");while(ac"+commatize(ac)+"
    ").css({position:"absolute",left:ah-1}));ac+=ai}this.content_div.children(":first").remove();this.content_div.append(ad)}});var f=function(ad,ac,ag){M.call(this,ad,ac,ag);this.drawables=[];this.left_offset=0;if("drawables" in ag){var af;for(var ae=0;aethis.left_offset){this.left_offset=af.left_offset}}this.enabled=true}if(this.drawables.length!==0){this.set_display_modes(this.drawables[0].display_modes,this.drawables[0].mode)}this.update_icons();this.obj_type="CompositeTrack"};q(f.prototype,M.prototype,{action_icons_def:[{name:"composite_icon",title:"Show individual tracks",css_class:"layers-stack",on_click_fn:function(ac){$(".bs-tooltip").remove();ac.show_group()}}].concat(M.prototype.action_icons_def),to_dict:z.prototype.to_dict,add_drawable:z.prototype.add_drawable,unpack_drawables:z.prototype.unpack_drawables,change_mode:function(ac){M.prototype.change_mode.call(this,ac);for(var ad=0;adview.canvas_manager.char_width_px){am=view.reference_track.data_manager.get_data(ah,aq.mode,av,view.reference_track.data_url_extra_params);if(V(am)){ao=false}}ak.push(am)}if(ao){q(aw,at.more_tile_data);this.tile_predraw_init();var ag=aq.view.canvas_manager.new_canvas(),ay=ah.get("start"),ae=ah.get("end"),az=0,ar=Math.ceil((ae-ay)*al)+this.left_offset,ap=0,af=[],ax;var ac=0;for(ax=0;axap){ap=ac}}ag.width=ar;ag.height=(at.height?at.height:ap);az=0;var au=ag.getContext("2d");au.translate(this.left_offset,0);au.globalAlpha=0.5;au.globalCompositeOperation="source-over";for(ax=0;axac){ac=ad.prefs.max_value}}}for(var ae=0;aeag){ag=ac}}for(var af=0;afthis.view.canvas_manager.char_width_px){this.tiles_div.show();return M.prototype.draw_helper.call(this,ae,af,ac,ag,ah,ad)}else{this.tiles_div.hide();return null}},can_subset:function(ac){return true},draw_tile:function(af,al,ag,ad,ai,am){var ae=this.data_manager.subset_entry(af,ai),ak=ae.data;var ac=al.canvas;al.font=al.canvas.manager.default_font;al.textAlign="center";for(var ah=0,aj=ak.length;ah").text(W(ac.prefs.min_value,3)).make_text_editable({num_cols:6,on_finish:function(aj){$(".bs-tooltip").remove();var aj=parseFloat(aj);if(!isNaN(aj)){ac.set_min_value(aj)}},help_text:"Set min value"}).addClass("yaxislabel bottom").attr("id","linetrack_"+ac.dataset_id+"_minval").prependTo(ac.container_div),af=$("
    ").text(W(ac.prefs.max_value,3)).make_text_editable({num_cols:6,on_finish:function(aj){$(".bs-tooltip").remove();var aj=parseFloat(aj);if(!isNaN(aj)){ac.set_max_value(aj)}},help_text:"Set max value"}).addClass("yaxislabel top").attr("id","linetrack_"+ac.dataset_id+"_maxval").prependTo(ac.container_div)})},draw_tile:function(al,aj,ae,ad,ag,ak){var ac=aj.canvas,af=ag.get("start"),ai=ag.get("end"),ah=new L.LinePainter(al.data,af,ai,this.prefs,ae);ah.draw(aj,ac.width,ac.height,ak);return new b(this,ag,ad,ac,al.data)},can_subset:function(ac){return(ac.data[1][0]-ac.data[0][0]===1)}});var t=function(ae,ad,af){var ac=this;this.display_modes=["Heatmap"];this.mode="Heatmap";M.call(this,ae,ad,af);this.hda_ldda=af.hda_ldda;this.dataset_id=af.dataset_id;this.original_dataset_id=this.dataset_id;this.left_offset=0;this.config=new F({track:this,params:[{key:"name",label:"Name",type:"text",default_value:this.name},{key:"pos_color",label:"Positive Color",type:"color",default_value:"#FF8C00"},{key:"neg_color",label:"Negative Color",type:"color",default_value:"#4169E1"},{key:"min_value",label:"Min Value",type:"float",default_value:-1},{key:"max_value",label:"Max Value",type:"float",default_value:1},{key:"mode",type:"string",default_value:this.mode,hidden:true},{key:"height",type:"int",default_value:500,hidden:true}],saved_values:af.prefs,onchange:function(){ac.set_name(ac.prefs.name);ac.vertical_range=ac.prefs.max_value-ac.prefs.min_value;ac.set_min_value(ac.prefs.min_value);ac.set_max_value(ac.prefs.max_value)}});this.prefs=this.config.values;this.visible_height_px=this.config.values.height;this.vertical_range=this.config.values.max_value-this.config.values.min_value};q(t.prototype,r.prototype,M.prototype,{on_resize:function(){this.request_draw(true)},set_min_value:function(ac){this.prefs.min_value=ac;this.tile_cache.clear();this.request_draw()},set_max_value:function(ac){this.prefs.max_value=ac;this.tile_cache.clear();this.request_draw()},draw_tile:function(ac,ae,ai,ag,ah,aj){var af=ae.canvas,ad=new L.DiagonalHeatmapPainter(ac.data,ah.get("start"),ah.get("end"),this.prefs,ai);ad.draw(ae,af.width,af.height,aj);return new b(this,ah,ag,af,ac.data)}});var c=function(af,ae,ah){var ad=this;this.display_modes=["Auto","Coverage","Dense","Squish","Pack"];M.call(this,af,ae,ah);var ag=l.get_random_color(),ac=l.get_random_color([ag,"#FFFFFF"]);this.config=new F({track:this,params:[{key:"name",label:"Name",type:"text",default_value:this.name},{key:"block_color",label:"Block color",type:"color",default_value:ag},{key:"reverse_strand_color",label:"Antisense strand color",type:"color",default_value:ac},{key:"label_color",label:"Label color",type:"color",default_value:"black"},{key:"show_counts",label:"Show summary counts",type:"bool",default_value:true,help:"Show the number of items in each bin when drawing summary histogram"},{key:"histogram_max",label:"Histogram maximum",type:"float",default_value:null,help:"clear value to set automatically"},{key:"connector_style",label:"Connector style",type:"select",default_value:"fishbones",options:[{label:"Line with arrows",value:"fishbone"},{label:"Arcs",value:"arcs"}]},{key:"mode",type:"string",default_value:this.mode,hidden:true},{key:"height",type:"int",default_value:this.visible_height_px,hidden:true}],saved_values:ah.prefs,onchange:function(){ad.set_name(ad.prefs.name);ad.tile_cache.clear();ad.set_painter_from_config();ad.request_draw()}});this.prefs=this.config.values;this.visible_height_px=this.config.values.height;this.container_div.addClass("feature-track");this.hda_ldda=ah.hda_ldda;this.dataset_id=ah.dataset_id;this.original_dataset_id=ah.dataset_id;this.show_labels_scale=0.001;this.showing_details=false;this.summary_draw_height=30;this.slotters={};this.start_end_dct={};this.left_offset=200;this.set_painter_from_config()};q(c.prototype,r.prototype,M.prototype,{set_dataset:function(ac){this.dataset_id=ac.get("id");this.hda_ldda=ac.get("hda_ldda");this.dataset=ac;this.data_manager.set("dataset",ac)},set_painter_from_config:function(){if(this.config.values.connector_style==="arcs"){this.painter=L.ArcLinkedFeaturePainter}else{this.painter=L.LinkedFeaturePainter}},before_draw:function(){this.max_height_px=0},postdraw_actions:function(ar,am,ah,ag){M.prototype.postdraw_actions.call(this,ar,ag);var al=this,ao;if(al.mode==="Coverage"){var ad=-1;for(ao=0;aoad){ad=an}}for(ao=0;ao").text(ak).make_text_editable({num_cols:12,on_finish:function(av){$(".bs-tooltip").remove();var av=parseFloat(av);al.prefs.histogram_max=(!isNaN(av)?av:null);al.tile_cache.clear();al.request_draw()},help_text:"Set max value; leave blank to use default"}).addClass("yaxislabel top").css("color",this.prefs.label_color);this.container_div.prepend(ae)}if(af instanceof O){var ap=true;for(ao=0;aoI){mode="Squish"}else{mode="Pack"}}}return mode},get_canvas_height:function(ac,ag,ah,ad){if(ag==="summary_tree"||ag==="Coverage"){return this.summary_draw_height}else{var af=this.incremental_slots(ah,ac.data,ag);var ae=new (this.painter)(null,null,null,this.prefs,ag);return Math.max(aa,ae.get_required_height(af,ad))}},draw_tile:function(am,aq,ao,ar,af,aj,ae){var ap=this,ad=aq.canvas,ay=af.get("start"),ac=af.get("end"),ag=this.left_offset;if(ao==="summary_tree"||ao==="Coverage"){var aA=new L.SummaryTreePainter(am,ay,ac,this.prefs);aA.draw(aq,ad.width,ad.height,aj);return new j(ap,af,ar,ad,am.data,am.max)}var ai=[],an=this.slotters[aj].slots;all_slotted=true;if(am.data){var ak=this.filters_manager.filters;for(var at=0,av=am.data.length;atah){$(this).insertAfter(ai);var ar=n[ai.attr("id")];ar.remove_drawable(am);ar.container.add_drawable(am);return}}}ai=null;for(al=0;al=aq&&ap.offsetY<=ah){if(ap.offsetY-aq0?al-1:al))}}}).bind("dragstart",function(){ad["border-top"]=ac.css("border-top");ad["border-bottom"]=ac.css("border-bottom");$(this).css({"border-top":"1px solid blue","border-bottom":"1px solid blue"})}).bind("dragend",function(){$(this).css(ad)})};var aa=16,G=9,D=20,A=100,I=12000,S=400,K=5000,w=100,o="Cannot display dataset due to an error. ",J="A converter for this dataset is not installed. Please check your datatypes_conf.xml file.",E="No data for this chrom/contig.",v="Preparing data. This can take a while for a large dataset. If the visualization is saved and closed, preparation will continue in the background.",y="Tool cannot be rerun: ",a="Loading data...",U="Ready for display",Q=10,H=20;function W(ad,ac){if(!ac){ac=0}var ae=Math.pow(10,ac);return Math.round(ad*ae)/ae}var r=function(ad,ac,af){if(!r.id_counter){r.id_counter=0}this.id=r.id_counter++;this.name=af.name;this.view=ad;this.container=ac;this.config=new F({track:this,params:[{key:"name",label:"Name",type:"text",default_value:this.name}],saved_values:af.prefs,onchange:function(){this.track.set_name(this.track.config.values.name)}});this.prefs=this.config.values;this.drag_handle_class=af.drag_handle_class;this.is_overview=false;this.action_icons={};this.content_visible=true;this.container_div=this.build_container_div();this.header_div=this.build_header_div();if(this.header_div){this.container_div.append(this.header_div);this.icons_div=$("
    ").css("float","left").hide().appendTo(this.header_div);this.build_action_icons(this.action_icons_def);this.header_div.append($("
    "));this.header_div.dblclick(function(ag){ag.stopPropagation()});var ae=this;this.container_div.hover(function(){ae.icons_div.show()},function(){ae.icons_div.hide()});$("
    ").appendTo(this.container_div)}};r.prototype.action_icons_def=[{name:"toggle_icon",title:"Hide/show content",css_class:"toggle",on_click_fn:function(ac){if(ac.content_visible){ac.action_icons.toggle_icon.addClass("toggle-expand").removeClass("toggle");ac.hide_contents();ac.content_visible=false}else{ac.action_icons.toggle_icon.addClass("toggle").removeClass("toggle-expand");ac.content_visible=true;ac.show_contents()}}},{name:"settings_icon",title:"Edit settings",css_class:"settings-icon",on_click_fn:function(ad){var af=function(){hide_modal();$(window).unbind("keypress.check_enter_esc")},ac=function(){ad.config.update_from_form($(".dialog-box"));hide_modal();$(window).unbind("keypress.check_enter_esc")},ae=function(ag){if((ag.keyCode||ag.which)===27){af()}else{if((ag.keyCode||ag.which)===13){ac()}}};$(window).bind("keypress.check_enter_esc",ae);show_modal("Configure",ad.config.build_form(),{Cancel:af,OK:ac})}},{name:"remove_icon",title:"Remove",css_class:"remove-icon",on_click_fn:function(ac){$(".bs-tooltip").remove();ac.remove()}}];q(r.prototype,{init:function(){},changed:function(){this.view.changed()},can_draw:function(){if(this.enabled&&this.content_visible){return true}return false},request_draw:function(){},_draw:function(){},to_dict:function(){},set_name:function(ac){this.old_name=this.name;this.name=ac;this.name_div.text(this.name)},revert_name:function(){if(this.old_name){this.name=this.old_name;this.name_div.text(this.name)}},remove:function(){this.changed();this.container.remove_drawable(this);var ac=this.view;this.container_div.hide(0,function(){$(this).remove();ac.update_intro_div()})},build_container_div:function(){},build_header_div:function(){},add_action_icon:function(ad,ai,ah,ag,ac,af){var ae=this;this.action_icons[ad]=$("").attr("href","javascript:void(0);").attr("title",ai).addClass("icon-button").addClass(ah).tooltip().click(function(){ag(ae)}).appendTo(this.icons_div);if(af){this.action_icons[ad].hide()}},build_action_icons:function(ac){var ae;for(var ad=0;ad").addClass("content-div").attr("id","group_"+this.id+"_content_div").appendTo(this.container_div);k(this.container_div,this);k(this.content_div,this);m(this.container_div,this.drag_handle_class,".group",this);this.filters_manager=new i.FiltersManager(this);this.header_div.after(this.filters_manager.parent_div);this.saved_filters_managers=[];if("drawables" in af){this.unpack_drawables(af.drawables)}if("filters" in af){var ae=this.filters_manager;this.filters_manager=new i.FiltersManager(this,af.filters);ae.parent_div.replaceWith(this.filters_manager.parent_div);if(af.filters.visible){this.setup_multitrack_filtering()}}};q(P.prototype,r.prototype,z.prototype,{action_icons_def:[r.prototype.action_icons_def[0],r.prototype.action_icons_def[1],{name:"composite_icon",title:"Show composite track",css_class:"layers-stack",on_click_fn:function(ac){$(".bs-tooltip").remove();ac.show_composite_track()}},{name:"filters_icon",title:"Filters",css_class:"filters-icon",on_click_fn:function(ac){if(ac.filters_manager.visible()){ac.filters_manager.clear_filters();ac._restore_filter_managers()}else{ac.setup_multitrack_filtering();ac.request_draw(true)}ac.filters_manager.toggle()}},r.prototype.action_icons_def[2]],build_container_div:function(){var ac=$("
    ").addClass("group").attr("id","group_"+this.id);if(this.container){this.container.content_div.append(ac)}return ac},build_header_div:function(){var ac=$("
    ").addClass("track-header");ac.append($("
    ").addClass(this.drag_handle_class));this.name_div=$("
    ").addClass("track-name").text(this.name).appendTo(ac);return ac},hide_contents:function(){this.tiles_div.hide()},show_contents:function(){this.tiles_div.show();this.request_draw()},update_icons:function(){var ae=this.drawables.length;if(ae===0){this.action_icons.composite_icon.hide();this.action_icons.filters_icon.hide()}else{if(ae===1){if(this.drawables[0] instanceof f){this.action_icons.composite_icon.show()}this.action_icons.filters_icon.hide()}else{var al,ak,ai,ao=true,ag=this.drawables[0].get_type(),ac=0;for(al=0;al1&&ac===this.drawables.length){var ap={},ad;ai=this.drawables[0];for(ak=0;ak0){this.action_icons.filters_icon.show()}else{this.action_icons.filters_icon.hide()}}else{this.action_icons.filters_icon.hide()}}}},_restore_filter_managers:function(){for(var ac=0;ac0){this.saved_filters_managers=[];for(var ac=0;ac").addClass("top-container").appendTo(ae);this.browser_content_div=$("
    ").addClass("content").css("position","relative").appendTo(ae);this.bottom_container=$("
    ").addClass("bottom-container").appendTo(ae);this.top_labeltrack=$("
    ").addClass("top-labeltrack").appendTo(this.top_container);this.viewport_container=$("
    ").addClass("viewport-container").attr("id","viewport-container").appendTo(this.browser_content_div);this.content_div=this.viewport_container;k(this.viewport_container,ac);this.intro_div=$("
    ").addClass("intro").appendTo(this.viewport_container).hide();var af=$("
    ").text("Add Datasets to Visualization").addClass("action-button").appendTo(this.intro_div).click(function(){x.select_datasets(select_datasets_url,add_track_async_url,{"f-dbkey":ac.dbkey},function(ag){ab.each(ag,function(ah){ac.add_drawable(p(ah,ac,ac))})})});this.nav_labeltrack=$("
    ").addClass("nav-labeltrack").appendTo(this.bottom_container);this.nav_container=$("
    ").addClass("trackster-nav-container").prependTo(this.top_container);this.nav=$("
    ").addClass("trackster-nav").appendTo(this.nav_container);this.overview=$("
    ").addClass("overview").appendTo(this.bottom_container);this.overview_viewport=$("
    ").addClass("overview-viewport").appendTo(this.overview);this.overview_close=$("").attr("href","javascript:void(0);").attr("title","Close overview").addClass("icon-button overview-close tooltip").hide().appendTo(this.overview_viewport);this.overview_highlight=$("
    ").addClass("overview-highlight").hide().appendTo(this.overview_viewport);this.overview_box_background=$("
    ").addClass("overview-boxback").appendTo(this.overview_viewport);this.overview_box=$("
    ").addClass("overview-box").appendTo(this.overview_viewport);this.default_overview_height=this.overview_box.height();this.nav_controls=$("
    ").addClass("nav-controls").appendTo(this.nav);this.chrom_select=$("").addClass("nav-input").hide().bind("keyup focusout",ad).appendTo(this.nav_controls);this.location_span=$("").addClass("location").attr("original-title","Click to change location").tooltip({placement:"bottom"}).appendTo(this.nav_controls);this.location_span.click(function(){ac.location_span.hide();ac.chrom_select.hide();ac.nav_input.val(ac.chrom+":"+ac.low+"-"+ac.high);ac.nav_input.css("display","inline-block");ac.nav_input.select();ac.nav_input.focus();ac.nav_input.autocomplete({source:function(ai,ag){var aj=[],ah=$.map(ac.get_drawables(),function(ak){return ak.data_manager.search_features(ai.term).success(function(al){aj=aj.concat(al)})});$.when.apply($,ah).done(function(){ag($.map(aj,function(ak){return{label:ak[0],value:ak[1]}}))})}})});if(this.vis_id!==undefined){this.hidden_input=$("").attr("type","hidden").val(this.vis_id).appendTo(this.nav_controls)}this.zo_link=$("").attr("id","zoom-out").attr("title","Zoom out").tooltip({placement:"bottom"}).click(function(){ac.zoom_out();ac.request_redraw()}).appendTo(this.nav_controls);this.zi_link=$("").attr("id","zoom-in").attr("title","Zoom in").tooltip({placement:"bottom"}).click(function(){ac.zoom_in();ac.request_redraw()}).appendTo(this.nav_controls);this.load_chroms_deferred=this.load_chroms({low:0});this.chrom_select.bind("change",function(){ac.change_chrom(ac.chrom_select.val())});this.browser_content_div.click(function(ag){$(this).find("input").trigger("blur")});this.browser_content_div.bind("dblclick",function(ag){ac.zoom_in(ag.pageX,this.viewport_container)});this.overview_box.bind("dragstart",function(ag,ah){this.current_x=ah.offsetX}).bind("drag",function(ag,ai){var aj=ai.offsetX-this.current_x;this.current_x=ai.offsetX;var ah=Math.round(aj/ac.viewport_container.width()*(ac.max_high-ac.max_low));ac.move_delta(-ah)});this.overview_close.click(function(){ac.reset_overview()});this.viewport_container.bind("draginit",function(ag,ah){if(ag.clientX>ac.viewport_container.width()-16){return false}}).bind("dragstart",function(ag,ah){ah.original_low=ac.low;ah.current_height=ag.clientY;ah.current_x=ah.offsetX}).bind("drag",function(ai,ak){var ag=$(this);var al=ak.offsetX-ak.current_x;var ah=ag.scrollTop()-(ai.clientY-ak.current_height);ag.scrollTop(ah);ak.current_height=ai.clientY;ak.current_x=ak.offsetX;var aj=Math.round(al/ac.viewport_container.width()*(ac.high-ac.low));ac.move_delta(aj)}).bind("mousewheel",function(ai,ak,ah,ag){if(ah){ah*=50;var aj=Math.round(-ah/ac.viewport_container.width()*(ac.high-ac.low));ac.move_delta(aj)}});this.top_labeltrack.bind("dragstart",function(ag,ah){return $("
    ").css({height:ac.browser_content_div.height()+ac.top_labeltrack.height()+ac.nav_labeltrack.height()+1,top:"0px",position:"absolute","background-color":"#ccf",opacity:0.5,"z-index":1000}).appendTo($(this))}).bind("drag",function(ak,al){$(al.proxy).css({left:Math.min(ak.pageX,al.startX)-ac.container.offset().left,width:Math.abs(ak.pageX-al.startX)});var ah=Math.min(ak.pageX,al.startX)-ac.container.offset().left,ag=Math.max(ak.pageX,al.startX)-ac.container.offset().left,aj=(ac.high-ac.low),ai=ac.viewport_container.width();ac.update_location(Math.round(ah/ai*aj)+ac.low,Math.round(ag/ai*aj)+ac.low)}).bind("dragend",function(al,am){var ah=Math.min(al.pageX,am.startX),ag=Math.max(al.pageX,am.startX),aj=(ac.high-ac.low),ai=ac.viewport_container.width(),ak=ac.low;ac.low=Math.round(ah/ai*aj)+ak;ac.high=Math.round(ag/ai*aj)+ak;$(am.proxy).remove();ac.request_redraw()});this.add_label_track(new X(this,{content_div:this.top_labeltrack}));this.add_label_track(new X(this,{content_div:this.nav_labeltrack}));$(window).bind("resize",function(){if(this.resize_timer){clearTimeout(this.resize_timer)}this.resize_timer=setTimeout(function(){ac.resize_window()},500)});$(document).bind("redraw",function(){ac.redraw()});this.reset();$(window).trigger("resize")},get_base_color:function(ac){return this.config.values[ac.toLowerCase()+"_color"]}});q(Z.prototype,z.prototype,{changed:function(){this.has_changes=true},update_intro_div:function(){if(this.drawables.length===0){this.intro_div.show()}else{this.intro_div.hide()}},trigger_navigate:function(ad,af,ac,ag){if(this.timer){clearTimeout(this.timer)}if(ag){var ae=this;this.timer=setTimeout(function(){ae.trigger("navigate",ad+":"+af+"-"+ac)},500)}else{view.trigger("navigate",ad+":"+af+"-"+ac)}},update_location:function(ac,ae){this.location_span.text(commatize(ac)+" - "+commatize(ae));this.nav_input.val(this.chrom+":"+commatize(ac)+"-"+commatize(ae));var ad=view.chrom_select.val();if(ad!==""){this.trigger_navigate(ad,view.low,view.high,true)}},load_chroms:function(ae){ae.num=w;var ac=this,ad=$.Deferred();$.ajax({url:chrom_url+"/"+this.dbkey,data:ae,dataType:"json",success:function(ag){if(ag.chrom_info.length===0){return}if(ag.reference){ac.add_label_track(new B(ac))}ac.chrom_data=ag.chrom_info;var aj='';for(var ai=0,af=ac.chrom_data.length;ai'+ah+""}if(ag.prev_chroms){aj+='"}if(ag.next_chroms){aj+='"}ac.chrom_select.html(aj);ac.chrom_start_index=ag.start_index;ad.resolve(ag.chrom_info)},error:function(){alert("Could not load chroms for this dbkey:",ac.dbkey)}});return ad},change_chrom:function(ah,ad,aj){var ae=this;if(!ae.chrom_data){ae.load_chroms_deferred.then(function(){ae.change_chrom(ah,ad,aj)});return}if(!ah||ah==="None"){return}if(ah==="previous"){ae.load_chroms({low:this.chrom_start_index-w});return}if(ah==="next"){ae.load_chroms({low:this.chrom_start_index+w});return}var ai=$.grep(ae.chrom_data,function(ak,al){return ak.chrom===ah})[0];if(ai===undefined){ae.load_chroms({chrom:ah},function(){ae.change_chrom(ah,ad,aj)});return}else{if(ah!==ae.chrom){ae.chrom=ah;ae.chrom_select.val(ae.chrom);ae.max_high=ai.len-1;ae.reset();ae.request_redraw(true);for(var ag=0,ac=ae.drawables.length;agac.max_high){ac.high=ac.max_high;ac.low=ac.max_high-ae}else{ac.high-=af;ac.low-=af}}ac.request_redraw();var ad=ac.chrom_select.val();this.trigger_navigate(ad,ac.low,ac.high,true)},add_drawable:function(ac){z.prototype.add_drawable.call(this,ac);ac.init();this.changed();this.update_intro_div()},add_label_track:function(ac){ac.view=this;ac.init();this.label_tracks.push(ac)},remove_drawable:function(ae,ad){z.prototype.remove_drawable.call(this,ae);if(ad){var ac=this;ae.container_div.hide(0,function(){$(this).remove();ac.update_intro_div()})}},reset:function(){this.low=this.max_low;this.high=this.max_high;this.viewport_container.find(".yaxislabel").remove()},request_redraw:function(ak,ac,aj,al){var ai=this,ah=(al?[al]:ai.drawables),ae;var ad;for(var ag=0;agthis.max_high){af=this.max_high}var al=this.high-this.low;if(this.high!==0&&al").addClass("dynamic-tool").hide();this.parent_div.bind("drag",function(au){au.stopPropagation()}).click(function(au){au.stopPropagation()}).bind("dblclick",function(au){au.stopPropagation()});var ao=$("
    ").appendTo(this.parent_div).text(this.name);var am=this.params;var ak=this;$.each(this.params,function(av,ay){var ax=$("
    ").addClass("param-row").appendTo(ak.parent_div);var au=$("
    ").addClass("param-label").text(ay.label).appendTo(ax);var aw=$("
    ").addClass("param-input").html(ay.html).appendTo(ax);aw.find(":input").val(ay.value);$("
    ").appendTo(ax)});this.parent_div.find("input").click(function(){$(this).select()});var at=$("
    ").addClass("param-row").appendTo(this.parent_div);var ai=$("").attr("value","Run on complete dataset").appendTo(at);var ac=$("").attr("value","Run on visible region").css("margin-left","3em").appendTo(at);ac.click(function(){ak.run_on_region()});ai.click(function(){ak.run_on_dataset()});if("visible" in af&&af.visible){this.parent_div.show()}};q(s.prototype,{update_params:function(){for(var ac=0;ac");var ae;function ad(ak,ag){for(var ao=0;ao").appendTo(ag);av.append($("").addClass("icon-button arrow-circle").appendTo(au).attr("title","Set new random color").tooltip(),ar=$("
    ").appendTo(au).hide(),aj=$("
    ").appendTo(ar),ah=$("
    ").appendTo(ar),al=$.farbtastic(aj,{width:100,height:100,callback:ap,color:at});au.append($("
    ").css("clear","both"));(function(aw){an.click(function(){aw.setColor(l.get_random_color())})})(al)}else{av.append($("").attr("id",ai).attr("name",ai).val(at))}}}}if(ae.help){av.append($("
    ").text(ae.help))}}}ad(this.params,ac);return ac},update_from_form:function(ac){var ae=this;var ad=false;$.each(this.params,function(af,ah){if(!ah.hidden){var ai="param_"+af;var ag=ac.find("#"+ai).val();if(ah.type==="float"){ag=parseFloat(ag)}else{if(ah.type==="int"){ag=parseInt(ag,10)}else{if(ah.type==="bool"){ag=ac.find("#"+ai).is(":checked")}}}if(ag!==ae.values[ah.key]){ae.values[ah.key]=ag;ad=true}}});if(ad){this.onchange();this.track.changed()}}});var b=function(ac,ag,ae,ad,af){this.track=ac;this.region=ag;this.low=ag.get("start");this.high=ag.get("end");this.resolution=ae;this.html_elt=$("
    ").append(ad).height($(ad).attr("height"));this.data=af;this.stale=false};b.prototype.predisplay_actions=function(){};var j=function(ac,ah,ae,ad,af,ag){b.call(this,ac,ah,ae,ad,af);this.max_val=ag};q(j.prototype,b.prototype);var O=function(af,an,ag,ae,ai,ap,aj,aq,ad,am){b.call(this,af,an,ag,ae,ai);this.mode=aj;this.all_slotted=ad;this.feature_mapper=am;this.has_icons=false;if(aq){this.has_icons=true;var ak=this;ae=this.html_elt.children()[0],message_div=$("
    ").addClass("tile-message").css({height:D-1,width:ae.width}).prependTo(this.html_elt);var al=new x.GenomeRegion({chrom:af.view.chrom,start:this.low,end:this.high}),ao=ai.length,ah=$("").addClass("icon more-down").attr("title","For speed, only the first "+ao+" features in this region were obtained from server. Click to get more data including depth").tooltip().appendTo(message_div),ac=$("").addClass("icon more-across").attr("title","For speed, only the first "+ao+" features in this region were obtained from server. Click to get more data excluding depth").tooltip().appendTo(message_div);ah.click(function(){ak.stale=true;af.data_manager.get_more_data(al,af.mode,ak.resolution,{},af.data_manager.DEEP_DATA_REQ);$(".bs-tooltip").hide();af.request_draw(true)}).dblclick(function(ar){ar.stopPropagation()});ac.click(function(){ak.stale=true;af.data_manager.get_more_data(al,af.mode,ak.resolution,{},af.data_manager.BROAD_DATA_REQ);$(".bs-tooltip").hide();af.request_draw(true)}).dblclick(function(ar){ar.stopPropagation()})}};q(O.prototype,b.prototype);O.prototype.predisplay_actions=function(){var ad=this,ac={};if(ad.mode!=="Pack"){return}$(this.html_elt).hover(function(){this.hovered=true;$(this).mousemove()},function(){this.hovered=false;$(this).parents(".track-content").children(".overlay").children(".feature-popup").remove()}).mousemove(function(ao){if(!this.hovered){return}var aj=$(this).offset(),an=ao.pageX-aj.left,am=ao.pageY-aj.top,at=ad.feature_mapper.get_feature_data(an,am),ak=(at?at[0]:null);$(this).parents(".track-content").children(".overlay").children(".feature-popup").each(function(){if(!ak||$(this).attr("id")!==ak.toString()){$(this).remove()}});if(at){var af=ac[ak];if(!af){var ak=at[0],ap={name:at[3],start:at[1],end:at[2],strand:at[4]},ai=ad.track.filters_manager.filters,ah;for(var al=0;al").attr("id",ak).addClass("feature-popup"),au=$(""),ar,aq,av;for(ar in ap){aq=ap[ar];av=$("").appendTo(au);$("\n \n ";return o}h+='

    By column:

    \n
    ").appendTo(av).text(ar);$("").attr("align","left").appendTo(av).text(typeof(aq)==="number"?W(aq,2):aq)}af.append($("
    ").append(au));ac[ak]=af}af.appendTo($(this).parents(".track-content").children(".overlay"));var ag=an+parseInt(ad.html_elt.css("left"))-af.width()/2,ae=am+parseInt(ad.html_elt.css("top"))+7;af.css("left",ag+"px").css("top",ae+"px")}else{if(!ao.isPropagationStopped()){ao.stopPropagation();$(this).siblings().each(function(){$(this).trigger(ao)})}}}).mouseleave(function(){$(this).parents(".track-content").children(".overlay").children(".feature-popup").remove()})};var g=function(ad,ac,ae){q(ae,{drag_handle_class:"draghandle"});r.call(this,ad,ac,ae);this.dataset=new Y.Dataset({id:ae.dataset_id,hda_ldda:ae.hda_ldda});this.dataset_check_type="converted_datasets_state";this.data_url_extra_params={};this.data_query_wait=("data_query_wait" in ae?ae.data_query_wait:K);this.data_manager=("data_manager" in ae?ae.data_manager:new x.GenomeDataManager({dataset:this.dataset,genome:new x.Genome({key:ad.dbkey,chroms_info:{chrom_info:ad.chrom_data}}),data_mode_compatible:this.data_and_mode_compatible,can_subset:this.can_subset}));this.min_height_px=16;this.max_height_px=800;this.visible_height_px=0;this.content_div=$("
    ").appendTo(this.container_div);if(this.container){this.container.content_div.append(this.container_div);if(!("resize" in ae)||ae.resize){this.add_resize_handle()}}};q(g.prototype,r.prototype,{action_icons_def:[{name:"mode_icon",title:"Set display mode",css_class:"chevron-expand",on_click_fn:function(){}},r.prototype.action_icons_def[0],{name:"overview_icon",title:"Set as overview",css_class:"overview-icon",on_click_fn:function(ac){ac.view.set_overview(ac)}},r.prototype.action_icons_def[1],{name:"filters_icon",title:"Filters",css_class:"filters-icon",on_click_fn:function(ac){if(ac.filters_manager.visible()){ac.filters_manager.clear_filters()}else{ac.filters_manager.init_filters()}ac.filters_manager.toggle()}},{name:"tools_icon",title:"Tool",css_class:"hammer",on_click_fn:function(ac){ac.dynamic_tool_div.toggle();if(ac.dynamic_tool_div.is(":visible")){ac.set_name(ac.name+ac.tool_region_and_parameters_str())}else{ac.revert_name()}$(".bs-tooltip").remove()}},{name:"param_space_viz_icon",title:"Tool parameter space visualization",css_class:"arrow-split",on_click_fn:function(ac){var af='Tool: <%= track.tool.name %>
    Dataset: <%= track.name %>
    Region(s): ',ae=ab.template(af,{track:ac});var ah=function(){hide_modal();$(window).unbind("keypress.check_enter_esc")},ad=function(){var aj=$('select[name="regions"] option:selected').val(),al,ai=new x.GenomeRegion({chrom:view.chrom,start:view.low,end:view.high}),ak=ab.map($(".bookmark"),function(am){return new x.GenomeRegion({from_str:$(am).children(".position").text()})});if(aj==="cur"){al=[ai]}else{if(aj==="bookmarks"){al=ak}else{al=[ai].concat(ak)}}hide_modal();window.location.href=galaxy_paths.get("sweepster_url")+"?"+$.param({dataset_id:ac.dataset_id,hda_ldda:ac.hda_ldda,regions:JSON.stringify(new Backbone.Collection(al).toJSON())})},ag=function(ai){if((ai.keyCode||ai.which)===27){ah()}else{if((ai.keyCode||ai.which)===13){ad()}}};show_modal("Visualize tool parameter space and output from different parameter settings?",ae,{No:ah,Yes:ad})}},r.prototype.action_icons_def[2]],can_draw:function(){if(this.dataset_id&&r.prototype.can_draw.call(this)){return true}return false},build_container_div:function(){return $("
    ").addClass("track").attr("id","track_"+this.id).css("position","relative")},build_header_div:function(){var ac=$("
    ");if(this.view.editor){this.drag_div=$("
    ").addClass(this.drag_handle_class).appendTo(ac)}this.name_div=$("
    ").addClass("track-name").appendTo(ac).text(this.name).attr("id",this.name.replace(/\s+/g,"-").replace(/[^a-zA-Z0-9\-]/g,"").toLowerCase());return ac},on_resize:function(){},add_resize_handle:function(){var ac=this;var af=false;var ae=false;var ad=$("
    ");$(ac.container_div).hover(function(){if(ac.content_visible){af=true;ad.show()}},function(){af=false;if(!ae){ad.hide()}});ad.hide().bind("dragstart",function(ag,ah){ae=true;ah.original_height=$(ac.content_div).height()}).bind("drag",function(ah,ai){var ag=Math.min(Math.max(ai.original_height+ai.deltaY,ac.min_height_px),ac.max_height_px);$(ac.tiles_div).css("height",ag);ac.visible_height_px=(ac.max_height_px===ag?0:ag);ac.on_resize()}).bind("dragend",function(ag,ah){ac.tile_cache.clear();ae=false;if(!af){ad.hide()}ac.config.values.height=ac.visible_height_px;ac.changed()}).appendTo(ac.container_div)},set_display_modes:function(af,ai){this.display_modes=af;this.mode=(ai?ai:(this.config&&this.config.values.mode?this.config.values.mode:this.display_modes[0]));this.action_icons.mode_icon.attr("title","Set display mode (now: "+this.mode+")");var ad=this,ag={};for(var ae=0,ac=ad.display_modes.length;ae").text("View error").click(function(){show_modal("Trackster Error","
    "+ag.message+"
    ",{Close:hide_modal})}));ad.tiles_div.append($("").text(" "));ad.tiles_div.append($("").text("Try again").click(function(){ad.init(true)}))}}else{if(ag==="no converter"){ad.container_div.addClass("error");ad.tiles_div.text(J)}else{if(ag==="no data"||(ag.data!==undefined&&(ag.data===null||ag.data.length===0))){ad.container_div.addClass("nodata");ad.tiles_div.text(E)}else{if(ag==="pending"){ad.container_div.addClass("pending");ad.tiles_div.html(v);setTimeout(function(){ad.init()},ad.data_query_wait)}else{if(ag==="data"||ag.status==="data"){if(ag.valid_chroms){ad.valid_chroms=ag.valid_chroms;ad.update_icons()}ad.tiles_div.text(U);if(ad.view.chrom){ad.tiles_div.text("");ad.tiles_div.css("height",ad.visible_height_px+"px");ad.enabled=true;$.when(ad.predraw_init()).done(function(){ac.resolve();ad.container_div.removeClass("nodata error pending");ad.request_draw()})}else{ac.resolve()}}}}}}});this.update_icons();return ac},predraw_init:function(){},get_drawables:function(){return this}});var M=function(ae,ad,af){g.call(this,ae,ad,af);var ac=this;m(ac.container_div,ac.drag_handle_class,".group",ac);this.filters_manager=new i.FiltersManager(this,("filters" in af?af.filters:null));this.data_manager.set("filters_manager",this.filters_manager);this.filters_available=false;this.tool=("tool" in af&&af.tool?new s(this,af.tool,af.tool_state):null);this.tile_cache=new x.Cache(Q);if(this.header_div){this.set_filters_manager(this.filters_manager);if(this.tool){this.dynamic_tool_div=this.tool.parent_div;this.header_div.after(this.dynamic_tool_div)}}this.tiles_div=$("
    ").addClass("tiles").appendTo(this.content_div);this.overlay_div=$("
    ").addClass("overlay").appendTo(this.content_div);if(af.mode){this.change_mode(af.mode)}};q(M.prototype,r.prototype,g.prototype,{action_icons_def:g.prototype.action_icons_def.concat([{name:"show_more_rows_icon",title:"To minimize track height, not all feature rows are displayed. Click to display more rows.",css_class:"exclamation",on_click_fn:function(ac){$(".bs-tooltip").remove();ac.slotters[ac.view.resolution_px_b].max_rows*=2;ac.request_draw(true)},hide:true}]),copy:function(ac){var ad=this.to_dict();q(ad,{data_manager:this.data_manager});var ae=new this.constructor(this.view,ac,ad);ae.change_mode(this.mode);ae.enabled=this.enabled;return ae},set_filters_manager:function(ac){this.filters_manager=ac;this.header_div.after(this.filters_manager.parent_div)},to_dict:function(){return{track_type:this.get_type(),name:this.name,hda_ldda:this.hda_ldda,dataset_id:this.dataset_id,prefs:this.prefs,mode:this.mode,filters:this.filters_manager.to_dict(),tool_state:(this.tool?this.tool.state_dict():{})}},change_mode:function(ad){var ac=this;ac.mode=ad;ac.config.values.mode=ad;ac.tile_cache.clear();ac.request_draw();this.action_icons.mode_icon.attr("title","Set display mode (now: "+ac.mode+")");return ac},update_icons:function(){var ac=this;if(ac.filters_available){ac.action_icons.filters_icon.show()}else{ac.action_icons.filters_icon.hide()}if(ac.tool){ac.action_icons.tools_icon.show();ac.action_icons.param_space_viz_icon.show()}else{ac.action_icons.tools_icon.hide();ac.action_icons.param_space_viz_icon.hide()}},_gen_tile_cache_key:function(ad,ac){return ad+"_"+ac},request_draw:function(ad,ac){this.view.request_redraw(false,ad,ac,this)},before_draw:function(){},_draw:function(ad,ao){if(!this.can_draw()){return}var al=this.view.low,ah=this.view.high,aj=ah-al,ae=this.view.container.width(),aq=this.view.resolution_px_b,ag=this.view.resolution_b_px;if(this.is_overview){al=this.view.max_low;ah=this.view.max_high;ag=(view.max_high-view.max_low)/ae;aq=1/ag}this.before_draw();this.tiles_div.children().addClass("remove");var ac=Math.floor(al/(ag*S)),ak=true,ap=[],ai=function(ar){return(ar&&"track" in ar)};while((ac*S*ag)
    ");while(ac"+commatize(ac)+"
    ").css({position:"absolute",left:ah-1}));ac+=ai}this.content_div.children(":first").remove();this.content_div.append(ad)}});var f=function(ad,ac,ag){M.call(this,ad,ac,ag);this.drawables=[];this.left_offset=0;if("drawables" in ag){var af;for(var ae=0;aethis.left_offset){this.left_offset=af.left_offset}}this.enabled=true}if(this.drawables.length!==0){this.set_display_modes(this.drawables[0].display_modes,this.drawables[0].mode)}this.update_icons();this.obj_type="CompositeTrack"};q(f.prototype,M.prototype,{action_icons_def:[{name:"composite_icon",title:"Show individual tracks",css_class:"layers-stack",on_click_fn:function(ac){$(".bs-tooltip").remove();ac.show_group()}}].concat(M.prototype.action_icons_def),to_dict:z.prototype.to_dict,add_drawable:z.prototype.add_drawable,unpack_drawables:z.prototype.unpack_drawables,change_mode:function(ac){M.prototype.change_mode.call(this,ac);for(var ad=0;adview.canvas_manager.char_width_px){am=view.reference_track.data_manager.get_data(ah,aq.mode,av,view.reference_track.data_url_extra_params);if(V(am)){ao=false}}ak.push(am)}if(ao){q(aw,at.more_tile_data);this.tile_predraw_init();var ag=aq.view.canvas_manager.new_canvas(),ay=ah.get("start"),ae=ah.get("end"),az=0,ar=Math.ceil((ae-ay)*al)+this.left_offset,ap=0,af=[],ax;var ac=0;for(ax=0;axap){ap=ac}}ag.width=ar;ag.height=(at.height?at.height:ap);az=0;var au=ag.getContext("2d");au.translate(this.left_offset,0);au.globalAlpha=0.5;au.globalCompositeOperation="source-over";for(ax=0;axac){ac=ad.prefs.max_value}}}for(var ae=0;aeag){ag=ac}}for(var af=0;afthis.view.canvas_manager.char_width_px){this.tiles_div.show();return M.prototype.draw_helper.call(this,ae,af,ac,ag,ah,ad)}else{this.tiles_div.hide();return null}},can_subset:function(ac){return true},draw_tile:function(af,al,ag,ad,ai,am){var ae=this.data_manager.subset_entry(af,ai),ak=ae.data;var ac=al.canvas;al.font=al.canvas.manager.default_font;al.textAlign="center";for(var ah=0,aj=ak.length;ah").text(W(ac.prefs.min_value,3)).make_text_editable({num_cols:6,on_finish:function(aj){$(".bs-tooltip").remove();var aj=parseFloat(aj);if(!isNaN(aj)){ac.set_min_value(aj)}},help_text:"Set min value"}).addClass("yaxislabel bottom").attr("id","linetrack_"+ac.dataset_id+"_minval").prependTo(ac.container_div),af=$("
    ").text(W(ac.prefs.max_value,3)).make_text_editable({num_cols:6,on_finish:function(aj){$(".bs-tooltip").remove();var aj=parseFloat(aj);if(!isNaN(aj)){ac.set_max_value(aj)}},help_text:"Set max value"}).addClass("yaxislabel top").attr("id","linetrack_"+ac.dataset_id+"_maxval").prependTo(ac.container_div)})},draw_tile:function(al,aj,ae,ad,ag,ak){var ac=aj.canvas,af=ag.get("start"),ai=ag.get("end"),ah=new L.LinePainter(al.data,af,ai,this.prefs,ae);ah.draw(aj,ac.width,ac.height,ak);return new b(this,ag,ad,ac,al.data)},can_subset:function(ac){return(ac.data[1][0]-ac.data[0][0]===1)}});var t=function(ae,ad,af){var ac=this;this.display_modes=["Heatmap"];this.mode="Heatmap";M.call(this,ae,ad,af);this.hda_ldda=af.hda_ldda;this.dataset_id=af.dataset_id;this.original_dataset_id=this.dataset_id;this.left_offset=0;this.config=new F({track:this,params:[{key:"name",label:"Name",type:"text",default_value:this.name},{key:"pos_color",label:"Positive Color",type:"color",default_value:"#FF8C00"},{key:"neg_color",label:"Negative Color",type:"color",default_value:"#4169E1"},{key:"min_value",label:"Min Value",type:"float",default_value:-1},{key:"max_value",label:"Max Value",type:"float",default_value:1},{key:"mode",type:"string",default_value:this.mode,hidden:true},{key:"height",type:"int",default_value:500,hidden:true}],saved_values:af.prefs,onchange:function(){ac.set_name(ac.prefs.name);ac.vertical_range=ac.prefs.max_value-ac.prefs.min_value;ac.set_min_value(ac.prefs.min_value);ac.set_max_value(ac.prefs.max_value)}});this.prefs=this.config.values;this.visible_height_px=this.config.values.height;this.vertical_range=this.config.values.max_value-this.config.values.min_value};q(t.prototype,r.prototype,M.prototype,{on_resize:function(){this.request_draw(true)},set_min_value:function(ac){this.prefs.min_value=ac;this.tile_cache.clear();this.request_draw()},set_max_value:function(ac){this.prefs.max_value=ac;this.tile_cache.clear();this.request_draw()},draw_tile:function(ac,ae,ai,ag,ah,aj){var af=ae.canvas,ad=new L.DiagonalHeatmapPainter(ac.data,ah.get("start"),ah.get("end"),this.prefs,ai);ad.draw(ae,af.width,af.height,aj);return new b(this,ah,ag,af,ac.data)}});var c=function(af,ae,ah){var ad=this;this.display_modes=["Auto","Coverage","Dense","Squish","Pack"];M.call(this,af,ae,ah);var ag=l.get_random_color(),ac=l.get_random_color([ag,"#FFFFFF"]);this.config=new F({track:this,params:[{key:"name",label:"Name",type:"text",default_value:this.name},{key:"block_color",label:"Block color",type:"color",default_value:ag},{key:"reverse_strand_color",label:"Antisense strand color",type:"color",default_value:ac},{key:"label_color",label:"Label color",type:"color",default_value:"black"},{key:"show_counts",label:"Show summary counts",type:"bool",default_value:true,help:"Show the number of items in each bin when drawing summary histogram"},{key:"histogram_max",label:"Histogram maximum",type:"float",default_value:null,help:"clear value to set automatically"},{key:"connector_style",label:"Connector style",type:"select",default_value:"fishbones",options:[{label:"Line with arrows",value:"fishbone"},{label:"Arcs",value:"arcs"}]},{key:"mode",type:"string",default_value:this.mode,hidden:true},{key:"height",type:"int",default_value:this.visible_height_px,hidden:true}],saved_values:ah.prefs,onchange:function(){ad.set_name(ad.prefs.name);ad.tile_cache.clear();ad.set_painter_from_config();ad.request_draw()}});this.prefs=this.config.values;this.visible_height_px=this.config.values.height;this.container_div.addClass("feature-track");this.hda_ldda=ah.hda_ldda;this.dataset_id=ah.dataset_id;this.original_dataset_id=ah.dataset_id;this.show_labels_scale=0.001;this.showing_details=false;this.summary_draw_height=30;this.slotters={};this.start_end_dct={};this.left_offset=200;this.set_painter_from_config()};q(c.prototype,r.prototype,M.prototype,{set_dataset:function(ac){this.dataset_id=ac.get("id");this.hda_ldda=ac.get("hda_ldda");this.dataset=ac;this.data_manager.set("dataset",ac)},set_painter_from_config:function(){if(this.config.values.connector_style==="arcs"){this.painter=L.ArcLinkedFeaturePainter}else{this.painter=L.LinkedFeaturePainter}},before_draw:function(){this.max_height_px=0},postdraw_actions:function(ar,am,ah,ag){M.prototype.postdraw_actions.call(this,ar,ag);var al=this,ao;if(al.mode==="Coverage"){var ad=-1;for(ao=0;aoad){ad=an}}for(ao=0;ao").text(ak).make_text_editable({num_cols:12,on_finish:function(av){$(".bs-tooltip").remove();var av=parseFloat(av);al.prefs.histogram_max=(!isNaN(av)?av:null);al.tile_cache.clear();al.request_draw()},help_text:"Set max value; leave blank to use default"}).addClass("yaxislabel top").css("color",this.prefs.label_color);this.container_div.prepend(ae)}if(af instanceof O){var ap=true;for(ao=0;aoI){mode="Squish"}else{mode="Pack"}}}return mode},get_canvas_height:function(ac,ag,ah,ad){if(ag==="summary_tree"||ag==="Coverage"){return this.summary_draw_height}else{var af=this.incremental_slots(ah,ac.data,ag);var ae=new (this.painter)(null,null,null,this.prefs,ag);return Math.max(aa,ae.get_required_height(af,ad))}},draw_tile:function(am,aq,ao,ar,af,aj,ae){var ap=this,ad=aq.canvas,ay=af.get("start"),ac=af.get("end"),ag=this.left_offset;if(ao==="summary_tree"||ao==="Coverage"){var aA=new L.SummaryTreePainter(am,ay,ac,this.prefs);aA.draw(aq,ad.width,ad.height,aj);return new j(ap,af,ar,ad,am.data,am.max)}var ai=[],an=this.slotters[aj].slots;all_slotted=true;if(am.data){var ak=this.filters_manager.filters;for(var at=0,av=am.data.length;at Date: Mon, 1 Apr 2013 12:28:44 -0400 Subject: [PATCH 055/523] api: history_contents.show, datasets.show add both types of display app links for consistency --- lib/galaxy/webapps/galaxy/api/datasets.py | 3 +++ lib/galaxy/webapps/galaxy/api/history_contents.py | 2 ++ 2 files changed, 5 insertions(+) diff --git a/lib/galaxy/webapps/galaxy/api/datasets.py b/lib/galaxy/webapps/galaxy/api/datasets.py index 52b2a4cea1e..85c096cb014 100644 --- a/lib/galaxy/webapps/galaxy/api/datasets.py +++ b/lib/galaxy/webapps/galaxy/api/datasets.py @@ -53,6 +53,9 @@ class DatasetsController( BaseAPIController, UsesVisualizationMixin, UsesHistory # Default: return dataset as dict. if hda_ldda == 'hda': rval = self.get_hda_dict( trans, dataset ) + # add these to match api/history_contents.py, show + rval[ 'display_types' ] = self.get_old_display_applications( trans, dataset ) + rval[ 'display_apps' ] = self.get_display_apps( trans, dataset ) else: rval = dataset.get_api_value() diff --git a/lib/galaxy/webapps/galaxy/api/history_contents.py b/lib/galaxy/webapps/galaxy/api/history_contents.py index cce39d6a9e6..bfb7a77333c 100644 --- a/lib/galaxy/webapps/galaxy/api/history_contents.py +++ b/lib/galaxy/webapps/galaxy/api/history_contents.py @@ -142,6 +142,8 @@ class HistoryContentsController( BaseAPIController, UsesHistoryDatasetAssociatio check_ownership=True, check_accessible=True ) hda_dict = self.get_hda_dict( trans, hda ) + hda_dict[ 'display_types' ] = self.get_old_display_applications( trans, hda ) + hda_dict[ 'display_apps' ] = self.get_display_apps( trans, hda ) except Exception, e: msg = "Error in history API at listing dataset: %s" % ( str(e) ) From 9fbdb560cf2e6e7313e3c5183f0bbd2a0b14eaf8 Mon Sep 17 00:00:00 2001 From: James Taylor Date: Mon, 1 Apr 2013 14:01:56 -0400 Subject: [PATCH 056/523] biostar: bugfix for display name of anonymous users --- lib/galaxy/webapps/galaxy/controllers/biostar.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/galaxy/webapps/galaxy/controllers/biostar.py b/lib/galaxy/webapps/galaxy/controllers/biostar.py index 8162a6c0672..88835baebb3 100644 --- a/lib/galaxy/webapps/galaxy/controllers/biostar.py +++ b/lib/galaxy/webapps/galaxy/controllers/biostar.py @@ -81,7 +81,7 @@ class BiostarController( BaseUIController ): else: encoded = trans.security.encode_id( trans.galaxy_session.id ) payload['username'] = "anon-" + encoded - payload['display_name'] = "Anonymous Galaxy User %d" % encoded[0:8] + payload['display_name'] = "Anonymous Galaxy User" data, digest = encode_data( trans.app.config.biostar_key, payload ) return trans.response.send_redirect( url_for( trans.app.config.biostar_url, data=data, digest=digest, name=trans.app.config.biostar_key_name, action=biostar_action ) ) From 2e577455322df262facd1d7d434b2560c87fe6e7 Mon Sep 17 00:00:00 2001 From: Raj Ayyampalayam Date: Mon, 1 Apr 2013 16:59:55 -0400 Subject: [PATCH 057/523] Changed the logic in the trinity wrapper to handle stranded/un-stranded ran-seq data. Got the wrapper working with the latest trinity package. --- tools/ngs_rna/trinity_all.xml | 44 +++++++++++++++++++---------------- 1 file changed, 24 insertions(+), 20 deletions(-) diff --git a/tools/ngs_rna/trinity_all.xml b/tools/ngs_rna/trinity_all.xml index b44493e04cf..e870e8a4780 100644 --- a/tools/ngs_rna/trinity_all.xml +++ b/tools/ngs_rna/trinity_all.xml @@ -20,9 +20,6 @@ #else: --seqType fq #end if - #if $inputs.library_type != 'None': - --SS_lib_type $inputs.library_type - #end if #else: --single $inputs.input #if $inputs.input.ext == 'fa': @@ -30,15 +27,16 @@ #else: --seqType fq #end if - #if $inputs.library_type != 'None': - --SS_lib_type $inputs.library_type - #end if + #end if + + #if $strand.is_strand_specific == "yes": + --SS_lib_type $strand.library_type #end if - --kmer_method meryl - + --JM 100G --full_cleanup + ## CPU and butterfly options. - --CPU 4 --bfly_opts "-V 10 --stderr" > $trinity_log 2>&1 + --CPU 6 --bfly_opts "-V 10 --stderr" > $trinity_log 2>&1 @@ -49,22 +47,28 @@ - - - - - - - - - - + + + + + + + + + + + + + + + + @@ -79,7 +83,7 @@ - + From 2935ca8e4229b128857a18e8e3590677742706f5 Mon Sep 17 00:00:00 2001 From: Dave Clements Date: Mon, 1 Apr 2013 14:49:17 -0700 Subject: [PATCH 058/523] New RST files describing Sphinx doc for tool shed changes. --- .../lib/galaxy.webapps.tool_shed.api.rst | 19 ++++ .../galaxy.webapps.tool_shed.controllers.rst | 59 ++++++++++ ...webapps.tool_shed.framework.middleware.rst | 27 +++++ .../galaxy.webapps.tool_shed.framework.rst | 18 +++ ...galaxy.webapps.tool_shed.model.migrate.rst | 11 ++ .../lib/galaxy.webapps.tool_shed.model.rst | 26 +++++ doc/source/lib/galaxy.webapps.tool_shed.rst | 47 ++++++++ .../lib/galaxy.webapps.tool_shed.security.rst | 11 ++ .../lib/galaxy.webapps.tool_shed.util.rst | 35 ++++++ .../lib/tool_shed.galaxy_install.grids.rst | 11 ++ .../lib/tool_shed.galaxy_install.migrate.rst | 19 ++++ doc/source/lib/tool_shed.galaxy_install.rst | 44 +++++++ ..._shed.galaxy_install.tool_dependencies.rst | 27 +++++ doc/source/lib/tool_shed.grids.rst | 35 ++++++ doc/source/lib/tool_shed.rst | 20 ++++ doc/source/lib/tool_shed.util.rst | 107 ++++++++++++++++++ 16 files changed, 516 insertions(+) create mode 100644 doc/source/lib/galaxy.webapps.tool_shed.api.rst create mode 100644 doc/source/lib/galaxy.webapps.tool_shed.controllers.rst create mode 100644 doc/source/lib/galaxy.webapps.tool_shed.framework.middleware.rst create mode 100644 doc/source/lib/galaxy.webapps.tool_shed.framework.rst create mode 100644 doc/source/lib/galaxy.webapps.tool_shed.model.migrate.rst create mode 100644 doc/source/lib/galaxy.webapps.tool_shed.model.rst create mode 100644 doc/source/lib/galaxy.webapps.tool_shed.rst create mode 100644 doc/source/lib/galaxy.webapps.tool_shed.security.rst create mode 100644 doc/source/lib/galaxy.webapps.tool_shed.util.rst create mode 100644 doc/source/lib/tool_shed.galaxy_install.grids.rst create mode 100644 doc/source/lib/tool_shed.galaxy_install.migrate.rst create mode 100644 doc/source/lib/tool_shed.galaxy_install.rst create mode 100644 doc/source/lib/tool_shed.galaxy_install.tool_dependencies.rst create mode 100644 doc/source/lib/tool_shed.grids.rst create mode 100644 doc/source/lib/tool_shed.rst create mode 100644 doc/source/lib/tool_shed.util.rst diff --git a/doc/source/lib/galaxy.webapps.tool_shed.api.rst b/doc/source/lib/galaxy.webapps.tool_shed.api.rst new file mode 100644 index 00000000000..854f99f3e03 --- /dev/null +++ b/doc/source/lib/galaxy.webapps.tool_shed.api.rst @@ -0,0 +1,19 @@ +api Package +=========== + +:mod:`repositories` Module +-------------------------- + +.. automodule:: galaxy.webapps.tool_shed.api.repositories + :members: + :undoc-members: + :show-inheritance: + +:mod:`repository_revisions` Module +---------------------------------- + +.. automodule:: galaxy.webapps.tool_shed.api.repository_revisions + :members: + :undoc-members: + :show-inheritance: + diff --git a/doc/source/lib/galaxy.webapps.tool_shed.controllers.rst b/doc/source/lib/galaxy.webapps.tool_shed.controllers.rst new file mode 100644 index 00000000000..01163fcd545 --- /dev/null +++ b/doc/source/lib/galaxy.webapps.tool_shed.controllers.rst @@ -0,0 +1,59 @@ +controllers Package +=================== + +:mod:`controllers` Package +-------------------------- + +.. automodule:: galaxy.webapps.tool_shed.controllers + :members: + :undoc-members: + :show-inheritance: + +:mod:`admin` Module +------------------- + +.. automodule:: galaxy.webapps.tool_shed.controllers.admin + :members: + :undoc-members: + :show-inheritance: + +:mod:`hg` Module +---------------- + +.. automodule:: galaxy.webapps.tool_shed.controllers.hg + :members: + :undoc-members: + :show-inheritance: + +:mod:`repository` Module +------------------------ + +.. automodule:: galaxy.webapps.tool_shed.controllers.repository + :members: + :undoc-members: + :show-inheritance: + +:mod:`repository_review` Module +------------------------------- + +.. automodule:: galaxy.webapps.tool_shed.controllers.repository_review + :members: + :undoc-members: + :show-inheritance: + +:mod:`upload` Module +-------------------- + +.. automodule:: galaxy.webapps.tool_shed.controllers.upload + :members: + :undoc-members: + :show-inheritance: + +:mod:`user` Module +------------------ + +.. automodule:: galaxy.webapps.tool_shed.controllers.user + :members: + :undoc-members: + :show-inheritance: + diff --git a/doc/source/lib/galaxy.webapps.tool_shed.framework.middleware.rst b/doc/source/lib/galaxy.webapps.tool_shed.framework.middleware.rst new file mode 100644 index 00000000000..4de7ea427c2 --- /dev/null +++ b/doc/source/lib/galaxy.webapps.tool_shed.framework.middleware.rst @@ -0,0 +1,27 @@ +middleware Package +================== + +:mod:`middleware` Package +------------------------- + +.. automodule:: galaxy.webapps.tool_shed.framework.middleware + :members: + :undoc-members: + :show-inheritance: + +:mod:`hg` Module +---------------- + +.. automodule:: galaxy.webapps.tool_shed.framework.middleware.hg + :members: + :undoc-members: + :show-inheritance: + +:mod:`remoteuser` Module +------------------------ + +.. automodule:: galaxy.webapps.tool_shed.framework.middleware.remoteuser + :members: + :undoc-members: + :show-inheritance: + diff --git a/doc/source/lib/galaxy.webapps.tool_shed.framework.rst b/doc/source/lib/galaxy.webapps.tool_shed.framework.rst new file mode 100644 index 00000000000..d0f4b8abdf8 --- /dev/null +++ b/doc/source/lib/galaxy.webapps.tool_shed.framework.rst @@ -0,0 +1,18 @@ +framework Package +================= + +:mod:`framework` Package +------------------------ + +.. automodule:: galaxy.webapps.tool_shed.framework + :members: + :undoc-members: + :show-inheritance: + +Subpackages +----------- + +.. toctree:: + + galaxy.webapps.tool_shed.framework.middleware + diff --git a/doc/source/lib/galaxy.webapps.tool_shed.model.migrate.rst b/doc/source/lib/galaxy.webapps.tool_shed.model.migrate.rst new file mode 100644 index 00000000000..2d4197c30a3 --- /dev/null +++ b/doc/source/lib/galaxy.webapps.tool_shed.model.migrate.rst @@ -0,0 +1,11 @@ +migrate Package +=============== + +:mod:`check` Module +------------------- + +.. automodule:: galaxy.webapps.tool_shed.model.migrate.check + :members: + :undoc-members: + :show-inheritance: + diff --git a/doc/source/lib/galaxy.webapps.tool_shed.model.rst b/doc/source/lib/galaxy.webapps.tool_shed.model.rst new file mode 100644 index 00000000000..429f0d57917 --- /dev/null +++ b/doc/source/lib/galaxy.webapps.tool_shed.model.rst @@ -0,0 +1,26 @@ +model Package +============= + +:mod:`model` Package +-------------------- + +.. automodule:: galaxy.webapps.tool_shed.model + :members: + :undoc-members: + :show-inheritance: + +:mod:`mapping` Module +--------------------- + +.. automodule:: galaxy.webapps.tool_shed.model.mapping + :members: + :undoc-members: + :show-inheritance: + +Subpackages +----------- + +.. toctree:: + + galaxy.webapps.tool_shed.model.migrate + diff --git a/doc/source/lib/galaxy.webapps.tool_shed.rst b/doc/source/lib/galaxy.webapps.tool_shed.rst new file mode 100644 index 00000000000..1590f2988c1 --- /dev/null +++ b/doc/source/lib/galaxy.webapps.tool_shed.rst @@ -0,0 +1,47 @@ +tool_shed Package +================= + +:mod:`tool_shed` Package +------------------------ + +.. automodule:: galaxy.webapps.tool_shed + :members: + :undoc-members: + :show-inheritance: + +:mod:`app` Module +----------------- + +.. automodule:: galaxy.webapps.tool_shed.app + :members: + :undoc-members: + :show-inheritance: + +:mod:`buildapp` Module +---------------------- + +.. automodule:: galaxy.webapps.tool_shed.buildapp + :members: + :undoc-members: + :show-inheritance: + +:mod:`config` Module +-------------------- + +.. automodule:: galaxy.webapps.tool_shed.config + :members: + :undoc-members: + :show-inheritance: + +Subpackages +----------- + +.. toctree:: + + galaxy.webapps.tool_shed.api + galaxy.webapps.tool_shed.controllers + galaxy.webapps.tool_shed.framework + galaxy.webapps.tool_shed.model + galaxy.webapps.tool_shed.security + galaxy.webapps.tool_shed.util + diff --git a/doc/source/lib/galaxy.webapps.tool_shed.security.rst b/doc/source/lib/galaxy.webapps.tool_shed.security.rst new file mode 100644 index 00000000000..ac3f17fb38d --- /dev/null +++ b/doc/source/lib/galaxy.webapps.tool_shed.security.rst @@ -0,0 +1,11 @@ +security Package +================ + +:mod:`security` Package +----------------------- + +.. automodule:: galaxy.webapps.tool_shed.security + :members: + :undoc-members: + :show-inheritance: + diff --git a/doc/source/lib/galaxy.webapps.tool_shed.util.rst b/doc/source/lib/galaxy.webapps.tool_shed.util.rst new file mode 100644 index 00000000000..a8623852afa --- /dev/null +++ b/doc/source/lib/galaxy.webapps.tool_shed.util.rst @@ -0,0 +1,35 @@ +util Package +============ + +:mod:`common_util` Module +------------------------- + +.. automodule:: galaxy.webapps.tool_shed.util.common_util + :members: + :undoc-members: + :show-inheritance: + +:mod:`container_util` Module +---------------------------- + +.. automodule:: galaxy.webapps.tool_shed.util.container_util + :members: + :undoc-members: + :show-inheritance: + +:mod:`hgweb_config` Module +-------------------------- + +.. automodule:: galaxy.webapps.tool_shed.util.hgweb_config + :members: + :undoc-members: + :show-inheritance: + +:mod:`shed_statistics` Module +----------------------------- + +.. automodule:: galaxy.webapps.tool_shed.util.shed_statistics + :members: + :undoc-members: + :show-inheritance: + diff --git a/doc/source/lib/tool_shed.galaxy_install.grids.rst b/doc/source/lib/tool_shed.galaxy_install.grids.rst new file mode 100644 index 00000000000..c75c5c716ea --- /dev/null +++ b/doc/source/lib/tool_shed.galaxy_install.grids.rst @@ -0,0 +1,11 @@ +grids Package +============= + +:mod:`admin_toolshed_grids` Module +---------------------------------- + +.. automodule:: tool_shed.galaxy_install.grids.admin_toolshed_grids + :members: + :undoc-members: + :show-inheritance: + diff --git a/doc/source/lib/tool_shed.galaxy_install.migrate.rst b/doc/source/lib/tool_shed.galaxy_install.migrate.rst new file mode 100644 index 00000000000..5d553b446d1 --- /dev/null +++ b/doc/source/lib/tool_shed.galaxy_install.migrate.rst @@ -0,0 +1,19 @@ +migrate Package +=============== + +:mod:`check` Module +------------------- + +.. automodule:: tool_shed.galaxy_install.migrate.check + :members: + :undoc-members: + :show-inheritance: + +:mod:`common` Module +-------------------- + +.. automodule:: tool_shed.galaxy_install.migrate.common + :members: + :undoc-members: + :show-inheritance: + diff --git a/doc/source/lib/tool_shed.galaxy_install.rst b/doc/source/lib/tool_shed.galaxy_install.rst new file mode 100644 index 00000000000..28e61519afe --- /dev/null +++ b/doc/source/lib/tool_shed.galaxy_install.rst @@ -0,0 +1,44 @@ +galaxy_install Package +====================== + +:mod:`galaxy_install` Package +----------------------------- + +.. automodule:: tool_shed.galaxy_install + :members: + :undoc-members: + :show-inheritance: + +:mod:`install_manager` Module +----------------------------- + +.. automodule:: tool_shed.galaxy_install.install_manager + :members: + :undoc-members: + :show-inheritance: + +:mod:`repository_util` Module +----------------------------- + +.. automodule:: tool_shed.galaxy_install.repository_util + :members: + :undoc-members: + :show-inheritance: + +:mod:`update_manager` Module +---------------------------- + +.. automodule:: tool_shed.galaxy_install.update_manager + :members: + :undoc-members: + :show-inheritance: + +Subpackages +----------- + +.. toctree:: + + tool_shed.galaxy_install.grids + tool_shed.galaxy_install.migrate + tool_shed.galaxy_install.tool_dependencies + diff --git a/doc/source/lib/tool_shed.galaxy_install.tool_dependencies.rst b/doc/source/lib/tool_shed.galaxy_install.tool_dependencies.rst new file mode 100644 index 00000000000..4871c0b1f58 --- /dev/null +++ b/doc/source/lib/tool_shed.galaxy_install.tool_dependencies.rst @@ -0,0 +1,27 @@ +tool_dependencies Package +========================= + +:mod:`common_util` Module +------------------------- + +.. automodule:: tool_shed.galaxy_install.tool_dependencies.common_util + :members: + :undoc-members: + :show-inheritance: + +:mod:`fabric_util` Module +------------------------- + +.. automodule:: tool_shed.galaxy_install.tool_dependencies.fabric_util + :members: + :undoc-members: + :show-inheritance: + +:mod:`install_util` Module +-------------------------- + +.. automodule:: tool_shed.galaxy_install.tool_dependencies.install_util + :members: + :undoc-members: + :show-inheritance: + diff --git a/doc/source/lib/tool_shed.grids.rst b/doc/source/lib/tool_shed.grids.rst new file mode 100644 index 00000000000..154f92b8369 --- /dev/null +++ b/doc/source/lib/tool_shed.grids.rst @@ -0,0 +1,35 @@ +grids Package +============= + +:mod:`admin_grids` Module +------------------------- + +.. automodule:: tool_shed.grids.admin_grids + :members: + :undoc-members: + :show-inheritance: + +:mod:`repository_grids` Module +------------------------------ + +.. automodule:: tool_shed.grids.repository_grids + :members: + :undoc-members: + :show-inheritance: + +:mod:`repository_review_grids` Module +------------------------------------- + +.. automodule:: tool_shed.grids.repository_review_grids + :members: + :undoc-members: + :show-inheritance: + +:mod:`util` Module +------------------ + +.. automodule:: tool_shed.grids.util + :members: + :undoc-members: + :show-inheritance: + diff --git a/doc/source/lib/tool_shed.rst b/doc/source/lib/tool_shed.rst new file mode 100644 index 00000000000..76794397c1c --- /dev/null +++ b/doc/source/lib/tool_shed.rst @@ -0,0 +1,20 @@ +tool_shed Package +================= + +:mod:`tool_shed_registry` Module +-------------------------------- + +.. automodule:: tool_shed.tool_shed_registry + :members: + :undoc-members: + :show-inheritance: + +Subpackages +----------- + +.. toctree:: + + tool_shed.galaxy_install + tool_shed.grids + tool_shed.util + diff --git a/doc/source/lib/tool_shed.util.rst b/doc/source/lib/tool_shed.util.rst new file mode 100644 index 00000000000..17d6e4c490d --- /dev/null +++ b/doc/source/lib/tool_shed.util.rst @@ -0,0 +1,107 @@ +util Package +============ + +:mod:`common_install_util` Module +--------------------------------- + +.. automodule:: tool_shed.util.common_install_util + :members: + :undoc-members: + :show-inheritance: + +:mod:`common_util` Module +------------------------- + +.. automodule:: tool_shed.util.common_util + :members: + :undoc-members: + :show-inheritance: + +:mod:`data_manager_util` Module +------------------------------- + +.. automodule:: tool_shed.util.data_manager_util + :members: + :undoc-members: + :show-inheritance: + +:mod:`datatype_util` Module +--------------------------- + +.. automodule:: tool_shed.util.datatype_util + :members: + :undoc-members: + :show-inheritance: + +:mod:`encoding_util` Module +--------------------------- + +.. automodule:: tool_shed.util.encoding_util + :members: + :undoc-members: + :show-inheritance: + +:mod:`metadata_util` Module +--------------------------- + +.. automodule:: tool_shed.util.metadata_util + :members: + :undoc-members: + :show-inheritance: + +:mod:`readme_util` Module +------------------------- + +.. automodule:: tool_shed.util.readme_util + :members: + :undoc-members: + :show-inheritance: + +:mod:`repository_dependency_util` Module +---------------------------------------- + +.. automodule:: tool_shed.util.repository_dependency_util + :members: + :undoc-members: + :show-inheritance: + +:mod:`review_util` Module +------------------------- + +.. automodule:: tool_shed.util.review_util + :members: + :undoc-members: + :show-inheritance: + +:mod:`shed_util_common` Module +------------------------------ + +.. automodule:: tool_shed.util.shed_util_common + :members: + :undoc-members: + :show-inheritance: + +:mod:`tool_dependency_util` Module +---------------------------------- + +.. automodule:: tool_shed.util.tool_dependency_util + :members: + :undoc-members: + :show-inheritance: + +:mod:`tool_util` Module +----------------------- + +.. automodule:: tool_shed.util.tool_util + :members: + :undoc-members: + :show-inheritance: + +:mod:`workflow_util` Module +--------------------------- + +.. automodule:: tool_shed.util.workflow_util + :members: + :undoc-members: + :show-inheritance: + From fa89edf316c9aa27d5f2fa0a105ab66628caf5fe Mon Sep 17 00:00:00 2001 From: Carl Eberhard Date: Tue, 2 Apr 2013 10:50:56 -0400 Subject: [PATCH 059/523] fix simple-galaxy.js util in browser test section --- test/casperjs/utils/simple-galaxy.js | 66 +++++++++++++++++++++++++++- 1 file changed, 65 insertions(+), 1 deletion(-) mode change 120000 => 100755 test/casperjs/utils/simple-galaxy.js diff --git a/test/casperjs/utils/simple-galaxy.js b/test/casperjs/utils/simple-galaxy.js deleted file mode 120000 index 7a9450eb4d0..00000000000 --- a/test/casperjs/utils/simple-galaxy.js +++ /dev/null @@ -1 +0,0 @@ -/Users/carleberhard/explore/phantom-casper/simple-galaxy.js \ No newline at end of file diff --git a/test/casperjs/utils/simple-galaxy.js b/test/casperjs/utils/simple-galaxy.js new file mode 100755 index 00000000000..7d459da8964 --- /dev/null +++ b/test/casperjs/utils/simple-galaxy.js @@ -0,0 +1,65 @@ +/* Utility to load a specific page and output html, page text, or a screenshot + * Optionally wait for some time, text, or dom selector + */ +try { + //...if there's a better way - please let me know, universe + var scriptDir = require( 'system' ).args[3] + // remove the script filename + .replace( /[\w|\.|\-|_]*$/, '' ) + // if given rel. path, prepend the curr dir + .replace( /^(?!\/)/, './' ), + spaceghost = require( scriptDir + 'spaceghost' ).create({ + // script options here (can be overridden by CLI) + //verbose: true, + //logLevel: debug, + scriptDir: scriptDir + }); + + +} catch( error ){ + console.debug( error ); + phantom.exit( 1 ); +} + +spaceghost.start(); + +// Use the --url to load a specific page (e.g. --url="http://localhost:8080/history") +spaceghost.thenOpen( spaceghost.baseUrl, function(){ + + // options for waiting before output/screenshot: + // --waitMs= --> wait some number of Ms before output + // --waitForText= --> wait for some text to be rendered before output + // --waitForSelector= --> wait for some DOM to be rendered before output + if( 'waitMs' in spaceghost.cli.options ){ + spaceghost.wait( spaceghost.cli.get( 'waitMs' ) ); + + } else if( 'waitForText' in spaceghost.cli.options ){ + spaceghost.waitForText( spaceghost.cli.get( 'waitForText' ) ); + + } else if( 'waitForSelector' in spaceghost.cli.options ){ + spaceghost.waitForSelector( spaceghost.cli.get( 'waitForSelector' ) ); + } + + // --capture= --> capture a screenshot of the page + // --html --> output the html of the page + // (if not --html) --> output the text of the page + spaceghost.then( function(){ + if( 'capture' in spaceghost.cli.options ){ + var sshotFilename = spaceghost.cli.get( 'capture' ); + spaceghost.debug( 'screenshot stored at: ' + sshotFilename ); + spaceghost.capture( sshotFilename ); + + } else { + if( spaceghost.cli.args.indexOf( 'html' ) != -1 ){ + spaceghost.debugHTML(); + + } else { + spaceghost.debugPage(); + } + } + }); + +}); + +spaceghost.run( function(){ +}); From 9769304769ad6993e046491371af0c2289df183d Mon Sep 17 00:00:00 2001 From: Dave Bouvier Date: Tue, 2 Apr 2013 11:47:38 -0400 Subject: [PATCH 060/523] Fix for adding new entries to a tool data table that is already present in app.tool_data_tables. --- lib/galaxy/tools/data/__init__.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/galaxy/tools/data/__init__.py b/lib/galaxy/tools/data/__init__.py index 92103823898..73699363a82 100644 --- a/lib/galaxy/tools/data/__init__.py +++ b/lib/galaxy/tools/data/__init__.py @@ -59,6 +59,11 @@ class ToolDataTableManager( object ): if table.name not in self.data_tables: self.data_tables[ table.name ] = table log.debug( "Loaded tool data table '%s'", table.name ) + else: + for table_row in table.data: + # FIXME: This does not account for an entry with the same unique build ID, but a different path. + if table_row not in self.data_tables[ table.name ].data: + self.data_tables[ table.name ].data.append( table_row ) return table_elems def add_new_entries_from_config_file( self, config_filename, tool_data_path, shed_tool_data_table_config, persist=False ): """ From 96f5b1854e33440df42890c202ab65b475151465 Mon Sep 17 00:00:00 2001 From: Carl Eberhard Date: Tue, 2 Apr 2013 14:20:55 -0400 Subject: [PATCH 061/523] pack_scripts.py: have yuicompressor use utf-8 as the charset; remove unused packed scripts; update d3.js from 2.9.2 to 3.1.4 --- static/scripts/libs/d3.js | 17464 ++++++++-------- static/scripts/pack_scripts.py | 2 +- static/scripts/packed/libs/d3.js | 2 +- static/scripts/packed/libs/handlebars.full.js | 1 - static/scripts/packed/mvc/history.js | 1 - .../packed/templates/compiled/panel_label.js | 1 - .../template-visualization-chartSettings.js | 1 - .../template-visualization-statsTable.js | 1 - 8 files changed, 8261 insertions(+), 9212 deletions(-) delete mode 100644 static/scripts/packed/libs/handlebars.full.js delete mode 100644 static/scripts/packed/mvc/history.js delete mode 100644 static/scripts/packed/templates/compiled/panel_label.js delete mode 100644 static/scripts/packed/templates/compiled/template-visualization-chartSettings.js delete mode 100644 static/scripts/packed/templates/compiled/template-visualization-statsTable.js diff --git a/static/scripts/libs/d3.js b/static/scripts/libs/d3.js index bb850ff5d14..143815e8564 100644 --- a/static/scripts/libs/d3.js +++ b/static/scripts/libs/d3.js @@ -1,9384 +1,8438 @@ -(function(){if (!Date.now) Date.now = function() { - return +new Date; -}; -try { - document.createElement("div").style.setProperty("opacity", 0, ""); -} catch (error) { - var d3_style_prototype = CSSStyleDeclaration.prototype, - d3_style_setProperty = d3_style_prototype.setProperty; - d3_style_prototype.setProperty = function(name, value, priority) { - d3_style_setProperty.call(this, name, value + "", priority); +d3 = function() { + var d3 = { + version: "3.1.4" }; -} -d3 = {version: "2.9.2"}; // semver -function d3_class(ctor, properties) { + if (!Date.now) Date.now = function() { + return +new Date(); + }; + var d3_document = document, d3_window = window; try { - for (var key in properties) { - Object.defineProperty(ctor.prototype, key, { - value: properties[key], - enumerable: false - }); - } - } catch (e) { - ctor.prototype = properties; - } -} -var d3_array = d3_arraySlice; // conversion for NodeLists - -function d3_arrayCopy(pseudoarray) { - var i = -1, n = pseudoarray.length, array = []; - while (++i < n) array.push(pseudoarray[i]); - return array; -} - -function d3_arraySlice(pseudoarray) { - return Array.prototype.slice.call(pseudoarray); -} - -try { - d3_array(document.documentElement.childNodes)[0].nodeType; -} catch(e) { - d3_array = d3_arrayCopy; -} - -var d3_arraySubclass = [].__proto__? - -// Until ECMAScript supports array subclassing, prototype injection works well. -function(array, prototype) { - array.__proto__ = prototype; -}: - -// And if your browser doesn't support __proto__, we'll use direct extension. -function(array, prototype) { - for (var property in prototype) array[property] = prototype[property]; -}; -d3.map = function(object) { - var map = new d3_Map; - for (var key in object) map.set(key, object[key]); - return map; -}; - -function d3_Map() {} - -d3_class(d3_Map, { - has: function(key) { - return d3_map_prefix + key in this; - }, - get: function(key) { - return this[d3_map_prefix + key]; - }, - set: function(key, value) { - return this[d3_map_prefix + key] = value; - }, - remove: function(key) { - key = d3_map_prefix + key; - return key in this && delete this[key]; - }, - keys: function() { - var keys = []; - this.forEach(function(key) { keys.push(key); }); - return keys; - }, - values: function() { - var values = []; - this.forEach(function(key, value) { values.push(value); }); - return values; - }, - entries: function() { - var entries = []; - this.forEach(function(key, value) { entries.push({key: key, value: value}); }); - return entries; - }, - forEach: function(f) { - for (var key in this) { - if (key.charCodeAt(0) === d3_map_prefixCode) { - f.call(this, key.substring(1), this[key]); - } - } - } -}); - -var d3_map_prefix = "\0", // prevent collision with built-ins - d3_map_prefixCode = d3_map_prefix.charCodeAt(0); -function d3_identity(d) { - return d; -} -function d3_this() { - return this; -} -function d3_true() { - return true; -} -function d3_functor(v) { - return typeof v === "function" ? v : function() { return v; }; -} - -d3.functor = d3_functor; -// Copies a variable number of methods from source to target. -d3.rebind = function(target, source) { - var i = 1, n = arguments.length, method; - while (++i < n) target[method = arguments[i]] = d3_rebind(target, source, source[method]); - return target; -}; - -// Method is assumed to be a standard D3 getter-setter: -// If passed with no arguments, gets the value. -// If passed with arguments, sets the value and returns the target. -function d3_rebind(target, source, method) { - return function() { - var value = method.apply(source, arguments); - return arguments.length ? target : value; - }; -} -d3.ascending = function(a, b) { - return a < b ? -1 : a > b ? 1 : a >= b ? 0 : NaN; -}; -d3.descending = function(a, b) { - return b < a ? -1 : b > a ? 1 : b >= a ? 0 : NaN; -}; -d3.mean = function(array, f) { - var n = array.length, - a, - m = 0, - i = -1, - j = 0; - if (arguments.length === 1) { - while (++i < n) if (d3_number(a = array[i])) m += (a - m) / ++j; - } else { - while (++i < n) if (d3_number(a = f.call(array, array[i], i))) m += (a - m) / ++j; - } - return j ? m : undefined; -}; -d3.median = function(array, f) { - if (arguments.length > 1) array = array.map(f); - array = array.filter(d3_number); - return array.length ? d3.quantile(array.sort(d3.ascending), .5) : undefined; -}; -d3.min = function(array, f) { - var i = -1, - n = array.length, - a, - b; - if (arguments.length === 1) { - while (++i < n && ((a = array[i]) == null || a != a)) a = undefined; - while (++i < n) if ((b = array[i]) != null && a > b) a = b; - } else { - while (++i < n && ((a = f.call(array, array[i], i)) == null || a != a)) a = undefined; - while (++i < n) if ((b = f.call(array, array[i], i)) != null && a > b) a = b; - } - return a; -}; -d3.max = function(array, f) { - var i = -1, - n = array.length, - a, - b; - if (arguments.length === 1) { - while (++i < n && ((a = array[i]) == null || a != a)) a = undefined; - while (++i < n) if ((b = array[i]) != null && b > a) a = b; - } else { - while (++i < n && ((a = f.call(array, array[i], i)) == null || a != a)) a = undefined; - while (++i < n) if ((b = f.call(array, array[i], i)) != null && b > a) a = b; - } - return a; -}; -d3.extent = function(array, f) { - var i = -1, - n = array.length, - a, - b, - c; - if (arguments.length === 1) { - while (++i < n && ((a = c = array[i]) == null || a != a)) a = c = undefined; - while (++i < n) if ((b = array[i]) != null) { - if (a > b) a = b; - if (c < b) c = b; - } - } else { - while (++i < n && ((a = c = f.call(array, array[i], i)) == null || a != a)) a = undefined; - while (++i < n) if ((b = f.call(array, array[i], i)) != null) { - if (a > b) a = b; - if (c < b) c = b; - } - } - return [a, c]; -}; -d3.random = { - normal: function(mean, deviation) { - if (arguments.length < 2) deviation = 1; - if (arguments.length < 1) mean = 0; - return function() { - var x, y, r; - do { - x = Math.random() * 2 - 1; - y = Math.random() * 2 - 1; - r = x * x + y * y; - } while (!r || r > 1); - return mean + deviation * x * Math.sqrt(-2 * Math.log(r) / r); + d3_document.createElement("div").style.setProperty("opacity", 0, ""); + } catch (error) { + var d3_style_prototype = d3_window.CSSStyleDeclaration.prototype, d3_style_setProperty = d3_style_prototype.setProperty; + d3_style_prototype.setProperty = function(name, value, priority) { + d3_style_setProperty.call(this, name, value + "", priority); }; } -}; -function d3_number(x) { - return x != null && !isNaN(x); -} -d3.sum = function(array, f) { - var s = 0, - n = array.length, - a, - i = -1; - - if (arguments.length === 1) { - while (++i < n) if (!isNaN(a = +array[i])) s += a; - } else { - while (++i < n) if (!isNaN(a = +f.call(array, array[i], i))) s += a; - } - - return s; -}; -// R-7 per -d3.quantile = function(values, p) { - var H = (values.length - 1) * p + 1, - h = Math.floor(H), - v = values[h - 1], - e = H - h; - return e ? v + e * (values[h] - v) : v; -}; -d3.transpose = function(matrix) { - return d3.zip.apply(d3, matrix); -}; -d3.zip = function() { - if (!(n = arguments.length)) return []; - for (var i = -1, m = d3.min(arguments, d3_zipLength), zips = new Array(m); ++i < m;) { - for (var j = -1, n, zip = zips[i] = new Array(n); ++j < n;) { - zip[j] = arguments[j][i]; - } - } - return zips; -}; - -function d3_zipLength(d) { - return d.length; -} -d3.bisector = function(f) { - return { - left: function(a, x, lo, hi) { - if (arguments.length < 3) lo = 0; - if (arguments.length < 4) hi = a.length; - while (lo < hi) { - var mid = lo + hi >> 1; - if (f.call(a, a[mid], mid) < x) lo = mid + 1; - else hi = mid; - } - return lo; - }, - right: function(a, x, lo, hi) { - if (arguments.length < 3) lo = 0; - if (arguments.length < 4) hi = a.length; - while (lo < hi) { - var mid = lo + hi >> 1; - if (x < f.call(a, a[mid], mid)) hi = mid; - else lo = mid + 1; - } - return lo; - } + d3.ascending = function(a, b) { + return a < b ? -1 : a > b ? 1 : a >= b ? 0 : NaN; }; -}; - -var d3_bisector = d3.bisector(function(d) { return d; }); -d3.bisectLeft = d3_bisector.left; -d3.bisect = d3.bisectRight = d3_bisector.right; -d3.first = function(array, f) { - var i = 0, - n = array.length, - a = array[0], - b; - if (arguments.length === 1) f = d3.ascending; - while (++i < n) { - if (f.call(array, a, b = array[i]) > 0) { - a = b; + d3.descending = function(a, b) { + return b < a ? -1 : b > a ? 1 : b >= a ? 0 : NaN; + }; + d3.min = function(array, f) { + var i = -1, n = array.length, a, b; + if (arguments.length === 1) { + while (++i < n && ((a = array[i]) == null || a != a)) a = undefined; + while (++i < n) if ((b = array[i]) != null && a > b) a = b; + } else { + while (++i < n && ((a = f.call(array, array[i], i)) == null || a != a)) a = undefined; + while (++i < n) if ((b = f.call(array, array[i], i)) != null && a > b) a = b; } - } - return a; -}; -d3.last = function(array, f) { - var i = 0, - n = array.length, - a = array[0], - b; - if (arguments.length === 1) f = d3.ascending; - while (++i < n) { - if (f.call(array, a, b = array[i]) <= 0) { - a = b; - } - } - return a; -}; -d3.nest = function() { - var nest = {}, - keys = [], - sortKeys = [], - sortValues, - rollup; - - function map(array, depth) { - if (depth >= keys.length) return rollup - ? rollup.call(nest, array) : (sortValues - ? array.sort(sortValues) - : array); - - var i = -1, - n = array.length, - key = keys[depth++], - keyValue, - object, - valuesByKey = new d3_Map, - values, - o = {}; - - while (++i < n) { - if (values = valuesByKey.get(keyValue = key(object = array[i]))) { - values.push(object); - } else { - valuesByKey.set(keyValue, [object]); - } - } - - valuesByKey.forEach(function(keyValue) { - o[keyValue] = map(valuesByKey.get(keyValue), depth); - }); - - return o; - } - - function entries(map, depth) { - if (depth >= keys.length) return map; - - var a = [], - sortKey = sortKeys[depth++], - key; - - for (key in map) { - a.push({key: key, values: entries(map[key], depth)}); - } - - if (sortKey) a.sort(function(a, b) { - return sortKey(a.key, b.key); - }); - return a; - } - - nest.map = function(array) { - return map(array, 0); }; - - nest.entries = function(array) { - return entries(map(array, 0), 0); - }; - - nest.key = function(d) { - keys.push(d); - return nest; - }; - - // Specifies the order for the most-recently specified key. - // Note: only applies to entries. Map keys are unordered! - nest.sortKeys = function(order) { - sortKeys[keys.length - 1] = order; - return nest; - }; - - // Specifies the order for leaf values. - // Applies to both maps and entries array. - nest.sortValues = function(order) { - sortValues = order; - return nest; - }; - - nest.rollup = function(f) { - rollup = f; - return nest; - }; - - return nest; -}; -d3.keys = function(map) { - var keys = []; - for (var key in map) keys.push(key); - return keys; -}; -d3.values = function(map) { - var values = []; - for (var key in map) values.push(map[key]); - return values; -}; -d3.entries = function(map) { - var entries = []; - for (var key in map) entries.push({key: key, value: map[key]}); - return entries; -}; -d3.permute = function(array, indexes) { - var permutes = [], - i = -1, - n = indexes.length; - while (++i < n) permutes[i] = array[indexes[i]]; - return permutes; -}; -d3.merge = function(arrays) { - return Array.prototype.concat.apply([], arrays); -}; -d3.split = function(array, f) { - var arrays = [], - values = [], - value, - i = -1, - n = array.length; - if (arguments.length < 2) f = d3_splitter; - while (++i < n) { - if (f.call(values, value = array[i], i)) { - values = []; + d3.max = function(array, f) { + var i = -1, n = array.length, a, b; + if (arguments.length === 1) { + while (++i < n && ((a = array[i]) == null || a != a)) a = undefined; + while (++i < n) if ((b = array[i]) != null && b > a) a = b; } else { - if (!values.length) arrays.push(values); - values.push(value); - } - } - return arrays; -}; - -function d3_splitter(d) { - return d == null; -} -function d3_collapse(s) { - return s.replace(/(^\s+)|(\s+$)/g, "").replace(/\s+/g, " "); -} -d3.range = function(start, stop, step) { - if (arguments.length < 3) { - step = 1; - if (arguments.length < 2) { - stop = start; - start = 0; - } - } - if ((stop - start) / step === Infinity) throw new Error("infinite range"); - var range = [], - k = d3_range_integerScale(Math.abs(step)), - i = -1, - j; - start *= k, stop *= k, step *= k; - if (step < 0) while ((j = start + step * ++i) > stop) range.push(j / k); - else while ((j = start + step * ++i) < stop) range.push(j / k); - return range; -}; - -function d3_range_integerScale(x) { - var k = 1; - while (x * k % 1) k *= 10; - return k; -} -d3.requote = function(s) { - return s.replace(d3_requote_re, "\\$&"); -}; - -var d3_requote_re = /[\\\^\$\*\+\?\|\[\]\(\)\.\{\}]/g; -d3.round = function(x, n) { - return n - ? Math.round(x * (n = Math.pow(10, n))) / n - : Math.round(x); -}; -d3.xhr = function(url, mime, callback) { - var req = new XMLHttpRequest; - if (arguments.length < 3) callback = mime, mime = null; - else if (mime && req.overrideMimeType) req.overrideMimeType(mime); - req.open("GET", url, true); - if (mime) req.setRequestHeader("Accept", mime); - req.onreadystatechange = function() { - if (req.readyState === 4) { - var s = req.status; - callback(s >= 200 && s < 300 || s === 304 ? req : null); + while (++i < n && ((a = f.call(array, array[i], i)) == null || a != a)) a = undefined; + while (++i < n) if ((b = f.call(array, array[i], i)) != null && b > a) a = b; } + return a; }; - req.send(null); -}; -d3.text = function(url, mime, callback) { - function ready(req) { - callback(req && req.responseText); - } - if (arguments.length < 3) { - callback = mime; - mime = null; - } - d3.xhr(url, mime, ready); -}; -d3.json = function(url, callback) { - d3.text(url, "application/json", function(text) { - callback(text ? JSON.parse(text) : null); - }); -}; -d3.html = function(url, callback) { - d3.text(url, "text/html", function(text) { - if (text != null) { // Treat empty string as valid HTML. - var range = document.createRange(); - range.selectNode(document.body); - text = range.createContextualFragment(text); - } - callback(text); - }); -}; -d3.xml = function(url, mime, callback) { - function ready(req) { - callback(req && req.responseXML); - } - if (arguments.length < 3) { - callback = mime; - mime = null; - } - d3.xhr(url, mime, ready); -}; -var d3_nsPrefix = { - svg: "http://www.w3.org/2000/svg", - xhtml: "http://www.w3.org/1999/xhtml", - xlink: "http://www.w3.org/1999/xlink", - xml: "http://www.w3.org/XML/1998/namespace", - xmlns: "http://www.w3.org/2000/xmlns/" -}; - -d3.ns = { - prefix: d3_nsPrefix, - qualify: function(name) { - var i = name.indexOf(":"), - prefix = name; - if (i >= 0) { - prefix = name.substring(0, i); - name = name.substring(i + 1); - } - return d3_nsPrefix.hasOwnProperty(prefix) - ? {space: d3_nsPrefix[prefix], local: name} - : name; - } -}; -d3.dispatch = function() { - var dispatch = new d3_dispatch, - i = -1, - n = arguments.length; - while (++i < n) dispatch[arguments[i]] = d3_dispatch_event(dispatch); - return dispatch; -}; - -function d3_dispatch() {} - -d3_dispatch.prototype.on = function(type, listener) { - var i = type.indexOf("."), - name = ""; - - // Extract optional namespace, e.g., "click.foo" - if (i > 0) { - name = type.substring(i + 1); - type = type.substring(0, i); - } - - return arguments.length < 2 - ? this[type].on(name) - : this[type].on(name, listener); -}; - -function d3_dispatch_event(dispatch) { - var listeners = [], - listenerByName = new d3_Map; - - function event() { - var z = listeners, // defensive reference - i = -1, - n = z.length, - l; - while (++i < n) if (l = z[i].on) l.apply(this, arguments); - return dispatch; - } - - event.on = function(name, listener) { - var l = listenerByName.get(name), - i; - - // return the current listener, if any - if (arguments.length < 2) return l && l.on; - - // remove the old listener, if any (with copy-on-write) - if (l) { - l.on = null; - listeners = listeners.slice(0, i = listeners.indexOf(l)).concat(listeners.slice(i + 1)); - listenerByName.remove(name); - } - - // add the new listener, if any - if (listener) listeners.push(listenerByName.set(name, {on: listener})); - - return dispatch; - }; - - return event; -} -// TODO align -d3.format = function(specifier) { - var match = d3_format_re.exec(specifier), - fill = match[1] || " ", - sign = match[3] || "", - zfill = match[5], - width = +match[6], - comma = match[7], - precision = match[8], - type = match[9], - scale = 1, - suffix = "", - integer = false; - - if (precision) precision = +precision.substring(1); - - if (zfill) { - fill = "0"; // TODO align = "="; - if (comma) width -= Math.floor((width - 1) / 4); - } - - switch (type) { - case "n": comma = true; type = "g"; break; - case "%": scale = 100; suffix = "%"; type = "f"; break; - case "p": scale = 100; suffix = "%"; type = "r"; break; - case "d": integer = true; precision = 0; break; - case "s": scale = -1; type = "r"; break; - } - - // If no precision is specified for r, fallback to general notation. - if (type == "r" && !precision) type = "g"; - - type = d3_format_types.get(type) || d3_format_typeDefault; - - return function(value) { - - // Return the empty string for floats formatted as ints. - if (integer && (value % 1)) return ""; - - // Convert negative to positive, and record the sign prefix. - var negative = (value < 0) && (value = -value) ? "\u2212" : sign; - - // Apply the scale, computing it from the value's exponent for si format. - if (scale < 0) { - var prefix = d3.formatPrefix(value, precision); - value = prefix.scale(value); - suffix = prefix.symbol; + d3.extent = function(array, f) { + var i = -1, n = array.length, a, b, c; + if (arguments.length === 1) { + while (++i < n && ((a = c = array[i]) == null || a != a)) a = c = undefined; + while (++i < n) if ((b = array[i]) != null) { + if (a > b) a = b; + if (c < b) c = b; + } } else { - value *= scale; + while (++i < n && ((a = c = f.call(array, array[i], i)) == null || a != a)) a = undefined; + while (++i < n) if ((b = f.call(array, array[i], i)) != null) { + if (a > b) a = b; + if (c < b) c = b; + } } - - // Convert to the desired precision. - value = type(value, precision); - - // If the fill character is 0, the sign and group is applied after the fill. - if (zfill) { - var length = value.length + negative.length; - if (length < width) value = new Array(width - length + 1).join(fill) + value; - if (comma) value = d3_format_group(value); - value = negative + value; - } - - // Otherwise (e.g., space-filling), the sign and group is applied before. - else { - if (comma) value = d3_format_group(value); - value = negative + value; - var length = value.length; - if (length < width) value = new Array(width - length + 1).join(fill) + value; - } - - return value + suffix; + return [ a, c ]; }; -}; - -// [[fill]align][sign][#][0][width][,][.precision][type] -var d3_format_re = /(?:([^{])?([<>=^]))?([+\- ])?(#)?(0)?([0-9]+)?(,)?(\.[0-9]+)?([a-zA-Z%])?/; - -var d3_format_types = d3.map({ - g: function(x, p) { return x.toPrecision(p); }, - e: function(x, p) { return x.toExponential(p); }, - f: function(x, p) { return x.toFixed(p); }, - r: function(x, p) { return d3.round(x, p = d3_format_precision(x, p)).toFixed(Math.max(0, Math.min(20, p))); } -}); - -function d3_format_precision(x, p) { - return p - (x ? 1 + Math.floor(Math.log(x + Math.pow(10, 1 + Math.floor(Math.log(x) / Math.LN10) - p)) / Math.LN10) : 1); -} - -function d3_format_typeDefault(x) { - return x + ""; -} - -// Apply comma grouping for thousands. -function d3_format_group(value) { - var i = value.lastIndexOf("."), - f = i >= 0 ? value.substring(i) : (i = value.length, ""), - t = []; - while (i > 0) t.push(value.substring(i -= 3, i + 3)); - return t.reverse().join(",") + f; -} -var d3_formatPrefixes = ["y","z","a","f","p","n","μ","m","","k","M","G","T","P","E","Z","Y"].map(d3_formatPrefix); - -d3.formatPrefix = function(value, precision) { - var i = 0; - if (value) { - if (value < 0) value *= -1; - if (precision) value = d3.round(value, d3_format_precision(value, precision)); - i = 1 + Math.floor(1e-12 + Math.log(value) / Math.LN10); - i = Math.max(-24, Math.min(24, Math.floor((i <= 0 ? i + 1 : i - 1) / 3) * 3)); + d3.sum = function(array, f) { + var s = 0, n = array.length, a, i = -1; + if (arguments.length === 1) { + while (++i < n) if (!isNaN(a = +array[i])) s += a; + } else { + while (++i < n) if (!isNaN(a = +f.call(array, array[i], i))) s += a; + } + return s; + }; + function d3_number(x) { + return x != null && !isNaN(x); } - return d3_formatPrefixes[8 + i / 3]; -}; - -function d3_formatPrefix(d, i) { - var k = Math.pow(10, Math.abs(8 - i) * 3); - return { - scale: i > 8 ? function(d) { return d / k; } : function(d) { return d * k; }, - symbol: d + d3.mean = function(array, f) { + var n = array.length, a, m = 0, i = -1, j = 0; + if (arguments.length === 1) { + while (++i < n) if (d3_number(a = array[i])) m += (a - m) / ++j; + } else { + while (++i < n) if (d3_number(a = f.call(array, array[i], i))) m += (a - m) / ++j; + } + return j ? m : undefined; }; -} -/* - * TERMS OF USE - EASING EQUATIONS - * - * Open source under the BSD License. - * - * Copyright 2001 Robert Penner - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * - Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * - Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * - Neither the name of the author nor the names of contributors may be used to - * endorse or promote products derived from this software without specific - * prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ - -var d3_ease_quad = d3_ease_poly(2), - d3_ease_cubic = d3_ease_poly(3), - d3_ease_default = function() { return d3_ease_identity; }; - -var d3_ease = d3.map({ - linear: d3_ease_default, - poly: d3_ease_poly, - quad: function() { return d3_ease_quad; }, - cubic: function() { return d3_ease_cubic; }, - sin: function() { return d3_ease_sin; }, - exp: function() { return d3_ease_exp; }, - circle: function() { return d3_ease_circle; }, - elastic: d3_ease_elastic, - back: d3_ease_back, - bounce: function() { return d3_ease_bounce; } -}); - -var d3_ease_mode = d3.map({ - "in": d3_ease_identity, - "out": d3_ease_reverse, - "in-out": d3_ease_reflect, - "out-in": function(f) { return d3_ease_reflect(d3_ease_reverse(f)); } -}); - -d3.ease = function(name) { - var i = name.indexOf("-"), - t = i >= 0 ? name.substring(0, i) : name, - m = i >= 0 ? name.substring(i + 1) : "in"; - t = d3_ease.get(t) || d3_ease_default; - m = d3_ease_mode.get(m) || d3_ease_identity; - return d3_ease_clamp(m(t.apply(null, Array.prototype.slice.call(arguments, 1)))); -}; - -function d3_ease_clamp(f) { - return function(t) { - return t <= 0 ? 0 : t >= 1 ? 1 : f(t); + d3.quantile = function(values, p) { + var H = (values.length - 1) * p + 1, h = Math.floor(H), v = +values[h - 1], e = H - h; + return e ? v + e * (values[h] - v) : v; }; -} - -function d3_ease_reverse(f) { - return function(t) { - return 1 - f(1 - t); + d3.median = function(array, f) { + if (arguments.length > 1) array = array.map(f); + array = array.filter(d3_number); + return array.length ? d3.quantile(array.sort(d3.ascending), .5) : undefined; }; -} - -function d3_ease_reflect(f) { - return function(t) { - return .5 * (t < .5 ? f(2 * t) : (2 - f(2 - 2 * t))); - }; -} - -function d3_ease_identity(t) { - return t; -} - -function d3_ease_poly(e) { - return function(t) { - return Math.pow(t, e); - }; -} - -function d3_ease_sin(t) { - return 1 - Math.cos(t * Math.PI / 2); -} - -function d3_ease_exp(t) { - return Math.pow(2, 10 * (t - 1)); -} - -function d3_ease_circle(t) { - return 1 - Math.sqrt(1 - t * t); -} - -function d3_ease_elastic(a, p) { - var s; - if (arguments.length < 2) p = 0.45; - if (arguments.length < 1) { a = 1; s = p / 4; } - else s = p / (2 * Math.PI) * Math.asin(1 / a); - return function(t) { - return 1 + a * Math.pow(2, 10 * -t) * Math.sin((t - s) * 2 * Math.PI / p); - }; -} - -function d3_ease_back(s) { - if (!s) s = 1.70158; - return function(t) { - return t * t * ((s + 1) * t - s); - }; -} - -function d3_ease_bounce(t) { - return t < 1 / 2.75 ? 7.5625 * t * t - : t < 2 / 2.75 ? 7.5625 * (t -= 1.5 / 2.75) * t + .75 - : t < 2.5 / 2.75 ? 7.5625 * (t -= 2.25 / 2.75) * t + .9375 - : 7.5625 * (t -= 2.625 / 2.75) * t + .984375; -} -d3.event = null; - -function d3_eventCancel() { - d3.event.stopPropagation(); - d3.event.preventDefault(); -} - -function d3_eventSource() { - var e = d3.event, s; - while (s = e.sourceEvent) e = s; - return e; -} - -// Like d3.dispatch, but for custom events abstracting native UI events. These -// events have a target component (such as a brush), a target element (such as -// the svg:g element containing the brush) and the standard arguments `d` (the -// target element's data) and `i` (the selection index of the target element). -function d3_eventDispatch(target) { - var dispatch = new d3_dispatch, - i = 0, - n = arguments.length; - - while (++i < n) dispatch[arguments[i]] = d3_dispatch_event(dispatch); - - // Creates a dispatch context for the specified `thiz` (typically, the target - // DOM element that received the source event) and `argumentz` (typically, the - // data `d` and index `i` of the target element). The returned function can be - // used to dispatch an event to any registered listeners; the function takes a - // single argument as input, being the event to dispatch. The event must have - // a "type" attribute which corresponds to a type registered in the - // constructor. This context will automatically populate the "sourceEvent" and - // "target" attributes of the event, as well as setting the `d3.event` global - // for the duration of the notification. - dispatch.of = function(thiz, argumentz) { - return function(e1) { - try { - var e0 = - e1.sourceEvent = d3.event; - e1.target = target; - d3.event = e1; - dispatch[e1.type].apply(thiz, argumentz); - } finally { - d3.event = e0; + d3.bisector = function(f) { + return { + left: function(a, x, lo, hi) { + if (arguments.length < 3) lo = 0; + if (arguments.length < 4) hi = a.length; + while (lo < hi) { + var mid = lo + hi >>> 1; + if (f.call(a, a[mid], mid) < x) lo = mid + 1; else hi = mid; + } + return lo; + }, + right: function(a, x, lo, hi) { + if (arguments.length < 3) lo = 0; + if (arguments.length < 4) hi = a.length; + while (lo < hi) { + var mid = lo + hi >>> 1; + if (x < f.call(a, a[mid], mid)) hi = mid; else lo = mid + 1; + } + return lo; } }; }; - - return dispatch; -} -d3.interpolate = function(a, b) { - var i = d3.interpolators.length, f; - while (--i >= 0 && !(f = d3.interpolators[i](a, b))); - return f; -}; - -d3.interpolateNumber = function(a, b) { - b -= a; - return function(t) { return a + b * t; }; -}; - -d3.interpolateRound = function(a, b) { - b -= a; - return function(t) { return Math.round(a + b * t); }; -}; - -d3.interpolateString = function(a, b) { - var m, // current match - i, // current index - j, // current index (for coallescing) - s0 = 0, // start index of current string prefix - s1 = 0, // end index of current string prefix - s = [], // string constants and placeholders - q = [], // number interpolators - n, // q.length - o; - - // Reset our regular expression! - d3_interpolate_number.lastIndex = 0; - - // Find all numbers in b. - for (i = 0; m = d3_interpolate_number.exec(b); ++i) { - if (m.index) s.push(b.substring(s0, s1 = m.index)); - q.push({i: s.length, x: m[0]}); - s.push(null); - s0 = d3_interpolate_number.lastIndex; - } - if (s0 < b.length) s.push(b.substring(s0)); - - // Find all numbers in a. - for (i = 0, n = q.length; (m = d3_interpolate_number.exec(a)) && i < n; ++i) { - o = q[i]; - if (o.x == m[0]) { // The numbers match, so coallesce. - if (o.i) { - if (s[o.i + 1] == null) { // This match is followed by another number. - s[o.i - 1] += o.x; - s.splice(o.i, 1); - for (j = i + 1; j < n; ++j) q[j].i--; - } else { // This match is followed by a string, so coallesce twice. - s[o.i - 1] += o.x + s[o.i + 1]; - s.splice(o.i, 2); - for (j = i + 1; j < n; ++j) q[j].i -= 2; - } - } else { - if (s[o.i + 1] == null) { // This match is followed by another number. - s[o.i] = o.x; - } else { // This match is followed by a string, so coallesce twice. - s[o.i] = o.x + s[o.i + 1]; - s.splice(o.i + 1, 1); - for (j = i + 1; j < n; ++j) q[j].i--; - } - } - q.splice(i, 1); - n--; - i--; - } else { - o.x = d3.interpolateNumber(parseFloat(m[0]), parseFloat(o.x)); - } - } - - // Remove any numbers in b not found in a. - while (i < n) { - o = q.pop(); - if (s[o.i + 1] == null) { // This match is followed by another number. - s[o.i] = o.x; - } else { // This match is followed by a string, so coallesce twice. - s[o.i] = o.x + s[o.i + 1]; - s.splice(o.i + 1, 1); - } - n--; - } - - // Special optimization for only a single match. - if (s.length === 1) { - return s[0] == null ? q[0].x : function() { return b; }; - } - - // Otherwise, interpolate each of the numbers and rejoin the string. - return function(t) { - for (i = 0; i < n; ++i) s[(o = q[i]).i] = o.x(t); - return s.join(""); - }; -}; - -d3.interpolateTransform = function(a, b) { - var s = [], // string constants and placeholders - q = [], // number interpolators - n, - A = d3.transform(a), - B = d3.transform(b), - ta = A.translate, - tb = B.translate, - ra = A.rotate, - rb = B.rotate, - wa = A.skew, - wb = B.skew, - ka = A.scale, - kb = B.scale; - - if (ta[0] != tb[0] || ta[1] != tb[1]) { - s.push("translate(", null, ",", null, ")"); - q.push({i: 1, x: d3.interpolateNumber(ta[0], tb[0])}, {i: 3, x: d3.interpolateNumber(ta[1], tb[1])}); - } else if (tb[0] || tb[1]) { - s.push("translate(" + tb + ")"); - } else { - s.push(""); - } - - if (ra != rb) { - q.push({i: s.push(s.pop() + "rotate(", null, ")") - 2, x: d3.interpolateNumber(ra, rb)}); - } else if (rb) { - s.push(s.pop() + "rotate(" + rb + ")"); - } - - if (wa != wb) { - q.push({i: s.push(s.pop() + "skewX(", null, ")") - 2, x: d3.interpolateNumber(wa, wb)}); - } else if (wb) { - s.push(s.pop() + "skewX(" + wb + ")"); - } - - if (ka[0] != kb[0] || ka[1] != kb[1]) { - n = s.push(s.pop() + "scale(", null, ",", null, ")"); - q.push({i: n - 4, x: d3.interpolateNumber(ka[0], kb[0])}, {i: n - 2, x: d3.interpolateNumber(ka[1], kb[1])}); - } else if (kb[0] != 1 || kb[1] != 1) { - s.push(s.pop() + "scale(" + kb + ")"); - } - - n = q.length; - return function(t) { - var i = -1, o; - while (++i < n) s[(o = q[i]).i] = o.x(t); - return s.join(""); - }; -}; - -d3.interpolateRgb = function(a, b) { - a = d3.rgb(a); - b = d3.rgb(b); - var ar = a.r, - ag = a.g, - ab = a.b, - br = b.r - ar, - bg = b.g - ag, - bb = b.b - ab; - return function(t) { - return "#" - + d3_rgb_hex(Math.round(ar + br * t)) - + d3_rgb_hex(Math.round(ag + bg * t)) - + d3_rgb_hex(Math.round(ab + bb * t)); - }; -}; - -// interpolates HSL space, but outputs RGB string (for compatibility) -d3.interpolateHsl = function(a, b) { - a = d3.hsl(a); - b = d3.hsl(b); - var h0 = a.h, - s0 = a.s, - l0 = a.l, - h1 = b.h - h0, - s1 = b.s - s0, - l1 = b.l - l0; - return function(t) { - return d3_hsl_rgb(h0 + h1 * t, s0 + s1 * t, l0 + l1 * t).toString(); - }; -}; - -d3.interpolateArray = function(a, b) { - var x = [], - c = [], - na = a.length, - nb = b.length, - n0 = Math.min(a.length, b.length), - i; - for (i = 0; i < n0; ++i) x.push(d3.interpolate(a[i], b[i])); - for (; i < na; ++i) c[i] = a[i]; - for (; i < nb; ++i) c[i] = b[i]; - return function(t) { - for (i = 0; i < n0; ++i) c[i] = x[i](t); - return c; - }; -}; - -d3.interpolateObject = function(a, b) { - var i = {}, - c = {}, - k; - for (k in a) { - if (k in b) { - i[k] = d3_interpolateByName(k)(a[k], b[k]); - } else { - c[k] = a[k]; - } - } - for (k in b) { - if (!(k in a)) { - c[k] = b[k]; - } - } - return function(t) { - for (k in i) c[k] = i[k](t); - return c; - }; -} - -var d3_interpolate_number = /[-+]?(?:\d+\.?\d*|\.?\d+)(?:[eE][-+]?\d+)?/g; - -function d3_interpolateByName(n) { - return n == "transform" - ? d3.interpolateTransform - : d3.interpolate; -} - -d3.interpolators = [ - d3.interpolateObject, - function(a, b) { return (b instanceof Array) && d3.interpolateArray(a, b); }, - function(a, b) { return (typeof a === "string" || typeof b === "string") && d3.interpolateString(a + "", b + ""); }, - function(a, b) { return (typeof b === "string" ? d3_rgb_names.has(b) || /^(#|rgb\(|hsl\()/.test(b) : b instanceof d3_Rgb || b instanceof d3_Hsl) && d3.interpolateRgb(a, b); }, - function(a, b) { return !isNaN(a = +a) && !isNaN(b = +b) && d3.interpolateNumber(a, b); } -]; -function d3_uninterpolateNumber(a, b) { - b = b - (a = +a) ? 1 / (b - a) : 0; - return function(x) { return (x - a) * b; }; -} - -function d3_uninterpolateClamp(a, b) { - b = b - (a = +a) ? 1 / (b - a) : 0; - return function(x) { return Math.max(0, Math.min(1, (x - a) * b)); }; -} -d3.rgb = function(r, g, b) { - return arguments.length === 1 - ? (r instanceof d3_Rgb ? d3_rgb(r.r, r.g, r.b) - : d3_rgb_parse("" + r, d3_rgb, d3_hsl_rgb)) - : d3_rgb(~~r, ~~g, ~~b); -}; - -function d3_rgb(r, g, b) { - return new d3_Rgb(r, g, b); -} - -function d3_Rgb(r, g, b) { - this.r = r; - this.g = g; - this.b = b; -} - -d3_Rgb.prototype.brighter = function(k) { - k = Math.pow(0.7, arguments.length ? k : 1); - var r = this.r, - g = this.g, - b = this.b, - i = 30; - if (!r && !g && !b) return d3_rgb(i, i, i); - if (r && r < i) r = i; - if (g && g < i) g = i; - if (b && b < i) b = i; - return d3_rgb( - Math.min(255, Math.floor(r / k)), - Math.min(255, Math.floor(g / k)), - Math.min(255, Math.floor(b / k))); -}; - -d3_Rgb.prototype.darker = function(k) { - k = Math.pow(0.7, arguments.length ? k : 1); - return d3_rgb( - Math.floor(k * this.r), - Math.floor(k * this.g), - Math.floor(k * this.b)); -}; - -d3_Rgb.prototype.hsl = function() { - return d3_rgb_hsl(this.r, this.g, this.b); -}; - -d3_Rgb.prototype.toString = function() { - return "#" + d3_rgb_hex(this.r) + d3_rgb_hex(this.g) + d3_rgb_hex(this.b); -}; - -function d3_rgb_hex(v) { - return v < 0x10 - ? "0" + Math.max(0, v).toString(16) - : Math.min(255, v).toString(16); -} - -function d3_rgb_parse(format, rgb, hsl) { - var r = 0, // red channel; int in [0, 255] - g = 0, // green channel; int in [0, 255] - b = 0, // blue channel; int in [0, 255] - m1, // CSS color specification match - m2, // CSS color specification type (e.g., rgb) - name; - - /* Handle hsl, rgb. */ - m1 = /([a-z]+)\((.*)\)/i.exec(format); - if (m1) { - m2 = m1[2].split(","); - switch (m1[1]) { - case "hsl": { - return hsl( - parseFloat(m2[0]), // degrees - parseFloat(m2[1]) / 100, // percentage - parseFloat(m2[2]) / 100 // percentage - ); - } - case "rgb": { - return rgb( - d3_rgb_parseNumber(m2[0]), - d3_rgb_parseNumber(m2[1]), - d3_rgb_parseNumber(m2[2]) - ); - } - } - } - - /* Named colors. */ - if (name = d3_rgb_names.get(format)) return rgb(name.r, name.g, name.b); - - /* Hexadecimal colors: #rgb and #rrggbb. */ - if (format != null && format.charAt(0) === "#") { - if (format.length === 4) { - r = format.charAt(1); r += r; - g = format.charAt(2); g += g; - b = format.charAt(3); b += b; - } else if (format.length === 7) { - r = format.substring(1, 3); - g = format.substring(3, 5); - b = format.substring(5, 7); - } - r = parseInt(r, 16); - g = parseInt(g, 16); - b = parseInt(b, 16); - } - - return rgb(r, g, b); -} - -function d3_rgb_hsl(r, g, b) { - var min = Math.min(r /= 255, g /= 255, b /= 255), - max = Math.max(r, g, b), - d = max - min, - h, - s, - l = (max + min) / 2; - if (d) { - s = l < .5 ? d / (max + min) : d / (2 - max - min); - if (r == max) h = (g - b) / d + (g < b ? 6 : 0); - else if (g == max) h = (b - r) / d + 2; - else h = (r - g) / d + 4; - h *= 60; - } else { - s = h = 0; - } - return d3_hsl(h, s, l); -} - -function d3_rgb_parseNumber(c) { // either integer or percentage - var f = parseFloat(c); - return c.charAt(c.length - 1) === "%" ? Math.round(f * 2.55) : f; -} - -var d3_rgb_names = d3.map({ - aliceblue: "#f0f8ff", - antiquewhite: "#faebd7", - aqua: "#00ffff", - aquamarine: "#7fffd4", - azure: "#f0ffff", - beige: "#f5f5dc", - bisque: "#ffe4c4", - black: "#000000", - blanchedalmond: "#ffebcd", - blue: "#0000ff", - blueviolet: "#8a2be2", - brown: "#a52a2a", - burlywood: "#deb887", - cadetblue: "#5f9ea0", - chartreuse: "#7fff00", - chocolate: "#d2691e", - coral: "#ff7f50", - cornflowerblue: "#6495ed", - cornsilk: "#fff8dc", - crimson: "#dc143c", - cyan: "#00ffff", - darkblue: "#00008b", - darkcyan: "#008b8b", - darkgoldenrod: "#b8860b", - darkgray: "#a9a9a9", - darkgreen: "#006400", - darkgrey: "#a9a9a9", - darkkhaki: "#bdb76b", - darkmagenta: "#8b008b", - darkolivegreen: "#556b2f", - darkorange: "#ff8c00", - darkorchid: "#9932cc", - darkred: "#8b0000", - darksalmon: "#e9967a", - darkseagreen: "#8fbc8f", - darkslateblue: "#483d8b", - darkslategray: "#2f4f4f", - darkslategrey: "#2f4f4f", - darkturquoise: "#00ced1", - darkviolet: "#9400d3", - deeppink: "#ff1493", - deepskyblue: "#00bfff", - dimgray: "#696969", - dimgrey: "#696969", - dodgerblue: "#1e90ff", - firebrick: "#b22222", - floralwhite: "#fffaf0", - forestgreen: "#228b22", - fuchsia: "#ff00ff", - gainsboro: "#dcdcdc", - ghostwhite: "#f8f8ff", - gold: "#ffd700", - goldenrod: "#daa520", - gray: "#808080", - green: "#008000", - greenyellow: "#adff2f", - grey: "#808080", - honeydew: "#f0fff0", - hotpink: "#ff69b4", - indianred: "#cd5c5c", - indigo: "#4b0082", - ivory: "#fffff0", - khaki: "#f0e68c", - lavender: "#e6e6fa", - lavenderblush: "#fff0f5", - lawngreen: "#7cfc00", - lemonchiffon: "#fffacd", - lightblue: "#add8e6", - lightcoral: "#f08080", - lightcyan: "#e0ffff", - lightgoldenrodyellow: "#fafad2", - lightgray: "#d3d3d3", - lightgreen: "#90ee90", - lightgrey: "#d3d3d3", - lightpink: "#ffb6c1", - lightsalmon: "#ffa07a", - lightseagreen: "#20b2aa", - lightskyblue: "#87cefa", - lightslategray: "#778899", - lightslategrey: "#778899", - lightsteelblue: "#b0c4de", - lightyellow: "#ffffe0", - lime: "#00ff00", - limegreen: "#32cd32", - linen: "#faf0e6", - magenta: "#ff00ff", - maroon: "#800000", - mediumaquamarine: "#66cdaa", - mediumblue: "#0000cd", - mediumorchid: "#ba55d3", - mediumpurple: "#9370db", - mediumseagreen: "#3cb371", - mediumslateblue: "#7b68ee", - mediumspringgreen: "#00fa9a", - mediumturquoise: "#48d1cc", - mediumvioletred: "#c71585", - midnightblue: "#191970", - mintcream: "#f5fffa", - mistyrose: "#ffe4e1", - moccasin: "#ffe4b5", - navajowhite: "#ffdead", - navy: "#000080", - oldlace: "#fdf5e6", - olive: "#808000", - olivedrab: "#6b8e23", - orange: "#ffa500", - orangered: "#ff4500", - orchid: "#da70d6", - palegoldenrod: "#eee8aa", - palegreen: "#98fb98", - paleturquoise: "#afeeee", - palevioletred: "#db7093", - papayawhip: "#ffefd5", - peachpuff: "#ffdab9", - peru: "#cd853f", - pink: "#ffc0cb", - plum: "#dda0dd", - powderblue: "#b0e0e6", - purple: "#800080", - red: "#ff0000", - rosybrown: "#bc8f8f", - royalblue: "#4169e1", - saddlebrown: "#8b4513", - salmon: "#fa8072", - sandybrown: "#f4a460", - seagreen: "#2e8b57", - seashell: "#fff5ee", - sienna: "#a0522d", - silver: "#c0c0c0", - skyblue: "#87ceeb", - slateblue: "#6a5acd", - slategray: "#708090", - slategrey: "#708090", - snow: "#fffafa", - springgreen: "#00ff7f", - steelblue: "#4682b4", - tan: "#d2b48c", - teal: "#008080", - thistle: "#d8bfd8", - tomato: "#ff6347", - turquoise: "#40e0d0", - violet: "#ee82ee", - wheat: "#f5deb3", - white: "#ffffff", - whitesmoke: "#f5f5f5", - yellow: "#ffff00", - yellowgreen: "#9acd32" -}); - -d3_rgb_names.forEach(function(key, value) { - d3_rgb_names.set(key, d3_rgb_parse(value, d3_rgb, d3_hsl_rgb)); -}); -d3.hsl = function(h, s, l) { - return arguments.length === 1 - ? (h instanceof d3_Hsl ? d3_hsl(h.h, h.s, h.l) - : d3_rgb_parse("" + h, d3_rgb_hsl, d3_hsl)) - : d3_hsl(+h, +s, +l); -}; - -function d3_hsl(h, s, l) { - return new d3_Hsl(h, s, l); -} - -function d3_Hsl(h, s, l) { - this.h = h; - this.s = s; - this.l = l; -} - -d3_Hsl.prototype.brighter = function(k) { - k = Math.pow(0.7, arguments.length ? k : 1); - return d3_hsl(this.h, this.s, this.l / k); -}; - -d3_Hsl.prototype.darker = function(k) { - k = Math.pow(0.7, arguments.length ? k : 1); - return d3_hsl(this.h, this.s, k * this.l); -}; - -d3_Hsl.prototype.rgb = function() { - return d3_hsl_rgb(this.h, this.s, this.l); -}; - -d3_Hsl.prototype.toString = function() { - return this.rgb().toString(); -}; - -function d3_hsl_rgb(h, s, l) { - var m1, - m2; - - /* Some simple corrections for h, s and l. */ - h = h % 360; if (h < 0) h += 360; - s = s < 0 ? 0 : s > 1 ? 1 : s; - l = l < 0 ? 0 : l > 1 ? 1 : l; - - /* From FvD 13.37, CSS Color Module Level 3 */ - m2 = l <= .5 ? l * (1 + s) : l + s - l * s; - m1 = 2 * l - m2; - - function v(h) { - if (h > 360) h -= 360; - else if (h < 0) h += 360; - if (h < 60) return m1 + (m2 - m1) * h / 60; - if (h < 180) return m2; - if (h < 240) return m1 + (m2 - m1) * (240 - h) / 60; - return m1; - } - - function vv(h) { - return Math.round(v(h) * 255); - } - - return d3_rgb(vv(h + 120), vv(h), vv(h - 120)); -} -function d3_selection(groups) { - d3_arraySubclass(groups, d3_selectionPrototype); - return groups; -} - -var d3_select = function(s, n) { return n.querySelector(s); }, - d3_selectAll = function(s, n) { return n.querySelectorAll(s); }, - d3_selectRoot = document.documentElement, - d3_selectMatcher = d3_selectRoot.matchesSelector || d3_selectRoot.webkitMatchesSelector || d3_selectRoot.mozMatchesSelector || d3_selectRoot.msMatchesSelector || d3_selectRoot.oMatchesSelector, - d3_selectMatches = function(n, s) { return d3_selectMatcher.call(n, s); }; - -// Prefer Sizzle, if available. -if (typeof Sizzle === "function") { - d3_select = function(s, n) { return Sizzle(s, n)[0]; }; - d3_selectAll = function(s, n) { return Sizzle.uniqueSort(Sizzle(s, n)); }; - d3_selectMatches = Sizzle.matchesSelector; -} - -var d3_selectionPrototype = []; - -d3.selection = function() { - return d3_selectionRoot; -}; - -d3.selection.prototype = d3_selectionPrototype; -d3_selectionPrototype.select = function(selector) { - var subgroups = [], - subgroup, - subnode, - group, - node; - - if (typeof selector !== "function") selector = d3_selection_selector(selector); - - for (var j = -1, m = this.length; ++j < m;) { - subgroups.push(subgroup = []); - subgroup.parentNode = (group = this[j]).parentNode; - for (var i = -1, n = group.length; ++i < n;) { - if (node = group[i]) { - subgroup.push(subnode = selector.call(node, node.__data__, i)); - if (subnode && "__data__" in node) subnode.__data__ = node.__data__; - } else { - subgroup.push(null); - } - } - } - - return d3_selection(subgroups); -}; - -function d3_selection_selector(selector) { - return function() { - return d3_select(selector, this); - }; -} -d3_selectionPrototype.selectAll = function(selector) { - var subgroups = [], - subgroup, - node; - - if (typeof selector !== "function") selector = d3_selection_selectorAll(selector); - - for (var j = -1, m = this.length; ++j < m;) { - for (var group = this[j], i = -1, n = group.length; ++i < n;) { - if (node = group[i]) { - subgroups.push(subgroup = d3_array(selector.call(node, node.__data__, i))); - subgroup.parentNode = node; - } - } - } - - return d3_selection(subgroups); -}; - -function d3_selection_selectorAll(selector) { - return function() { - return d3_selectAll(selector, this); - }; -} -d3_selectionPrototype.attr = function(name, value) { - name = d3.ns.qualify(name); - - // If no value is specified, return the first value. - if (arguments.length < 2) { - var node = this.node(); - return name.local - ? node.getAttributeNS(name.space, name.local) - : node.getAttribute(name); - } - - function attrNull() { - this.removeAttribute(name); - } - - function attrNullNS() { - this.removeAttributeNS(name.space, name.local); - } - - function attrConstant() { - this.setAttribute(name, value); - } - - function attrConstantNS() { - this.setAttributeNS(name.space, name.local, value); - } - - function attrFunction() { - var x = value.apply(this, arguments); - if (x == null) this.removeAttribute(name); - else this.setAttribute(name, x); - } - - function attrFunctionNS() { - var x = value.apply(this, arguments); - if (x == null) this.removeAttributeNS(name.space, name.local); - else this.setAttributeNS(name.space, name.local, x); - } - - return this.each(value == null - ? (name.local ? attrNullNS : attrNull) : (typeof value === "function" - ? (name.local ? attrFunctionNS : attrFunction) - : (name.local ? attrConstantNS : attrConstant))); -}; -d3_selectionPrototype.classed = function(name, value) { - var names = name.split(d3_selection_classedWhitespace), - n = names.length, - i = -1; - if (arguments.length > 1) { - while (++i < n) d3_selection_classed.call(this, names[i], value); - return this; - } else { - while (++i < n) if (!d3_selection_classed.call(this, names[i])) return false; - return true; - } -}; - -var d3_selection_classedWhitespace = /\s+/g; - -function d3_selection_classed(name, value) { - var re = new RegExp("(^|\\s+)" + d3.requote(name) + "(\\s+|$)", "g"); - - // If no value is specified, return the first value. - if (arguments.length < 2) { - var node = this.node(); - if (c = node.classList) return c.contains(name); - var c = node.className; - re.lastIndex = 0; - return re.test(c.baseVal != null ? c.baseVal : c); - } - - function classedAdd() { - if (c = this.classList) return c.add(name); - var c = this.className, - cb = c.baseVal != null, - cv = cb ? c.baseVal : c; - re.lastIndex = 0; - if (!re.test(cv)) { - cv = d3_collapse(cv + " " + name); - if (cb) c.baseVal = cv; - else this.className = cv; - } - } - - function classedRemove() { - if (c = this.classList) return c.remove(name); - var c = this.className, - cb = c.baseVal != null, - cv = cb ? c.baseVal : c; - cv = d3_collapse(cv.replace(re, " ")); - if (cb) c.baseVal = cv; - else this.className = cv; - } - - function classedFunction() { - (value.apply(this, arguments) - ? classedAdd - : classedRemove).call(this); - } - - return this.each(typeof value === "function" - ? classedFunction : value - ? classedAdd - : classedRemove); -} -d3_selectionPrototype.style = function(name, value, priority) { - if (arguments.length < 3) priority = ""; - - // If no value is specified, return the first value. - if (arguments.length < 2) return window - .getComputedStyle(this.node(), null) - .getPropertyValue(name); - - function styleNull() { - this.style.removeProperty(name); - } - - function styleConstant() { - this.style.setProperty(name, value, priority); - } - - function styleFunction() { - var x = value.apply(this, arguments); - if (x == null) this.style.removeProperty(name); - else this.style.setProperty(name, x, priority); - } - - return this.each(value == null - ? styleNull : (typeof value === "function" - ? styleFunction : styleConstant)); -}; -d3_selectionPrototype.property = function(name, value) { - - // If no value is specified, return the first value. - if (arguments.length < 2) return this.node()[name]; - - function propertyNull() { - delete this[name]; - } - - function propertyConstant() { - this[name] = value; - } - - function propertyFunction() { - var x = value.apply(this, arguments); - if (x == null) delete this[name]; - else this[name] = x; - } - - return this.each(value == null - ? propertyNull : (typeof value === "function" - ? propertyFunction : propertyConstant)); -}; -d3_selectionPrototype.text = function(value) { - return arguments.length < 1 - ? this.node().textContent : this.each(typeof value === "function" - ? function() { var v = value.apply(this, arguments); this.textContent = v == null ? "" : v; } : value == null - ? function() { this.textContent = ""; } - : function() { this.textContent = value; }); -}; -d3_selectionPrototype.html = function(value) { - return arguments.length < 1 - ? this.node().innerHTML : this.each(typeof value === "function" - ? function() { var v = value.apply(this, arguments); this.innerHTML = v == null ? "" : v; } : value == null - ? function() { this.innerHTML = ""; } - : function() { this.innerHTML = value; }); -}; -// TODO append(node)? -// TODO append(function)? -d3_selectionPrototype.append = function(name) { - name = d3.ns.qualify(name); - - function append() { - return this.appendChild(document.createElementNS(this.namespaceURI, name)); - } - - function appendNS() { - return this.appendChild(document.createElementNS(name.space, name.local)); - } - - return this.select(name.local ? appendNS : append); -}; -// TODO insert(node, function)? -// TODO insert(function, string)? -// TODO insert(function, function)? -d3_selectionPrototype.insert = function(name, before) { - name = d3.ns.qualify(name); - - function insert() { - return this.insertBefore( - document.createElementNS(this.namespaceURI, name), - d3_select(before, this)); - } - - function insertNS() { - return this.insertBefore( - document.createElementNS(name.space, name.local), - d3_select(before, this)); - } - - return this.select(name.local ? insertNS : insert); -}; -// TODO remove(selector)? -// TODO remove(node)? -// TODO remove(function)? -d3_selectionPrototype.remove = function() { - return this.each(function() { - var parent = this.parentNode; - if (parent) parent.removeChild(this); + var d3_bisector = d3.bisector(function(d) { + return d; }); -}; -d3_selectionPrototype.data = function(value, key) { - var i = -1, - n = this.length, - group, - node; - - // If no value is specified, return the first value. - if (!arguments.length) { - value = new Array(n = (group = this[0]).length); - while (++i < n) { - if (node = group[i]) { - value[i] = node.__data__; + d3.bisectLeft = d3_bisector.left; + d3.bisect = d3.bisectRight = d3_bisector.right; + d3.shuffle = function(array) { + var m = array.length, t, i; + while (m) { + i = Math.random() * m-- | 0; + t = array[m], array[m] = array[i], array[i] = t; + } + return array; + }; + d3.permute = function(array, indexes) { + var permutes = [], i = -1, n = indexes.length; + while (++i < n) permutes[i] = array[indexes[i]]; + return permutes; + }; + d3.zip = function() { + if (!(n = arguments.length)) return []; + for (var i = -1, m = d3.min(arguments, d3_zipLength), zips = new Array(m); ++i < m; ) { + for (var j = -1, n, zip = zips[i] = new Array(n); ++j < n; ) { + zip[j] = arguments[j][i]; } } - return value; + return zips; + }; + function d3_zipLength(d) { + return d.length; } - - function bind(group, groupData) { - var i, - n = group.length, - m = groupData.length, - n0 = Math.min(n, m), - n1 = Math.max(n, m), - updateNodes = [], - enterNodes = [], - exitNodes = [], - node, - nodeData; - - if (key) { - var nodeByKeyValue = new d3_Map, - keyValues = [], - keyValue, - j = groupData.length; - - for (i = -1; ++i < n;) { - keyValue = key.call(node = group[i], node.__data__, i); - if (nodeByKeyValue.has(keyValue)) { - exitNodes[j++] = node; // duplicate key - } else { - nodeByKeyValue.set(keyValue, node); - } - keyValues.push(keyValue); + d3.transpose = function(matrix) { + return d3.zip.apply(d3, matrix); + }; + d3.keys = function(map) { + var keys = []; + for (var key in map) keys.push(key); + return keys; + }; + d3.values = function(map) { + var values = []; + for (var key in map) values.push(map[key]); + return values; + }; + d3.entries = function(map) { + var entries = []; + for (var key in map) entries.push({ + key: key, + value: map[key] + }); + return entries; + }; + d3.merge = function(arrays) { + return Array.prototype.concat.apply([], arrays); + }; + d3.range = function(start, stop, step) { + if (arguments.length < 3) { + step = 1; + if (arguments.length < 2) { + stop = start; + start = 0; } - - for (i = -1; ++i < m;) { - keyValue = key.call(groupData, nodeData = groupData[i], i) - if (nodeByKeyValue.has(keyValue)) { - updateNodes[i] = node = nodeByKeyValue.get(keyValue); - node.__data__ = nodeData; - enterNodes[i] = exitNodes[i] = null; - } else { - enterNodes[i] = d3_selection_dataNode(nodeData); - updateNodes[i] = exitNodes[i] = null; - } - nodeByKeyValue.remove(keyValue); + } + if ((stop - start) / step === Infinity) throw new Error("infinite range"); + var range = [], k = d3_range_integerScale(Math.abs(step)), i = -1, j; + start *= k, stop *= k, step *= k; + if (step < 0) while ((j = start + step * ++i) > stop) range.push(j / k); else while ((j = start + step * ++i) < stop) range.push(j / k); + return range; + }; + function d3_range_integerScale(x) { + var k = 1; + while (x * k % 1) k *= 10; + return k; + } + function d3_class(ctor, properties) { + try { + for (var key in properties) { + Object.defineProperty(ctor.prototype, key, { + value: properties[key], + enumerable: false + }); } - - for (i = -1; ++i < n;) { - if (nodeByKeyValue.has(keyValues[i])) { + } catch (e) { + ctor.prototype = properties; + } + } + d3.map = function(object) { + var map = new d3_Map(); + for (var key in object) map.set(key, object[key]); + return map; + }; + function d3_Map() {} + d3_class(d3_Map, { + has: function(key) { + return d3_map_prefix + key in this; + }, + get: function(key) { + return this[d3_map_prefix + key]; + }, + set: function(key, value) { + return this[d3_map_prefix + key] = value; + }, + remove: function(key) { + key = d3_map_prefix + key; + return key in this && delete this[key]; + }, + keys: function() { + var keys = []; + this.forEach(function(key) { + keys.push(key); + }); + return keys; + }, + values: function() { + var values = []; + this.forEach(function(key, value) { + values.push(value); + }); + return values; + }, + entries: function() { + var entries = []; + this.forEach(function(key, value) { + entries.push({ + key: key, + value: value + }); + }); + return entries; + }, + forEach: function(f) { + for (var key in this) { + if (key.charCodeAt(0) === d3_map_prefixCode) { + f.call(this, key.substring(1), this[key]); + } + } + } + }); + var d3_map_prefix = "\0", d3_map_prefixCode = d3_map_prefix.charCodeAt(0); + d3.nest = function() { + var nest = {}, keys = [], sortKeys = [], sortValues, rollup; + function map(mapType, array, depth) { + if (depth >= keys.length) return rollup ? rollup.call(nest, array) : sortValues ? array.sort(sortValues) : array; + var i = -1, n = array.length, key = keys[depth++], keyValue, object, setter, valuesByKey = new d3_Map(), values; + while (++i < n) { + if (values = valuesByKey.get(keyValue = key(object = array[i]))) { + values.push(object); + } else { + valuesByKey.set(keyValue, [ object ]); + } + } + if (mapType) { + object = mapType(); + setter = function(keyValue, values) { + object.set(keyValue, map(mapType, values, depth)); + }; + } else { + object = {}; + setter = function(keyValue, values) { + object[keyValue] = map(mapType, values, depth); + }; + } + valuesByKey.forEach(setter); + return object; + } + function entries(map, depth) { + if (depth >= keys.length) return map; + var array = [], sortKey = sortKeys[depth++]; + map.forEach(function(key, keyMap) { + array.push({ + key: key, + values: entries(keyMap, depth) + }); + }); + return sortKey ? array.sort(function(a, b) { + return sortKey(a.key, b.key); + }) : array; + } + nest.map = function(array, mapType) { + return map(mapType, array, 0); + }; + nest.entries = function(array) { + return entries(map(d3.map, array, 0), 0); + }; + nest.key = function(d) { + keys.push(d); + return nest; + }; + nest.sortKeys = function(order) { + sortKeys[keys.length - 1] = order; + return nest; + }; + nest.sortValues = function(order) { + sortValues = order; + return nest; + }; + nest.rollup = function(f) { + rollup = f; + return nest; + }; + return nest; + }; + d3.set = function(array) { + var set = new d3_Set(); + if (array) for (var i = 0; i < array.length; i++) set.add(array[i]); + return set; + }; + function d3_Set() {} + d3_class(d3_Set, { + has: function(value) { + return d3_map_prefix + value in this; + }, + add: function(value) { + this[d3_map_prefix + value] = true; + return value; + }, + remove: function(value) { + value = d3_map_prefix + value; + return value in this && delete this[value]; + }, + values: function() { + var values = []; + this.forEach(function(value) { + values.push(value); + }); + return values; + }, + forEach: function(f) { + for (var value in this) { + if (value.charCodeAt(0) === d3_map_prefixCode) { + f.call(this, value.substring(1)); + } + } + } + }); + d3.behavior = {}; + d3.rebind = function(target, source) { + var i = 1, n = arguments.length, method; + while (++i < n) target[method = arguments[i]] = d3_rebind(target, source, source[method]); + return target; + }; + function d3_rebind(target, source, method) { + return function() { + var value = method.apply(source, arguments); + return value === source ? target : value; + }; + } + d3.dispatch = function() { + var dispatch = new d3_dispatch(), i = -1, n = arguments.length; + while (++i < n) dispatch[arguments[i]] = d3_dispatch_event(dispatch); + return dispatch; + }; + function d3_dispatch() {} + d3_dispatch.prototype.on = function(type, listener) { + var i = type.indexOf("."), name = ""; + if (i >= 0) { + name = type.substring(i + 1); + type = type.substring(0, i); + } + if (type) return arguments.length < 2 ? this[type].on(name) : this[type].on(name, listener); + if (arguments.length === 2) { + if (listener == null) for (type in this) { + if (this.hasOwnProperty(type)) this[type].on(name, null); + } + return this; + } + }; + function d3_dispatch_event(dispatch) { + var listeners = [], listenerByName = new d3_Map(); + function event() { + var z = listeners, i = -1, n = z.length, l; + while (++i < n) if (l = z[i].on) l.apply(this, arguments); + return dispatch; + } + event.on = function(name, listener) { + var l = listenerByName.get(name), i; + if (arguments.length < 2) return l && l.on; + if (l) { + l.on = null; + listeners = listeners.slice(0, i = listeners.indexOf(l)).concat(listeners.slice(i + 1)); + listenerByName.remove(name); + } + if (listener) listeners.push(listenerByName.set(name, { + on: listener + })); + return dispatch; + }; + return event; + } + d3.event = null; + function d3_eventCancel() { + d3.event.stopPropagation(); + d3.event.preventDefault(); + } + function d3_eventSource() { + var e = d3.event, s; + while (s = e.sourceEvent) e = s; + return e; + } + function d3_eventDispatch(target) { + var dispatch = new d3_dispatch(), i = 0, n = arguments.length; + while (++i < n) dispatch[arguments[i]] = d3_dispatch_event(dispatch); + dispatch.of = function(thiz, argumentz) { + return function(e1) { + try { + var e0 = e1.sourceEvent = d3.event; + e1.target = target; + d3.event = e1; + dispatch[e1.type].apply(thiz, argumentz); + } finally { + d3.event = e0; + } + }; + }; + return dispatch; + } + d3.mouse = function(container) { + return d3_mousePoint(container, d3_eventSource()); + }; + var d3_mouse_bug44083 = /WebKit/.test(d3_window.navigator.userAgent) ? -1 : 0; + function d3_mousePoint(container, e) { + var svg = container.ownerSVGElement || container; + if (svg.createSVGPoint) { + var point = svg.createSVGPoint(); + if (d3_mouse_bug44083 < 0 && (d3_window.scrollX || d3_window.scrollY)) { + svg = d3.select(d3_document.body).append("svg").style("position", "absolute").style("top", 0).style("left", 0); + var ctm = svg[0][0].getScreenCTM(); + d3_mouse_bug44083 = !(ctm.f || ctm.e); + svg.remove(); + } + if (d3_mouse_bug44083) { + point.x = e.pageX; + point.y = e.pageY; + } else { + point.x = e.clientX; + point.y = e.clientY; + } + point = point.matrixTransform(container.getScreenCTM().inverse()); + return [ point.x, point.y ]; + } + var rect = container.getBoundingClientRect(); + return [ e.clientX - rect.left - container.clientLeft, e.clientY - rect.top - container.clientTop ]; + } + var d3_array = d3_arraySlice; + function d3_arrayCopy(pseudoarray) { + var i = -1, n = pseudoarray.length, array = []; + while (++i < n) array.push(pseudoarray[i]); + return array; + } + function d3_arraySlice(pseudoarray) { + return Array.prototype.slice.call(pseudoarray); + } + try { + d3_array(d3_document.documentElement.childNodes)[0].nodeType; + } catch (e) { + d3_array = d3_arrayCopy; + } + var d3_arraySubclass = [].__proto__ ? function(array, prototype) { + array.__proto__ = prototype; + } : function(array, prototype) { + for (var property in prototype) array[property] = prototype[property]; + }; + d3.touches = function(container, touches) { + if (arguments.length < 2) touches = d3_eventSource().touches; + return touches ? d3_array(touches).map(function(touch) { + var point = d3_mousePoint(container, touch); + point.identifier = touch.identifier; + return point; + }) : []; + }; + d3.behavior.drag = function() { + var event = d3_eventDispatch(drag, "drag", "dragstart", "dragend"), origin = null; + function drag() { + this.on("mousedown.drag", mousedown).on("touchstart.drag", mousedown); + } + function mousedown() { + var target = this, event_ = event.of(target, arguments), eventTarget = d3.event.target, touchId = d3.event.touches ? d3.event.changedTouches[0].identifier : null, offset, origin_ = point(), moved = 0; + var w = d3.select(d3_window).on(touchId != null ? "touchmove.drag-" + touchId : "mousemove.drag", dragmove).on(touchId != null ? "touchend.drag-" + touchId : "mouseup.drag", dragend, true); + if (origin) { + offset = origin.apply(target, arguments); + offset = [ offset.x - origin_[0], offset.y - origin_[1] ]; + } else { + offset = [ 0, 0 ]; + } + if (touchId == null) d3_eventCancel(); + event_({ + type: "dragstart" + }); + function point() { + var p = target.parentNode; + return touchId != null ? d3.touches(p).filter(function(p) { + return p.identifier === touchId; + })[0] : d3.mouse(p); + } + function dragmove() { + if (!target.parentNode) return dragend(); + var p = point(), dx = p[0] - origin_[0], dy = p[1] - origin_[1]; + moved |= dx | dy; + origin_ = p; + d3_eventCancel(); + event_({ + type: "drag", + x: p[0] + offset[0], + y: p[1] + offset[1], + dx: dx, + dy: dy + }); + } + function dragend() { + event_({ + type: "dragend" + }); + if (moved) { + d3_eventCancel(); + if (d3.event.target === eventTarget) w.on("click.drag", click, true); + } + w.on(touchId != null ? "touchmove.drag-" + touchId : "mousemove.drag", null).on(touchId != null ? "touchend.drag-" + touchId : "mouseup.drag", null); + } + function click() { + d3_eventCancel(); + w.on("click.drag", null); + } + } + drag.origin = function(x) { + if (!arguments.length) return origin; + origin = x; + return drag; + }; + return d3.rebind(drag, event, "on"); + }; + function d3_selection(groups) { + d3_arraySubclass(groups, d3_selectionPrototype); + return groups; + } + var d3_select = function(s, n) { + return n.querySelector(s); + }, d3_selectAll = function(s, n) { + return n.querySelectorAll(s); + }, d3_selectRoot = d3_document.documentElement, d3_selectMatcher = d3_selectRoot.matchesSelector || d3_selectRoot.webkitMatchesSelector || d3_selectRoot.mozMatchesSelector || d3_selectRoot.msMatchesSelector || d3_selectRoot.oMatchesSelector, d3_selectMatches = function(n, s) { + return d3_selectMatcher.call(n, s); + }; + if (typeof Sizzle === "function") { + d3_select = function(s, n) { + return Sizzle(s, n)[0] || null; + }; + d3_selectAll = function(s, n) { + return Sizzle.uniqueSort(Sizzle(s, n)); + }; + d3_selectMatches = Sizzle.matchesSelector; + } + var d3_selectionPrototype = []; + d3.selection = function() { + return d3_selectionRoot; + }; + d3.selection.prototype = d3_selectionPrototype; + d3_selectionPrototype.select = function(selector) { + var subgroups = [], subgroup, subnode, group, node; + if (typeof selector !== "function") selector = d3_selection_selector(selector); + for (var j = -1, m = this.length; ++j < m; ) { + subgroups.push(subgroup = []); + subgroup.parentNode = (group = this[j]).parentNode; + for (var i = -1, n = group.length; ++i < n; ) { + if (node = group[i]) { + subgroup.push(subnode = selector.call(node, node.__data__, i)); + if (subnode && "__data__" in node) subnode.__data__ = node.__data__; + } else { + subgroup.push(null); + } + } + } + return d3_selection(subgroups); + }; + function d3_selection_selector(selector) { + return function() { + return d3_select(selector, this); + }; + } + d3_selectionPrototype.selectAll = function(selector) { + var subgroups = [], subgroup, node; + if (typeof selector !== "function") selector = d3_selection_selectorAll(selector); + for (var j = -1, m = this.length; ++j < m; ) { + for (var group = this[j], i = -1, n = group.length; ++i < n; ) { + if (node = group[i]) { + subgroups.push(subgroup = d3_array(selector.call(node, node.__data__, i))); + subgroup.parentNode = node; + } + } + } + return d3_selection(subgroups); + }; + function d3_selection_selectorAll(selector) { + return function() { + return d3_selectAll(selector, this); + }; + } + var d3_nsPrefix = { + svg: "http://www.w3.org/2000/svg", + xhtml: "http://www.w3.org/1999/xhtml", + xlink: "http://www.w3.org/1999/xlink", + xml: "http://www.w3.org/XML/1998/namespace", + xmlns: "http://www.w3.org/2000/xmlns/" + }; + d3.ns = { + prefix: d3_nsPrefix, + qualify: function(name) { + var i = name.indexOf(":"), prefix = name; + if (i >= 0) { + prefix = name.substring(0, i); + name = name.substring(i + 1); + } + return d3_nsPrefix.hasOwnProperty(prefix) ? { + space: d3_nsPrefix[prefix], + local: name + } : name; + } + }; + d3_selectionPrototype.attr = function(name, value) { + if (arguments.length < 2) { + if (typeof name === "string") { + var node = this.node(); + name = d3.ns.qualify(name); + return name.local ? node.getAttributeNS(name.space, name.local) : node.getAttribute(name); + } + for (value in name) this.each(d3_selection_attr(value, name[value])); + return this; + } + return this.each(d3_selection_attr(name, value)); + }; + function d3_selection_attr(name, value) { + name = d3.ns.qualify(name); + function attrNull() { + this.removeAttribute(name); + } + function attrNullNS() { + this.removeAttributeNS(name.space, name.local); + } + function attrConstant() { + this.setAttribute(name, value); + } + function attrConstantNS() { + this.setAttributeNS(name.space, name.local, value); + } + function attrFunction() { + var x = value.apply(this, arguments); + if (x == null) this.removeAttribute(name); else this.setAttribute(name, x); + } + function attrFunctionNS() { + var x = value.apply(this, arguments); + if (x == null) this.removeAttributeNS(name.space, name.local); else this.setAttributeNS(name.space, name.local, x); + } + return value == null ? name.local ? attrNullNS : attrNull : typeof value === "function" ? name.local ? attrFunctionNS : attrFunction : name.local ? attrConstantNS : attrConstant; + } + function d3_collapse(s) { + return s.trim().replace(/\s+/g, " "); + } + d3.requote = function(s) { + return s.replace(d3_requote_re, "\\$&"); + }; + var d3_requote_re = /[\\\^\$\*\+\?\|\[\]\(\)\.\{\}]/g; + d3_selectionPrototype.classed = function(name, value) { + if (arguments.length < 2) { + if (typeof name === "string") { + var node = this.node(), n = (name = name.trim().split(/^|\s+/g)).length, i = -1; + if (value = node.classList) { + while (++i < n) if (!value.contains(name[i])) return false; + } else { + value = node.getAttribute("class"); + while (++i < n) if (!d3_selection_classedRe(name[i]).test(value)) return false; + } + return true; + } + for (value in name) this.each(d3_selection_classed(value, name[value])); + return this; + } + return this.each(d3_selection_classed(name, value)); + }; + function d3_selection_classedRe(name) { + return new RegExp("(?:^|\\s+)" + d3.requote(name) + "(?:\\s+|$)", "g"); + } + function d3_selection_classed(name, value) { + name = name.trim().split(/\s+/).map(d3_selection_classedName); + var n = name.length; + function classedConstant() { + var i = -1; + while (++i < n) name[i](this, value); + } + function classedFunction() { + var i = -1, x = value.apply(this, arguments); + while (++i < n) name[i](this, x); + } + return typeof value === "function" ? classedFunction : classedConstant; + } + function d3_selection_classedName(name) { + var re = d3_selection_classedRe(name); + return function(node, value) { + if (c = node.classList) return value ? c.add(name) : c.remove(name); + var c = node.getAttribute("class") || ""; + if (value) { + re.lastIndex = 0; + if (!re.test(c)) node.setAttribute("class", d3_collapse(c + " " + name)); + } else { + node.setAttribute("class", d3_collapse(c.replace(re, " "))); + } + }; + } + d3_selectionPrototype.style = function(name, value, priority) { + var n = arguments.length; + if (n < 3) { + if (typeof name !== "string") { + if (n < 2) value = ""; + for (priority in name) this.each(d3_selection_style(priority, name[priority], value)); + return this; + } + if (n < 2) return d3_window.getComputedStyle(this.node(), null).getPropertyValue(name); + priority = ""; + } + return this.each(d3_selection_style(name, value, priority)); + }; + function d3_selection_style(name, value, priority) { + function styleNull() { + this.style.removeProperty(name); + } + function styleConstant() { + this.style.setProperty(name, value, priority); + } + function styleFunction() { + var x = value.apply(this, arguments); + if (x == null) this.style.removeProperty(name); else this.style.setProperty(name, x, priority); + } + return value == null ? styleNull : typeof value === "function" ? styleFunction : styleConstant; + } + d3_selectionPrototype.property = function(name, value) { + if (arguments.length < 2) { + if (typeof name === "string") return this.node()[name]; + for (value in name) this.each(d3_selection_property(value, name[value])); + return this; + } + return this.each(d3_selection_property(name, value)); + }; + function d3_selection_property(name, value) { + function propertyNull() { + delete this[name]; + } + function propertyConstant() { + this[name] = value; + } + function propertyFunction() { + var x = value.apply(this, arguments); + if (x == null) delete this[name]; else this[name] = x; + } + return value == null ? propertyNull : typeof value === "function" ? propertyFunction : propertyConstant; + } + d3_selectionPrototype.text = function(value) { + return arguments.length ? this.each(typeof value === "function" ? function() { + var v = value.apply(this, arguments); + this.textContent = v == null ? "" : v; + } : value == null ? function() { + this.textContent = ""; + } : function() { + this.textContent = value; + }) : this.node().textContent; + }; + d3_selectionPrototype.html = function(value) { + return arguments.length ? this.each(typeof value === "function" ? function() { + var v = value.apply(this, arguments); + this.innerHTML = v == null ? "" : v; + } : value == null ? function() { + this.innerHTML = ""; + } : function() { + this.innerHTML = value; + }) : this.node().innerHTML; + }; + d3_selectionPrototype.append = function(name) { + name = d3.ns.qualify(name); + function append() { + return this.appendChild(d3_document.createElementNS(this.namespaceURI, name)); + } + function appendNS() { + return this.appendChild(d3_document.createElementNS(name.space, name.local)); + } + return this.select(name.local ? appendNS : append); + }; + d3_selectionPrototype.insert = function(name, before) { + name = d3.ns.qualify(name); + if (typeof before !== "function") before = d3_selection_selector(before); + function insert(d, i) { + return this.insertBefore(d3_document.createElementNS(this.namespaceURI, name), before.call(this, d, i)); + } + function insertNS(d, i) { + return this.insertBefore(d3_document.createElementNS(name.space, name.local), before.call(this, d, i)); + } + return this.select(name.local ? insertNS : insert); + }; + d3_selectionPrototype.remove = function() { + return this.each(function() { + var parent = this.parentNode; + if (parent) parent.removeChild(this); + }); + }; + d3_selectionPrototype.data = function(value, key) { + var i = -1, n = this.length, group, node; + if (!arguments.length) { + value = new Array(n = (group = this[0]).length); + while (++i < n) { + if (node = group[i]) { + value[i] = node.__data__; + } + } + return value; + } + function bind(group, groupData) { + var i, n = group.length, m = groupData.length, n0 = Math.min(n, m), updateNodes = new Array(m), enterNodes = new Array(m), exitNodes = new Array(n), node, nodeData; + if (key) { + var nodeByKeyValue = new d3_Map(), dataByKeyValue = new d3_Map(), keyValues = [], keyValue; + for (i = -1; ++i < n; ) { + keyValue = key.call(node = group[i], node.__data__, i); + if (nodeByKeyValue.has(keyValue)) { + exitNodes[i] = node; + } else { + nodeByKeyValue.set(keyValue, node); + } + keyValues.push(keyValue); + } + for (i = -1; ++i < m; ) { + keyValue = key.call(groupData, nodeData = groupData[i], i); + if (node = nodeByKeyValue.get(keyValue)) { + updateNodes[i] = node; + node.__data__ = nodeData; + } else if (!dataByKeyValue.has(keyValue)) { + enterNodes[i] = d3_selection_dataNode(nodeData); + } + dataByKeyValue.set(keyValue, nodeData); + nodeByKeyValue.remove(keyValue); + } + for (i = -1; ++i < n; ) { + if (nodeByKeyValue.has(keyValues[i])) { + exitNodes[i] = group[i]; + } + } + } else { + for (i = -1; ++i < n0; ) { + node = group[i]; + nodeData = groupData[i]; + if (node) { + node.__data__ = nodeData; + updateNodes[i] = node; + } else { + enterNodes[i] = d3_selection_dataNode(nodeData); + } + } + for (;i < m; ++i) { + enterNodes[i] = d3_selection_dataNode(groupData[i]); + } + for (;i < n; ++i) { exitNodes[i] = group[i]; } } + enterNodes.update = updateNodes; + enterNodes.parentNode = updateNodes.parentNode = exitNodes.parentNode = group.parentNode; + enter.push(enterNodes); + update.push(updateNodes); + exit.push(exitNodes); + } + var enter = d3_selection_enter([]), update = d3_selection([]), exit = d3_selection([]); + if (typeof value === "function") { + while (++i < n) { + bind(group = this[i], value.call(group, group.parentNode.__data__, i)); + } } else { - for (i = -1; ++i < n0;) { - node = group[i]; - nodeData = groupData[i]; - if (node) { - node.__data__ = nodeData; - updateNodes[i] = node; - enterNodes[i] = exitNodes[i] = null; - } else { - enterNodes[i] = d3_selection_dataNode(nodeData); - updateNodes[i] = exitNodes[i] = null; + while (++i < n) { + bind(group = this[i], value); + } + } + update.enter = function() { + return enter; + }; + update.exit = function() { + return exit; + }; + return update; + }; + function d3_selection_dataNode(data) { + return { + __data__: data + }; + } + d3_selectionPrototype.datum = function(value) { + return arguments.length ? this.property("__data__", value) : this.property("__data__"); + }; + d3_selectionPrototype.filter = function(filter) { + var subgroups = [], subgroup, group, node; + if (typeof filter !== "function") filter = d3_selection_filter(filter); + for (var j = 0, m = this.length; j < m; j++) { + subgroups.push(subgroup = []); + subgroup.parentNode = (group = this[j]).parentNode; + for (var i = 0, n = group.length; i < n; i++) { + if ((node = group[i]) && filter.call(node, node.__data__, i)) { + subgroup.push(node); } } - for (; i < m; ++i) { - enterNodes[i] = d3_selection_dataNode(groupData[i]); - updateNodes[i] = exitNodes[i] = null; - } - for (; i < n1; ++i) { - exitNodes[i] = group[i]; - enterNodes[i] = updateNodes[i] = null; - } } - - enterNodes.update - = updateNodes; - - enterNodes.parentNode - = updateNodes.parentNode - = exitNodes.parentNode - = group.parentNode; - - enter.push(enterNodes); - update.push(updateNodes); - exit.push(exitNodes); - } - - var enter = d3_selection_enter([]), - update = d3_selection([]), - exit = d3_selection([]); - - if (typeof value === "function") { - while (++i < n) { - bind(group = this[i], value.call(group, group.parentNode.__data__, i)); - } - } else { - while (++i < n) { - bind(group = this[i], value); - } - } - - update.enter = function() { return enter; }; - update.exit = function() { return exit; }; - return update; -}; - -function d3_selection_dataNode(data) { - return {__data__: data}; -} -d3_selectionPrototype.datum = -d3_selectionPrototype.map = function(value) { - return arguments.length < 1 - ? this.property("__data__") - : this.property("__data__", value); -}; -d3_selectionPrototype.filter = function(filter) { - var subgroups = [], - subgroup, - group, - node; - - if (typeof filter !== "function") filter = d3_selection_filter(filter); - - for (var j = 0, m = this.length; j < m; j++) { - subgroups.push(subgroup = []); - subgroup.parentNode = (group = this[j]).parentNode; - for (var i = 0, n = group.length; i < n; i++) { - if ((node = group[i]) && filter.call(node, node.__data__, i)) { - subgroup.push(node); - } - } - } - - return d3_selection(subgroups); -}; - -function d3_selection_filter(selector) { - return function() { - return d3_selectMatches(this, selector); + return d3_selection(subgroups); }; -} -d3_selectionPrototype.order = function() { - for (var j = -1, m = this.length; ++j < m;) { - for (var group = this[j], i = group.length - 1, next = group[i], node; --i >= 0;) { - if (node = group[i]) { - if (next && next !== node.nextSibling) next.parentNode.insertBefore(node, next); - next = node; + function d3_selection_filter(selector) { + return function() { + return d3_selectMatches(this, selector); + }; + } + d3_selectionPrototype.order = function() { + for (var j = -1, m = this.length; ++j < m; ) { + for (var group = this[j], i = group.length - 1, next = group[i], node; --i >= 0; ) { + if (node = group[i]) { + if (next && next !== node.nextSibling) next.parentNode.insertBefore(node, next); + next = node; + } } } - } - return this; -}; -d3_selectionPrototype.sort = function(comparator) { - comparator = d3_selection_sortComparator.apply(this, arguments); - for (var j = -1, m = this.length; ++j < m;) this[j].sort(comparator); - return this.order(); -}; - -function d3_selection_sortComparator(comparator) { - if (!arguments.length) comparator = d3.ascending; - return function(a, b) { - return comparator(a && a.__data__, b && b.__data__); + return this; }; -} -// type can be namespaced, e.g., "click.foo" -// listener can be null for removal -d3_selectionPrototype.on = function(type, listener, capture) { - if (arguments.length < 3) capture = false; - - // parse the type specifier - var name = "__on" + type, i = type.indexOf("."); - if (i > 0) type = type.substring(0, i); - - // if called with only one argument, return the current listener - if (arguments.length < 2) return (i = this.node()[name]) && i._; - - // remove the old event listener, and add the new event listener - return this.each(function(d, i) { - var node = this, - o = node[name]; - - // remove the old listener, if any (using the previously-set capture) - if (o) { - node.removeEventListener(type, o, o.$); - delete node[name]; + d3_selectionPrototype.sort = function(comparator) { + comparator = d3_selection_sortComparator.apply(this, arguments); + for (var j = -1, m = this.length; ++j < m; ) this[j].sort(comparator); + return this.order(); + }; + function d3_selection_sortComparator(comparator) { + if (!arguments.length) comparator = d3.ascending; + return function(a, b) { + return !a - !b || comparator(a.__data__, b.__data__); + }; + } + function d3_noop() {} + d3_selectionPrototype.on = function(type, listener, capture) { + var n = arguments.length; + if (n < 3) { + if (typeof type !== "string") { + if (n < 2) listener = false; + for (capture in type) this.each(d3_selection_on(capture, type[capture], listener)); + return this; + } + if (n < 2) return (n = this.node()["__on" + type]) && n._; + capture = false; } - - // add the new listener, if any (remembering the capture flag) - if (listener) { - node.addEventListener(type, node[name] = l, l.$ = capture); - l._ = listener; // stash the unwrapped listener for get + return this.each(d3_selection_on(type, listener, capture)); + }; + function d3_selection_on(type, listener, capture) { + var name = "__on" + type, i = type.indexOf("."), wrap = d3_selection_onListener; + if (i > 0) type = type.substring(0, i); + var filter = d3_selection_onFilters.get(type); + if (filter) type = filter, wrap = d3_selection_onFilter; + function onRemove() { + var l = this[name]; + if (l) { + this.removeEventListener(type, l, l.$); + delete this[name]; + } } - - // wrapped event listener that preserves i - function l(e) { - var o = d3.event; // Events can be reentrant (e.g., focus). + function onAdd() { + var l = wrap(listener, d3_array(arguments)); + onRemove.call(this); + this.addEventListener(type, this[name] = l, l.$ = capture); + l._ = listener; + } + function removeAll() { + var re = new RegExp("^__on([^.]+)" + d3.requote(type) + "$"), match; + for (var name in this) { + if (match = name.match(re)) { + var l = this[name]; + this.removeEventListener(match[1], l, l.$); + delete this[name]; + } + } + } + return i ? listener ? onAdd : onRemove : listener ? d3_noop : removeAll; + } + var d3_selection_onFilters = d3.map({ + mouseenter: "mouseover", + mouseleave: "mouseout" + }); + d3_selection_onFilters.forEach(function(k) { + if ("on" + k in d3_document) d3_selection_onFilters.remove(k); + }); + function d3_selection_onListener(listener, argumentz) { + return function(e) { + var o = d3.event; d3.event = e; + argumentz[0] = this.__data__; try { - listener.call(node, node.__data__, i); + listener.apply(this, argumentz); } finally { d3.event = o; } - } - }); -}; -d3_selectionPrototype.each = function(callback) { - for (var j = -1, m = this.length; ++j < m;) { - for (var group = this[j], i = -1, n = group.length; ++i < n;) { - var node = group[i]; - if (node) callback.call(node, node.__data__, i, j); - } - } - return this; -}; -// -// Note: assigning to the arguments array simultaneously changes the value of -// the corresponding argument! -// -// TODO The `this` argument probably shouldn't be the first argument to the -// callback, anyway, since it's redundant. However, that will require a major -// version bump due to backwards compatibility, so I'm not changing it right -// away. -// -d3_selectionPrototype.call = function(callback) { - callback.apply(this, (arguments[0] = this, arguments)); - return this; -}; -d3_selectionPrototype.empty = function() { - return !this.node(); -}; -d3_selectionPrototype.node = function(callback) { - for (var j = 0, m = this.length; j < m; j++) { - for (var group = this[j], i = 0, n = group.length; i < n; i++) { - var node = group[i]; - if (node) return node; - } - } - return null; -}; -d3_selectionPrototype.transition = function() { - var subgroups = [], - subgroup, - node; - - for (var j = -1, m = this.length; ++j < m;) { - subgroups.push(subgroup = []); - for (var group = this[j], i = -1, n = group.length; ++i < n;) { - subgroup.push((node = group[i]) ? {node: node, delay: d3_transitionDelay, duration: d3_transitionDuration} : null); - } - } - - return d3_transition(subgroups, d3_transitionId || ++d3_transitionNextId, Date.now()); -}; -var d3_selectionRoot = d3_selection([[document]]); - -d3_selectionRoot[0].parentNode = d3_selectRoot; - -// TODO fast singleton implementation! -// TODO select(function) -d3.select = function(selector) { - return typeof selector === "string" - ? d3_selectionRoot.select(selector) - : d3_selection([[selector]]); // assume node -}; - -// TODO selectAll(function) -d3.selectAll = function(selector) { - return typeof selector === "string" - ? d3_selectionRoot.selectAll(selector) - : d3_selection([d3_array(selector)]); // assume node[] -}; -function d3_selection_enter(selection) { - d3_arraySubclass(selection, d3_selection_enterPrototype); - return selection; -} - -var d3_selection_enterPrototype = []; - -d3.selection.enter = d3_selection_enter; -d3.selection.enter.prototype = d3_selection_enterPrototype; - -d3_selection_enterPrototype.append = d3_selectionPrototype.append; -d3_selection_enterPrototype.insert = d3_selectionPrototype.insert; -d3_selection_enterPrototype.empty = d3_selectionPrototype.empty; -d3_selection_enterPrototype.node = d3_selectionPrototype.node; -d3_selection_enterPrototype.select = function(selector) { - var subgroups = [], - subgroup, - subnode, - upgroup, - group, - node; - - for (var j = -1, m = this.length; ++j < m;) { - upgroup = (group = this[j]).update; - subgroups.push(subgroup = []); - subgroup.parentNode = group.parentNode; - for (var i = -1, n = group.length; ++i < n;) { - if (node = group[i]) { - subgroup.push(upgroup[i] = subnode = selector.call(group.parentNode, node.__data__, i)); - subnode.__data__ = node.__data__; - } else { - subgroup.push(null); - } - } - } - - return d3_selection(subgroups); -}; -function d3_transition(groups, id, time) { - d3_arraySubclass(groups, d3_transitionPrototype); - - var tweens = new d3_Map, - event = d3.dispatch("start", "end"), - ease = d3_transitionEase; - - groups.id = id; - - groups.time = time; - - groups.tween = function(name, tween) { - if (arguments.length < 2) return tweens.get(name); - if (tween == null) tweens.remove(name); - else tweens.set(name, tween); - return groups; - }; - - groups.ease = function(value) { - if (!arguments.length) return ease; - ease = typeof value === "function" ? value : d3.ease.apply(d3, arguments); - return groups; - }; - - groups.each = function(type, listener) { - if (arguments.length < 2) return d3_transition_each.call(groups, type); - event.on(type, listener); - return groups; - }; - - d3.timer(function(elapsed) { - groups.each(function(d, i, j) { - var tweened = [], - node = this, - delay = groups[j][i].delay, - duration = groups[j][i].duration, - lock = node.__transition__ || (node.__transition__ = {active: 0, count: 0}); - - ++lock.count; - - delay <= elapsed ? start(elapsed) : d3.timer(start, delay, time); - - function start(elapsed) { - if (lock.active > id) return stop(); - lock.active = id; - - tweens.forEach(function(key, value) { - if (value = value.call(node, d, i)) { - tweened.push(value); - } - }); - - event.start.call(node, d, i); - if (!tick(elapsed)) d3.timer(tick, 0, time); - return 1; - } - - function tick(elapsed) { - if (lock.active !== id) return stop(); - - var t = (elapsed - delay) / duration, - e = ease(t), - n = tweened.length; - - while (n > 0) { - tweened[--n].call(node, e); - } - - if (t >= 1) { - stop(); - d3_transitionId = id; - event.end.call(node, d, i); - d3_transitionId = 0; - return 1; - } - } - - function stop() { - if (!--lock.count) delete node.__transition__; - return 1; - } - }); - return 1; - }, 0, time); - - return groups; -} - -var d3_transitionRemove = {}; - -function d3_transitionNull(d, i, a) { - return a != "" && d3_transitionRemove; -} - -function d3_transitionTween(name, b) { - var interpolate = d3_interpolateByName(name); - - function transitionFunction(d, i, a) { - var v = b.call(this, d, i); - return v == null - ? a != "" && d3_transitionRemove - : a != v && interpolate(a, v); - } - - function transitionString(d, i, a) { - return a != b && interpolate(a, b); - } - - return typeof b === "function" ? transitionFunction - : b == null ? d3_transitionNull - : (b += "", transitionString); -} - -var d3_transitionPrototype = [], - d3_transitionNextId = 0, - d3_transitionId = 0, - d3_transitionDefaultDelay = 0, - d3_transitionDefaultDuration = 250, - d3_transitionDefaultEase = d3.ease("cubic-in-out"), - d3_transitionDelay = d3_transitionDefaultDelay, - d3_transitionDuration = d3_transitionDefaultDuration, - d3_transitionEase = d3_transitionDefaultEase; - -d3_transitionPrototype.call = d3_selectionPrototype.call; - -d3.transition = function(selection) { - return arguments.length - ? (d3_transitionId ? selection.transition() : selection) - : d3_selectionRoot.transition(); -}; - -d3.transition.prototype = d3_transitionPrototype; -d3_transitionPrototype.select = function(selector) { - var subgroups = [], - subgroup, - subnode, - node; - - if (typeof selector !== "function") selector = d3_selection_selector(selector); - - for (var j = -1, m = this.length; ++j < m;) { - subgroups.push(subgroup = []); - for (var group = this[j], i = -1, n = group.length; ++i < n;) { - if ((node = group[i]) && (subnode = selector.call(node.node, node.node.__data__, i))) { - if ("__data__" in node.node) subnode.__data__ = node.node.__data__; - subgroup.push({node: subnode, delay: node.delay, duration: node.duration}); - } else { - subgroup.push(null); - } - } - } - - return d3_transition(subgroups, this.id, this.time).ease(this.ease()); -}; -d3_transitionPrototype.selectAll = function(selector) { - var subgroups = [], - subgroup, - subnodes, - node; - - if (typeof selector !== "function") selector = d3_selection_selectorAll(selector); - - for (var j = -1, m = this.length; ++j < m;) { - for (var group = this[j], i = -1, n = group.length; ++i < n;) { - if (node = group[i]) { - subnodes = selector.call(node.node, node.node.__data__, i); - subgroups.push(subgroup = []); - for (var k = -1, o = subnodes.length; ++k < o;) { - subgroup.push({node: subnodes[k], delay: node.delay, duration: node.duration}); - } - } - } - } - - return d3_transition(subgroups, this.id, this.time).ease(this.ease()); -}; -d3_transitionPrototype.attr = function(name, value) { - return this.attrTween(name, d3_transitionTween(name, value)); -}; - -d3_transitionPrototype.attrTween = function(nameNS, tween) { - var name = d3.ns.qualify(nameNS); - - function attrTween(d, i) { - var f = tween.call(this, d, i, this.getAttribute(name)); - return f === d3_transitionRemove - ? (this.removeAttribute(name), null) - : f && function(t) { this.setAttribute(name, f(t)); }; - } - - function attrTweenNS(d, i) { - var f = tween.call(this, d, i, this.getAttributeNS(name.space, name.local)); - return f === d3_transitionRemove - ? (this.removeAttributeNS(name.space, name.local), null) - : f && function(t) { this.setAttributeNS(name.space, name.local, f(t)); }; - } - - return this.tween("attr." + nameNS, name.local ? attrTweenNS : attrTween); -}; -d3_transitionPrototype.style = function(name, value, priority) { - if (arguments.length < 3) priority = ""; - return this.styleTween(name, d3_transitionTween(name, value), priority); -}; - -d3_transitionPrototype.styleTween = function(name, tween, priority) { - if (arguments.length < 3) priority = ""; - return this.tween("style." + name, function(d, i) { - var f = tween.call(this, d, i, window.getComputedStyle(this, null).getPropertyValue(name)); - return f === d3_transitionRemove - ? (this.style.removeProperty(name), null) - : f && function(t) { this.style.setProperty(name, f(t), priority); }; - }); -}; -d3_transitionPrototype.text = function(value) { - return this.tween("text", function(d, i) { - this.textContent = typeof value === "function" - ? value.call(this, d, i) - : value; - }); -}; -d3_transitionPrototype.remove = function() { - return this.each("end.transition", function() { - var p; - if (!this.__transition__ && (p = this.parentNode)) p.removeChild(this); - }); -}; -d3_transitionPrototype.delay = function(value) { - var groups = this; - return groups.each(typeof value === "function" - ? function(d, i, j) { groups[j][i].delay = value.apply(this, arguments) | 0; } - : (value = value | 0, function(d, i, j) { groups[j][i].delay = value; })); -}; -d3_transitionPrototype.duration = function(value) { - var groups = this; - return groups.each(typeof value === "function" - ? function(d, i, j) { groups[j][i].duration = Math.max(1, value.apply(this, arguments) | 0); } - : (value = Math.max(1, value | 0), function(d, i, j) { groups[j][i].duration = value; })); -}; -function d3_transition_each(callback) { - var id = d3_transitionId, - ease = d3_transitionEase, - delay = d3_transitionDelay, - duration = d3_transitionDuration; - - d3_transitionId = this.id; - d3_transitionEase = this.ease(); - for (var j = 0, m = this.length; j < m; j++) { - for (var group = this[j], i = 0, n = group.length; i < n; i++) { - var node = group[i]; - if (node) { - d3_transitionDelay = this[j][i].delay; - d3_transitionDuration = this[j][i].duration; - callback.call(node = node.node, node.__data__, i, j); - } - } - } - - d3_transitionId = id; - d3_transitionEase = ease; - d3_transitionDelay = delay; - d3_transitionDuration = duration; - return this; -} -d3_transitionPrototype.transition = function() { - return this.select(d3_this); -}; -var d3_timer_queue = null, - d3_timer_interval, // is an interval (or frame) active? - d3_timer_timeout; // is a timeout active? - -// The timer will continue to fire until callback returns true. -d3.timer = function(callback, delay, then) { - var found = false, - t0, - t1 = d3_timer_queue; - - if (arguments.length < 3) { - if (arguments.length < 2) delay = 0; - else if (!isFinite(delay)) return; - then = Date.now(); - } - - // See if the callback's already in the queue. - while (t1) { - if (t1.callback === callback) { - t1.then = then; - t1.delay = delay; - found = true; - break; - } - t0 = t1; - t1 = t1.next; - } - - // Otherwise, add the callback to the queue. - if (!found) d3_timer_queue = { - callback: callback, - then: then, - delay: delay, - next: d3_timer_queue - }; - - // Start animatin'! - if (!d3_timer_interval) { - d3_timer_timeout = clearTimeout(d3_timer_timeout); - d3_timer_interval = 1; - d3_timer_frame(d3_timer_step); - } -} - -function d3_timer_step() { - var elapsed, - now = Date.now(), - t1 = d3_timer_queue; - - while (t1) { - elapsed = now - t1.then; - if (elapsed >= t1.delay) t1.flush = t1.callback(elapsed); - t1 = t1.next; - } - - var delay = d3_timer_flush() - now; - if (delay > 24) { - if (isFinite(delay)) { - clearTimeout(d3_timer_timeout); - d3_timer_timeout = setTimeout(d3_timer_step, delay); - } - d3_timer_interval = 0; - } else { - d3_timer_interval = 1; - d3_timer_frame(d3_timer_step); - } -} - -d3.timer.flush = function() { - var elapsed, - now = Date.now(), - t1 = d3_timer_queue; - - while (t1) { - elapsed = now - t1.then; - if (!t1.delay) t1.flush = t1.callback(elapsed); - t1 = t1.next; - } - - d3_timer_flush(); -}; - -// Flush after callbacks, to avoid concurrent queue modification. -function d3_timer_flush() { - var t0 = null, - t1 = d3_timer_queue, - then = Infinity; - while (t1) { - if (t1.flush) { - t1 = t0 ? t0.next = t1.next : d3_timer_queue = t1.next; - } else { - then = Math.min(then, t1.then + t1.delay); - t1 = (t0 = t1).next; - } - } - return then; -} - -var d3_timer_frame = window.requestAnimationFrame - || window.webkitRequestAnimationFrame - || window.mozRequestAnimationFrame - || window.oRequestAnimationFrame - || window.msRequestAnimationFrame - || function(callback) { setTimeout(callback, 17); }; -d3.transform = function(string) { - var g = document.createElementNS(d3.ns.prefix.svg, "g"), - identity = {a: 1, b: 0, c: 0, d: 1, e: 0, f: 0}; - return (d3.transform = function(string) { - g.setAttribute("transform", string); - var t = g.transform.baseVal.consolidate(); - return new d3_transform(t ? t.matrix : identity); - })(string); -}; - -// Compute x-scale and normalize the first row. -// Compute shear and make second row orthogonal to first. -// Compute y-scale and normalize the second row. -// Finally, compute the rotation. -function d3_transform(m) { - var r0 = [m.a, m.b], - r1 = [m.c, m.d], - kx = d3_transformNormalize(r0), - kz = d3_transformDot(r0, r1), - ky = d3_transformNormalize(d3_transformCombine(r1, r0, -kz)) || 0; - if (r0[0] * r1[1] < r1[0] * r0[1]) { - r0[0] *= -1; - r0[1] *= -1; - kx *= -1; - kz *= -1; - } - this.rotate = (kx ? Math.atan2(r0[1], r0[0]) : Math.atan2(-r1[0], r1[1])) * d3_transformDegrees; - this.translate = [m.e, m.f]; - this.scale = [kx, ky]; - this.skew = ky ? Math.atan2(kz, ky) * d3_transformDegrees : 0; -}; - -d3_transform.prototype.toString = function() { - return "translate(" + this.translate - + ")rotate(" + this.rotate - + ")skewX(" + this.skew - + ")scale(" + this.scale - + ")"; -}; - -function d3_transformDot(a, b) { - return a[0] * b[0] + a[1] * b[1]; -} - -function d3_transformNormalize(a) { - var k = Math.sqrt(d3_transformDot(a, a)); - if (k) { - a[0] /= k; - a[1] /= k; - } - return k; -} - -function d3_transformCombine(a, b, k) { - a[0] += k * b[0]; - a[1] += k * b[1]; - return a; -} - -var d3_transformDegrees = 180 / Math.PI; -d3.mouse = function(container) { - return d3_mousePoint(container, d3_eventSource()); -}; - -// https://bugs.webkit.org/show_bug.cgi?id=44083 -var d3_mouse_bug44083 = /WebKit/.test(navigator.userAgent) ? -1 : 0; - -function d3_mousePoint(container, e) { - var svg = container.ownerSVGElement || container; - if (svg.createSVGPoint) { - var point = svg.createSVGPoint(); - if ((d3_mouse_bug44083 < 0) && (window.scrollX || window.scrollY)) { - svg = d3.select(document.body) - .append("svg") - .style("position", "absolute") - .style("top", 0) - .style("left", 0); - var ctm = svg[0][0].getScreenCTM(); - d3_mouse_bug44083 = !(ctm.f || ctm.e); - svg.remove(); - } - if (d3_mouse_bug44083) { - point.x = e.pageX; - point.y = e.pageY; - } else { - point.x = e.clientX; - point.y = e.clientY; - } - point = point.matrixTransform(container.getScreenCTM().inverse()); - return [point.x, point.y]; - } - var rect = container.getBoundingClientRect(); - return [e.clientX - rect.left - container.clientLeft, e.clientY - rect.top - container.clientTop]; -}; -d3.touches = function(container, touches) { - if (arguments.length < 2) touches = d3_eventSource().touches; - return touches ? d3_array(touches).map(function(touch) { - var point = d3_mousePoint(container, touch); - point.identifier = touch.identifier; - return point; - }) : []; -}; -function d3_noop() {} -d3.scale = {}; - -function d3_scaleExtent(domain) { - var start = domain[0], stop = domain[domain.length - 1]; - return start < stop ? [start, stop] : [stop, start]; -} - -function d3_scaleRange(scale) { - return scale.rangeExtent ? scale.rangeExtent() : d3_scaleExtent(scale.range()); -} -function d3_scale_nice(domain, nice) { - var i0 = 0, - i1 = domain.length - 1, - x0 = domain[i0], - x1 = domain[i1], - dx; - - if (x1 < x0) { - dx = i0; i0 = i1; i1 = dx; - dx = x0; x0 = x1; x1 = dx; - } - - if (dx = x1 - x0) { - nice = nice(dx); - domain[i0] = nice.floor(x0); - domain[i1] = nice.ceil(x1); - } - - return domain; -} - -function d3_scale_niceDefault() { - return Math; -} -d3.scale.linear = function() { - return d3_scale_linear([0, 1], [0, 1], d3.interpolate, false); -}; - -function d3_scale_linear(domain, range, interpolate, clamp) { - var output, - input; - - function rescale() { - var linear = Math.min(domain.length, range.length) > 2 ? d3_scale_polylinear : d3_scale_bilinear, - uninterpolate = clamp ? d3_uninterpolateClamp : d3_uninterpolateNumber; - output = linear(domain, range, uninterpolate, interpolate); - input = linear(range, domain, uninterpolate, d3.interpolate); - return scale; - } - - function scale(x) { - return output(x); - } - - // Note: requires range is coercible to number! - scale.invert = function(y) { - return input(y); - }; - - scale.domain = function(x) { - if (!arguments.length) return domain; - domain = x.map(Number); - return rescale(); - }; - - scale.range = function(x) { - if (!arguments.length) return range; - range = x; - return rescale(); - }; - - scale.rangeRound = function(x) { - return scale.range(x).interpolate(d3.interpolateRound); - }; - - scale.clamp = function(x) { - if (!arguments.length) return clamp; - clamp = x; - return rescale(); - }; - - scale.interpolate = function(x) { - if (!arguments.length) return interpolate; - interpolate = x; - return rescale(); - }; - - scale.ticks = function(m) { - return d3_scale_linearTicks(domain, m); - }; - - scale.tickFormat = function(m) { - return d3_scale_linearTickFormat(domain, m); - }; - - scale.nice = function() { - d3_scale_nice(domain, d3_scale_linearNice); - return rescale(); - }; - - scale.copy = function() { - return d3_scale_linear(domain, range, interpolate, clamp); - }; - - return rescale(); -} - -function d3_scale_linearRebind(scale, linear) { - return d3.rebind(scale, linear, "range", "rangeRound", "interpolate", "clamp"); -} - -function d3_scale_linearNice(dx) { - dx = Math.pow(10, Math.round(Math.log(dx) / Math.LN10) - 1); - return { - floor: function(x) { return Math.floor(x / dx) * dx; }, - ceil: function(x) { return Math.ceil(x / dx) * dx; } - }; -} - -function d3_scale_linearTickRange(domain, m) { - var extent = d3_scaleExtent(domain), - span = extent[1] - extent[0], - step = Math.pow(10, Math.floor(Math.log(span / m) / Math.LN10)), - err = m / span * step; - - // Filter ticks to get closer to the desired count. - if (err <= .15) step *= 10; - else if (err <= .35) step *= 5; - else if (err <= .75) step *= 2; - - // Round start and stop values to step interval. - extent[0] = Math.ceil(extent[0] / step) * step; - extent[1] = Math.floor(extent[1] / step) * step + step * .5; // inclusive - extent[2] = step; - return extent; -} - -function d3_scale_linearTicks(domain, m) { - return d3.range.apply(d3, d3_scale_linearTickRange(domain, m)); -} - -function d3_scale_linearTickFormat(domain, m) { - return d3.format(",." + Math.max(0, -Math.floor(Math.log(d3_scale_linearTickRange(domain, m)[2]) / Math.LN10 + .01)) + "f"); -} -function d3_scale_bilinear(domain, range, uninterpolate, interpolate) { - var u = uninterpolate(domain[0], domain[1]), - i = interpolate(range[0], range[1]); - return function(x) { - return i(u(x)); - }; -} -function d3_scale_polylinear(domain, range, uninterpolate, interpolate) { - var u = [], - i = [], - j = 0, - k = Math.min(domain.length, range.length) - 1; - - // Handle descending domains. - if (domain[k] < domain[0]) { - domain = domain.slice().reverse(); - range = range.slice().reverse(); - } - - while (++j <= k) { - u.push(uninterpolate(domain[j - 1], domain[j])); - i.push(interpolate(range[j - 1], range[j])); - } - - return function(x) { - var j = d3.bisect(domain, x, 1, k) - 1; - return i[j](u[j](x)); - }; -} -d3.scale.log = function() { - return d3_scale_log(d3.scale.linear(), d3_scale_logp); -}; - -function d3_scale_log(linear, log) { - var pow = log.pow; - - function scale(x) { - return linear(log(x)); - } - - scale.invert = function(x) { - return pow(linear.invert(x)); - }; - - scale.domain = function(x) { - if (!arguments.length) return linear.domain().map(pow); - log = x[0] < 0 ? d3_scale_logn : d3_scale_logp; - pow = log.pow; - linear.domain(x.map(log)); - return scale; - }; - - scale.nice = function() { - linear.domain(d3_scale_nice(linear.domain(), d3_scale_niceDefault)); - return scale; - }; - - scale.ticks = function() { - var extent = d3_scaleExtent(linear.domain()), - ticks = []; - if (extent.every(isFinite)) { - var i = Math.floor(extent[0]), - j = Math.ceil(extent[1]), - u = pow(extent[0]), - v = pow(extent[1]); - if (log === d3_scale_logn) { - ticks.push(pow(i)); - for (; i++ < j;) for (var k = 9; k > 0; k--) ticks.push(pow(i) * k); - } else { - for (; i < j; i++) for (var k = 1; k < 10; k++) ticks.push(pow(i) * k); - ticks.push(pow(i)); - } - for (i = 0; ticks[i] < u; i++) {} // strip small values - for (j = ticks.length; ticks[j - 1] > v; j--) {} // strip big values - ticks = ticks.slice(i, j); - } - return ticks; - }; - - scale.tickFormat = function(n, format) { - if (arguments.length < 2) format = d3_scale_logFormat; - if (arguments.length < 1) return format; - var k = n / scale.ticks().length, - f = log === d3_scale_logn ? (e = -1e-12, Math.floor) : (e = 1e-12, Math.ceil), - e; - return function(d) { - return d / pow(f(log(d) + e)) < k ? format(d) : ""; - }; - }; - - scale.copy = function() { - return d3_scale_log(linear.copy(), log); - }; - - return d3_scale_linearRebind(scale, linear); -} - -var d3_scale_logFormat = d3.format(".0e"); - -function d3_scale_logp(x) { - return Math.log(x < 0 ? 0 : x) / Math.LN10; -} - -function d3_scale_logn(x) { - return -Math.log(x > 0 ? 0 : -x) / Math.LN10; -} - -d3_scale_logp.pow = function(x) { - return Math.pow(10, x); -}; - -d3_scale_logn.pow = function(x) { - return -Math.pow(10, -x); -}; -d3.scale.pow = function() { - return d3_scale_pow(d3.scale.linear(), 1); -}; - -function d3_scale_pow(linear, exponent) { - var powp = d3_scale_powPow(exponent), - powb = d3_scale_powPow(1 / exponent); - - function scale(x) { - return linear(powp(x)); - } - - scale.invert = function(x) { - return powb(linear.invert(x)); - }; - - scale.domain = function(x) { - if (!arguments.length) return linear.domain().map(powb); - linear.domain(x.map(powp)); - return scale; - }; - - scale.ticks = function(m) { - return d3_scale_linearTicks(scale.domain(), m); - }; - - scale.tickFormat = function(m) { - return d3_scale_linearTickFormat(scale.domain(), m); - }; - - scale.nice = function() { - return scale.domain(d3_scale_nice(scale.domain(), d3_scale_linearNice)); - }; - - scale.exponent = function(x) { - if (!arguments.length) return exponent; - var domain = scale.domain(); - powp = d3_scale_powPow(exponent = x); - powb = d3_scale_powPow(1 / exponent); - return scale.domain(domain); - }; - - scale.copy = function() { - return d3_scale_pow(linear.copy(), exponent); - }; - - return d3_scale_linearRebind(scale, linear); -} - -function d3_scale_powPow(e) { - return function(x) { - return x < 0 ? -Math.pow(-x, e) : Math.pow(x, e); - }; -} -d3.scale.sqrt = function() { - return d3.scale.pow().exponent(.5); -}; -d3.scale.ordinal = function() { - return d3_scale_ordinal([], {t: "range", x: []}); -}; - -function d3_scale_ordinal(domain, ranger) { - var index, - range, - rangeBand; - - function scale(x) { - return range[((index.get(x) || index.set(x, domain.push(x))) - 1) % range.length]; - } - - function steps(start, step) { - return d3.range(domain.length).map(function(i) { return start + step * i; }); - } - - scale.domain = function(x) { - if (!arguments.length) return domain; - domain = []; - index = new d3_Map; - var i = -1, n = x.length, xi; - while (++i < n) if (!index.has(xi = x[i])) index.set(xi, domain.push(xi)); - return scale[ranger.t](ranger.x, ranger.p); - }; - - scale.range = function(x) { - if (!arguments.length) return range; - range = x; - rangeBand = 0; - ranger = {t: "range", x: x}; - return scale; - }; - - scale.rangePoints = function(x, padding) { - if (arguments.length < 2) padding = 0; - var start = x[0], - stop = x[1], - step = (stop - start) / (domain.length - 1 + padding); - range = steps(domain.length < 2 ? (start + stop) / 2 : start + step * padding / 2, step); - rangeBand = 0; - ranger = {t: "rangePoints", x: x, p: padding}; - return scale; - }; - - scale.rangeBands = function(x, padding) { - if (arguments.length < 2) padding = 0; - var reverse = x[1] < x[0], - start = x[reverse - 0], - stop = x[1 - reverse], - step = (stop - start) / (domain.length + padding); - range = steps(start + step * padding, step); - if (reverse) range.reverse(); - rangeBand = step * (1 - padding); - ranger = {t: "rangeBands", x: x, p: padding}; - return scale; - }; - - scale.rangeRoundBands = function(x, padding) { - if (arguments.length < 2) padding = 0; - var reverse = x[1] < x[0], - start = x[reverse - 0], - stop = x[1 - reverse], - step = Math.floor((stop - start) / (domain.length + padding)), - error = stop - start - (domain.length - padding) * step; - range = steps(start + Math.round(error / 2), step); - if (reverse) range.reverse(); - rangeBand = Math.round(step * (1 - padding)); - ranger = {t: "rangeRoundBands", x: x, p: padding}; - return scale; - }; - - scale.rangeBand = function() { - return rangeBand; - }; - - scale.rangeExtent = function() { - return d3_scaleExtent(ranger.x); - }; - - scale.copy = function() { - return d3_scale_ordinal(domain, ranger); - }; - - return scale.domain(domain); -} -/* - * This product includes color specifications and designs developed by Cynthia - * Brewer (http://colorbrewer.org/). See lib/colorbrewer for more information. - */ - -d3.scale.category10 = function() { - return d3.scale.ordinal().range(d3_category10); -}; - -d3.scale.category20 = function() { - return d3.scale.ordinal().range(d3_category20); -}; - -d3.scale.category20b = function() { - return d3.scale.ordinal().range(d3_category20b); -}; - -d3.scale.category20c = function() { - return d3.scale.ordinal().range(d3_category20c); -}; - -var d3_category10 = [ - "#1f77b4", "#ff7f0e", "#2ca02c", "#d62728", "#9467bd", - "#8c564b", "#e377c2", "#7f7f7f", "#bcbd22", "#17becf" -]; - -var d3_category20 = [ - "#1f77b4", "#aec7e8", - "#ff7f0e", "#ffbb78", - "#2ca02c", "#98df8a", - "#d62728", "#ff9896", - "#9467bd", "#c5b0d5", - "#8c564b", "#c49c94", - "#e377c2", "#f7b6d2", - "#7f7f7f", "#c7c7c7", - "#bcbd22", "#dbdb8d", - "#17becf", "#9edae5" -]; - -var d3_category20b = [ - "#393b79", "#5254a3", "#6b6ecf", "#9c9ede", - "#637939", "#8ca252", "#b5cf6b", "#cedb9c", - "#8c6d31", "#bd9e39", "#e7ba52", "#e7cb94", - "#843c39", "#ad494a", "#d6616b", "#e7969c", - "#7b4173", "#a55194", "#ce6dbd", "#de9ed6" -]; - -var d3_category20c = [ - "#3182bd", "#6baed6", "#9ecae1", "#c6dbef", - "#e6550d", "#fd8d3c", "#fdae6b", "#fdd0a2", - "#31a354", "#74c476", "#a1d99b", "#c7e9c0", - "#756bb1", "#9e9ac8", "#bcbddc", "#dadaeb", - "#636363", "#969696", "#bdbdbd", "#d9d9d9" -]; -d3.scale.quantile = function() { - return d3_scale_quantile([], []); -}; - -function d3_scale_quantile(domain, range) { - var thresholds; - - function rescale() { - var k = 0, - n = domain.length, - q = range.length; - thresholds = []; - while (++k < q) thresholds[k - 1] = d3.quantile(domain, k / q); - return scale; - } - - function scale(x) { - if (isNaN(x = +x)) return NaN; - return range[d3.bisect(thresholds, x)]; - } - - scale.domain = function(x) { - if (!arguments.length) return domain; - domain = x.filter(function(d) { return !isNaN(d); }).sort(d3.ascending); - return rescale(); - }; - - scale.range = function(x) { - if (!arguments.length) return range; - range = x; - return rescale(); - }; - - scale.quantiles = function() { - return thresholds; - }; - - scale.copy = function() { - return d3_scale_quantile(domain, range); // copy on write! - }; - - return rescale(); -} -d3.scale.quantize = function() { - return d3_scale_quantize(0, 1, [0, 1]); -}; - -function d3_scale_quantize(x0, x1, range) { - var kx, i; - - function scale(x) { - return range[Math.max(0, Math.min(i, Math.floor(kx * (x - x0))))]; - } - - function rescale() { - kx = range.length / (x1 - x0); - i = range.length - 1; - return scale; - } - - scale.domain = function(x) { - if (!arguments.length) return [x0, x1]; - x0 = +x[0]; - x1 = +x[x.length - 1]; - return rescale(); - }; - - scale.range = function(x) { - if (!arguments.length) return range; - range = x; - return rescale(); - }; - - scale.copy = function() { - return d3_scale_quantize(x0, x1, range); // copy on write - }; - - return rescale(); -} -d3.scale.identity = function() { - return d3_scale_identity([0, 1]); -}; - -function d3_scale_identity(domain) { - - function identity(x) { return +x; } - - identity.invert = identity; - - identity.domain = identity.range = function(x) { - if (!arguments.length) return domain; - domain = x.map(identity); - return identity; - }; - - identity.ticks = function(m) { - return d3_scale_linearTicks(domain, m); - }; - - identity.tickFormat = function(m) { - return d3_scale_linearTickFormat(domain, m); - }; - - identity.copy = function() { - return d3_scale_identity(domain); - }; - - return identity; -} -d3.svg = {}; -d3.svg.arc = function() { - var innerRadius = d3_svg_arcInnerRadius, - outerRadius = d3_svg_arcOuterRadius, - startAngle = d3_svg_arcStartAngle, - endAngle = d3_svg_arcEndAngle; - - function arc() { - var r0 = innerRadius.apply(this, arguments), - r1 = outerRadius.apply(this, arguments), - a0 = startAngle.apply(this, arguments) + d3_svg_arcOffset, - a1 = endAngle.apply(this, arguments) + d3_svg_arcOffset, - da = (a1 < a0 && (da = a0, a0 = a1, a1 = da), a1 - a0), - df = da < Math.PI ? "0" : "1", - c0 = Math.cos(a0), - s0 = Math.sin(a0), - c1 = Math.cos(a1), - s1 = Math.sin(a1); - return da >= d3_svg_arcMax - ? (r0 - ? "M0," + r1 - + "A" + r1 + "," + r1 + " 0 1,1 0," + (-r1) - + "A" + r1 + "," + r1 + " 0 1,1 0," + r1 - + "M0," + r0 - + "A" + r0 + "," + r0 + " 0 1,0 0," + (-r0) - + "A" + r0 + "," + r0 + " 0 1,0 0," + r0 - + "Z" - : "M0," + r1 - + "A" + r1 + "," + r1 + " 0 1,1 0," + (-r1) - + "A" + r1 + "," + r1 + " 0 1,1 0," + r1 - + "Z") - : (r0 - ? "M" + r1 * c0 + "," + r1 * s0 - + "A" + r1 + "," + r1 + " 0 " + df + ",1 " + r1 * c1 + "," + r1 * s1 - + "L" + r0 * c1 + "," + r0 * s1 - + "A" + r0 + "," + r0 + " 0 " + df + ",0 " + r0 * c0 + "," + r0 * s0 - + "Z" - : "M" + r1 * c0 + "," + r1 * s0 - + "A" + r1 + "," + r1 + " 0 " + df + ",1 " + r1 * c1 + "," + r1 * s1 - + "L0,0" - + "Z"); - } - - arc.innerRadius = function(v) { - if (!arguments.length) return innerRadius; - innerRadius = d3_functor(v); - return arc; - }; - - arc.outerRadius = function(v) { - if (!arguments.length) return outerRadius; - outerRadius = d3_functor(v); - return arc; - }; - - arc.startAngle = function(v) { - if (!arguments.length) return startAngle; - startAngle = d3_functor(v); - return arc; - }; - - arc.endAngle = function(v) { - if (!arguments.length) return endAngle; - endAngle = d3_functor(v); - return arc; - }; - - arc.centroid = function() { - var r = (innerRadius.apply(this, arguments) - + outerRadius.apply(this, arguments)) / 2, - a = (startAngle.apply(this, arguments) - + endAngle.apply(this, arguments)) / 2 + d3_svg_arcOffset; - return [Math.cos(a) * r, Math.sin(a) * r]; - }; - - return arc; -}; - -var d3_svg_arcOffset = -Math.PI / 2, - d3_svg_arcMax = 2 * Math.PI - 1e-6; - -function d3_svg_arcInnerRadius(d) { - return d.innerRadius; -} - -function d3_svg_arcOuterRadius(d) { - return d.outerRadius; -} - -function d3_svg_arcStartAngle(d) { - return d.startAngle; -} - -function d3_svg_arcEndAngle(d) { - return d.endAngle; -} -function d3_svg_line(projection) { - var x = d3_svg_lineX, - y = d3_svg_lineY, - defined = d3_true, - interpolate = d3_svg_lineInterpolatorDefault, - interpolator = d3_svg_lineLinear, - tension = .7; - - function line(data) { - var segments = [], - points = [], - i = -1, - n = data.length, - d, - fx = d3_functor(x), - fy = d3_functor(y); - - function segment() { - segments.push("M", interpolator(projection(points), tension)); - } - - while (++i < n) { - if (defined.call(this, d = data[i], i)) { - points.push([+fx.call(this, d, i), +fy.call(this, d, i)]); - } else if (points.length) { - segment(); - points = []; - } - } - - if (points.length) segment(); - - return segments.length ? segments.join("") : null; - } - - line.x = function(_) { - if (!arguments.length) return x; - x = _; - return line; - }; - - line.y = function(_) { - if (!arguments.length) return y; - y = _; - return line; - }; - - line.defined = function(_) { - if (!arguments.length) return defined; - defined = _; - return line; - }; - - line.interpolate = function(_) { - if (!arguments.length) return interpolate; - if (!d3_svg_lineInterpolators.has(_ += "")) _ = d3_svg_lineInterpolatorDefault; - interpolator = d3_svg_lineInterpolators.get(interpolate = _); - return line; - }; - - line.tension = function(_) { - if (!arguments.length) return tension; - tension = _; - return line; - }; - - return line; -} - -d3.svg.line = function() { - return d3_svg_line(d3_identity); -}; - -// The default `x` property, which references d[0]. -function d3_svg_lineX(d) { - return d[0]; -} - -// The default `y` property, which references d[1]. -function d3_svg_lineY(d) { - return d[1]; -} - -var d3_svg_lineInterpolatorDefault = "linear"; - -// The various interpolators supported by the `line` class. -var d3_svg_lineInterpolators = d3.map({ - "linear": d3_svg_lineLinear, - "step-before": d3_svg_lineStepBefore, - "step-after": d3_svg_lineStepAfter, - "basis": d3_svg_lineBasis, - "basis-open": d3_svg_lineBasisOpen, - "basis-closed": d3_svg_lineBasisClosed, - "bundle": d3_svg_lineBundle, - "cardinal": d3_svg_lineCardinal, - "cardinal-open": d3_svg_lineCardinalOpen, - "cardinal-closed": d3_svg_lineCardinalClosed, - "monotone": d3_svg_lineMonotone -}); - -// Linear interpolation; generates "L" commands. -function d3_svg_lineLinear(points) { - var i = 0, - n = points.length, - p = points[0], - path = [p[0], ",", p[1]]; - while (++i < n) path.push("L", (p = points[i])[0], ",", p[1]); - return path.join(""); -} - -// Step interpolation; generates "H" and "V" commands. -function d3_svg_lineStepBefore(points) { - var i = 0, - n = points.length, - p = points[0], - path = [p[0], ",", p[1]]; - while (++i < n) path.push("V", (p = points[i])[1], "H", p[0]); - return path.join(""); -} - -// Step interpolation; generates "H" and "V" commands. -function d3_svg_lineStepAfter(points) { - var i = 0, - n = points.length, - p = points[0], - path = [p[0], ",", p[1]]; - while (++i < n) path.push("H", (p = points[i])[0], "V", p[1]); - return path.join(""); -} - -// Open cardinal spline interpolation; generates "C" commands. -function d3_svg_lineCardinalOpen(points, tension) { - return points.length < 4 - ? d3_svg_lineLinear(points) - : points[1] + d3_svg_lineHermite(points.slice(1, points.length - 1), - d3_svg_lineCardinalTangents(points, tension)); -} - -// Closed cardinal spline interpolation; generates "C" commands. -function d3_svg_lineCardinalClosed(points, tension) { - return points.length < 3 - ? d3_svg_lineLinear(points) - : points[0] + d3_svg_lineHermite((points.push(points[0]), points), - d3_svg_lineCardinalTangents([points[points.length - 2]] - .concat(points, [points[1]]), tension)); -} - -// Cardinal spline interpolation; generates "C" commands. -function d3_svg_lineCardinal(points, tension, closed) { - return points.length < 3 - ? d3_svg_lineLinear(points) - : points[0] + d3_svg_lineHermite(points, - d3_svg_lineCardinalTangents(points, tension)); -} - -// Hermite spline construction; generates "C" commands. -function d3_svg_lineHermite(points, tangents) { - if (tangents.length < 1 - || (points.length != tangents.length - && points.length != tangents.length + 2)) { - return d3_svg_lineLinear(points); - } - - var quad = points.length != tangents.length, - path = "", - p0 = points[0], - p = points[1], - t0 = tangents[0], - t = t0, - pi = 1; - - if (quad) { - path += "Q" + (p[0] - t0[0] * 2 / 3) + "," + (p[1] - t0[1] * 2 / 3) - + "," + p[0] + "," + p[1]; - p0 = points[1]; - pi = 2; - } - - if (tangents.length > 1) { - t = tangents[1]; - p = points[pi]; - pi++; - path += "C" + (p0[0] + t0[0]) + "," + (p0[1] + t0[1]) - + "," + (p[0] - t[0]) + "," + (p[1] - t[1]) - + "," + p[0] + "," + p[1]; - for (var i = 2; i < tangents.length; i++, pi++) { - p = points[pi]; - t = tangents[i]; - path += "S" + (p[0] - t[0]) + "," + (p[1] - t[1]) - + "," + p[0] + "," + p[1]; - } - } - - if (quad) { - var lp = points[pi]; - path += "Q" + (p[0] + t[0] * 2 / 3) + "," + (p[1] + t[1] * 2 / 3) - + "," + lp[0] + "," + lp[1]; - } - - return path; -} - -// Generates tangents for a cardinal spline. -function d3_svg_lineCardinalTangents(points, tension) { - var tangents = [], - a = (1 - tension) / 2, - p0, - p1 = points[0], - p2 = points[1], - i = 1, - n = points.length; - while (++i < n) { - p0 = p1; - p1 = p2; - p2 = points[i]; - tangents.push([a * (p2[0] - p0[0]), a * (p2[1] - p0[1])]); - } - return tangents; -} - -// B-spline interpolation; generates "C" commands. -function d3_svg_lineBasis(points) { - if (points.length < 3) return d3_svg_lineLinear(points); - var i = 1, - n = points.length, - pi = points[0], - x0 = pi[0], - y0 = pi[1], - px = [x0, x0, x0, (pi = points[1])[0]], - py = [y0, y0, y0, pi[1]], - path = [x0, ",", y0]; - d3_svg_lineBasisBezier(path, px, py); - while (++i < n) { - pi = points[i]; - px.shift(); px.push(pi[0]); - py.shift(); py.push(pi[1]); - d3_svg_lineBasisBezier(path, px, py); - } - i = -1; - while (++i < 2) { - px.shift(); px.push(pi[0]); - py.shift(); py.push(pi[1]); - d3_svg_lineBasisBezier(path, px, py); - } - return path.join(""); -} - -// Open B-spline interpolation; generates "C" commands. -function d3_svg_lineBasisOpen(points) { - if (points.length < 4) return d3_svg_lineLinear(points); - var path = [], - i = -1, - n = points.length, - pi, - px = [0], - py = [0]; - while (++i < 3) { - pi = points[i]; - px.push(pi[0]); - py.push(pi[1]); - } - path.push(d3_svg_lineDot4(d3_svg_lineBasisBezier3, px) - + "," + d3_svg_lineDot4(d3_svg_lineBasisBezier3, py)); - --i; while (++i < n) { - pi = points[i]; - px.shift(); px.push(pi[0]); - py.shift(); py.push(pi[1]); - d3_svg_lineBasisBezier(path, px, py); - } - return path.join(""); -} - -// Closed B-spline interpolation; generates "C" commands. -function d3_svg_lineBasisClosed(points) { - var path, - i = -1, - n = points.length, - m = n + 4, - pi, - px = [], - py = []; - while (++i < 4) { - pi = points[i % n]; - px.push(pi[0]); - py.push(pi[1]); - } - path = [ - d3_svg_lineDot4(d3_svg_lineBasisBezier3, px), ",", - d3_svg_lineDot4(d3_svg_lineBasisBezier3, py) - ]; - --i; while (++i < m) { - pi = points[i % n]; - px.shift(); px.push(pi[0]); - py.shift(); py.push(pi[1]); - d3_svg_lineBasisBezier(path, px, py); - } - return path.join(""); -} - -function d3_svg_lineBundle(points, tension) { - var n = points.length - 1, - x0 = points[0][0], - y0 = points[0][1], - dx = points[n][0] - x0, - dy = points[n][1] - y0, - i = -1, - p, - t; - while (++i <= n) { - p = points[i]; - t = i / n; - p[0] = tension * p[0] + (1 - tension) * (x0 + t * dx); - p[1] = tension * p[1] + (1 - tension) * (y0 + t * dy); - } - return d3_svg_lineBasis(points); -} - -// Returns the dot product of the given four-element vectors. -function d3_svg_lineDot4(a, b) { - return a[0] * b[0] + a[1] * b[1] + a[2] * b[2] + a[3] * b[3]; -} - -// Matrix to transform basis (b-spline) control points to bezier -// control points. Derived from FvD 11.2.8. -var d3_svg_lineBasisBezier1 = [0, 2/3, 1/3, 0], - d3_svg_lineBasisBezier2 = [0, 1/3, 2/3, 0], - d3_svg_lineBasisBezier3 = [0, 1/6, 2/3, 1/6]; - -// Pushes a "C" Bézier curve onto the specified path array, given the -// two specified four-element arrays which define the control points. -function d3_svg_lineBasisBezier(path, x, y) { - path.push( - "C", d3_svg_lineDot4(d3_svg_lineBasisBezier1, x), - ",", d3_svg_lineDot4(d3_svg_lineBasisBezier1, y), - ",", d3_svg_lineDot4(d3_svg_lineBasisBezier2, x), - ",", d3_svg_lineDot4(d3_svg_lineBasisBezier2, y), - ",", d3_svg_lineDot4(d3_svg_lineBasisBezier3, x), - ",", d3_svg_lineDot4(d3_svg_lineBasisBezier3, y)); -} - -// Computes the slope from points p0 to p1. -function d3_svg_lineSlope(p0, p1) { - return (p1[1] - p0[1]) / (p1[0] - p0[0]); -} - -// Compute three-point differences for the given points. -// http://en.wikipedia.org/wiki/Cubic_Hermite_spline#Finite_difference -function d3_svg_lineFiniteDifferences(points) { - var i = 0, - j = points.length - 1, - m = [], - p0 = points[0], - p1 = points[1], - d = m[0] = d3_svg_lineSlope(p0, p1); - while (++i < j) { - m[i] = d + (d = d3_svg_lineSlope(p0 = p1, p1 = points[i + 1])); - } - m[i] = d; - return m; -} - -// Interpolates the given points using Fritsch-Carlson Monotone cubic Hermite -// interpolation. Returns an array of tangent vectors. For details, see -// http://en.wikipedia.org/wiki/Monotone_cubic_interpolation -function d3_svg_lineMonotoneTangents(points) { - var tangents = [], - d, - a, - b, - s, - m = d3_svg_lineFiniteDifferences(points), - i = -1, - j = points.length - 1; - - // The first two steps are done by computing finite-differences: - // 1. Compute the slopes of the secant lines between successive points. - // 2. Initialize the tangents at every point as the average of the secants. - - // Then, for each segment… - while (++i < j) { - d = d3_svg_lineSlope(points[i], points[i + 1]); - - // 3. If two successive yk = y{k + 1} are equal (i.e., d is zero), then set - // mk = m{k + 1} = 0 as the spline connecting these points must be flat to - // preserve monotonicity. Ignore step 4 and 5 for those k. - - if (Math.abs(d) < 1e-6) { - m[i] = m[i + 1] = 0; - } else { - // 4. Let ak = mk / dk and bk = m{k + 1} / dk. - a = m[i] / d; - b = m[i + 1] / d; - - // 5. Prevent overshoot and ensure monotonicity by restricting the - // magnitude of vector to a circle of radius 3. - s = a * a + b * b; - if (s > 9) { - s = d * 3 / Math.sqrt(s); - m[i] = s * a; - m[i + 1] = s * b; - } - } - } - - // Compute the normalized tangent vector from the slopes. Note that if x is - // not monotonic, it's possible that the slope will be infinite, so we protect - // against NaN by setting the coordinate to zero. - i = -1; while (++i <= j) { - s = (points[Math.min(j, i + 1)][0] - points[Math.max(0, i - 1)][0]) / (6 * (1 + m[i] * m[i])); - tangents.push([s || 0, m[i] * s || 0]); - } - - return tangents; -} - -function d3_svg_lineMonotone(points) { - return points.length < 3 - ? d3_svg_lineLinear(points) - : points[0] + d3_svg_lineHermite(points, d3_svg_lineMonotoneTangents(points)); -} -d3.svg.line.radial = function() { - var line = d3_svg_line(d3_svg_lineRadial); - line.radius = line.x, delete line.x; - line.angle = line.y, delete line.y; - return line; -}; - -function d3_svg_lineRadial(points) { - var point, - i = -1, - n = points.length, - r, - a; - while (++i < n) { - point = points[i]; - r = point[0]; - a = point[1] + d3_svg_arcOffset; - point[0] = r * Math.cos(a); - point[1] = r * Math.sin(a); - } - return points; -} -function d3_svg_area(projection) { - var x0 = d3_svg_lineX, - x1 = d3_svg_lineX, - y0 = 0, - y1 = d3_svg_lineY, - defined = d3_true, - interpolate = d3_svg_lineInterpolatorDefault, - i0 = d3_svg_lineLinear, - i1 = d3_svg_lineLinear, - L = "L", - tension = .7; - - function area(data) { - var segments = [], - points0 = [], - points1 = [], - i = -1, - n = data.length, - d, - fx0 = d3_functor(x0), - fy0 = d3_functor(y0), - fx1 = x0 === x1 ? function() { return x; } : d3_functor(x1), - fy1 = y0 === y1 ? function() { return y; } : d3_functor(y1), - x, - y; - - function segment() { - segments.push("M", i0(projection(points1), tension), - L, i1(projection(points0.reverse()), tension), - "Z"); - } - - while (++i < n) { - if (defined.call(this, d = data[i], i)) { - points0.push([x = +fx0.call(this, d, i), y = +fy0.call(this, d, i)]); - points1.push([+fx1.call(this, d, i), +fy1.call(this, d, i)]); - } else if (points0.length) { - segment(); - points0 = []; - points1 = []; - } - } - - if (points0.length) segment(); - - return segments.length ? segments.join("") : null; - } - - area.x = function(_) { - if (!arguments.length) return x1; - x0 = x1 = _; - return area; - }; - - area.x0 = function(_) { - if (!arguments.length) return x0; - x0 = _; - return area; - }; - - area.x1 = function(_) { - if (!arguments.length) return x1; - x1 = _; - return area; - }; - - area.y = function(_) { - if (!arguments.length) return y1; - y0 = y1 = _; - return area; - }; - - area.y0 = function(_) { - if (!arguments.length) return y0; - y0 = _; - return area; - }; - - area.y1 = function(_) { - if (!arguments.length) return y1; - y1 = _; - return area; - }; - - area.defined = function(_) { - if (!arguments.length) return defined; - defined = _; - return area; - }; - - area.interpolate = function(_) { - if (!arguments.length) return interpolate; - if (!d3_svg_lineInterpolators.has(_ += "")) _ = d3_svg_lineInterpolatorDefault; - i0 = d3_svg_lineInterpolators.get(interpolate = _); - i1 = i0.reverse || i0; - L = /-closed$/.test(_) ? "M" : "L"; - return area; - }; - - area.tension = function(_) { - if (!arguments.length) return tension; - tension = _; - return area; - }; - - return area; -} - -d3_svg_lineStepBefore.reverse = d3_svg_lineStepAfter; -d3_svg_lineStepAfter.reverse = d3_svg_lineStepBefore; - -d3.svg.area = function() { - return d3_svg_area(Object); -}; -d3.svg.area.radial = function() { - var area = d3_svg_area(d3_svg_lineRadial); - area.radius = area.x, delete area.x; - area.innerRadius = area.x0, delete area.x0; - area.outerRadius = area.x1, delete area.x1; - area.angle = area.y, delete area.y; - area.startAngle = area.y0, delete area.y0; - area.endAngle = area.y1, delete area.y1; - return area; -}; -d3.svg.chord = function() { - var source = d3_svg_chordSource, - target = d3_svg_chordTarget, - radius = d3_svg_chordRadius, - startAngle = d3_svg_arcStartAngle, - endAngle = d3_svg_arcEndAngle; - - // TODO Allow control point to be customized. - - function chord(d, i) { - var s = subgroup(this, source, d, i), - t = subgroup(this, target, d, i); - return "M" + s.p0 - + arc(s.r, s.p1, s.a1 - s.a0) + (equals(s, t) - ? curve(s.r, s.p1, s.r, s.p0) - : curve(s.r, s.p1, t.r, t.p0) - + arc(t.r, t.p1, t.a1 - t.a0) - + curve(t.r, t.p1, s.r, s.p0)) - + "Z"; - } - - function subgroup(self, f, d, i) { - var subgroup = f.call(self, d, i), - r = radius.call(self, subgroup, i), - a0 = startAngle.call(self, subgroup, i) + d3_svg_arcOffset, - a1 = endAngle.call(self, subgroup, i) + d3_svg_arcOffset; - return { - r: r, - a0: a0, - a1: a1, - p0: [r * Math.cos(a0), r * Math.sin(a0)], - p1: [r * Math.cos(a1), r * Math.sin(a1)] }; } - - function equals(a, b) { - return a.a0 == b.a0 && a.a1 == b.a1; - } - - function arc(r, p, a) { - return "A" + r + "," + r + " 0 " + +(a > Math.PI) + ",1 " + p; - } - - function curve(r0, p0, r1, p1) { - return "Q 0,0 " + p1; - } - - chord.radius = function(v) { - if (!arguments.length) return radius; - radius = d3_functor(v); - return chord; - }; - - chord.source = function(v) { - if (!arguments.length) return source; - source = d3_functor(v); - return chord; - }; - - chord.target = function(v) { - if (!arguments.length) return target; - target = d3_functor(v); - return chord; - }; - - chord.startAngle = function(v) { - if (!arguments.length) return startAngle; - startAngle = d3_functor(v); - return chord; - }; - - chord.endAngle = function(v) { - if (!arguments.length) return endAngle; - endAngle = d3_functor(v); - return chord; - }; - - return chord; -}; - -function d3_svg_chordSource(d) { - return d.source; -} - -function d3_svg_chordTarget(d) { - return d.target; -} - -function d3_svg_chordRadius(d) { - return d.radius; -} - -function d3_svg_chordStartAngle(d) { - return d.startAngle; -} - -function d3_svg_chordEndAngle(d) { - return d.endAngle; -} -d3.svg.diagonal = function() { - var source = d3_svg_chordSource, - target = d3_svg_chordTarget, - projection = d3_svg_diagonalProjection; - - function diagonal(d, i) { - var p0 = source.call(this, d, i), - p3 = target.call(this, d, i), - m = (p0.y + p3.y) / 2, - p = [p0, {x: p0.x, y: m}, {x: p3.x, y: m}, p3]; - p = p.map(projection); - return "M" + p[0] + "C" + p[1] + " " + p[2] + " " + p[3]; - } - - diagonal.source = function(x) { - if (!arguments.length) return source; - source = d3_functor(x); - return diagonal; - }; - - diagonal.target = function(x) { - if (!arguments.length) return target; - target = d3_functor(x); - return diagonal; - }; - - diagonal.projection = function(x) { - if (!arguments.length) return projection; - projection = x; - return diagonal; - }; - - return diagonal; -}; - -function d3_svg_diagonalProjection(d) { - return [d.x, d.y]; -} -d3.svg.diagonal.radial = function() { - var diagonal = d3.svg.diagonal(), - projection = d3_svg_diagonalProjection, - projection_ = diagonal.projection; - - diagonal.projection = function(x) { - return arguments.length - ? projection_(d3_svg_diagonalRadialProjection(projection = x)) - : projection; - }; - - return diagonal; -}; - -function d3_svg_diagonalRadialProjection(projection) { - return function() { - var d = projection.apply(this, arguments), - r = d[0], - a = d[1] + d3_svg_arcOffset; - return [r * Math.cos(a), r * Math.sin(a)]; - }; -} -d3.svg.mouse = d3.mouse; -d3.svg.touches = d3.touches; -d3.svg.symbol = function() { - var type = d3_svg_symbolType, - size = d3_svg_symbolSize; - - function symbol(d, i) { - return (d3_svg_symbols.get(type.call(this, d, i)) - || d3_svg_symbolCircle) - (size.call(this, d, i)); - } - - symbol.type = function(x) { - if (!arguments.length) return type; - type = d3_functor(x); - return symbol; - }; - - // size of symbol in square pixels - symbol.size = function(x) { - if (!arguments.length) return size; - size = d3_functor(x); - return symbol; - }; - - return symbol; -}; - -function d3_svg_symbolSize() { - return 64; -} - -function d3_svg_symbolType() { - return "circle"; -} - -function d3_svg_symbolCircle(size) { - var r = Math.sqrt(size / Math.PI); - return "M0," + r - + "A" + r + "," + r + " 0 1,1 0," + (-r) - + "A" + r + "," + r + " 0 1,1 0," + r - + "Z"; -} - -// TODO cross-diagonal? -var d3_svg_symbols = d3.map({ - "circle": d3_svg_symbolCircle, - "cross": function(size) { - var r = Math.sqrt(size / 5) / 2; - return "M" + -3 * r + "," + -r - + "H" + -r - + "V" + -3 * r - + "H" + r - + "V" + -r - + "H" + 3 * r - + "V" + r - + "H" + r - + "V" + 3 * r - + "H" + -r - + "V" + r - + "H" + -3 * r - + "Z"; - }, - "diamond": function(size) { - var ry = Math.sqrt(size / (2 * d3_svg_symbolTan30)), - rx = ry * d3_svg_symbolTan30; - return "M0," + -ry - + "L" + rx + ",0" - + " 0," + ry - + " " + -rx + ",0" - + "Z"; - }, - "square": function(size) { - var r = Math.sqrt(size) / 2; - return "M" + -r + "," + -r - + "L" + r + "," + -r - + " " + r + "," + r - + " " + -r + "," + r - + "Z"; - }, - "triangle-down": function(size) { - var rx = Math.sqrt(size / d3_svg_symbolSqrt3), - ry = rx * d3_svg_symbolSqrt3 / 2; - return "M0," + ry - + "L" + rx +"," + -ry - + " " + -rx + "," + -ry - + "Z"; - }, - "triangle-up": function(size) { - var rx = Math.sqrt(size / d3_svg_symbolSqrt3), - ry = rx * d3_svg_symbolSqrt3 / 2; - return "M0," + -ry - + "L" + rx +"," + ry - + " " + -rx + "," + ry - + "Z"; - } -}); - -d3.svg.symbolTypes = d3_svg_symbols.keys(); - -var d3_svg_symbolSqrt3 = Math.sqrt(3), - d3_svg_symbolTan30 = Math.tan(30 * Math.PI / 180); -d3.svg.axis = function() { - var scale = d3.scale.linear(), - orient = "bottom", - tickMajorSize = 6, - tickMinorSize = 6, - tickEndSize = 6, - tickPadding = 3, - tickArguments_ = [10], - tickValues = null, - tickFormat_, - tickSubdivide = 0; - - function axis(g) { - g.each(function() { - var g = d3.select(this); - - // Ticks, or domain values for ordinal scales. - var ticks = tickValues == null ? (scale.ticks ? scale.ticks.apply(scale, tickArguments_) : scale.domain()) : tickValues, - tickFormat = tickFormat_ == null ? (scale.tickFormat ? scale.tickFormat.apply(scale, tickArguments_) : String) : tickFormat_; - - // Minor ticks. - var subticks = d3_svg_axisSubdivide(scale, ticks, tickSubdivide), - subtick = g.selectAll(".minor").data(subticks, String), - subtickEnter = subtick.enter().insert("line", "g").attr("class", "tick minor").style("opacity", 1e-6), - subtickExit = d3.transition(subtick.exit()).style("opacity", 1e-6).remove(), - subtickUpdate = d3.transition(subtick).style("opacity", 1); - - // Major ticks. - var tick = g.selectAll("g").data(ticks, String), - tickEnter = tick.enter().insert("g", "path").style("opacity", 1e-6), - tickExit = d3.transition(tick.exit()).style("opacity", 1e-6).remove(), - tickUpdate = d3.transition(tick).style("opacity", 1), - tickTransform; - - // Domain. - var range = d3_scaleRange(scale), - path = g.selectAll(".domain").data([0]), - pathEnter = path.enter().append("path").attr("class", "domain"), - pathUpdate = d3.transition(path); - - // Stash a snapshot of the new scale, and retrieve the old snapshot. - var scale1 = scale.copy(), - scale0 = this.__chart__ || scale1; - this.__chart__ = scale1; - - tickEnter.append("line").attr("class", "tick"); - tickEnter.append("text"); - tickUpdate.select("text").text(tickFormat); - - switch (orient) { - case "bottom": { - tickTransform = d3_svg_axisX; - subtickEnter.attr("y2", tickMinorSize); - subtickUpdate.attr("x2", 0).attr("y2", tickMinorSize); - tickEnter.select("line").attr("y2", tickMajorSize); - tickEnter.select("text").attr("y", Math.max(tickMajorSize, 0) + tickPadding); - tickUpdate.select("line").attr("x2", 0).attr("y2", tickMajorSize); - tickUpdate.select("text").attr("x", 0).attr("y", Math.max(tickMajorSize, 0) + tickPadding).attr("dy", ".71em").attr("text-anchor", "middle"); - pathUpdate.attr("d", "M" + range[0] + "," + tickEndSize + "V0H" + range[1] + "V" + tickEndSize); - break; - } - case "top": { - tickTransform = d3_svg_axisX; - subtickEnter.attr("y2", -tickMinorSize); - subtickUpdate.attr("x2", 0).attr("y2", -tickMinorSize); - tickEnter.select("line").attr("y2", -tickMajorSize); - tickEnter.select("text").attr("y", -(Math.max(tickMajorSize, 0) + tickPadding)); - tickUpdate.select("line").attr("x2", 0).attr("y2", -tickMajorSize); - tickUpdate.select("text").attr("x", 0).attr("y", -(Math.max(tickMajorSize, 0) + tickPadding)).attr("dy", "0em").attr("text-anchor", "middle"); - pathUpdate.attr("d", "M" + range[0] + "," + -tickEndSize + "V0H" + range[1] + "V" + -tickEndSize); - break; - } - case "left": { - tickTransform = d3_svg_axisY; - subtickEnter.attr("x2", -tickMinorSize); - subtickUpdate.attr("x2", -tickMinorSize).attr("y2", 0); - tickEnter.select("line").attr("x2", -tickMajorSize); - tickEnter.select("text").attr("x", -(Math.max(tickMajorSize, 0) + tickPadding)); - tickUpdate.select("line").attr("x2", -tickMajorSize).attr("y2", 0); - tickUpdate.select("text").attr("x", -(Math.max(tickMajorSize, 0) + tickPadding)).attr("y", 0).attr("dy", ".32em").attr("text-anchor", "end"); - pathUpdate.attr("d", "M" + -tickEndSize + "," + range[0] + "H0V" + range[1] + "H" + -tickEndSize); - break; - } - case "right": { - tickTransform = d3_svg_axisY; - subtickEnter.attr("x2", tickMinorSize); - subtickUpdate.attr("x2", tickMinorSize).attr("y2", 0); - tickEnter.select("line").attr("x2", tickMajorSize); - tickEnter.select("text").attr("x", Math.max(tickMajorSize, 0) + tickPadding); - tickUpdate.select("line").attr("x2", tickMajorSize).attr("y2", 0); - tickUpdate.select("text").attr("x", Math.max(tickMajorSize, 0) + tickPadding).attr("y", 0).attr("dy", ".32em").attr("text-anchor", "start"); - pathUpdate.attr("d", "M" + tickEndSize + "," + range[0] + "H0V" + range[1] + "H" + tickEndSize); - break; - } - } - - // For quantitative scales: - // - enter new ticks from the old scale - // - exit old ticks to the new scale - if (scale.ticks) { - tickEnter.call(tickTransform, scale0); - tickUpdate.call(tickTransform, scale1); - tickExit.call(tickTransform, scale1); - subtickEnter.call(tickTransform, scale0); - subtickUpdate.call(tickTransform, scale1); - subtickExit.call(tickTransform, scale1); - } - - // For ordinal scales: - // - any entering ticks are undefined in the old scale - // - any exiting ticks are undefined in the new scale - // Therefore, we only need to transition updating ticks. - else { - var dx = scale1.rangeBand() / 2, x = function(d) { return scale1(d) + dx; }; - tickEnter.call(tickTransform, x); - tickUpdate.call(tickTransform, x); + function d3_selection_onFilter(listener, argumentz) { + var l = d3_selection_onListener(listener, argumentz); + return function(e) { + var target = this, related = e.relatedTarget; + if (!related || related !== target && !(related.compareDocumentPosition(target) & 8)) { + l.call(target, e); } + }; + } + d3_selectionPrototype.each = function(callback) { + return d3_selection_each(this, function(node, i, j) { + callback.call(node, node.__data__, i, j); }); + }; + function d3_selection_each(groups, callback) { + for (var j = 0, m = groups.length; j < m; j++) { + for (var group = groups[j], i = 0, n = group.length, node; i < n; i++) { + if (node = group[i]) callback(node, i, j); + } + } + return groups; } - - axis.scale = function(x) { - if (!arguments.length) return scale; - scale = x; - return axis; + d3_selectionPrototype.call = function(callback) { + var args = d3_array(arguments); + callback.apply(args[0] = this, args); + return this; }; - - axis.orient = function(x) { - if (!arguments.length) return orient; - orient = x; - return axis; + d3_selectionPrototype.empty = function() { + return !this.node(); }; - - axis.ticks = function() { - if (!arguments.length) return tickArguments_; - tickArguments_ = arguments; - return axis; - }; - - axis.tickValues = function(x) { - if (!arguments.length) return tickValues; - tickValues = x; - return axis; - }; - - axis.tickFormat = function(x) { - if (!arguments.length) return tickFormat_; - tickFormat_ = x; - return axis; - }; - - axis.tickSize = function(x, y, z) { - if (!arguments.length) return tickMajorSize; - var n = arguments.length - 1; - tickMajorSize = +x; - tickMinorSize = n > 1 ? +y : tickMajorSize; - tickEndSize = n > 0 ? +arguments[n] : tickMajorSize; - return axis; - }; - - axis.tickPadding = function(x) { - if (!arguments.length) return tickPadding; - tickPadding = +x; - return axis; - }; - - axis.tickSubdivide = function(x) { - if (!arguments.length) return tickSubdivide; - tickSubdivide = +x; - return axis; - }; - - return axis; -}; - -function d3_svg_axisX(selection, x) { - selection.attr("transform", function(d) { return "translate(" + x(d) + ",0)"; }); -} - -function d3_svg_axisY(selection, y) { - selection.attr("transform", function(d) { return "translate(0," + y(d) + ")"; }); -} - -function d3_svg_axisSubdivide(scale, ticks, m) { - subticks = []; - if (m && ticks.length > 1) { - var extent = d3_scaleExtent(scale.domain()), - subticks, - i = -1, - n = ticks.length, - d = (ticks[1] - ticks[0]) / ++m, - j, - v; - while (++i < n) { - for (j = m; --j > 0;) { - if ((v = +ticks[i] - j * d) >= extent[0]) { - subticks.push(v); - } + d3_selectionPrototype.node = function() { + for (var j = 0, m = this.length; j < m; j++) { + for (var group = this[j], i = 0, n = group.length; i < n; i++) { + var node = group[i]; + if (node) return node; } } - for (--i, j = 0; ++j < m && (v = +ticks[i] + j * d) < extent[1];) { - subticks.push(v); - } + return null; + }; + function d3_selection_enter(selection) { + d3_arraySubclass(selection, d3_selection_enterPrototype); + return selection; } - return subticks; -} -d3.svg.brush = function() { - var event = d3_eventDispatch(brush, "brushstart", "brush", "brushend"), - x = null, // x-scale, optional - y = null, // y-scale, optional - resizes = d3_svg_brushResizes[0], - extent = [[0, 0], [0, 0]], // [x0, y0], [x1, y1], in pixels (integers) - extentDomain; // the extent in data space, lazily created - - function brush(g) { - g.each(function() { - var g = d3.select(this), - bg = g.selectAll(".background").data([0]), - fg = g.selectAll(".extent").data([0]), - tz = g.selectAll(".resize").data(resizes, String), - e; - - // Prepare the brush container for events. - g - .style("pointer-events", "all") - .on("mousedown.brush", brushstart) - .on("touchstart.brush", brushstart); - - // An invisible, mouseable area for starting a new brush. - bg.enter().append("rect") - .attr("class", "background") - .style("visibility", "hidden") - .style("cursor", "crosshair"); - - // The visible brush extent; style this as you like! - fg.enter().append("rect") - .attr("class", "extent") - .style("cursor", "move"); - - // More invisible rects for resizing the extent. - tz.enter().append("g") - .attr("class", function(d) { return "resize " + d; }) - .style("cursor", function(d) { return d3_svg_brushCursor[d]; }) - .append("rect") - .attr("x", function(d) { return /[ew]$/.test(d) ? -3 : null; }) - .attr("y", function(d) { return /^[ns]/.test(d) ? -3 : null; }) - .attr("width", 6) - .attr("height", 6) - .style("visibility", "hidden"); - - // Show or hide the resizers. - tz.style("display", brush.empty() ? "none" : null); - - // Remove any superfluous resizers. - tz.exit().remove(); - - // Initialize the background to fill the defined range. - // If the range isn't defined, you can post-process. - if (x) { - e = d3_scaleRange(x); - bg.attr("x", e[0]).attr("width", e[1] - e[0]); - redrawX(g); - } - if (y) { - e = d3_scaleRange(y); - bg.attr("y", e[0]).attr("height", e[1] - e[0]); - redrawY(g); - } - redraw(g); - }); - } - - function redraw(g) { - g.selectAll(".resize").attr("transform", function(d) { - return "translate(" + extent[+/e$/.test(d)][0] + "," + extent[+/^s/.test(d)][1] + ")"; - }); - } - - function redrawX(g) { - g.select(".extent").attr("x", extent[0][0]); - g.selectAll(".extent,.n>rect,.s>rect").attr("width", extent[1][0] - extent[0][0]); - } - - function redrawY(g) { - g.select(".extent").attr("y", extent[0][1]); - g.selectAll(".extent,.e>rect,.w>rect").attr("height", extent[1][1] - extent[0][1]); - } - - function brushstart() { - var target = this, - eventTarget = d3.select(d3.event.target), - event_ = event.of(target, arguments), - g = d3.select(target), - resizing = eventTarget.datum(), - resizingX = !/^(n|s)$/.test(resizing) && x, - resizingY = !/^(e|w)$/.test(resizing) && y, - dragging = eventTarget.classed("extent"), - center, - origin = mouse(), - offset; - - var w = d3.select(window) - .on("mousemove.brush", brushmove) - .on("mouseup.brush", brushend) - .on("touchmove.brush", brushmove) - .on("touchend.brush", brushend) - .on("keydown.brush", keydown) - .on("keyup.brush", keyup); - - // If the extent was clicked on, drag rather than brush; - // store the point between the mouse and extent origin instead. - if (dragging) { - origin[0] = extent[0][0] - origin[0]; - origin[1] = extent[0][1] - origin[1]; - } - - // If a resizer was clicked on, record which side is to be resized. - // Also, set the origin to the opposite side. - else if (resizing) { - var ex = +/w$/.test(resizing), - ey = +/^n/.test(resizing); - offset = [extent[1 - ex][0] - origin[0], extent[1 - ey][1] - origin[1]]; - origin[0] = extent[ex][0]; - origin[1] = extent[ey][1]; - } - - // If the ALT key is down when starting a brush, the center is at the mouse. - else if (d3.event.altKey) center = origin.slice(); - - // Propagate the active cursor to the body for the drag duration. - g.style("pointer-events", "none").selectAll(".resize").style("display", null); - d3.select("body").style("cursor", eventTarget.style("cursor")); - - // Notify listeners. - event_({type: "brushstart"}); - brushmove(); - d3_eventCancel(); - - function mouse() { - var touches = d3.event.changedTouches; - return touches ? d3.touches(target, touches)[0] : d3.mouse(target); - } - - function keydown() { - if (d3.event.keyCode == 32) { - if (!dragging) { - center = null; - origin[0] -= extent[1][0]; - origin[1] -= extent[1][1]; - dragging = 2; - } - d3_eventCancel(); - } - } - - function keyup() { - if (d3.event.keyCode == 32 && dragging == 2) { - origin[0] += extent[1][0]; - origin[1] += extent[1][1]; - dragging = 0; - d3_eventCancel(); - } - } - - function brushmove() { - var point = mouse(), - moved = false; - - // Preserve the offset for thick resizers. - if (offset) { - point[0] += offset[0]; - point[1] += offset[1]; - } - - if (!dragging) { - - // If needed, determine the center from the current extent. - if (d3.event.altKey) { - if (!center) center = [(extent[0][0] + extent[1][0]) / 2, (extent[0][1] + extent[1][1]) / 2]; - - // Update the origin, for when the ALT key is released. - origin[0] = extent[+(point[0] < center[0])][0]; - origin[1] = extent[+(point[1] < center[1])][1]; - } - - // When the ALT key is released, we clear the center. - else center = null; - } - - // Update the brush extent for each dimension. - if (resizingX && move1(point, x, 0)) { - redrawX(g); - moved = true; - } - if (resizingY && move1(point, y, 1)) { - redrawY(g); - moved = true; - } - - // Final redraw and notify listeners. - if (moved) { - redraw(g); - event_({type: "brush", mode: dragging ? "move" : "resize"}); - } - } - - function move1(point, scale, i) { - var range = d3_scaleRange(scale), - r0 = range[0], - r1 = range[1], - position = origin[i], - size = extent[1][i] - extent[0][i], - min, - max; - - // When dragging, reduce the range by the extent size and position. - if (dragging) { - r0 -= position; - r1 -= size + position; - } - - // Clamp the point so that the extent fits within the range extent. - min = Math.max(r0, Math.min(r1, point[i])); - - // Compute the new extent bounds. - if (dragging) { - max = (min += position) + size; - } else { - - // If the ALT key is pressed, then preserve the center of the extent. - if (center) position = Math.max(r0, Math.min(r1, 2 * center[i] - min)); - - // Compute the min and max of the position and point. - if (position < min) { - max = min; - min = position; + var d3_selection_enterPrototype = []; + d3.selection.enter = d3_selection_enter; + d3.selection.enter.prototype = d3_selection_enterPrototype; + d3_selection_enterPrototype.append = d3_selectionPrototype.append; + d3_selection_enterPrototype.insert = d3_selectionPrototype.insert; + d3_selection_enterPrototype.empty = d3_selectionPrototype.empty; + d3_selection_enterPrototype.node = d3_selectionPrototype.node; + d3_selection_enterPrototype.select = function(selector) { + var subgroups = [], subgroup, subnode, upgroup, group, node; + for (var j = -1, m = this.length; ++j < m; ) { + upgroup = (group = this[j]).update; + subgroups.push(subgroup = []); + subgroup.parentNode = group.parentNode; + for (var i = -1, n = group.length; ++i < n; ) { + if (node = group[i]) { + subgroup.push(upgroup[i] = subnode = selector.call(group.parentNode, node.__data__, i)); + subnode.__data__ = node.__data__; } else { - max = position; + subgroup.push(null); } } - - // Update the stored bounds. - if (extent[0][i] !== min || extent[1][i] !== max) { - extentDomain = null; - extent[0][i] = min; - extent[1][i] = max; - return true; + } + return d3_selection(subgroups); + }; + d3_selectionPrototype.transition = function() { + var id = d3_transitionInheritId || ++d3_transitionId, subgroups = [], subgroup, node, transition = Object.create(d3_transitionInherit); + transition.time = Date.now(); + for (var j = -1, m = this.length; ++j < m; ) { + subgroups.push(subgroup = []); + for (var group = this[j], i = -1, n = group.length; ++i < n; ) { + if (node = group[i]) d3_transitionNode(node, i, id, transition); + subgroup.push(node); } } - - function brushend() { - brushmove(); - - // reset the cursor styles - g.style("pointer-events", "all").selectAll(".resize").style("display", brush.empty() ? "none" : null); - d3.select("body").style("cursor", null); - - w .on("mousemove.brush", null) - .on("mouseup.brush", null) - .on("touchmove.brush", null) - .on("touchend.brush", null) - .on("keydown.brush", null) - .on("keyup.brush", null); - - event_({type: "brushend"}); + return d3_transition(subgroups, id); + }; + var d3_selectionRoot = d3_selection([ [ d3_document ] ]); + d3_selectionRoot[0].parentNode = d3_selectRoot; + d3.select = function(selector) { + return typeof selector === "string" ? d3_selectionRoot.select(selector) : d3_selection([ [ selector ] ]); + }; + d3.selectAll = function(selector) { + return typeof selector === "string" ? d3_selectionRoot.selectAll(selector) : d3_selection([ d3_array(selector) ]); + }; + d3.behavior.zoom = function() { + var translate = [ 0, 0 ], translate0, scale = 1, scale0, scaleExtent = d3_behavior_zoomInfinity, event = d3_eventDispatch(zoom, "zoom"), x0, x1, y0, y1, touchtime; + function zoom() { + this.on("mousedown.zoom", mousedown).on("mousemove.zoom", mousemove).on(d3_behavior_zoomWheel + ".zoom", mousewheel).on("dblclick.zoom", dblclick).on("touchstart.zoom", touchstart).on("touchmove.zoom", touchmove).on("touchend.zoom", touchstart); + } + zoom.translate = function(x) { + if (!arguments.length) return translate; + translate = x.map(Number); + rescale(); + return zoom; + }; + zoom.scale = function(x) { + if (!arguments.length) return scale; + scale = +x; + rescale(); + return zoom; + }; + zoom.scaleExtent = function(x) { + if (!arguments.length) return scaleExtent; + scaleExtent = x == null ? d3_behavior_zoomInfinity : x.map(Number); + return zoom; + }; + zoom.x = function(z) { + if (!arguments.length) return x1; + x1 = z; + x0 = z.copy(); + translate = [ 0, 0 ]; + scale = 1; + return zoom; + }; + zoom.y = function(z) { + if (!arguments.length) return y1; + y1 = z; + y0 = z.copy(); + translate = [ 0, 0 ]; + scale = 1; + return zoom; + }; + function location(p) { + return [ (p[0] - translate[0]) / scale, (p[1] - translate[1]) / scale ]; + } + function point(l) { + return [ l[0] * scale + translate[0], l[1] * scale + translate[1] ]; + } + function scaleTo(s) { + scale = Math.max(scaleExtent[0], Math.min(scaleExtent[1], s)); + } + function translateTo(p, l) { + l = point(l); + translate[0] += p[0] - l[0]; + translate[1] += p[1] - l[1]; + } + function rescale() { + if (x1) x1.domain(x0.range().map(function(x) { + return (x - translate[0]) / scale; + }).map(x0.invert)); + if (y1) y1.domain(y0.range().map(function(y) { + return (y - translate[1]) / scale; + }).map(y0.invert)); + } + function dispatch(event) { + rescale(); + d3.event.preventDefault(); + event({ + type: "zoom", + scale: scale, + translate: translate + }); + } + function mousedown() { + var target = this, event_ = event.of(target, arguments), eventTarget = d3.event.target, moved = 0, w = d3.select(d3_window).on("mousemove.zoom", mousemove).on("mouseup.zoom", mouseup), l = location(d3.mouse(target)); + d3_window.focus(); d3_eventCancel(); - } - } - - brush.x = function(z) { - if (!arguments.length) return x; - x = z; - resizes = d3_svg_brushResizes[!x << 1 | !y]; // fore! - return brush; - }; - - brush.y = function(z) { - if (!arguments.length) return y; - y = z; - resizes = d3_svg_brushResizes[!x << 1 | !y]; // fore! - return brush; - }; - - brush.extent = function(z) { - var x0, x1, y0, y1, t; - - // Invert the pixel extent to data-space. - if (!arguments.length) { - z = extentDomain || extent; - if (x) { - x0 = z[0][0], x1 = z[1][0]; - if (!extentDomain) { - x0 = extent[0][0], x1 = extent[1][0]; - if (x.invert) x0 = x.invert(x0), x1 = x.invert(x1); - if (x1 < x0) t = x0, x0 = x1, x1 = t; - } + function mousemove() { + moved = 1; + translateTo(d3.mouse(target), l); + dispatch(event_); } - if (y) { - y0 = z[0][1], y1 = z[1][1]; - if (!extentDomain) { - y0 = extent[0][1], y1 = extent[1][1]; - if (y.invert) y0 = y.invert(y0), y1 = y.invert(y1); - if (y1 < y0) t = y0, y0 = y1, y1 = t; - } + function mouseup() { + if (moved) d3_eventCancel(); + w.on("mousemove.zoom", null).on("mouseup.zoom", null); + if (moved && d3.event.target === eventTarget) w.on("click.zoom", click, true); } - return x && y ? [[x0, y0], [x1, y1]] : x ? [x0, x1] : y && [y0, y1]; - } - - // Scale the data-space extent to pixels. - extentDomain = [[0, 0], [0, 0]]; - if (x) { - x0 = z[0], x1 = z[1]; - if (y) x0 = x0[0], x1 = x1[0]; - extentDomain[0][0] = x0, extentDomain[1][0] = x1; - if (x.invert) x0 = x(x0), x1 = x(x1); - if (x1 < x0) t = x0, x0 = x1, x1 = t; - extent[0][0] = x0 | 0, extent[1][0] = x1 | 0; - } - if (y) { - y0 = z[0], y1 = z[1]; - if (x) y0 = y0[1], y1 = y1[1]; - extentDomain[0][1] = y0, extentDomain[1][1] = y1; - if (y.invert) y0 = y(y0), y1 = y(y1); - if (y1 < y0) t = y0, y0 = y1, y1 = t; - extent[0][1] = y0 | 0, extent[1][1] = y1 | 0; - } - - return brush; - }; - - brush.clear = function() { - extentDomain = null; - extent[0][0] = - extent[0][1] = - extent[1][0] = - extent[1][1] = 0; - return brush; - }; - - brush.empty = function() { - return (x && extent[0][0] === extent[1][0]) - || (y && extent[0][1] === extent[1][1]); - }; - - return d3.rebind(brush, event, "on"); -}; - -var d3_svg_brushCursor = { - n: "ns-resize", - e: "ew-resize", - s: "ns-resize", - w: "ew-resize", - nw: "nwse-resize", - ne: "nesw-resize", - se: "nwse-resize", - sw: "nesw-resize" -}; - -var d3_svg_brushResizes = [ - ["n", "e", "s", "w", "nw", "ne", "se", "sw"], - ["e", "w"], - ["n", "s"], - [] -]; -d3.behavior = {}; -// TODO Track touch points by identifier. - -d3.behavior.drag = function() { - var event = d3_eventDispatch(drag, "drag", "dragstart", "dragend"), - origin = null; - - function drag() { - this.on("mousedown.drag", mousedown) - .on("touchstart.drag", mousedown); - } - - function mousedown() { - var target = this, - event_ = event.of(target, arguments), - eventTarget = d3.event.target, - offset, - origin_ = point(), - moved = 0; - - var w = d3.select(window) - .on("mousemove.drag", dragmove) - .on("touchmove.drag", dragmove) - .on("mouseup.drag", dragend, true) - .on("touchend.drag", dragend, true); - - if (origin) { - offset = origin.apply(target, arguments); - offset = [offset.x - origin_[0], offset.y - origin_[1]]; - } else { - offset = [0, 0]; - } - - d3_eventCancel(); - event_({type: "dragstart"}); - - function point() { - var p = target.parentNode, - t = d3.event.changedTouches; - return t ? d3.touches(p, t)[0] : d3.mouse(p); - } - - function dragmove() { - if (!target.parentNode) return dragend(); // target removed from DOM - - var p = point(), - dx = p[0] - origin_[0], - dy = p[1] - origin_[1]; - - moved |= dx | dy; - origin_ = p; - d3_eventCancel(); - - event_({type: "drag", x: p[0] + offset[0], y: p[1] + offset[1], dx: dx, dy: dy}); - } - - function dragend() { - event_({type: "dragend"}); - - // if moved, prevent the mouseup (and possibly click) from propagating - if (moved) { + function click() { d3_eventCancel(); - if (d3.event.target === eventTarget) w.on("click.drag", click, true); + w.on("click.zoom", null); } - - w .on("mousemove.drag", null) - .on("touchmove.drag", null) - .on("mouseup.drag", null) - .on("touchend.drag", null); } - - // prevent the subsequent click from propagating (e.g., for anchors) - function click() { - d3_eventCancel(); - w.on("click.drag", null); + function mousewheel() { + if (!translate0) translate0 = location(d3.mouse(this)); + scaleTo(Math.pow(2, d3_behavior_zoomDelta() * .002) * scale); + translateTo(d3.mouse(this), translate0); + dispatch(event.of(this, arguments)); } - } - - drag.origin = function(x) { - if (!arguments.length) return origin; - origin = x; - return drag; - }; - - return d3.rebind(drag, event, "on"); -}; -d3.behavior.zoom = function() { - var translate = [0, 0], - translate0, // translate when we started zooming (to avoid drift) - scale = 1, - scale0, // scale when we started touching - scaleExtent = d3_behavior_zoomInfinity, - event = d3_eventDispatch(zoom, "zoom"), - x0, - x1, - y0, - y1, - touchtime; // time of last touchstart (to detect double-tap) - - function zoom() { - this - .on("mousedown.zoom", mousedown) - .on("mousewheel.zoom", mousewheel) - .on("mousemove.zoom", mousemove) - .on("DOMMouseScroll.zoom", mousewheel) - .on("dblclick.zoom", dblclick) - .on("touchstart.zoom", touchstart) - .on("touchmove.zoom", touchmove) - .on("touchend.zoom", touchstart); - } - - zoom.translate = function(x) { - if (!arguments.length) return translate; - translate = x.map(Number); - return zoom; - }; - - zoom.scale = function(x) { - if (!arguments.length) return scale; - scale = +x; - return zoom; - }; - - zoom.scaleExtent = function(x) { - if (!arguments.length) return scaleExtent; - scaleExtent = x == null ? d3_behavior_zoomInfinity : x.map(Number); - return zoom; - }; - - zoom.x = function(z) { - if (!arguments.length) return x1; - x1 = z; - x0 = z.copy(); - return zoom; - }; - - zoom.y = function(z) { - if (!arguments.length) return y1; - y1 = z; - y0 = z.copy(); - return zoom; - }; - - function location(p) { - return [(p[0] - translate[0]) / scale, (p[1] - translate[1]) / scale]; - } - - function point(l) { - return [l[0] * scale + translate[0], l[1] * scale + translate[1]]; - } - - function scaleTo(s) { - scale = Math.max(scaleExtent[0], Math.min(scaleExtent[1], s)); - } - - function translateTo(p, l) { - l = point(l); - translate[0] += p[0] - l[0]; - translate[1] += p[1] - l[1]; - } - - function dispatch(event) { - if (x1) x1.domain(x0.range().map(function(x) { return (x - translate[0]) / scale; }).map(x0.invert)); - if (y1) y1.domain(y0.range().map(function(y) { return (y - translate[1]) / scale; }).map(y0.invert)); - d3.event.preventDefault(); - event({type: "zoom", scale: scale, translate: translate}); - } - - function mousedown() { - var target = this, - event_ = event.of(target, arguments), - eventTarget = d3.event.target, - moved = 0, - w = d3.select(window).on("mousemove.zoom", mousemove).on("mouseup.zoom", mouseup), - l = location(d3.mouse(target)); - - window.focus(); - d3_eventCancel(); - function mousemove() { - moved = 1; - translateTo(d3.mouse(target), l); - dispatch(event_); + translate0 = null; } - - function mouseup() { - if (moved) d3_eventCancel(); - w.on("mousemove.zoom", null).on("mouseup.zoom", null); - if (moved && d3.event.target === eventTarget) w.on("click.zoom", click, true); - } - - function click() { - d3_eventCancel(); - w.on("click.zoom", null); - } - } - - function mousewheel() { - if (!translate0) translate0 = location(d3.mouse(this)); - scaleTo(Math.pow(2, d3_behavior_zoomDelta() * .002) * scale); - translateTo(d3.mouse(this), translate0); - dispatch(event.of(this, arguments)); - } - - function mousemove() { - translate0 = null; - } - - function dblclick() { - var p = d3.mouse(this), l = location(p); - scaleTo(d3.event.shiftKey ? scale / 2 : scale * 2); - translateTo(p, l); - dispatch(event.of(this, arguments)); - } - - function touchstart() { - var touches = d3.touches(this), - now = Date.now(); - - scale0 = scale; - translate0 = {}; - touches.forEach(function(t) { translate0[t.identifier] = location(t); }); - d3_eventCancel(); - - if ((touches.length === 1) && (now - touchtime < 500)) { // dbltap - var p = touches[0], l = location(touches[0]); - scaleTo(scale * 2); + function dblclick() { + var p = d3.mouse(this), l = location(p), k = Math.log(scale) / Math.LN2; + scaleTo(Math.pow(2, d3.event.shiftKey ? Math.ceil(k) - 1 : Math.floor(k) + 1)); translateTo(p, l); dispatch(event.of(this, arguments)); } - touchtime = now; - } - - function touchmove() { - var touches = d3.touches(this), - p0 = touches[0], - l0 = translate0[p0.identifier]; - if (p1 = touches[1]) { - var p1, l1 = translate0[p1.identifier]; - p0 = [(p0[0] + p1[0]) / 2, (p0[1] + p1[1]) / 2]; - l0 = [(l0[0] + l1[0]) / 2, (l0[1] + l1[1]) / 2]; - scaleTo(d3.event.scale * scale0); - } - translateTo(p0, l0); - dispatch(event.of(this, arguments)); - } - - return d3.rebind(zoom, event, "on"); -}; - -var d3_behavior_zoomDiv, // for interpreting mousewheel events - d3_behavior_zoomInfinity = [0, Infinity]; // default scale extent - -function d3_behavior_zoomDelta() { - - // mousewheel events are totally broken! - // https://bugs.webkit.org/show_bug.cgi?id=40441 - // not only that, but Chrome and Safari differ in re. to acceleration! - if (!d3_behavior_zoomDiv) { - d3_behavior_zoomDiv = d3.select("body").append("div") - .style("visibility", "hidden") - .style("top", 0) - .style("height", 0) - .style("width", 0) - .style("overflow-y", "scroll") - .append("div") - .style("height", "2000px") - .node().parentNode; - } - - var e = d3.event, delta; - try { - d3_behavior_zoomDiv.scrollTop = 1000; - d3_behavior_zoomDiv.dispatchEvent(e); - delta = 1000 - d3_behavior_zoomDiv.scrollTop; - } catch (error) { - delta = e.wheelDelta || (-e.detail * 5); - } - - return delta; -} -d3.layout = {}; -// Implements hierarchical edge bundling using Holten's algorithm. For each -// input link, a path is computed that travels through the tree, up the parent -// hierarchy to the least common ancestor, and then back down to the destination -// node. Each path is simply an array of nodes. -d3.layout.bundle = function() { - return function(links) { - var paths = [], - i = -1, - n = links.length; - while (++i < n) paths.push(d3_layout_bundlePath(links[i])); - return paths; - }; -}; - -function d3_layout_bundlePath(link) { - var start = link.source, - end = link.target, - lca = d3_layout_bundleLeastCommonAncestor(start, end), - points = [start]; - while (start !== lca) { - start = start.parent; - points.push(start); - } - var k = points.length; - while (end !== lca) { - points.splice(k, 0, end); - end = end.parent; - } - return points; -} - -function d3_layout_bundleAncestors(node) { - var ancestors = [], - parent = node.parent; - while (parent != null) { - ancestors.push(node); - node = parent; - parent = parent.parent; - } - ancestors.push(node); - return ancestors; -} - -function d3_layout_bundleLeastCommonAncestor(a, b) { - if (a === b) return a; - var aNodes = d3_layout_bundleAncestors(a), - bNodes = d3_layout_bundleAncestors(b), - aNode = aNodes.pop(), - bNode = bNodes.pop(), - sharedNode = null; - while (aNode === bNode) { - sharedNode = aNode; - aNode = aNodes.pop(); - bNode = bNodes.pop(); - } - return sharedNode; -} -d3.layout.chord = function() { - var chord = {}, - chords, - groups, - matrix, - n, - padding = 0, - sortGroups, - sortSubgroups, - sortChords; - - function relayout() { - var subgroups = {}, - groupSums = [], - groupIndex = d3.range(n), - subgroupIndex = [], - k, - x, - x0, - i, - j; - - chords = []; - groups = []; - - // Compute the sum. - k = 0, i = -1; while (++i < n) { - x = 0, j = -1; while (++j < n) { - x += matrix[i][j]; - } - groupSums.push(x); - subgroupIndex.push(d3.range(n)); - k += x; - } - - // Sort groups… - if (sortGroups) { - groupIndex.sort(function(a, b) { - return sortGroups(groupSums[a], groupSums[b]); + function touchstart() { + var touches = d3.touches(this), now = Date.now(); + scale0 = scale; + translate0 = {}; + touches.forEach(function(t) { + translate0[t.identifier] = location(t); }); - } - - // Sort subgroups… - if (sortSubgroups) { - subgroupIndex.forEach(function(d, i) { - d.sort(function(a, b) { - return sortSubgroups(matrix[i][a], matrix[i][b]); - }); - }); - } - - // Convert the sum to scaling factor for [0, 2pi]. - // TODO Allow start and end angle to be specified. - // TODO Allow padding to be specified as percentage? - k = (2 * Math.PI - padding * n) / k; - - // Compute the start and end angle for each group and subgroup. - // Note: Opera has a bug reordering object literal properties! - x = 0, i = -1; while (++i < n) { - x0 = x, j = -1; while (++j < n) { - var di = groupIndex[i], - dj = subgroupIndex[di][j], - v = matrix[di][dj], - a0 = x, - a1 = x += v * k; - subgroups[di + "-" + dj] = { - index: di, - subindex: dj, - startAngle: a0, - endAngle: a1, - value: v - }; - } - groups[di] = { - index: di, - startAngle: x0, - endAngle: x, - value: (x - x0) / k - }; - x += padding; - } - - // Generate chords for each (non-empty) subgroup-subgroup link. - i = -1; while (++i < n) { - j = i - 1; while (++j < n) { - var source = subgroups[i + "-" + j], - target = subgroups[j + "-" + i]; - if (source.value || target.value) { - chords.push(source.value < target.value - ? {source: target, target: source} - : {source: source, target: target}); + d3_eventCancel(); + if (touches.length === 1) { + if (now - touchtime < 500) { + var p = touches[0], l = location(touches[0]); + scaleTo(scale * 2); + translateTo(p, l); + dispatch(event.of(this, arguments)); } + touchtime = now; } } - - if (sortChords) resort(); + function touchmove() { + var touches = d3.touches(this), p0 = touches[0], l0 = translate0[p0.identifier]; + if (p1 = touches[1]) { + var p1, l1 = translate0[p1.identifier]; + p0 = [ (p0[0] + p1[0]) / 2, (p0[1] + p1[1]) / 2 ]; + l0 = [ (l0[0] + l1[0]) / 2, (l0[1] + l1[1]) / 2 ]; + scaleTo(d3.event.scale * scale0); + } + translateTo(p0, l0); + touchtime = null; + dispatch(event.of(this, arguments)); + } + return d3.rebind(zoom, event, "on"); + }; + var d3_behavior_zoomInfinity = [ 0, Infinity ]; + var d3_behavior_zoomDelta, d3_behavior_zoomWheel = "onwheel" in d3_document ? (d3_behavior_zoomDelta = function() { + return -d3.event.deltaY * (d3.event.deltaMode ? 120 : 1); + }, "wheel") : "onmousewheel" in d3_document ? (d3_behavior_zoomDelta = function() { + return d3.event.wheelDelta; + }, "mousewheel") : (d3_behavior_zoomDelta = function() { + return -d3.event.detail; + }, "MozMousePixelScroll"); + function d3_Color() {} + d3_Color.prototype.toString = function() { + return this.rgb() + ""; + }; + d3.hsl = function(h, s, l) { + return arguments.length === 1 ? h instanceof d3_Hsl ? d3_hsl(h.h, h.s, h.l) : d3_rgb_parse("" + h, d3_rgb_hsl, d3_hsl) : d3_hsl(+h, +s, +l); + }; + function d3_hsl(h, s, l) { + return new d3_Hsl(h, s, l); } - - function resort() { - chords.sort(function(a, b) { - return sortChords( - (a.source.value + a.target.value) / 2, - (b.source.value + b.target.value) / 2); - }); + function d3_Hsl(h, s, l) { + this.h = h; + this.s = s; + this.l = l; } - - chord.matrix = function(x) { - if (!arguments.length) return matrix; - n = (matrix = x) && matrix.length; - chords = groups = null; - return chord; + var d3_hslPrototype = d3_Hsl.prototype = new d3_Color(); + d3_hslPrototype.brighter = function(k) { + k = Math.pow(.7, arguments.length ? k : 1); + return d3_hsl(this.h, this.s, this.l / k); }; - - chord.padding = function(x) { - if (!arguments.length) return padding; - padding = x; - chords = groups = null; - return chord; + d3_hslPrototype.darker = function(k) { + k = Math.pow(.7, arguments.length ? k : 1); + return d3_hsl(this.h, this.s, k * this.l); }; - - chord.sortGroups = function(x) { - if (!arguments.length) return sortGroups; - sortGroups = x; - chords = groups = null; - return chord; + d3_hslPrototype.rgb = function() { + return d3_hsl_rgb(this.h, this.s, this.l); }; - - chord.sortSubgroups = function(x) { - if (!arguments.length) return sortSubgroups; - sortSubgroups = x; - chords = null; - return chord; + function d3_hsl_rgb(h, s, l) { + var m1, m2; + h = h % 360; + if (h < 0) h += 360; + s = s < 0 ? 0 : s > 1 ? 1 : s; + l = l < 0 ? 0 : l > 1 ? 1 : l; + m2 = l <= .5 ? l * (1 + s) : l + s - l * s; + m1 = 2 * l - m2; + function v(h) { + if (h > 360) h -= 360; else if (h < 0) h += 360; + if (h < 60) return m1 + (m2 - m1) * h / 60; + if (h < 180) return m2; + if (h < 240) return m1 + (m2 - m1) * (240 - h) / 60; + return m1; + } + function vv(h) { + return Math.round(v(h) * 255); + } + return d3_rgb(vv(h + 120), vv(h), vv(h - 120)); + } + var π = Math.PI, ε = 1e-6, d3_radians = π / 180, d3_degrees = 180 / π; + function d3_sgn(x) { + return x > 0 ? 1 : x < 0 ? -1 : 0; + } + function d3_acos(x) { + return Math.acos(Math.max(-1, Math.min(1, x))); + } + function d3_asin(x) { + return x > 1 ? π / 2 : x < -1 ? -π / 2 : Math.asin(x); + } + function d3_sinh(x) { + return (Math.exp(x) - Math.exp(-x)) / 2; + } + function d3_cosh(x) { + return (Math.exp(x) + Math.exp(-x)) / 2; + } + function d3_haversin(x) { + return (x = Math.sin(x / 2)) * x; + } + d3.hcl = function(h, c, l) { + return arguments.length === 1 ? h instanceof d3_Hcl ? d3_hcl(h.h, h.c, h.l) : h instanceof d3_Lab ? d3_lab_hcl(h.l, h.a, h.b) : d3_lab_hcl((h = d3_rgb_lab((h = d3.rgb(h)).r, h.g, h.b)).l, h.a, h.b) : d3_hcl(+h, +c, +l); }; - - chord.sortChords = function(x) { - if (!arguments.length) return sortChords; - sortChords = x; - if (chords) resort(); - return chord; + function d3_hcl(h, c, l) { + return new d3_Hcl(h, c, l); + } + function d3_Hcl(h, c, l) { + this.h = h; + this.c = c; + this.l = l; + } + var d3_hclPrototype = d3_Hcl.prototype = new d3_Color(); + d3_hclPrototype.brighter = function(k) { + return d3_hcl(this.h, this.c, Math.min(100, this.l + d3_lab_K * (arguments.length ? k : 1))); }; - - chord.chords = function() { - if (!chords) relayout(); - return chords; + d3_hclPrototype.darker = function(k) { + return d3_hcl(this.h, this.c, Math.max(0, this.l - d3_lab_K * (arguments.length ? k : 1))); }; - - chord.groups = function() { - if (!groups) relayout(); - return groups; + d3_hclPrototype.rgb = function() { + return d3_hcl_lab(this.h, this.c, this.l).rgb(); }; - - return chord; -}; -// A rudimentary force layout using Gauss-Seidel. -d3.layout.force = function() { - var force = {}, - event = d3.dispatch("start", "tick", "end"), - size = [1, 1], - drag, - alpha, - friction = .9, - linkDistance = d3_layout_forceLinkDistance, - linkStrength = d3_layout_forceLinkStrength, - charge = -30, - gravity = .1, - theta = .8, - interval, - nodes = [], - links = [], - distances, - strengths, - charges; - - function repulse(node) { - return function(quad, x1, y1, x2, y2) { - if (quad.point !== node) { - var dx = quad.cx - node.x, - dy = quad.cy - node.y, - dn = 1 / Math.sqrt(dx * dx + dy * dy); - - /* Barnes-Hut criterion. */ - if ((x2 - x1) * dn < theta) { - var k = quad.charge * dn * dn; - node.px -= dx * k; - node.py -= dy * k; - return true; + function d3_hcl_lab(h, c, l) { + return d3_lab(l, Math.cos(h *= d3_radians) * c, Math.sin(h) * c); + } + d3.lab = function(l, a, b) { + return arguments.length === 1 ? l instanceof d3_Lab ? d3_lab(l.l, l.a, l.b) : l instanceof d3_Hcl ? d3_hcl_lab(l.l, l.c, l.h) : d3_rgb_lab((l = d3.rgb(l)).r, l.g, l.b) : d3_lab(+l, +a, +b); + }; + function d3_lab(l, a, b) { + return new d3_Lab(l, a, b); + } + function d3_Lab(l, a, b) { + this.l = l; + this.a = a; + this.b = b; + } + var d3_lab_K = 18; + var d3_lab_X = .95047, d3_lab_Y = 1, d3_lab_Z = 1.08883; + var d3_labPrototype = d3_Lab.prototype = new d3_Color(); + d3_labPrototype.brighter = function(k) { + return d3_lab(Math.min(100, this.l + d3_lab_K * (arguments.length ? k : 1)), this.a, this.b); + }; + d3_labPrototype.darker = function(k) { + return d3_lab(Math.max(0, this.l - d3_lab_K * (arguments.length ? k : 1)), this.a, this.b); + }; + d3_labPrototype.rgb = function() { + return d3_lab_rgb(this.l, this.a, this.b); + }; + function d3_lab_rgb(l, a, b) { + var y = (l + 16) / 116, x = y + a / 500, z = y - b / 200; + x = d3_lab_xyz(x) * d3_lab_X; + y = d3_lab_xyz(y) * d3_lab_Y; + z = d3_lab_xyz(z) * d3_lab_Z; + return d3_rgb(d3_xyz_rgb(3.2404542 * x - 1.5371385 * y - .4985314 * z), d3_xyz_rgb(-.969266 * x + 1.8760108 * y + .041556 * z), d3_xyz_rgb(.0556434 * x - .2040259 * y + 1.0572252 * z)); + } + function d3_lab_hcl(l, a, b) { + return d3_hcl(Math.atan2(b, a) * d3_degrees, Math.sqrt(a * a + b * b), l); + } + function d3_lab_xyz(x) { + return x > .206893034 ? x * x * x : (x - 4 / 29) / 7.787037; + } + function d3_xyz_lab(x) { + return x > .008856 ? Math.pow(x, 1 / 3) : 7.787037 * x + 4 / 29; + } + function d3_xyz_rgb(r) { + return Math.round(255 * (r <= .00304 ? 12.92 * r : 1.055 * Math.pow(r, 1 / 2.4) - .055)); + } + d3.rgb = function(r, g, b) { + return arguments.length === 1 ? r instanceof d3_Rgb ? d3_rgb(r.r, r.g, r.b) : d3_rgb_parse("" + r, d3_rgb, d3_hsl_rgb) : d3_rgb(~~r, ~~g, ~~b); + }; + function d3_rgb(r, g, b) { + return new d3_Rgb(r, g, b); + } + function d3_Rgb(r, g, b) { + this.r = r; + this.g = g; + this.b = b; + } + var d3_rgbPrototype = d3_Rgb.prototype = new d3_Color(); + d3_rgbPrototype.brighter = function(k) { + k = Math.pow(.7, arguments.length ? k : 1); + var r = this.r, g = this.g, b = this.b, i = 30; + if (!r && !g && !b) return d3_rgb(i, i, i); + if (r && r < i) r = i; + if (g && g < i) g = i; + if (b && b < i) b = i; + return d3_rgb(Math.min(255, Math.floor(r / k)), Math.min(255, Math.floor(g / k)), Math.min(255, Math.floor(b / k))); + }; + d3_rgbPrototype.darker = function(k) { + k = Math.pow(.7, arguments.length ? k : 1); + return d3_rgb(Math.floor(k * this.r), Math.floor(k * this.g), Math.floor(k * this.b)); + }; + d3_rgbPrototype.hsl = function() { + return d3_rgb_hsl(this.r, this.g, this.b); + }; + d3_rgbPrototype.toString = function() { + return "#" + d3_rgb_hex(this.r) + d3_rgb_hex(this.g) + d3_rgb_hex(this.b); + }; + function d3_rgb_hex(v) { + return v < 16 ? "0" + Math.max(0, v).toString(16) : Math.min(255, v).toString(16); + } + function d3_rgb_parse(format, rgb, hsl) { + var r = 0, g = 0, b = 0, m1, m2, name; + m1 = /([a-z]+)\((.*)\)/i.exec(format); + if (m1) { + m2 = m1[2].split(","); + switch (m1[1]) { + case "hsl": + { + return hsl(parseFloat(m2[0]), parseFloat(m2[1]) / 100, parseFloat(m2[2]) / 100); } - if (quad.point && isFinite(dn)) { - var k = quad.pointCharge * dn * dn; - node.px -= dx * k; - node.py -= dy * k; + case "rgb": + { + return rgb(d3_rgb_parseNumber(m2[0]), d3_rgb_parseNumber(m2[1]), d3_rgb_parseNumber(m2[2])); } } - return !quad.charge; + } + if (name = d3_rgb_names.get(format)) return rgb(name.r, name.g, name.b); + if (format != null && format.charAt(0) === "#") { + if (format.length === 4) { + r = format.charAt(1); + r += r; + g = format.charAt(2); + g += g; + b = format.charAt(3); + b += b; + } else if (format.length === 7) { + r = format.substring(1, 3); + g = format.substring(3, 5); + b = format.substring(5, 7); + } + r = parseInt(r, 16); + g = parseInt(g, 16); + b = parseInt(b, 16); + } + return rgb(r, g, b); + } + function d3_rgb_hsl(r, g, b) { + var min = Math.min(r /= 255, g /= 255, b /= 255), max = Math.max(r, g, b), d = max - min, h, s, l = (max + min) / 2; + if (d) { + s = l < .5 ? d / (max + min) : d / (2 - max - min); + if (r == max) h = (g - b) / d + (g < b ? 6 : 0); else if (g == max) h = (b - r) / d + 2; else h = (r - g) / d + 4; + h *= 60; + } else { + s = h = 0; + } + return d3_hsl(h, s, l); + } + function d3_rgb_lab(r, g, b) { + r = d3_rgb_xyz(r); + g = d3_rgb_xyz(g); + b = d3_rgb_xyz(b); + var x = d3_xyz_lab((.4124564 * r + .3575761 * g + .1804375 * b) / d3_lab_X), y = d3_xyz_lab((.2126729 * r + .7151522 * g + .072175 * b) / d3_lab_Y), z = d3_xyz_lab((.0193339 * r + .119192 * g + .9503041 * b) / d3_lab_Z); + return d3_lab(116 * y - 16, 500 * (x - y), 200 * (y - z)); + } + function d3_rgb_xyz(r) { + return (r /= 255) <= .04045 ? r / 12.92 : Math.pow((r + .055) / 1.055, 2.4); + } + function d3_rgb_parseNumber(c) { + var f = parseFloat(c); + return c.charAt(c.length - 1) === "%" ? Math.round(f * 2.55) : f; + } + var d3_rgb_names = d3.map({ + aliceblue: "#f0f8ff", + antiquewhite: "#faebd7", + aqua: "#00ffff", + aquamarine: "#7fffd4", + azure: "#f0ffff", + beige: "#f5f5dc", + bisque: "#ffe4c4", + black: "#000000", + blanchedalmond: "#ffebcd", + blue: "#0000ff", + blueviolet: "#8a2be2", + brown: "#a52a2a", + burlywood: "#deb887", + cadetblue: "#5f9ea0", + chartreuse: "#7fff00", + chocolate: "#d2691e", + coral: "#ff7f50", + cornflowerblue: "#6495ed", + cornsilk: "#fff8dc", + crimson: "#dc143c", + cyan: "#00ffff", + darkblue: "#00008b", + darkcyan: "#008b8b", + darkgoldenrod: "#b8860b", + darkgray: "#a9a9a9", + darkgreen: "#006400", + darkgrey: "#a9a9a9", + darkkhaki: "#bdb76b", + darkmagenta: "#8b008b", + darkolivegreen: "#556b2f", + darkorange: "#ff8c00", + darkorchid: "#9932cc", + darkred: "#8b0000", + darksalmon: "#e9967a", + darkseagreen: "#8fbc8f", + darkslateblue: "#483d8b", + darkslategray: "#2f4f4f", + darkslategrey: "#2f4f4f", + darkturquoise: "#00ced1", + darkviolet: "#9400d3", + deeppink: "#ff1493", + deepskyblue: "#00bfff", + dimgray: "#696969", + dimgrey: "#696969", + dodgerblue: "#1e90ff", + firebrick: "#b22222", + floralwhite: "#fffaf0", + forestgreen: "#228b22", + fuchsia: "#ff00ff", + gainsboro: "#dcdcdc", + ghostwhite: "#f8f8ff", + gold: "#ffd700", + goldenrod: "#daa520", + gray: "#808080", + green: "#008000", + greenyellow: "#adff2f", + grey: "#808080", + honeydew: "#f0fff0", + hotpink: "#ff69b4", + indianred: "#cd5c5c", + indigo: "#4b0082", + ivory: "#fffff0", + khaki: "#f0e68c", + lavender: "#e6e6fa", + lavenderblush: "#fff0f5", + lawngreen: "#7cfc00", + lemonchiffon: "#fffacd", + lightblue: "#add8e6", + lightcoral: "#f08080", + lightcyan: "#e0ffff", + lightgoldenrodyellow: "#fafad2", + lightgray: "#d3d3d3", + lightgreen: "#90ee90", + lightgrey: "#d3d3d3", + lightpink: "#ffb6c1", + lightsalmon: "#ffa07a", + lightseagreen: "#20b2aa", + lightskyblue: "#87cefa", + lightslategray: "#778899", + lightslategrey: "#778899", + lightsteelblue: "#b0c4de", + lightyellow: "#ffffe0", + lime: "#00ff00", + limegreen: "#32cd32", + linen: "#faf0e6", + magenta: "#ff00ff", + maroon: "#800000", + mediumaquamarine: "#66cdaa", + mediumblue: "#0000cd", + mediumorchid: "#ba55d3", + mediumpurple: "#9370db", + mediumseagreen: "#3cb371", + mediumslateblue: "#7b68ee", + mediumspringgreen: "#00fa9a", + mediumturquoise: "#48d1cc", + mediumvioletred: "#c71585", + midnightblue: "#191970", + mintcream: "#f5fffa", + mistyrose: "#ffe4e1", + moccasin: "#ffe4b5", + navajowhite: "#ffdead", + navy: "#000080", + oldlace: "#fdf5e6", + olive: "#808000", + olivedrab: "#6b8e23", + orange: "#ffa500", + orangered: "#ff4500", + orchid: "#da70d6", + palegoldenrod: "#eee8aa", + palegreen: "#98fb98", + paleturquoise: "#afeeee", + palevioletred: "#db7093", + papayawhip: "#ffefd5", + peachpuff: "#ffdab9", + peru: "#cd853f", + pink: "#ffc0cb", + plum: "#dda0dd", + powderblue: "#b0e0e6", + purple: "#800080", + red: "#ff0000", + rosybrown: "#bc8f8f", + royalblue: "#4169e1", + saddlebrown: "#8b4513", + salmon: "#fa8072", + sandybrown: "#f4a460", + seagreen: "#2e8b57", + seashell: "#fff5ee", + sienna: "#a0522d", + silver: "#c0c0c0", + skyblue: "#87ceeb", + slateblue: "#6a5acd", + slategray: "#708090", + slategrey: "#708090", + snow: "#fffafa", + springgreen: "#00ff7f", + steelblue: "#4682b4", + tan: "#d2b48c", + teal: "#008080", + thistle: "#d8bfd8", + tomato: "#ff6347", + turquoise: "#40e0d0", + violet: "#ee82ee", + wheat: "#f5deb3", + white: "#ffffff", + whitesmoke: "#f5f5f5", + yellow: "#ffff00", + yellowgreen: "#9acd32" + }); + d3_rgb_names.forEach(function(key, value) { + d3_rgb_names.set(key, d3_rgb_parse(value, d3_rgb, d3_hsl_rgb)); + }); + function d3_functor(v) { + return typeof v === "function" ? v : function() { + return v; }; } - - force.tick = function() { - // simulated annealing, basically - if ((alpha *= .99) < .005) { - event.end({type: "end", alpha: alpha = 0}); - return true; - } - - var n = nodes.length, - m = links.length, - q, - i, // current index - o, // current object - s, // current source - t, // current target - l, // current distance - k, // current force - x, // x-distance - y; // y-distance - - // gauss-seidel relaxation for links - for (i = 0; i < m; ++i) { - o = links[i]; - s = o.source; - t = o.target; - x = t.x - s.x; - y = t.y - s.y; - if (l = (x * x + y * y)) { - l = alpha * strengths[i] * ((l = Math.sqrt(l)) - distances[i]) / l; - x *= l; - y *= l; - t.x -= x * (k = s.weight / (t.weight + s.weight)); - t.y -= y * k; - s.x += x * (k = 1 - k); - s.y += y * k; - } - } - - // apply gravity forces - if (k = alpha * gravity) { - x = size[0] / 2; - y = size[1] / 2; - i = -1; if (k) while (++i < n) { - o = nodes[i]; - o.x += (x - o.x) * k; - o.y += (y - o.y) * k; - } - } - - // compute quadtree center of mass and apply charge forces - if (charge) { - d3_layout_forceAccumulate(q = d3.geom.quadtree(nodes), alpha, charges); - i = -1; while (++i < n) { - if (!(o = nodes[i]).fixed) { - q.visit(repulse(o)); - } - } - } - - // position verlet integration - i = -1; while (++i < n) { - o = nodes[i]; - if (o.fixed) { - o.x = o.px; - o.y = o.py; - } else { - o.x -= (o.px - (o.px = o.x)) * friction; - o.y -= (o.py - (o.py = o.y)) * friction; - } - } - - event.tick({type: "tick", alpha: alpha}); - }; - - force.nodes = function(x) { - if (!arguments.length) return nodes; - nodes = x; - return force; - }; - - force.links = function(x) { - if (!arguments.length) return links; - links = x; - return force; - }; - - force.size = function(x) { - if (!arguments.length) return size; - size = x; - return force; - }; - - force.linkDistance = function(x) { - if (!arguments.length) return linkDistance; - linkDistance = d3_functor(x); - return force; - }; - - // For backwards-compatibility. - force.distance = force.linkDistance; - - force.linkStrength = function(x) { - if (!arguments.length) return linkStrength; - linkStrength = d3_functor(x); - return force; - }; - - force.friction = function(x) { - if (!arguments.length) return friction; - friction = x; - return force; - }; - - force.charge = function(x) { - if (!arguments.length) return charge; - charge = typeof x === "function" ? x : +x; - return force; - }; - - force.gravity = function(x) { - if (!arguments.length) return gravity; - gravity = x; - return force; - }; - - force.theta = function(x) { - if (!arguments.length) return theta; - theta = x; - return force; - }; - - force.alpha = function(x) { - if (!arguments.length) return alpha; - - if (alpha) { // if we're already running - if (x > 0) alpha = x; // we might keep it hot - else alpha = 0; // or, next tick will dispatch "end" - } else if (x > 0) { // otherwise, fire it up! - event.start({type: "start", alpha: alpha = x}); - d3.timer(force.tick); - } - - return force; - }; - - force.start = function() { - var i, - j, - n = nodes.length, - m = links.length, - w = size[0], - h = size[1], - neighbors, - o; - - for (i = 0; i < n; ++i) { - (o = nodes[i]).index = i; - o.weight = 0; - } - - distances = []; - strengths = []; - for (i = 0; i < m; ++i) { - o = links[i]; - if (typeof o.source == "number") o.source = nodes[o.source]; - if (typeof o.target == "number") o.target = nodes[o.target]; - distances[i] = linkDistance.call(this, o, i); - strengths[i] = linkStrength.call(this, o, i); - ++o.source.weight; - ++o.target.weight; - } - - for (i = 0; i < n; ++i) { - o = nodes[i]; - if (isNaN(o.x)) o.x = position("x", w); - if (isNaN(o.y)) o.y = position("y", h); - if (isNaN(o.px)) o.px = o.x; - if (isNaN(o.py)) o.py = o.y; - } - - charges = []; - if (typeof charge === "function") { - for (i = 0; i < n; ++i) { - charges[i] = +charge.call(this, nodes[i], i); - } - } else { - for (i = 0; i < n; ++i) { - charges[i] = charge; - } - } - - // initialize node position based on first neighbor - function position(dimension, size) { - var neighbors = neighbor(i), - j = -1, - m = neighbors.length, - x; - while (++j < m) if (!isNaN(x = neighbors[j][dimension])) return x; - return Math.random() * size; - } - - // initialize neighbors lazily - function neighbor() { - if (!neighbors) { - neighbors = []; - for (j = 0; j < n; ++j) { - neighbors[j] = []; - } - for (j = 0; j < m; ++j) { - var o = links[j]; - neighbors[o.source.index].push(o.target); - neighbors[o.target.index].push(o.source); - } - } - return neighbors[i]; - } - - return force.resume(); - }; - - force.resume = function() { - return force.alpha(.1); - }; - - force.stop = function() { - return force.alpha(0); - }; - - // use `node.call(force.drag)` to make nodes draggable - force.drag = function() { - if (!drag) drag = d3.behavior.drag() - .origin(d3_identity) - .on("dragstart", dragstart) - .on("drag", d3_layout_forceDrag) - .on("dragend", d3_layout_forceDragEnd); - - this.on("mouseover.force", d3_layout_forceDragOver) - .on("mouseout.force", d3_layout_forceDragOut) - .call(drag); - }; - - function dragstart(d) { - d3_layout_forceDragOver(d3_layout_forceDragNode = d); - d3_layout_forceDragForce = force; + d3.functor = d3_functor; + function d3_identity(d) { + return d; } - - return d3.rebind(force, event, "on"); -}; - -var d3_layout_forceDragForce, - d3_layout_forceDragNode; - -function d3_layout_forceDragOver(d) { - d.fixed |= 2; -} - -function d3_layout_forceDragOut(d) { - if (d !== d3_layout_forceDragNode) d.fixed &= 1; -} - -function d3_layout_forceDragEnd() { - d3_layout_forceDragNode.fixed &= 1; - d3_layout_forceDragForce = d3_layout_forceDragNode = null; -} - -function d3_layout_forceDrag() { - d3_layout_forceDragNode.px = d3.event.x; - d3_layout_forceDragNode.py = d3.event.y; - d3_layout_forceDragForce.resume(); // restart annealing -} - -function d3_layout_forceAccumulate(quad, alpha, charges) { - var cx = 0, - cy = 0; - quad.charge = 0; - if (!quad.leaf) { - var nodes = quad.nodes, - n = nodes.length, - i = -1, - c; - while (++i < n) { - c = nodes[i]; - if (c == null) continue; - d3_layout_forceAccumulate(c, alpha, charges); - quad.charge += c.charge; - cx += c.charge * c.cx; - cy += c.charge * c.cy; + d3.xhr = function(url, mimeType, callback) { + var xhr = {}, dispatch = d3.dispatch("progress", "load", "error"), headers = {}, response = d3_identity, request = new (d3_window.XDomainRequest && /^(http(s)?:)?\/\//.test(url) ? XDomainRequest : XMLHttpRequest)(); + "onload" in request ? request.onload = request.onerror = respond : request.onreadystatechange = function() { + request.readyState > 3 && respond(); + }; + function respond() { + var s = request.status; + !s && request.responseText || s >= 200 && s < 300 || s === 304 ? dispatch.load.call(xhr, response.call(xhr, request)) : dispatch.error.call(xhr, request); } - } - if (quad.point) { - // jitter internal nodes that are coincident - if (!quad.leaf) { - quad.point.x += Math.random() - .5; - quad.point.y += Math.random() - .5; - } - var k = alpha * charges[quad.point.index]; - quad.charge += quad.pointCharge = k; - cx += k * quad.point.x; - cy += k * quad.point.y; - } - quad.cx = cx / quad.charge; - quad.cy = cy / quad.charge; -} - -function d3_layout_forceLinkDistance(link) { - return 20; -} - -function d3_layout_forceLinkStrength(link) { - return 1; -} -d3.layout.partition = function() { - var hierarchy = d3.layout.hierarchy(), - size = [1, 1]; // width, height - - function position(node, x, dx, dy) { - var children = node.children; - node.x = x; - node.y = node.depth * dy; - node.dx = dx; - node.dy = dy; - if (children && (n = children.length)) { - var i = -1, - n, - c, - d; - dx = node.value ? dx / node.value : 0; - while (++i < n) { - position(c = children[i], x, d = c.value * dx, dy); - x += d; + request.onprogress = function(event) { + var o = d3.event; + d3.event = event; + try { + dispatch.progress.call(xhr, request); + } finally { + d3.event = o; } - } - } - - function depth(node) { - var children = node.children, - d = 0; - if (children && (n = children.length)) { - var i = -1, - n; - while (++i < n) d = Math.max(d, depth(children[i])); - } - return 1 + d; - } - - function partition(d, i) { - var nodes = hierarchy.call(this, d, i); - position(nodes[0], 0, size[0], size[1] / depth(nodes[0])); - return nodes; - } - - partition.size = function(x) { - if (!arguments.length) return size; - size = x; - return partition; - }; - - return d3_layout_hierarchyRebind(partition, hierarchy); -}; -d3.layout.pie = function() { - var value = Number, - sort = d3_layout_pieSortByValue, - startAngle = 0, - endAngle = 2 * Math.PI; - - function pie(data, i) { - - // Compute the numeric values for each data element. - var values = data.map(function(d, i) { return +value.call(pie, d, i); }); - - // Compute the start angle. - var a = +(typeof startAngle === "function" - ? startAngle.apply(this, arguments) - : startAngle); - - // Compute the angular scale factor: from value to radians. - var k = ((typeof endAngle === "function" - ? endAngle.apply(this, arguments) - : endAngle) - startAngle) - / d3.sum(values); - - // Optionally sort the data. - var index = d3.range(data.length); - if (sort != null) index.sort(sort === d3_layout_pieSortByValue - ? function(i, j) { return values[j] - values[i]; } - : function(i, j) { return sort(data[i], data[j]); }); - - // Compute the arcs! - // They are stored in the original data's order. - var arcs = []; - index.forEach(function(i) { - var d; - arcs[i] = { - data: data[i], - value: d = values[i], - startAngle: a, - endAngle: a += d * k + }; + xhr.header = function(name, value) { + name = (name + "").toLowerCase(); + if (arguments.length < 2) return headers[name]; + if (value == null) delete headers[name]; else headers[name] = value + ""; + return xhr; + }; + xhr.mimeType = function(value) { + if (!arguments.length) return mimeType; + mimeType = value == null ? null : value + ""; + return xhr; + }; + xhr.response = function(value) { + response = value; + return xhr; + }; + [ "get", "post" ].forEach(function(method) { + xhr[method] = function() { + return xhr.send.apply(xhr, [ method ].concat(d3_array(arguments))); }; }); - return arcs; - } - - /** - * Specifies the value function *x*, which returns a nonnegative numeric value - * for each datum. The default value function is `Number`. The value function - * is passed two arguments: the current datum and the current index. - */ - pie.value = function(x) { - if (!arguments.length) return value; - value = x; - return pie; - }; - - /** - * Specifies a sort comparison operator *x*. The comparator is passed two data - * elements from the data array, a and b; it returns a negative value if a is - * less than b, a positive value if a is greater than b, and zero if a equals - * b. - */ - pie.sort = function(x) { - if (!arguments.length) return sort; - sort = x; - return pie; - }; - - /** - * Specifies the overall start angle of the pie chart. Defaults to 0. The - * start angle can be specified either as a constant or as a function; in the - * case of a function, it is evaluated once per array (as opposed to per - * element). - */ - pie.startAngle = function(x) { - if (!arguments.length) return startAngle; - startAngle = x; - return pie; - }; - - /** - * Specifies the overall end angle of the pie chart. Defaults to 2π. The - * end angle can be specified either as a constant or as a function; in the - * case of a function, it is evaluated once per array (as opposed to per - * element). - */ - pie.endAngle = function(x) { - if (!arguments.length) return endAngle; - endAngle = x; - return pie; - }; - - return pie; -}; - -var d3_layout_pieSortByValue = {}; -// data is two-dimensional array of x,y; we populate y0 -d3.layout.stack = function() { - var values = d3_identity, - order = d3_layout_stackOrderDefault, - offset = d3_layout_stackOffsetZero, - out = d3_layout_stackOut, - x = d3_layout_stackX, - y = d3_layout_stackY; - - function stack(data, index) { - - // Convert series to canonical two-dimensional representation. - var series = data.map(function(d, i) { - return values.call(stack, d, i); - }); - - // Convert each series to canonical [[x,y]] representation. - var points = series.map(function(d, i) { - return d.map(function(v, i) { - return [x.call(stack, v, i), y.call(stack, v, i)]; + xhr.send = function(method, data, callback) { + if (arguments.length === 2 && typeof data === "function") callback = data, data = null; + request.open(method, url, true); + if (mimeType != null && !("accept" in headers)) headers["accept"] = mimeType + ",*/*"; + if (request.setRequestHeader) for (var name in headers) request.setRequestHeader(name, headers[name]); + if (mimeType != null && request.overrideMimeType) request.overrideMimeType(mimeType); + if (callback != null) xhr.on("error", callback).on("load", function(request) { + callback(null, request); }); - }); - - // Compute the order of series, and permute them. - var orders = order.call(stack, points, index); - series = d3.permute(series, orders); - points = d3.permute(points, orders); - - // Compute the baseline… - var offsets = offset.call(stack, points, index); - - // And propagate it to other series. - var n = series.length, - m = series[0].length, - i, - j, - o; - for (j = 0; j < m; ++j) { - out.call(stack, series[0][j], o = offsets[j], points[0][j][1]); - for (i = 1; i < n; ++i) { - out.call(stack, series[i][j], o += points[i - 1][j][1], points[i][j][1]); - } - } - - return data; + request.send(data == null ? null : data); + return xhr; + }; + xhr.abort = function() { + request.abort(); + return xhr; + }; + d3.rebind(xhr, dispatch, "on"); + if (arguments.length === 2 && typeof mimeType === "function") callback = mimeType, + mimeType = null; + return callback == null ? xhr : xhr.get(d3_xhr_fixCallback(callback)); + }; + function d3_xhr_fixCallback(callback) { + return callback.length === 1 ? function(error, request) { + callback(error == null ? request : null); + } : callback; } - - stack.values = function(x) { - if (!arguments.length) return values; - values = x; - return stack; - }; - - stack.order = function(x) { - if (!arguments.length) return order; - order = typeof x === "function" ? x : d3_layout_stackOrders.get(x) || d3_layout_stackOrderDefault; - return stack; - }; - - stack.offset = function(x) { - if (!arguments.length) return offset; - offset = typeof x === "function" ? x : d3_layout_stackOffsets.get(x) || d3_layout_stackOffsetZero; - return stack; - }; - - stack.x = function(z) { - if (!arguments.length) return x; - x = z; - return stack; - }; - - stack.y = function(z) { - if (!arguments.length) return y; - y = z; - return stack; - }; - - stack.out = function(z) { - if (!arguments.length) return out; - out = z; - return stack; - }; - - return stack; -} - -function d3_layout_stackX(d) { - return d.x; -} - -function d3_layout_stackY(d) { - return d.y; -} - -function d3_layout_stackOut(d, y0, y) { - d.y0 = y0; - d.y = y; -} - -var d3_layout_stackOrders = d3.map({ - - "inside-out": function(data) { - var n = data.length, - i, - j, - max = data.map(d3_layout_stackMaxIndex), - sums = data.map(d3_layout_stackReduceSum), - index = d3.range(n).sort(function(a, b) { return max[a] - max[b]; }), - top = 0, - bottom = 0, - tops = [], - bottoms = []; - for (i = 0; i < n; ++i) { - j = index[i]; - if (top < bottom) { - top += sums[j]; - tops.push(j); - } else { - bottom += sums[j]; - bottoms.push(j); - } + function d3_dsv(delimiter, mimeType) { + var reFormat = new RegExp('["' + delimiter + "\n]"), delimiterCode = delimiter.charCodeAt(0); + function dsv(url, row, callback) { + if (arguments.length < 3) callback = row, row = null; + var xhr = d3.xhr(url, mimeType, callback); + xhr.row = function(_) { + return arguments.length ? xhr.response((row = _) == null ? response : typedResponse(_)) : row; + }; + return xhr.row(row); } - return bottoms.reverse().concat(tops); - }, - - "reverse": function(data) { - return d3.range(data.length).reverse(); - }, - - "default": d3_layout_stackOrderDefault - -}); - -var d3_layout_stackOffsets = d3.map({ - - "silhouette": function(data) { - var n = data.length, - m = data[0].length, - sums = [], - max = 0, - i, - j, - o, - y0 = []; - for (j = 0; j < m; ++j) { - for (i = 0, o = 0; i < n; i++) o += data[i][j][1]; - if (o > max) max = o; - sums.push(o); + function response(request) { + return dsv.parse(request.responseText); } - for (j = 0; j < m; ++j) { - y0[j] = (max - sums[j]) / 2; + function typedResponse(f) { + return function(request) { + return dsv.parse(request.responseText, f); + }; } - return y0; - }, - - "wiggle": function(data) { - var n = data.length, - x = data[0], - m = x.length, - max = 0, - i, - j, - k, - s1, - s2, - s3, - dx, - o, - o0, - y0 = []; - y0[0] = o = o0 = 0; - for (j = 1; j < m; ++j) { - for (i = 0, s1 = 0; i < n; ++i) s1 += data[i][j][1]; - for (i = 0, s2 = 0, dx = x[j][0] - x[j - 1][0]; i < n; ++i) { - for (k = 0, s3 = (data[i][j][1] - data[i][j - 1][1]) / (2 * dx); k < i; ++k) { - s3 += (data[k][j][1] - data[k][j - 1][1]) / dx; + dsv.parse = function(text, f) { + var o; + return dsv.parseRows(text, function(row, i) { + if (o) return o(row, i - 1); + var a = new Function("d", "return {" + row.map(function(name, i) { + return JSON.stringify(name) + ": d[" + i + "]"; + }).join(",") + "}"); + o = f ? function(row, i) { + return f(a(row), i); + } : a; + }); + }; + dsv.parseRows = function(text, f) { + var EOL = {}, EOF = {}, rows = [], N = text.length, I = 0, n = 0, t, eol; + function token() { + if (I >= N) return EOF; + if (eol) return eol = false, EOL; + var j = I; + if (text.charCodeAt(j) === 34) { + var i = j; + while (i++ < N) { + if (text.charCodeAt(i) === 34) { + if (text.charCodeAt(i + 1) !== 34) break; + ++i; + } + } + I = i + 2; + var c = text.charCodeAt(i + 1); + if (c === 13) { + eol = true; + if (text.charCodeAt(i + 2) === 10) ++I; + } else if (c === 10) { + eol = true; + } + return text.substring(j + 1, i).replace(/""/g, '"'); } - s2 += s3 * data[i][j][1]; - } - y0[j] = o -= s1 ? s2 / s1 * dx : 0; - if (o < o0) o0 = o; - } - for (j = 0; j < m; ++j) y0[j] -= o0; - return y0; - }, - - "expand": function(data) { - var n = data.length, - m = data[0].length, - k = 1 / n, - i, - j, - o, - y0 = []; - for (j = 0; j < m; ++j) { - for (i = 0, o = 0; i < n; i++) o += data[i][j][1]; - if (o) for (i = 0; i < n; i++) data[i][j][1] /= o; - else for (i = 0; i < n; i++) data[i][j][1] = k; - } - for (j = 0; j < m; ++j) y0[j] = 0; - return y0; - }, - - "zero": d3_layout_stackOffsetZero - -}); - -function d3_layout_stackOrderDefault(data) { - return d3.range(data.length); -} - -function d3_layout_stackOffsetZero(data) { - var j = -1, - m = data[0].length, - y0 = []; - while (++j < m) y0[j] = 0; - return y0; -} - -function d3_layout_stackMaxIndex(array) { - var i = 1, - j = 0, - v = array[0][1], - k, - n = array.length; - for (; i < n; ++i) { - if ((k = array[i][1]) > v) { - j = i; - v = k; - } - } - return j; -} - -function d3_layout_stackReduceSum(d) { - return d.reduce(d3_layout_stackSum, 0); -} - -function d3_layout_stackSum(p, d) { - return p + d[1]; -} -d3.layout.histogram = function() { - var frequency = true, - valuer = Number, - ranger = d3_layout_histogramRange, - binner = d3_layout_histogramBinSturges; - - function histogram(data, i) { - var bins = [], - values = data.map(valuer, this), - range = ranger.call(this, values, i), - thresholds = binner.call(this, range, values, i), - bin, - i = -1, - n = values.length, - m = thresholds.length - 1, - k = frequency ? 1 : 1 / n, - x; - - // Initialize the bins. - while (++i < m) { - bin = bins[i] = []; - bin.dx = thresholds[i + 1] - (bin.x = thresholds[i]); - bin.y = 0; - } - - // Fill the bins, ignoring values outside the range. - if (m > 0) { - i = -1; while(++i < n) { - x = values[i]; - if ((x >= range[0]) && (x <= range[1])) { - bin = bins[d3.bisect(thresholds, x, 1, m) - 1]; - bin.y += k; - bin.push(data[i]); + while (I < N) { + var c = text.charCodeAt(I++), k = 1; + if (c === 10) eol = true; else if (c === 13) { + eol = true; + if (text.charCodeAt(I) === 10) ++I, ++k; + } else if (c !== delimiterCode) continue; + return text.substring(j, I - k); } + return text.substring(j); } - } - - return bins; - } - - // Specifies how to extract a value from the associated data. The default - // value function is `Number`, which is equivalent to the identity function. - histogram.value = function(x) { - if (!arguments.length) return valuer; - valuer = x; - return histogram; - }; - - // Specifies the range of the histogram. Values outside the specified range - // will be ignored. The argument `x` may be specified either as a two-element - // array representing the minimum and maximum value of the range, or as a - // function that returns the range given the array of values and the current - // index `i`. The default range is the extent (minimum and maximum) of the - // values. - histogram.range = function(x) { - if (!arguments.length) return ranger; - ranger = d3_functor(x); - return histogram; - }; - - // Specifies how to bin values in the histogram. The argument `x` may be - // specified as a number, in which case the range of values will be split - // uniformly into the given number of bins. Or, `x` may be an array of - // threshold values, defining the bins; the specified array must contain the - // rightmost (upper) value, thus specifying n + 1 values for n bins. Or, `x` - // may be a function which is evaluated, being passed the range, the array of - // values, and the current index `i`, returning an array of thresholds. The - // default bin function will divide the values into uniform bins using - // Sturges' formula. - histogram.bins = function(x) { - if (!arguments.length) return binner; - binner = typeof x === "number" - ? function(range) { return d3_layout_histogramBinFixed(range, x); } - : d3_functor(x); - return histogram; - }; - - // Specifies whether the histogram's `y` value is a count (frequency) or a - // probability (density). The default value is true. - histogram.frequency = function(x) { - if (!arguments.length) return frequency; - frequency = !!x; - return histogram; - }; - - return histogram; -}; - -function d3_layout_histogramBinSturges(range, values) { - return d3_layout_histogramBinFixed(range, Math.ceil(Math.log(values.length) / Math.LN2 + 1)); -} - -function d3_layout_histogramBinFixed(range, n) { - var x = -1, - b = +range[0], - m = (range[1] - b) / n, - f = []; - while (++x <= n) f[x] = m * x + b; - return f; -} - -function d3_layout_histogramRange(values) { - return [d3.min(values), d3.max(values)]; -} -d3.layout.hierarchy = function() { - var sort = d3_layout_hierarchySort, - children = d3_layout_hierarchyChildren, - value = d3_layout_hierarchyValue; - - // Recursively compute the node depth and value. - // Also converts the data representation into a standard hierarchy structure. - function recurse(data, depth, nodes) { - var childs = children.call(hierarchy, data, depth), - node = d3_layout_hierarchyInline ? data : {data: data}; - node.depth = depth; - nodes.push(node); - if (childs && (n = childs.length)) { - var i = -1, - n, - c = node.children = [], - v = 0, - j = depth + 1, - d; - while (++i < n) { - d = recurse(childs[i], j, nodes); - d.parent = node; - c.push(d); - v += d.value; + while ((t = token()) !== EOF) { + var a = []; + while (t !== EOL && t !== EOF) { + a.push(t); + t = token(); + } + if (f && !(a = f(a, n++))) continue; + rows.push(a); } - if (sort) c.sort(sort); - if (value) node.value = v; - } else if (value) { - node.value = +value.call(hierarchy, data, depth) || 0; - } - return node; - } - - // Recursively re-evaluates the node value. - function revalue(node, depth) { - var children = node.children, - v = 0; - if (children && (n = children.length)) { - var i = -1, - n, - j = depth + 1; - while (++i < n) v += revalue(children[i], j); - } else if (value) { - v = +value.call(hierarchy, d3_layout_hierarchyInline ? node : node.data, depth) || 0; - } - if (value) node.value = v; - return v; - } - - function hierarchy(d) { - var nodes = []; - recurse(d, 0, nodes); - return nodes; - } - - hierarchy.sort = function(x) { - if (!arguments.length) return sort; - sort = x; - return hierarchy; - }; - - hierarchy.children = function(x) { - if (!arguments.length) return children; - children = x; - return hierarchy; - }; - - hierarchy.value = function(x) { - if (!arguments.length) return value; - value = x; - return hierarchy; - }; - - // Re-evaluates the `value` property for the specified hierarchy. - hierarchy.revalue = function(root) { - revalue(root, 0); - return root; - }; - - return hierarchy; -}; - -// A method assignment helper for hierarchy subclasses. -function d3_layout_hierarchyRebind(object, hierarchy) { - d3.rebind(object, hierarchy, "sort", "children", "value"); - - // Add an alias for links, for convenience. - object.links = d3_layout_hierarchyLinks; - - // If the new API is used, enabling inlining. - object.nodes = function(d) { - d3_layout_hierarchyInline = true; - return (object.nodes = object)(d); - }; - - return object; -} - -function d3_layout_hierarchyChildren(d) { - return d.children; -} - -function d3_layout_hierarchyValue(d) { - return d.value; -} - -function d3_layout_hierarchySort(a, b) { - return b.value - a.value; -} - -// Returns an array source+target objects for the specified nodes. -function d3_layout_hierarchyLinks(nodes) { - return d3.merge(nodes.map(function(parent) { - return (parent.children || []).map(function(child) { - return {source: parent, target: child}; - }); - })); -} - -// For backwards-compatibility, don't enable inlining by default. -var d3_layout_hierarchyInline = false; -d3.layout.pack = function() { - var hierarchy = d3.layout.hierarchy().sort(d3_layout_packSort), - size = [1, 1]; - - function pack(d, i) { - var nodes = hierarchy.call(this, d, i), - root = nodes[0]; - - // Recursively compute the layout. - root.x = 0; - root.y = 0; - d3_layout_packTree(root); - - // Scale the layout to fit the requested size. - var w = size[0], - h = size[1], - k = 1 / Math.max(2 * root.r / w, 2 * root.r / h); - d3_layout_packTransform(root, w / 2, h / 2, k); - - return nodes; - } - - pack.size = function(x) { - if (!arguments.length) return size; - size = x; - return pack; - }; - - return d3_layout_hierarchyRebind(pack, hierarchy); -}; - -function d3_layout_packSort(a, b) { - return a.value - b.value; -} - -function d3_layout_packInsert(a, b) { - var c = a._pack_next; - a._pack_next = b; - b._pack_prev = a; - b._pack_next = c; - c._pack_prev = b; -} - -function d3_layout_packSplice(a, b) { - a._pack_next = b; - b._pack_prev = a; -} - -function d3_layout_packIntersects(a, b) { - var dx = b.x - a.x, - dy = b.y - a.y, - dr = a.r + b.r; - return dr * dr - dx * dx - dy * dy > .001; // within epsilon -} - -function d3_layout_packCircle(nodes) { - var xMin = Infinity, - xMax = -Infinity, - yMin = Infinity, - yMax = -Infinity, - n = nodes.length, - a, b, c, j, k; - - function bound(node) { - xMin = Math.min(node.x - node.r, xMin); - xMax = Math.max(node.x + node.r, xMax); - yMin = Math.min(node.y - node.r, yMin); - yMax = Math.max(node.y + node.r, yMax); - } - - // Create node links. - nodes.forEach(d3_layout_packLink); - - // Create first node. - a = nodes[0]; - a.x = -a.r; - a.y = 0; - bound(a); - - // Create second node. - if (n > 1) { - b = nodes[1]; - b.x = b.r; - b.y = 0; - bound(b); - - // Create third node and build chain. - if (n > 2) { - c = nodes[2]; - d3_layout_packPlace(a, b, c); - bound(c); - d3_layout_packInsert(a, c); - a._pack_prev = c; - d3_layout_packInsert(c, b); - b = a._pack_next; - - // Now iterate through the rest. - for (var i = 3; i < n; i++) { - d3_layout_packPlace(a, b, c = nodes[i]); - - // Search for the closest intersection. - var isect = 0, s1 = 1, s2 = 1; - for (j = b._pack_next; j !== b; j = j._pack_next, s1++) { - if (d3_layout_packIntersects(j, c)) { - isect = 1; - break; + return rows; + }; + dsv.format = function(rows) { + if (Array.isArray(rows[0])) return dsv.formatRows(rows); + var fieldSet = new d3_Set(), fields = []; + rows.forEach(function(row) { + for (var field in row) { + if (!fieldSet.has(field)) { + fields.push(fieldSet.add(field)); } } - if (isect == 1) { - for (k = a._pack_prev; k !== j._pack_prev; k = k._pack_prev, s2++) { - if (d3_layout_packIntersects(k, c)) { - break; + }); + return [ fields.map(formatValue).join(delimiter) ].concat(rows.map(function(row) { + return fields.map(function(field) { + return formatValue(row[field]); + }).join(delimiter); + })).join("\n"); + }; + dsv.formatRows = function(rows) { + return rows.map(formatRow).join("\n"); + }; + function formatRow(row) { + return row.map(formatValue).join(delimiter); + } + function formatValue(text) { + return reFormat.test(text) ? '"' + text.replace(/\"/g, '""') + '"' : text; + } + return dsv; + } + d3.csv = d3_dsv(",", "text/csv"); + d3.tsv = d3_dsv(" ", "text/tab-separated-values"); + var d3_timer_id = 0, d3_timer_byId = {}, d3_timer_queue = null, d3_timer_interval, d3_timer_timeout; + d3.timer = function(callback, delay, then) { + if (arguments.length < 3) { + if (arguments.length < 2) delay = 0; else if (!isFinite(delay)) return; + then = Date.now(); + } + var timer = d3_timer_byId[callback.id]; + if (timer && timer.callback === callback) { + timer.then = then; + timer.delay = delay; + } else d3_timer_byId[callback.id = ++d3_timer_id] = d3_timer_queue = { + callback: callback, + then: then, + delay: delay, + next: d3_timer_queue + }; + if (!d3_timer_interval) { + d3_timer_timeout = clearTimeout(d3_timer_timeout); + d3_timer_interval = 1; + d3_timer_frame(d3_timer_step); + } + }; + function d3_timer_step() { + var elapsed, now = Date.now(), t1 = d3_timer_queue; + while (t1) { + elapsed = now - t1.then; + if (elapsed >= t1.delay) t1.flush = t1.callback(elapsed); + t1 = t1.next; + } + var delay = d3_timer_flush() - now; + if (delay > 24) { + if (isFinite(delay)) { + clearTimeout(d3_timer_timeout); + d3_timer_timeout = setTimeout(d3_timer_step, delay); + } + d3_timer_interval = 0; + } else { + d3_timer_interval = 1; + d3_timer_frame(d3_timer_step); + } + } + d3.timer.flush = function() { + var elapsed, now = Date.now(), t1 = d3_timer_queue; + while (t1) { + elapsed = now - t1.then; + if (!t1.delay) t1.flush = t1.callback(elapsed); + t1 = t1.next; + } + d3_timer_flush(); + }; + function d3_timer_flush() { + var t0 = null, t1 = d3_timer_queue, then = Infinity; + while (t1) { + if (t1.flush) { + delete d3_timer_byId[t1.callback.id]; + t1 = t0 ? t0.next = t1.next : d3_timer_queue = t1.next; + } else { + then = Math.min(then, t1.then + t1.delay); + t1 = (t0 = t1).next; + } + } + return then; + } + var d3_timer_frame = d3_window.requestAnimationFrame || d3_window.webkitRequestAnimationFrame || d3_window.mozRequestAnimationFrame || d3_window.oRequestAnimationFrame || d3_window.msRequestAnimationFrame || function(callback) { + setTimeout(callback, 17); + }; + var d3_format_decimalPoint = ".", d3_format_thousandsSeparator = ",", d3_format_grouping = [ 3, 3 ]; + var d3_formatPrefixes = [ "y", "z", "a", "f", "p", "n", "µ", "m", "", "k", "M", "G", "T", "P", "E", "Z", "Y" ].map(d3_formatPrefix); + d3.formatPrefix = function(value, precision) { + var i = 0; + if (value) { + if (value < 0) value *= -1; + if (precision) value = d3.round(value, d3_format_precision(value, precision)); + i = 1 + Math.floor(1e-12 + Math.log(value) / Math.LN10); + i = Math.max(-24, Math.min(24, Math.floor((i <= 0 ? i + 1 : i - 1) / 3) * 3)); + } + return d3_formatPrefixes[8 + i / 3]; + }; + function d3_formatPrefix(d, i) { + var k = Math.pow(10, Math.abs(8 - i) * 3); + return { + scale: i > 8 ? function(d) { + return d / k; + } : function(d) { + return d * k; + }, + symbol: d + }; + } + d3.round = function(x, n) { + return n ? Math.round(x * (n = Math.pow(10, n))) / n : Math.round(x); + }; + d3.format = function(specifier) { + var match = d3_format_re.exec(specifier), fill = match[1] || " ", align = match[2] || ">", sign = match[3] || "", basePrefix = match[4] || "", zfill = match[5], width = +match[6], comma = match[7], precision = match[8], type = match[9], scale = 1, suffix = "", integer = false; + if (precision) precision = +precision.substring(1); + if (zfill || fill === "0" && align === "=") { + zfill = fill = "0"; + align = "="; + if (comma) width -= Math.floor((width - 1) / 4); + } + switch (type) { + case "n": + comma = true; + type = "g"; + break; + + case "%": + scale = 100; + suffix = "%"; + type = "f"; + break; + + case "p": + scale = 100; + suffix = "%"; + type = "r"; + break; + + case "b": + case "o": + case "x": + case "X": + if (basePrefix) basePrefix = "0" + type.toLowerCase(); + + case "c": + case "d": + integer = true; + precision = 0; + break; + + case "s": + scale = -1; + type = "r"; + break; + } + if (basePrefix === "#") basePrefix = ""; + if (type == "r" && !precision) type = "g"; + if (precision != null) { + if (type == "g") precision = Math.max(1, Math.min(21, precision)); else if (type == "e" || type == "f") precision = Math.max(0, Math.min(20, precision)); + } + type = d3_format_types.get(type) || d3_format_typeDefault; + var zcomma = zfill && comma; + return function(value) { + if (integer && value % 1) return ""; + var negative = value < 0 || value === 0 && 1 / value < 0 ? (value = -value, "-") : sign; + if (scale < 0) { + var prefix = d3.formatPrefix(value, precision); + value = prefix.scale(value); + suffix = prefix.symbol; + } else { + value *= scale; + } + value = type(value, precision); + if (!zfill && comma) value = d3_format_group(value); + var length = basePrefix.length + value.length + (zcomma ? 0 : negative.length), padding = length < width ? new Array(length = width - length + 1).join(fill) : ""; + if (zcomma) value = d3_format_group(padding + value); + if (d3_format_decimalPoint) value.replace(".", d3_format_decimalPoint); + negative += basePrefix; + return (align === "<" ? negative + value + padding : align === ">" ? padding + negative + value : align === "^" ? padding.substring(0, length >>= 1) + negative + value + padding.substring(length) : negative + (zcomma ? value : padding + value)) + suffix; + }; + }; + var d3_format_re = /(?:([^{])?([<>=^]))?([+\- ])?(#)?(0)?(\d+)?(,)?(\.-?\d+)?([a-z%])?/i; + var d3_format_types = d3.map({ + b: function(x) { + return x.toString(2); + }, + c: function(x) { + return String.fromCharCode(x); + }, + o: function(x) { + return x.toString(8); + }, + x: function(x) { + return x.toString(16); + }, + X: function(x) { + return x.toString(16).toUpperCase(); + }, + g: function(x, p) { + return x.toPrecision(p); + }, + e: function(x, p) { + return x.toExponential(p); + }, + f: function(x, p) { + return x.toFixed(p); + }, + r: function(x, p) { + return (x = d3.round(x, d3_format_precision(x, p))).toFixed(Math.max(0, Math.min(20, d3_format_precision(x * (1 + 1e-15), p)))); + } + }); + function d3_format_precision(x, p) { + return p - (x ? Math.ceil(Math.log(x) / Math.LN10) : 1); + } + function d3_format_typeDefault(x) { + return x + ""; + } + var d3_format_group = d3_identity; + if (d3_format_grouping) { + var d3_format_groupingLength = d3_format_grouping.length; + d3_format_group = function(value) { + var i = value.lastIndexOf("."), f = i >= 0 ? "." + value.substring(i + 1) : (i = value.length, + ""), t = [], j = 0, g = d3_format_grouping[0]; + while (i > 0 && g > 0) { + t.push(value.substring(i -= g, i + g)); + g = d3_format_grouping[j = (j + 1) % d3_format_groupingLength]; + } + return t.reverse().join(d3_format_thousandsSeparator || "") + f; + }; + } + d3.geo = {}; + d3.geo.stream = function(object, listener) { + if (d3_geo_streamObjectType.hasOwnProperty(object.type)) { + d3_geo_streamObjectType[object.type](object, listener); + } else { + d3_geo_streamGeometry(object, listener); + } + }; + function d3_geo_streamGeometry(geometry, listener) { + if (d3_geo_streamGeometryType.hasOwnProperty(geometry.type)) { + d3_geo_streamGeometryType[geometry.type](geometry, listener); + } + } + var d3_geo_streamObjectType = { + Feature: function(feature, listener) { + d3_geo_streamGeometry(feature.geometry, listener); + }, + FeatureCollection: function(object, listener) { + var features = object.features, i = -1, n = features.length; + while (++i < n) d3_geo_streamGeometry(features[i].geometry, listener); + } + }; + var d3_geo_streamGeometryType = { + Sphere: function(object, listener) { + listener.sphere(); + }, + Point: function(object, listener) { + var coordinate = object.coordinates; + listener.point(coordinate[0], coordinate[1]); + }, + MultiPoint: function(object, listener) { + var coordinates = object.coordinates, i = -1, n = coordinates.length, coordinate; + while (++i < n) coordinate = coordinates[i], listener.point(coordinate[0], coordinate[1]); + }, + LineString: function(object, listener) { + d3_geo_streamLine(object.coordinates, listener, 0); + }, + MultiLineString: function(object, listener) { + var coordinates = object.coordinates, i = -1, n = coordinates.length; + while (++i < n) d3_geo_streamLine(coordinates[i], listener, 0); + }, + Polygon: function(object, listener) { + d3_geo_streamPolygon(object.coordinates, listener); + }, + MultiPolygon: function(object, listener) { + var coordinates = object.coordinates, i = -1, n = coordinates.length; + while (++i < n) d3_geo_streamPolygon(coordinates[i], listener); + }, + GeometryCollection: function(object, listener) { + var geometries = object.geometries, i = -1, n = geometries.length; + while (++i < n) d3_geo_streamGeometry(geometries[i], listener); + } + }; + function d3_geo_streamLine(coordinates, listener, closed) { + var i = -1, n = coordinates.length - closed, coordinate; + listener.lineStart(); + while (++i < n) coordinate = coordinates[i], listener.point(coordinate[0], coordinate[1]); + listener.lineEnd(); + } + function d3_geo_streamPolygon(coordinates, listener) { + var i = -1, n = coordinates.length; + listener.polygonStart(); + while (++i < n) d3_geo_streamLine(coordinates[i], listener, 1); + listener.polygonEnd(); + } + d3.geo.area = function(object) { + d3_geo_areaSum = 0; + d3.geo.stream(object, d3_geo_area); + return d3_geo_areaSum; + }; + var d3_geo_areaSum, d3_geo_areaRingU, d3_geo_areaRingV; + var d3_geo_area = { + sphere: function() { + d3_geo_areaSum += 4 * π; + }, + point: d3_noop, + lineStart: d3_noop, + lineEnd: d3_noop, + polygonStart: function() { + d3_geo_areaRingU = 1, d3_geo_areaRingV = 0; + d3_geo_area.lineStart = d3_geo_areaRingStart; + }, + polygonEnd: function() { + var area = 2 * Math.atan2(d3_geo_areaRingV, d3_geo_areaRingU); + d3_geo_areaSum += area < 0 ? 4 * π + area : area; + d3_geo_area.lineStart = d3_geo_area.lineEnd = d3_geo_area.point = d3_noop; + } + }; + function d3_geo_areaRingStart() { + var λ00, φ00, λ0, cosφ0, sinφ0; + d3_geo_area.point = function(λ, φ) { + d3_geo_area.point = nextPoint; + λ0 = (λ00 = λ) * d3_radians, cosφ0 = Math.cos(φ = (φ00 = φ) * d3_radians / 2 + π / 4), + sinφ0 = Math.sin(φ); + }; + function nextPoint(λ, φ) { + λ *= d3_radians; + φ = φ * d3_radians / 2 + π / 4; + var dλ = λ - λ0, cosφ = Math.cos(φ), sinφ = Math.sin(φ), k = sinφ0 * sinφ, u0 = d3_geo_areaRingU, v0 = d3_geo_areaRingV, u = cosφ0 * cosφ + k * Math.cos(dλ), v = k * Math.sin(dλ); + d3_geo_areaRingU = u0 * u - v0 * v; + d3_geo_areaRingV = v0 * u + u0 * v; + λ0 = λ, cosφ0 = cosφ, sinφ0 = sinφ; + } + d3_geo_area.lineEnd = function() { + nextPoint(λ00, φ00); + }; + } + d3.geo.bounds = d3_geo_bounds(d3_identity); + function d3_geo_bounds(projectStream) { + var x0, y0, x1, y1; + var bound = { + point: boundPoint, + lineStart: d3_noop, + lineEnd: d3_noop, + polygonStart: function() { + bound.lineEnd = boundPolygonLineEnd; + }, + polygonEnd: function() { + bound.point = boundPoint; + } + }; + function boundPoint(x, y) { + if (x < x0) x0 = x; + if (x > x1) x1 = x; + if (y < y0) y0 = y; + if (y > y1) y1 = y; + } + function boundPolygonLineEnd() { + bound.point = bound.lineEnd = d3_noop; + } + return function(feature) { + y1 = x1 = -(x0 = y0 = Infinity); + d3.geo.stream(feature, projectStream(bound)); + return [ [ x0, y0 ], [ x1, y1 ] ]; + }; + } + d3.geo.centroid = function(object) { + d3_geo_centroidDimension = d3_geo_centroidW = d3_geo_centroidX = d3_geo_centroidY = d3_geo_centroidZ = 0; + d3.geo.stream(object, d3_geo_centroid); + var m; + if (d3_geo_centroidW && Math.abs(m = Math.sqrt(d3_geo_centroidX * d3_geo_centroidX + d3_geo_centroidY * d3_geo_centroidY + d3_geo_centroidZ * d3_geo_centroidZ)) > ε) { + return [ Math.atan2(d3_geo_centroidY, d3_geo_centroidX) * d3_degrees, Math.asin(Math.max(-1, Math.min(1, d3_geo_centroidZ / m))) * d3_degrees ]; + } + }; + var d3_geo_centroidDimension, d3_geo_centroidW, d3_geo_centroidX, d3_geo_centroidY, d3_geo_centroidZ; + var d3_geo_centroid = { + sphere: function() { + if (d3_geo_centroidDimension < 2) { + d3_geo_centroidDimension = 2; + d3_geo_centroidW = d3_geo_centroidX = d3_geo_centroidY = d3_geo_centroidZ = 0; + } + }, + point: d3_geo_centroidPoint, + lineStart: d3_geo_centroidLineStart, + lineEnd: d3_geo_centroidLineEnd, + polygonStart: function() { + if (d3_geo_centroidDimension < 2) { + d3_geo_centroidDimension = 2; + d3_geo_centroidW = d3_geo_centroidX = d3_geo_centroidY = d3_geo_centroidZ = 0; + } + d3_geo_centroid.lineStart = d3_geo_centroidRingStart; + }, + polygonEnd: function() { + d3_geo_centroid.lineStart = d3_geo_centroidLineStart; + } + }; + function d3_geo_centroidPoint(λ, φ) { + if (d3_geo_centroidDimension) return; + ++d3_geo_centroidW; + λ *= d3_radians; + var cosφ = Math.cos(φ *= d3_radians); + d3_geo_centroidX += (cosφ * Math.cos(λ) - d3_geo_centroidX) / d3_geo_centroidW; + d3_geo_centroidY += (cosφ * Math.sin(λ) - d3_geo_centroidY) / d3_geo_centroidW; + d3_geo_centroidZ += (Math.sin(φ) - d3_geo_centroidZ) / d3_geo_centroidW; + } + function d3_geo_centroidRingStart() { + var λ00, φ00; + d3_geo_centroidDimension = 1; + d3_geo_centroidLineStart(); + d3_geo_centroidDimension = 2; + var linePoint = d3_geo_centroid.point; + d3_geo_centroid.point = function(λ, φ) { + linePoint(λ00 = λ, φ00 = φ); + }; + d3_geo_centroid.lineEnd = function() { + d3_geo_centroid.point(λ00, φ00); + d3_geo_centroidLineEnd(); + d3_geo_centroid.lineEnd = d3_geo_centroidLineEnd; + }; + } + function d3_geo_centroidLineStart() { + var x0, y0, z0; + if (d3_geo_centroidDimension > 1) return; + if (d3_geo_centroidDimension < 1) { + d3_geo_centroidDimension = 1; + d3_geo_centroidW = d3_geo_centroidX = d3_geo_centroidY = d3_geo_centroidZ = 0; + } + d3_geo_centroid.point = function(λ, φ) { + λ *= d3_radians; + var cosφ = Math.cos(φ *= d3_radians); + x0 = cosφ * Math.cos(λ); + y0 = cosφ * Math.sin(λ); + z0 = Math.sin(φ); + d3_geo_centroid.point = nextPoint; + }; + function nextPoint(λ, φ) { + λ *= d3_radians; + var cosφ = Math.cos(φ *= d3_radians), x = cosφ * Math.cos(λ), y = cosφ * Math.sin(λ), z = Math.sin(φ), w = Math.atan2(Math.sqrt((w = y0 * z - z0 * y) * w + (w = z0 * x - x0 * z) * w + (w = x0 * y - y0 * x) * w), x0 * x + y0 * y + z0 * z); + d3_geo_centroidW += w; + d3_geo_centroidX += w * (x0 + (x0 = x)); + d3_geo_centroidY += w * (y0 + (y0 = y)); + d3_geo_centroidZ += w * (z0 + (z0 = z)); + } + } + function d3_geo_centroidLineEnd() { + d3_geo_centroid.point = d3_geo_centroidPoint; + } + function d3_geo_cartesian(spherical) { + var λ = spherical[0], φ = spherical[1], cosφ = Math.cos(φ); + return [ cosφ * Math.cos(λ), cosφ * Math.sin(λ), Math.sin(φ) ]; + } + function d3_geo_cartesianDot(a, b) { + return a[0] * b[0] + a[1] * b[1] + a[2] * b[2]; + } + function d3_geo_cartesianCross(a, b) { + return [ a[1] * b[2] - a[2] * b[1], a[2] * b[0] - a[0] * b[2], a[0] * b[1] - a[1] * b[0] ]; + } + function d3_geo_cartesianAdd(a, b) { + a[0] += b[0]; + a[1] += b[1]; + a[2] += b[2]; + } + function d3_geo_cartesianScale(vector, k) { + return [ vector[0] * k, vector[1] * k, vector[2] * k ]; + } + function d3_geo_cartesianNormalize(d) { + var l = Math.sqrt(d[0] * d[0] + d[1] * d[1] + d[2] * d[2]); + d[0] /= l; + d[1] /= l; + d[2] /= l; + } + function d3_true() { + return true; + } + function d3_geo_spherical(cartesian) { + return [ Math.atan2(cartesian[1], cartesian[0]), Math.asin(Math.max(-1, Math.min(1, cartesian[2]))) ]; + } + function d3_geo_sphericalEqual(a, b) { + return Math.abs(a[0] - b[0]) < ε && Math.abs(a[1] - b[1]) < ε; + } + function d3_geo_clipPolygon(segments, compare, inside, interpolate, listener) { + var subject = [], clip = []; + segments.forEach(function(segment) { + if ((n = segment.length - 1) <= 0) return; + var n, p0 = segment[0], p1 = segment[n]; + if (d3_geo_sphericalEqual(p0, p1)) { + listener.lineStart(); + for (var i = 0; i < n; ++i) listener.point((p0 = segment[i])[0], p0[1]); + listener.lineEnd(); + return; + } + var a = { + point: p0, + points: segment, + other: null, + visited: false, + entry: true, + subject: true + }, b = { + point: p0, + points: [ p0 ], + other: a, + visited: false, + entry: false, + subject: false + }; + a.other = b; + subject.push(a); + clip.push(b); + a = { + point: p1, + points: [ p1 ], + other: null, + visited: false, + entry: false, + subject: true + }; + b = { + point: p1, + points: [ p1 ], + other: a, + visited: false, + entry: true, + subject: false + }; + a.other = b; + subject.push(a); + clip.push(b); + }); + clip.sort(compare); + d3_geo_clipPolygonLinkCircular(subject); + d3_geo_clipPolygonLinkCircular(clip); + if (!subject.length) return; + if (inside) for (var i = 1, e = !inside(clip[0].point), n = clip.length; i < n; ++i) { + clip[i].entry = e = !e; + } + var start = subject[0], current, points, point; + while (1) { + current = start; + while (current.visited) if ((current = current.next) === start) return; + points = current.points; + listener.lineStart(); + do { + current.visited = current.other.visited = true; + if (current.entry) { + if (current.subject) { + for (var i = 0; i < points.length; i++) listener.point((point = points[i])[0], point[1]); + } else { + interpolate(current.point, current.next.point, 1, listener); + } + current = current.next; + } else { + if (current.subject) { + points = current.prev.points; + for (var i = points.length; --i >= 0; ) listener.point((point = points[i])[0], point[1]); + } else { + interpolate(current.point, current.prev.point, -1, listener); + } + current = current.prev; + } + current = current.other; + points = current.points; + } while (!current.visited); + listener.lineEnd(); + } + } + function d3_geo_clipPolygonLinkCircular(array) { + if (!(n = array.length)) return; + var n, i = 0, a = array[0], b; + while (++i < n) { + a.next = b = array[i]; + b.prev = a; + a = b; + } + a.next = b = array[0]; + b.prev = a; + } + function d3_geo_clip(pointVisible, clipLine, interpolate) { + return function(listener) { + var line = clipLine(listener); + var clip = { + point: point, + lineStart: lineStart, + lineEnd: lineEnd, + polygonStart: function() { + clip.point = pointRing; + clip.lineStart = ringStart; + clip.lineEnd = ringEnd; + invisible = false; + invisibleArea = visibleArea = 0; + segments = []; + listener.polygonStart(); + }, + polygonEnd: function() { + clip.point = point; + clip.lineStart = lineStart; + clip.lineEnd = lineEnd; + segments = d3.merge(segments); + if (segments.length) { + d3_geo_clipPolygon(segments, d3_geo_clipSort, null, interpolate, listener); + } else if (visibleArea < -ε || invisible && invisibleArea < -ε) { + listener.lineStart(); + interpolate(null, null, 1, listener); + listener.lineEnd(); + } + listener.polygonEnd(); + segments = null; + }, + sphere: function() { + listener.polygonStart(); + listener.lineStart(); + interpolate(null, null, 1, listener); + listener.lineEnd(); + listener.polygonEnd(); + } + }; + function point(λ, φ) { + if (pointVisible(λ, φ)) listener.point(λ, φ); + } + function pointLine(λ, φ) { + line.point(λ, φ); + } + function lineStart() { + clip.point = pointLine; + line.lineStart(); + } + function lineEnd() { + clip.point = point; + line.lineEnd(); + } + var segments, visibleArea, invisibleArea, invisible; + var buffer = d3_geo_clipBufferListener(), ringListener = clipLine(buffer), ring; + function pointRing(λ, φ) { + ringListener.point(λ, φ); + ring.push([ λ, φ ]); + } + function ringStart() { + ringListener.lineStart(); + ring = []; + } + function ringEnd() { + pointRing(ring[0][0], ring[0][1]); + ringListener.lineEnd(); + var clean = ringListener.clean(), ringSegments = buffer.buffer(), segment, n = ringSegments.length; + if (!n) { + invisible = true; + invisibleArea += d3_geo_clipAreaRing(ring, -1); + ring = null; + return; + } + ring = null; + if (clean & 1) { + segment = ringSegments[0]; + visibleArea += d3_geo_clipAreaRing(segment, 1); + var n = segment.length - 1, i = -1, point; + listener.lineStart(); + while (++i < n) listener.point((point = segment[i])[0], point[1]); + listener.lineEnd(); + return; + } + if (n > 1 && clean & 2) ringSegments.push(ringSegments.pop().concat(ringSegments.shift())); + segments.push(ringSegments.filter(d3_geo_clipSegmentLength1)); + } + return clip; + }; + } + function d3_geo_clipSegmentLength1(segment) { + return segment.length > 1; + } + function d3_geo_clipBufferListener() { + var lines = [], line; + return { + lineStart: function() { + lines.push(line = []); + }, + point: function(λ, φ) { + line.push([ λ, φ ]); + }, + lineEnd: d3_noop, + buffer: function() { + var buffer = lines; + lines = []; + line = null; + return buffer; + }, + rejoin: function() { + if (lines.length > 1) lines.push(lines.pop().concat(lines.shift())); + } + }; + } + function d3_geo_clipAreaRing(ring, invisible) { + if (!(n = ring.length)) return 0; + var n, i = 0, area = 0, p = ring[0], λ = p[0], φ = p[1], cosφ = Math.cos(φ), x0 = Math.atan2(invisible * Math.sin(λ) * cosφ, Math.sin(φ)), y0 = 1 - invisible * Math.cos(λ) * cosφ, x1 = x0, x, y; + while (++i < n) { + p = ring[i]; + cosφ = Math.cos(φ = p[1]); + x = Math.atan2(invisible * Math.sin(λ = p[0]) * cosφ, Math.sin(φ)); + y = 1 - invisible * Math.cos(λ) * cosφ; + if (Math.abs(y0 - 2) < ε && Math.abs(y - 2) < ε) continue; + if (Math.abs(y) < ε || Math.abs(y0) < ε) {} else if (Math.abs(Math.abs(x - x0) - π) < ε) { + if (y + y0 > 2) area += 4 * (x - x0); + } else if (Math.abs(y0 - 2) < ε) area += 4 * (x - x1); else area += ((3 * π + x - x0) % (2 * π) - π) * (y0 + y); + x1 = x0, x0 = x, y0 = y; + } + return area; + } + function d3_geo_clipSort(a, b) { + return ((a = a.point)[0] < 0 ? a[1] - π / 2 - ε : π / 2 - a[1]) - ((b = b.point)[0] < 0 ? b[1] - π / 2 - ε : π / 2 - b[1]); + } + var d3_geo_clipAntimeridian = d3_geo_clip(d3_true, d3_geo_clipAntimeridianLine, d3_geo_clipAntimeridianInterpolate); + function d3_geo_clipAntimeridianLine(listener) { + var λ0 = NaN, φ0 = NaN, sλ0 = NaN, clean; + return { + lineStart: function() { + listener.lineStart(); + clean = 1; + }, + point: function(λ1, φ1) { + var sλ1 = λ1 > 0 ? π : -π, dλ = Math.abs(λ1 - λ0); + if (Math.abs(dλ - π) < ε) { + listener.point(λ0, φ0 = (φ0 + φ1) / 2 > 0 ? π / 2 : -π / 2); + listener.point(sλ0, φ0); + listener.lineEnd(); + listener.lineStart(); + listener.point(sλ1, φ0); + listener.point(λ1, φ0); + clean = 0; + } else if (sλ0 !== sλ1 && dλ >= π) { + if (Math.abs(λ0 - sλ0) < ε) λ0 -= sλ0 * ε; + if (Math.abs(λ1 - sλ1) < ε) λ1 -= sλ1 * ε; + φ0 = d3_geo_clipAntimeridianIntersect(λ0, φ0, λ1, φ1); + listener.point(sλ0, φ0); + listener.lineEnd(); + listener.lineStart(); + listener.point(sλ1, φ0); + clean = 0; + } + listener.point(λ0 = λ1, φ0 = φ1); + sλ0 = sλ1; + }, + lineEnd: function() { + listener.lineEnd(); + λ0 = φ0 = NaN; + }, + clean: function() { + return 2 - clean; + } + }; + } + function d3_geo_clipAntimeridianIntersect(λ0, φ0, λ1, φ1) { + var cosφ0, cosφ1, sinλ0_λ1 = Math.sin(λ0 - λ1); + return Math.abs(sinλ0_λ1) > ε ? Math.atan((Math.sin(φ0) * (cosφ1 = Math.cos(φ1)) * Math.sin(λ1) - Math.sin(φ1) * (cosφ0 = Math.cos(φ0)) * Math.sin(λ0)) / (cosφ0 * cosφ1 * sinλ0_λ1)) : (φ0 + φ1) / 2; + } + function d3_geo_clipAntimeridianInterpolate(from, to, direction, listener) { + var φ; + if (from == null) { + φ = direction * π / 2; + listener.point(-π, φ); + listener.point(0, φ); + listener.point(π, φ); + listener.point(π, 0); + listener.point(π, -φ); + listener.point(0, -φ); + listener.point(-π, -φ); + listener.point(-π, 0); + listener.point(-π, φ); + } else if (Math.abs(from[0] - to[0]) > ε) { + var s = (from[0] < to[0] ? 1 : -1) * π; + φ = direction * s / 2; + listener.point(-s, φ); + listener.point(0, φ); + listener.point(s, φ); + } else { + listener.point(to[0], to[1]); + } + } + function d3_geo_clipCircle(radius) { + var cr = Math.cos(radius), smallRadius = cr > 0, notHemisphere = Math.abs(cr) > ε, interpolate = d3_geo_circleInterpolate(radius, 6 * d3_radians); + return d3_geo_clip(visible, clipLine, interpolate); + function visible(λ, φ) { + return Math.cos(λ) * Math.cos(φ) > cr; + } + function clipLine(listener) { + var point0, c0, v0, v00, clean; + return { + lineStart: function() { + v00 = v0 = false; + clean = 1; + }, + point: function(λ, φ) { + var point1 = [ λ, φ ], point2, v = visible(λ, φ), c = smallRadius ? v ? 0 : code(λ, φ) : v ? code(λ + (λ < 0 ? π : -π), φ) : 0; + if (!point0 && (v00 = v0 = v)) listener.lineStart(); + if (v !== v0) { + point2 = intersect(point0, point1); + if (d3_geo_sphericalEqual(point0, point2) || d3_geo_sphericalEqual(point1, point2)) { + point1[0] += ε; + point1[1] += ε; + v = visible(point1[0], point1[1]); + } + } + if (v !== v0) { + clean = 0; + if (v) { + listener.lineStart(); + point2 = intersect(point1, point0); + listener.point(point2[0], point2[1]); + } else { + point2 = intersect(point0, point1); + listener.point(point2[0], point2[1]); + listener.lineEnd(); + } + point0 = point2; + } else if (notHemisphere && point0 && smallRadius ^ v) { + var t; + if (!(c & c0) && (t = intersect(point1, point0, true))) { + clean = 0; + if (smallRadius) { + listener.lineStart(); + listener.point(t[0][0], t[0][1]); + listener.point(t[1][0], t[1][1]); + listener.lineEnd(); + } else { + listener.point(t[1][0], t[1][1]); + listener.lineEnd(); + listener.lineStart(); + listener.point(t[0][0], t[0][1]); + } + } + } + if (v && (!point0 || !d3_geo_sphericalEqual(point0, point1))) { + listener.point(point1[0], point1[1]); + } + point0 = point1, v0 = v, c0 = c; + }, + lineEnd: function() { + if (v0) listener.lineEnd(); + point0 = null; + }, + clean: function() { + return clean | (v00 && v0) << 1; + } + }; + } + function intersect(a, b, two) { + var pa = d3_geo_cartesian(a), pb = d3_geo_cartesian(b); + var n1 = [ 1, 0, 0 ], n2 = d3_geo_cartesianCross(pa, pb), n2n2 = d3_geo_cartesianDot(n2, n2), n1n2 = n2[0], determinant = n2n2 - n1n2 * n1n2; + if (!determinant) return !two && a; + var c1 = cr * n2n2 / determinant, c2 = -cr * n1n2 / determinant, n1xn2 = d3_geo_cartesianCross(n1, n2), A = d3_geo_cartesianScale(n1, c1), B = d3_geo_cartesianScale(n2, c2); + d3_geo_cartesianAdd(A, B); + var u = n1xn2, w = d3_geo_cartesianDot(A, u), uu = d3_geo_cartesianDot(u, u), t2 = w * w - uu * (d3_geo_cartesianDot(A, A) - 1); + if (t2 < 0) return; + var t = Math.sqrt(t2), q = d3_geo_cartesianScale(u, (-w - t) / uu); + d3_geo_cartesianAdd(q, A); + q = d3_geo_spherical(q); + if (!two) return q; + var λ0 = a[0], λ1 = b[0], φ0 = a[1], φ1 = b[1], z; + if (λ1 < λ0) z = λ0, λ0 = λ1, λ1 = z; + var δλ = λ1 - λ0, polar = Math.abs(δλ - π) < ε, meridian = polar || δλ < ε; + if (!polar && φ1 < φ0) z = φ0, φ0 = φ1, φ1 = z; + if (meridian ? polar ? φ0 + φ1 > 0 ^ q[1] < (Math.abs(q[0] - λ0) < ε ? φ0 : φ1) : φ0 <= q[1] && q[1] <= φ1 : δλ > π ^ (λ0 <= q[0] && q[0] <= λ1)) { + var q1 = d3_geo_cartesianScale(u, (-w + t) / uu); + d3_geo_cartesianAdd(q1, A); + return [ q, d3_geo_spherical(q1) ]; + } + } + function code(λ, φ) { + var r = smallRadius ? radius : π - radius, code = 0; + if (λ < -r) code |= 1; else if (λ > r) code |= 2; + if (φ < -r) code |= 4; else if (φ > r) code |= 8; + return code; + } + } + var d3_geo_clipViewMAX = 1e9; + function d3_geo_clipView(x0, y0, x1, y1) { + return function(listener) { + var listener_ = listener, bufferListener = d3_geo_clipBufferListener(), segments, polygon, ring; + var clip = { + point: point, + lineStart: lineStart, + lineEnd: lineEnd, + polygonStart: function() { + listener = bufferListener; + segments = []; + polygon = []; + }, + polygonEnd: function() { + listener = listener_; + if ((segments = d3.merge(segments)).length) { + listener.polygonStart(); + d3_geo_clipPolygon(segments, compare, inside, interpolate, listener); + listener.polygonEnd(); + } else if (insidePolygon([ x0, y0 ])) { + listener.polygonStart(), listener.lineStart(); + interpolate(null, null, 1, listener); + listener.lineEnd(), listener.polygonEnd(); + } + segments = polygon = ring = null; + } + }; + function inside(point) { + var a = corner(point, -1), i = insidePolygon([ a === 0 || a === 3 ? x0 : x1, a > 1 ? y1 : y0 ]); + return i; + } + function insidePolygon(p) { + var wn = 0, n = polygon.length, y = p[1]; + for (var i = 0; i < n; ++i) { + for (var j = 1, v = polygon[i], m = v.length, a = v[0]; j < m; ++j) { + b = v[j]; + if (a[1] <= y) { + if (b[1] > y && isLeft(a, b, p) > 0) ++wn; + } else { + if (b[1] <= y && isLeft(a, b, p) < 0) --wn; + } + a = b; + } + } + return wn !== 0; + } + function isLeft(a, b, c) { + return (b[0] - a[0]) * (c[1] - a[1]) - (c[0] - a[0]) * (b[1] - a[1]); + } + function interpolate(from, to, direction, listener) { + var a = 0, a1 = 0; + if (from == null || (a = corner(from, direction)) !== (a1 = corner(to, direction)) || comparePoints(from, to) < 0 ^ direction > 0) { + do { + listener.point(a === 0 || a === 3 ? x0 : x1, a > 1 ? y1 : y0); + } while ((a = (a + direction + 4) % 4) !== a1); + } else { + listener.point(to[0], to[1]); + } + } + function visible(x, y) { + return x0 <= x && x <= x1 && y0 <= y && y <= y1; + } + function point(x, y) { + if (visible(x, y)) listener.point(x, y); + } + var x__, y__, v__, x_, y_, v_, first; + function lineStart() { + clip.point = linePoint; + if (polygon) polygon.push(ring = []); + first = true; + v_ = false; + x_ = y_ = NaN; + } + function lineEnd() { + if (segments) { + linePoint(x__, y__); + if (v__ && v_) bufferListener.rejoin(); + segments.push(bufferListener.buffer()); + } + clip.point = point; + if (v_) listener.lineEnd(); + } + function linePoint(x, y) { + x = Math.max(-d3_geo_clipViewMAX, Math.min(d3_geo_clipViewMAX, x)); + y = Math.max(-d3_geo_clipViewMAX, Math.min(d3_geo_clipViewMAX, y)); + var v = visible(x, y); + if (polygon) ring.push([ x, y ]); + if (first) { + x__ = x, y__ = y, v__ = v; + first = false; + if (v) { + listener.lineStart(); + listener.point(x, y); + } + } else { + if (v && v_) listener.point(x, y); else { + var a = [ x_, y_ ], b = [ x, y ]; + if (clipLine(a, b)) { + if (!v_) { + listener.lineStart(); + listener.point(a[0], a[1]); + } + listener.point(b[0], b[1]); + if (!v) listener.lineEnd(); + } else { + listener.lineStart(); + listener.point(x, y); } } } - - // Update node chain. - if (isect) { - if (s1 < s2 || (s1 == s2 && b.r < a.r)) d3_layout_packSplice(a, b = j); - else d3_layout_packSplice(a = k, b); - i--; - } else { - d3_layout_packInsert(a, c); - b = c; - bound(c); + x_ = x, y_ = y, v_ = v; + } + return clip; + }; + function corner(p, direction) { + return Math.abs(p[0] - x0) < ε ? direction > 0 ? 0 : 3 : Math.abs(p[0] - x1) < ε ? direction > 0 ? 2 : 1 : Math.abs(p[1] - y0) < ε ? direction > 0 ? 1 : 0 : direction > 0 ? 3 : 2; + } + function compare(a, b) { + return comparePoints(a.point, b.point); + } + function comparePoints(a, b) { + var ca = corner(a, 1), cb = corner(b, 1); + return ca !== cb ? ca - cb : ca === 0 ? b[1] - a[1] : ca === 1 ? a[0] - b[0] : ca === 2 ? a[1] - b[1] : b[0] - a[0]; + } + function clipLine(a, b) { + var dx = b[0] - a[0], dy = b[1] - a[1], t = [ 0, 1 ]; + if (Math.abs(dx) < ε && Math.abs(dy) < ε) return x0 <= a[0] && a[0] <= x1 && y0 <= a[1] && a[1] <= y1; + if (d3_geo_clipViewT(x0 - a[0], dx, t) && d3_geo_clipViewT(a[0] - x1, -dx, t) && d3_geo_clipViewT(y0 - a[1], dy, t) && d3_geo_clipViewT(a[1] - y1, -dy, t)) { + if (t[1] < 1) { + b[0] = a[0] + t[1] * dx; + b[1] = a[1] + t[1] * dy; } - } - } - } - - // Re-center the circles and return the encompassing radius. - var cx = (xMin + xMax) / 2, - cy = (yMin + yMax) / 2, - cr = 0; - for (var i = 0; i < n; i++) { - var node = nodes[i]; - node.x -= cx; - node.y -= cy; - cr = Math.max(cr, node.r + Math.sqrt(node.x * node.x + node.y * node.y)); - } - - // Remove node links. - nodes.forEach(d3_layout_packUnlink); - - return cr; -} - -function d3_layout_packLink(node) { - node._pack_next = node._pack_prev = node; -} - -function d3_layout_packUnlink(node) { - delete node._pack_next; - delete node._pack_prev; -} - -function d3_layout_packTree(node) { - var children = node.children; - if (children && children.length) { - children.forEach(d3_layout_packTree); - node.r = d3_layout_packCircle(children); - } else { - node.r = Math.sqrt(node.value); - } -} - -function d3_layout_packTransform(node, x, y, k) { - var children = node.children; - node.x = (x += k * node.x); - node.y = (y += k * node.y); - node.r *= k; - if (children) { - var i = -1, n = children.length; - while (++i < n) d3_layout_packTransform(children[i], x, y, k); - } -} - -function d3_layout_packPlace(a, b, c) { - var db = a.r + c.r, - dx = b.x - a.x, - dy = b.y - a.y; - if (db && (dx || dy)) { - var da = b.r + c.r, - dc = Math.sqrt(dx * dx + dy * dy), - cos = Math.max(-1, Math.min(1, (db * db + dc * dc - da * da) / (2 * db * dc))), - theta = Math.acos(cos), - x = cos * (db /= dc), - y = Math.sin(theta) * db; - c.x = a.x + x * dx + y * dy; - c.y = a.y + x * dy - y * dx; - } else { - c.x = a.x + db; - c.y = a.y; - } -} -// Implements a hierarchical layout using the cluster (or dendrogram) -// algorithm. -d3.layout.cluster = function() { - var hierarchy = d3.layout.hierarchy().sort(null).value(null), - separation = d3_layout_treeSeparation, - size = [1, 1]; // width, height - - function cluster(d, i) { - var nodes = hierarchy.call(this, d, i), - root = nodes[0], - previousNode, - x = 0, - kx, - ky; - - // First walk, computing the initial x & y values. - d3_layout_treeVisitAfter(root, function(node) { - var children = node.children; - if (children && children.length) { - node.x = d3_layout_clusterX(children); - node.y = d3_layout_clusterY(children); - } else { - node.x = previousNode ? x += separation(node, previousNode) : 0; - node.y = 0; - previousNode = node; - } - }); - - // Compute the left-most, right-most, and depth-most nodes for extents. - var left = d3_layout_clusterLeft(root), - right = d3_layout_clusterRight(root), - x0 = left.x - separation(left, right) / 2, - x1 = right.x + separation(right, left) / 2; - - // Second walk, normalizing x & y to the desired size. - d3_layout_treeVisitAfter(root, function(node) { - node.x = (node.x - x0) / (x1 - x0) * size[0]; - node.y = (1 - (root.y ? node.y / root.y : 1)) * size[1]; - }); - - return nodes; - } - - cluster.separation = function(x) { - if (!arguments.length) return separation; - separation = x; - return cluster; - }; - - cluster.size = function(x) { - if (!arguments.length) return size; - size = x; - return cluster; - }; - - return d3_layout_hierarchyRebind(cluster, hierarchy); -}; - -function d3_layout_clusterY(children) { - return 1 + d3.max(children, function(child) { - return child.y; - }); -} - -function d3_layout_clusterX(children) { - return children.reduce(function(x, child) { - return x + child.x; - }, 0) / children.length; -} - -function d3_layout_clusterLeft(node) { - var children = node.children; - return children && children.length ? d3_layout_clusterLeft(children[0]) : node; -} - -function d3_layout_clusterRight(node) { - var children = node.children, n; - return children && (n = children.length) ? d3_layout_clusterRight(children[n - 1]) : node; -} -// Node-link tree diagram using the Reingold-Tilford "tidy" algorithm -d3.layout.tree = function() { - var hierarchy = d3.layout.hierarchy().sort(null).value(null), - separation = d3_layout_treeSeparation, - size = [1, 1]; // width, height - - function tree(d, i) { - var nodes = hierarchy.call(this, d, i), - root = nodes[0]; - - function firstWalk(node, previousSibling) { - var children = node.children, - layout = node._tree; - if (children && (n = children.length)) { - var n, - firstChild = children[0], - previousChild, - ancestor = firstChild, - child, - i = -1; - while (++i < n) { - child = children[i]; - firstWalk(child, previousChild); - ancestor = apportion(child, previousChild, ancestor); - previousChild = child; - } - d3_layout_treeShift(node); - var midpoint = .5 * (firstChild._tree.prelim + child._tree.prelim); - if (previousSibling) { - layout.prelim = previousSibling._tree.prelim + separation(node, previousSibling); - layout.mod = layout.prelim - midpoint; - } else { - layout.prelim = midpoint; - } - } else { - if (previousSibling) { - layout.prelim = previousSibling._tree.prelim + separation(node, previousSibling); + if (t[0] > 0) { + a[0] += t[0] * dx; + a[1] += t[0] * dy; } + return true; } - } - - function secondWalk(node, x) { - node.x = node._tree.prelim + x; - var children = node.children; - if (children && (n = children.length)) { - var i = -1, - n; - x += node._tree.mod; - while (++i < n) { - secondWalk(children[i], x); - } - } - } - - function apportion(node, previousSibling, ancestor) { - if (previousSibling) { - var vip = node, - vop = node, - vim = previousSibling, - vom = node.parent.children[0], - sip = vip._tree.mod, - sop = vop._tree.mod, - sim = vim._tree.mod, - som = vom._tree.mod, - shift; - while (vim = d3_layout_treeRight(vim), vip = d3_layout_treeLeft(vip), vim && vip) { - vom = d3_layout_treeLeft(vom); - vop = d3_layout_treeRight(vop); - vop._tree.ancestor = node; - shift = vim._tree.prelim + sim - vip._tree.prelim - sip + separation(vim, vip); - if (shift > 0) { - d3_layout_treeMove(d3_layout_treeAncestor(vim, node, ancestor), node, shift); - sip += shift; - sop += shift; - } - sim += vim._tree.mod; - sip += vip._tree.mod; - som += vom._tree.mod; - sop += vop._tree.mod; - } - if (vim && !d3_layout_treeRight(vop)) { - vop._tree.thread = vim; - vop._tree.mod += sim - sop; - } - if (vip && !d3_layout_treeLeft(vom)) { - vom._tree.thread = vip; - vom._tree.mod += sip - som; - ancestor = node; - } - } - return ancestor; - } - - // Initialize temporary layout variables. - d3_layout_treeVisitAfter(root, function(node, previousSibling) { - node._tree = { - ancestor: node, - prelim: 0, - mod: 0, - change: 0, - shift: 0, - number: previousSibling ? previousSibling._tree.number + 1 : 0 - }; - }); - - // Compute the layout using Buchheim et al.'s algorithm. - firstWalk(root); - secondWalk(root, -root._tree.prelim); - - // Compute the left-most, right-most, and depth-most nodes for extents. - var left = d3_layout_treeSearch(root, d3_layout_treeLeftmost), - right = d3_layout_treeSearch(root, d3_layout_treeRightmost), - deep = d3_layout_treeSearch(root, d3_layout_treeDeepest), - x0 = left.x - separation(left, right) / 2, - x1 = right.x + separation(right, left) / 2, - y1 = deep.depth || 1; - - // Clear temporary layout variables; transform x and y. - d3_layout_treeVisitAfter(root, function(node) { - node.x = (node.x - x0) / (x1 - x0) * size[0]; - node.y = node.depth / y1 * size[1]; - delete node._tree; - }); - - return nodes; - } - - tree.separation = function(x) { - if (!arguments.length) return separation; - separation = x; - return tree; - }; - - tree.size = function(x) { - if (!arguments.length) return size; - size = x; - return tree; - }; - - return d3_layout_hierarchyRebind(tree, hierarchy); -}; - -function d3_layout_treeSeparation(a, b) { - return a.parent == b.parent ? 1 : 2; -} - -// function d3_layout_treeSeparationRadial(a, b) { -// return (a.parent == b.parent ? 1 : 2) / a.depth; -// } - -function d3_layout_treeLeft(node) { - var children = node.children; - return children && children.length ? children[0] : node._tree.thread; -} - -function d3_layout_treeRight(node) { - var children = node.children, - n; - return children && (n = children.length) ? children[n - 1] : node._tree.thread; -} - -function d3_layout_treeSearch(node, compare) { - var children = node.children; - if (children && (n = children.length)) { - var child, - n, - i = -1; - while (++i < n) { - if (compare(child = d3_layout_treeSearch(children[i], compare), node) > 0) { - node = child; - } + return false; } } - return node; -} - -function d3_layout_treeRightmost(a, b) { - return a.x - b.x; -} - -function d3_layout_treeLeftmost(a, b) { - return b.x - a.x; -} - -function d3_layout_treeDeepest(a, b) { - return a.depth - b.depth; -} - -function d3_layout_treeVisitAfter(node, callback) { - function visit(node, previousSibling) { - var children = node.children; - if (children && (n = children.length)) { - var child, - previousChild = null, - i = -1, - n; - while (++i < n) { - child = children[i]; - visit(child, previousChild); - previousChild = child; - } - } - callback(node, previousSibling); - } - visit(node, null); -} - -function d3_layout_treeShift(node) { - var shift = 0, - change = 0, - children = node.children, - i = children.length, - child; - while (--i >= 0) { - child = children[i]._tree; - child.prelim += shift; - child.mod += shift; - shift += child.shift + (change += child.change); - } -} - -function d3_layout_treeMove(ancestor, node, shift) { - ancestor = ancestor._tree; - node = node._tree; - var change = shift / (node.number - ancestor.number); - ancestor.change += change; - node.change -= change; - node.shift += shift; - node.prelim += shift; - node.mod += shift; -} - -function d3_layout_treeAncestor(vim, node, ancestor) { - return vim._tree.ancestor.parent == node.parent - ? vim._tree.ancestor - : ancestor; -} -// Squarified Treemaps by Mark Bruls, Kees Huizing, and Jarke J. van Wijk -// Modified to support a target aspect ratio by Jeff Heer -d3.layout.treemap = function() { - var hierarchy = d3.layout.hierarchy(), - round = Math.round, - size = [1, 1], // width, height - padding = null, - pad = d3_layout_treemapPadNull, - sticky = false, - stickies, - ratio = 0.5 * (1 + Math.sqrt(5)); // golden ratio - - // Compute the area for each child based on value & scale. - function scale(children, k) { - var i = -1, - n = children.length, - child, - area; - while (++i < n) { - area = (child = children[i]).value * (k < 0 ? 0 : k); - child.area = isNaN(area) || area <= 0 ? 0 : area; - } - } - - // Recursively arranges the specified node's children into squarified rows. - function squarify(node) { - var children = node.children; - if (children && children.length) { - var rect = pad(node), - row = [], - remaining = children.slice(), // copy-on-write - child, - best = Infinity, // the best row score so far - score, // the current row score - u = Math.min(rect.dx, rect.dy), // initial orientation - n; - scale(remaining, rect.dx * rect.dy / node.value); - row.area = 0; - while ((n = remaining.length) > 0) { - row.push(child = remaining[n - 1]); - row.area += child.area; - if ((score = worst(row, u)) <= best) { // continue with this orientation - remaining.pop(); - best = score; - } else { // abort, and try a different orientation - row.area -= row.pop().area; - position(row, u, rect, false); - u = Math.min(rect.dx, rect.dy); - row.length = row.area = 0; - best = Infinity; - } - } - if (row.length) { - position(row, u, rect, true); - row.length = row.area = 0; - } - children.forEach(squarify); - } - } - - // Recursively resizes the specified node's children into existing rows. - // Preserves the existing layout! - function stickify(node) { - var children = node.children; - if (children && children.length) { - var rect = pad(node), - remaining = children.slice(), // copy-on-write - child, - row = []; - scale(remaining, rect.dx * rect.dy / node.value); - row.area = 0; - while (child = remaining.pop()) { - row.push(child); - row.area += child.area; - if (child.z != null) { - position(row, child.z ? rect.dx : rect.dy, rect, !remaining.length); - row.length = row.area = 0; - } - } - children.forEach(stickify); - } - } - - // Computes the score for the specified row, as the worst aspect ratio. - function worst(row, u) { - var s = row.area, - r, - rmax = 0, - rmin = Infinity, - i = -1, - n = row.length; - while (++i < n) { - if (!(r = row[i].area)) continue; - if (r < rmin) rmin = r; - if (r > rmax) rmax = r; - } - s *= s; - u *= u; - return s - ? Math.max((u * rmax * ratio) / s, s / (u * rmin * ratio)) - : Infinity; - } - - // Positions the specified row of nodes. Modifies `rect`. - function position(row, u, rect, flush) { - var i = -1, - n = row.length, - x = rect.x, - y = rect.y, - v = u ? round(row.area / u) : 0, - o; - if (u == rect.dx) { // horizontal subdivision - if (flush || v > rect.dy) v = rect.dy; // over+underflow - while (++i < n) { - o = row[i]; - o.x = x; - o.y = y; - o.dy = v; - x += o.dx = Math.min(rect.x + rect.dx - x, v ? round(o.area / v) : 0); - } - o.z = true; - o.dx += rect.x + rect.dx - x; // rounding error - rect.y += v; - rect.dy -= v; - } else { // vertical subdivision - if (flush || v > rect.dx) v = rect.dx; // over+underflow - while (++i < n) { - o = row[i]; - o.x = x; - o.y = y; - o.dx = v; - y += o.dy = Math.min(rect.y + rect.dy - y, v ? round(o.area / v) : 0); - } - o.z = false; - o.dy += rect.y + rect.dy - y; // rounding error - rect.x += v; - rect.dx -= v; - } - } - - function treemap(d) { - var nodes = stickies || hierarchy(d), - root = nodes[0]; - root.x = 0; - root.y = 0; - root.dx = size[0]; - root.dy = size[1]; - if (stickies) hierarchy.revalue(root); - scale([root], root.dx * root.dy / root.value); - (stickies ? stickify : squarify)(root); - if (sticky) stickies = nodes; - return nodes; - } - - treemap.size = function(x) { - if (!arguments.length) return size; - size = x; - return treemap; - }; - - treemap.padding = function(x) { - if (!arguments.length) return padding; - - function padFunction(node) { - var p = x.call(treemap, node, node.depth); - return p == null - ? d3_layout_treemapPadNull(node) - : d3_layout_treemapPad(node, typeof p === "number" ? [p, p, p, p] : p); - } - - function padConstant(node) { - return d3_layout_treemapPad(node, x); - } - - var type; - pad = (padding = x) == null ? d3_layout_treemapPadNull - : (type = typeof x) === "function" ? padFunction - : type === "number" ? (x = [x, x, x, x], padConstant) - : padConstant; - return treemap; - }; - - treemap.round = function(x) { - if (!arguments.length) return round != Number; - round = x ? Math.round : Number; - return treemap; - }; - - treemap.sticky = function(x) { - if (!arguments.length) return sticky; - sticky = x; - stickies = null; - return treemap; - }; - - treemap.ratio = function(x) { - if (!arguments.length) return ratio; - ratio = x; - return treemap; - }; - - return d3_layout_hierarchyRebind(treemap, hierarchy); -}; - -function d3_layout_treemapPadNull(node) { - return {x: node.x, y: node.y, dx: node.dx, dy: node.dy}; -} - -function d3_layout_treemapPad(node, padding) { - var x = node.x + padding[3], - y = node.y + padding[0], - dx = node.dx - padding[1] - padding[3], - dy = node.dy - padding[0] - padding[2]; - if (dx < 0) { x += dx / 2; dx = 0; } - if (dy < 0) { y += dy / 2; dy = 0; } - return {x: x, y: y, dx: dx, dy: dy}; -} -d3.csv = function(url, callback) { - d3.text(url, "text/csv", function(text) { - callback(text && d3.csv.parse(text)); - }); -}; -d3.csv.parse = function(text) { - var header; - return d3.csv.parseRows(text, function(row, i) { - if (i) { - var o = {}, j = -1, m = header.length; - while (++j < m) o[header[j]] = row[j]; - return o; + function d3_geo_clipViewT(num, denominator, t) { + if (Math.abs(denominator) < ε) return num <= 0; + var u = num / denominator; + if (denominator > 0) { + if (u > t[1]) return false; + if (u > t[0]) t[0] = u; } else { - header = row; - return null; + if (u < t[0]) return false; + if (u < t[1]) t[1] = u; } + return true; + } + function d3_geo_compose(a, b) { + function compose(x, y) { + return x = a(x, y), b(x[0], x[1]); + } + if (a.invert && b.invert) compose.invert = function(x, y) { + return x = b.invert(x, y), x && a.invert(x[0], x[1]); + }; + return compose; + } + function d3_geo_resample(project) { + var δ2 = .5, maxDepth = 16; + function resample(stream) { + var λ0, x0, y0, a0, b0, c0; + var resample = { + point: point, + lineStart: lineStart, + lineEnd: lineEnd, + polygonStart: function() { + stream.polygonStart(); + resample.lineStart = polygonLineStart; + }, + polygonEnd: function() { + stream.polygonEnd(); + resample.lineStart = lineStart; + } + }; + function point(x, y) { + x = project(x, y); + stream.point(x[0], x[1]); + } + function lineStart() { + x0 = NaN; + resample.point = linePoint; + stream.lineStart(); + } + function linePoint(λ, φ) { + var c = d3_geo_cartesian([ λ, φ ]), p = project(λ, φ); + resampleLineTo(x0, y0, λ0, a0, b0, c0, x0 = p[0], y0 = p[1], λ0 = λ, a0 = c[0], b0 = c[1], c0 = c[2], maxDepth, stream); + stream.point(x0, y0); + } + function lineEnd() { + resample.point = point; + stream.lineEnd(); + } + function polygonLineStart() { + var λ00, φ00, x00, y00, a00, b00, c00; + lineStart(); + resample.point = function(λ, φ) { + linePoint(λ00 = λ, φ00 = φ), x00 = x0, y00 = y0, a00 = a0, b00 = b0, c00 = c0; + resample.point = linePoint; + }; + resample.lineEnd = function() { + resampleLineTo(x0, y0, λ0, a0, b0, c0, x00, y00, λ00, a00, b00, c00, maxDepth, stream); + resample.lineEnd = lineEnd; + lineEnd(); + }; + } + return resample; + } + function resampleLineTo(x0, y0, λ0, a0, b0, c0, x1, y1, λ1, a1, b1, c1, depth, stream) { + var dx = x1 - x0, dy = y1 - y0, d2 = dx * dx + dy * dy; + if (d2 > 4 * δ2 && depth--) { + var a = a0 + a1, b = b0 + b1, c = c0 + c1, m = Math.sqrt(a * a + b * b + c * c), φ2 = Math.asin(c /= m), λ2 = Math.abs(Math.abs(c) - 1) < ε ? (λ0 + λ1) / 2 : Math.atan2(b, a), p = project(λ2, φ2), x2 = p[0], y2 = p[1], dx2 = x2 - x0, dy2 = y2 - y0, dz = dy * dx2 - dx * dy2; + if (dz * dz / d2 > δ2 || Math.abs((dx * dx2 + dy * dy2) / d2 - .5) > .3) { + resampleLineTo(x0, y0, λ0, a0, b0, c0, x2, y2, λ2, a /= m, b /= m, c, depth, stream); + stream.point(x2, y2); + resampleLineTo(x2, y2, λ2, a, b, c, x1, y1, λ1, a1, b1, c1, depth, stream); + } + } + } + resample.precision = function(_) { + if (!arguments.length) return Math.sqrt(δ2); + maxDepth = (δ2 = _ * _) > 0 && 16; + return resample; + }; + return resample; + } + d3.geo.projection = d3_geo_projection; + d3.geo.projectionMutator = d3_geo_projectionMutator; + function d3_geo_projection(project) { + return d3_geo_projectionMutator(function() { + return project; + })(); + } + function d3_geo_projectionMutator(projectAt) { + var project, rotate, projectRotate, projectResample = d3_geo_resample(function(x, y) { + x = project(x, y); + return [ x[0] * k + δx, δy - x[1] * k ]; + }), k = 150, x = 480, y = 250, λ = 0, φ = 0, δλ = 0, δφ = 0, δγ = 0, δx, δy, preclip = d3_geo_clipAntimeridian, postclip = d3_identity, clipAngle = null, clipExtent = null; + function projection(point) { + point = projectRotate(point[0] * d3_radians, point[1] * d3_radians); + return [ point[0] * k + δx, δy - point[1] * k ]; + } + function invert(point) { + point = projectRotate.invert((point[0] - δx) / k, (δy - point[1]) / k); + return point && [ point[0] * d3_degrees, point[1] * d3_degrees ]; + } + projection.stream = function(stream) { + return d3_geo_projectionRadiansRotate(rotate, preclip(projectResample(postclip(stream)))); + }; + projection.clipAngle = function(_) { + if (!arguments.length) return clipAngle; + preclip = _ == null ? (clipAngle = _, d3_geo_clipAntimeridian) : d3_geo_clipCircle((clipAngle = +_) * d3_radians); + return projection; + }; + projection.clipExtent = function(_) { + if (!arguments.length) return clipExtent; + clipExtent = _; + postclip = _ == null ? d3_identity : d3_geo_clipView(_[0][0], _[0][1], _[1][0], _[1][1]); + return projection; + }; + projection.scale = function(_) { + if (!arguments.length) return k; + k = +_; + return reset(); + }; + projection.translate = function(_) { + if (!arguments.length) return [ x, y ]; + x = +_[0]; + y = +_[1]; + return reset(); + }; + projection.center = function(_) { + if (!arguments.length) return [ λ * d3_degrees, φ * d3_degrees ]; + λ = _[0] % 360 * d3_radians; + φ = _[1] % 360 * d3_radians; + return reset(); + }; + projection.rotate = function(_) { + if (!arguments.length) return [ δλ * d3_degrees, δφ * d3_degrees, δγ * d3_degrees ]; + δλ = _[0] % 360 * d3_radians; + δφ = _[1] % 360 * d3_radians; + δγ = _.length > 2 ? _[2] % 360 * d3_radians : 0; + return reset(); + }; + d3.rebind(projection, projectResample, "precision"); + function reset() { + projectRotate = d3_geo_compose(rotate = d3_geo_rotation(δλ, δφ, δγ), project); + var center = project(λ, φ); + δx = x - center[0] * k; + δy = y + center[1] * k; + return projection; + } + return function() { + project = projectAt.apply(this, arguments); + projection.invert = project.invert && invert; + return reset(); + }; + } + function d3_geo_projectionRadiansRotate(rotate, stream) { + return { + point: function(x, y) { + y = rotate(x * d3_radians, y * d3_radians), x = y[0]; + stream.point(x > π ? x - 2 * π : x < -π ? x + 2 * π : x, y[1]); + }, + sphere: function() { + stream.sphere(); + }, + lineStart: function() { + stream.lineStart(); + }, + lineEnd: function() { + stream.lineEnd(); + }, + polygonStart: function() { + stream.polygonStart(); + }, + polygonEnd: function() { + stream.polygonEnd(); + } + }; + } + function d3_geo_equirectangular(λ, φ) { + return [ λ, φ ]; + } + (d3.geo.equirectangular = function() { + return d3_geo_projection(d3_geo_equirectangular); + }).raw = d3_geo_equirectangular.invert = d3_geo_equirectangular; + d3.geo.rotation = function(rotate) { + rotate = d3_geo_rotation(rotate[0] % 360 * d3_radians, rotate[1] * d3_radians, rotate.length > 2 ? rotate[2] * d3_radians : 0); + function forward(coordinates) { + coordinates = rotate(coordinates[0] * d3_radians, coordinates[1] * d3_radians); + return coordinates[0] *= d3_degrees, coordinates[1] *= d3_degrees, coordinates; + } + forward.invert = function(coordinates) { + coordinates = rotate.invert(coordinates[0] * d3_radians, coordinates[1] * d3_radians); + return coordinates[0] *= d3_degrees, coordinates[1] *= d3_degrees, coordinates; + }; + return forward; + }; + function d3_geo_rotation(δλ, δφ, δγ) { + return δλ ? δφ || δγ ? d3_geo_compose(d3_geo_rotationλ(δλ), d3_geo_rotationφγ(δφ, δγ)) : d3_geo_rotationλ(δλ) : δφ || δγ ? d3_geo_rotationφγ(δφ, δγ) : d3_geo_equirectangular; + } + function d3_geo_forwardRotationλ(δλ) { + return function(λ, φ) { + return λ += δλ, [ λ > π ? λ - 2 * π : λ < -π ? λ + 2 * π : λ, φ ]; + }; + } + function d3_geo_rotationλ(δλ) { + var rotation = d3_geo_forwardRotationλ(δλ); + rotation.invert = d3_geo_forwardRotationλ(-δλ); + return rotation; + } + function d3_geo_rotationφγ(δφ, δγ) { + var cosδφ = Math.cos(δφ), sinδφ = Math.sin(δφ), cosδγ = Math.cos(δγ), sinδγ = Math.sin(δγ); + function rotation(λ, φ) { + var cosφ = Math.cos(φ), x = Math.cos(λ) * cosφ, y = Math.sin(λ) * cosφ, z = Math.sin(φ), k = z * cosδφ + x * sinδφ; + return [ Math.atan2(y * cosδγ - k * sinδγ, x * cosδφ - z * sinδφ), Math.asin(Math.max(-1, Math.min(1, k * cosδγ + y * sinδγ))) ]; + } + rotation.invert = function(λ, φ) { + var cosφ = Math.cos(φ), x = Math.cos(λ) * cosφ, y = Math.sin(λ) * cosφ, z = Math.sin(φ), k = z * cosδγ - y * sinδγ; + return [ Math.atan2(y * cosδγ + z * sinδγ, x * cosδφ + k * sinδφ), Math.asin(Math.max(-1, Math.min(1, k * cosδφ - x * sinδφ))) ]; + }; + return rotation; + } + d3.geo.circle = function() { + var origin = [ 0, 0 ], angle, precision = 6, interpolate; + function circle() { + var center = typeof origin === "function" ? origin.apply(this, arguments) : origin, rotate = d3_geo_rotation(-center[0] * d3_radians, -center[1] * d3_radians, 0).invert, ring = []; + interpolate(null, null, 1, { + point: function(x, y) { + ring.push(x = rotate(x, y)); + x[0] *= d3_degrees, x[1] *= d3_degrees; + } + }); + return { + type: "Polygon", + coordinates: [ ring ] + }; + } + circle.origin = function(x) { + if (!arguments.length) return origin; + origin = x; + return circle; + }; + circle.angle = function(x) { + if (!arguments.length) return angle; + interpolate = d3_geo_circleInterpolate((angle = +x) * d3_radians, precision * d3_radians); + return circle; + }; + circle.precision = function(_) { + if (!arguments.length) return precision; + interpolate = d3_geo_circleInterpolate(angle * d3_radians, (precision = +_) * d3_radians); + return circle; + }; + return circle.angle(90); + }; + function d3_geo_circleInterpolate(radius, precision) { + var cr = Math.cos(radius), sr = Math.sin(radius); + return function(from, to, direction, listener) { + if (from != null) { + from = d3_geo_circleAngle(cr, from); + to = d3_geo_circleAngle(cr, to); + if (direction > 0 ? from < to : from > to) from += direction * 2 * π; + } else { + from = radius + direction * 2 * π; + to = radius; + } + var point; + for (var step = direction * precision, t = from; direction > 0 ? t > to : t < to; t -= step) { + listener.point((point = d3_geo_spherical([ cr, -sr * Math.cos(t), -sr * Math.sin(t) ]))[0], point[1]); + } + }; + } + function d3_geo_circleAngle(cr, point) { + var a = d3_geo_cartesian(point); + a[0] -= cr; + d3_geo_cartesianNormalize(a); + var angle = d3_acos(-a[1]); + return ((-a[2] < 0 ? -angle : angle) + 2 * Math.PI - ε) % (2 * Math.PI); + } + d3.geo.distance = function(a, b) { + var Δλ = (b[0] - a[0]) * d3_radians, φ0 = a[1] * d3_radians, φ1 = b[1] * d3_radians, sinΔλ = Math.sin(Δλ), cosΔλ = Math.cos(Δλ), sinφ0 = Math.sin(φ0), cosφ0 = Math.cos(φ0), sinφ1 = Math.sin(φ1), cosφ1 = Math.cos(φ1), t; + return Math.atan2(Math.sqrt((t = cosφ1 * sinΔλ) * t + (t = cosφ0 * sinφ1 - sinφ0 * cosφ1 * cosΔλ) * t), sinφ0 * sinφ1 + cosφ0 * cosφ1 * cosΔλ); + }; + d3.geo.graticule = function() { + var x1, x0, X1, X0, y1, y0, Y1, Y0, dx = 10, dy = dx, DX = 90, DY = 360, x, y, X, Y, precision = 2.5; + function graticule() { + return { + type: "MultiLineString", + coordinates: lines() + }; + } + function lines() { + return d3.range(Math.ceil(X0 / DX) * DX, X1, DX).map(X).concat(d3.range(Math.ceil(Y0 / DY) * DY, Y1, DY).map(Y)).concat(d3.range(Math.ceil(x0 / dx) * dx, x1, dx).filter(function(x) { + return Math.abs(x % DX) > ε; + }).map(x)).concat(d3.range(Math.ceil(y0 / dy) * dy, y1, dy).filter(function(y) { + return Math.abs(y % DY) > ε; + }).map(y)); + } + graticule.lines = function() { + return lines().map(function(coordinates) { + return { + type: "LineString", + coordinates: coordinates + }; + }); + }; + graticule.outline = function() { + return { + type: "Polygon", + coordinates: [ X(X0).concat(Y(Y1).slice(1), X(X1).reverse().slice(1), Y(Y0).reverse().slice(1)) ] + }; + }; + graticule.extent = function(_) { + if (!arguments.length) return graticule.minorExtent(); + return graticule.majorExtent(_).minorExtent(_); + }; + graticule.majorExtent = function(_) { + if (!arguments.length) return [ [ X0, Y0 ], [ X1, Y1 ] ]; + X0 = +_[0][0], X1 = +_[1][0]; + Y0 = +_[0][1], Y1 = +_[1][1]; + if (X0 > X1) _ = X0, X0 = X1, X1 = _; + if (Y0 > Y1) _ = Y0, Y0 = Y1, Y1 = _; + return graticule.precision(precision); + }; + graticule.minorExtent = function(_) { + if (!arguments.length) return [ [ x0, y0 ], [ x1, y1 ] ]; + x0 = +_[0][0], x1 = +_[1][0]; + y0 = +_[0][1], y1 = +_[1][1]; + if (x0 > x1) _ = x0, x0 = x1, x1 = _; + if (y0 > y1) _ = y0, y0 = y1, y1 = _; + return graticule.precision(precision); + }; + graticule.step = function(_) { + if (!arguments.length) return graticule.minorStep(); + return graticule.majorStep(_).minorStep(_); + }; + graticule.majorStep = function(_) { + if (!arguments.length) return [ DX, DY ]; + DX = +_[0], DY = +_[1]; + return graticule; + }; + graticule.minorStep = function(_) { + if (!arguments.length) return [ dx, dy ]; + dx = +_[0], dy = +_[1]; + return graticule; + }; + graticule.precision = function(_) { + if (!arguments.length) return precision; + precision = +_; + x = d3_geo_graticuleX(y0, y1, 90); + y = d3_geo_graticuleY(x0, x1, precision); + X = d3_geo_graticuleX(Y0, Y1, 90); + Y = d3_geo_graticuleY(X0, X1, precision); + return graticule; + }; + return graticule.majorExtent([ [ -180, -90 + ε ], [ 180, 90 - ε ] ]).minorExtent([ [ -180, -80 - ε ], [ 180, 80 + ε ] ]); + }; + function d3_geo_graticuleX(y0, y1, dy) { + var y = d3.range(y0, y1 - ε, dy).concat(y1); + return function(x) { + return y.map(function(y) { + return [ x, y ]; + }); + }; + } + function d3_geo_graticuleY(x0, x1, dx) { + var x = d3.range(x0, x1 - ε, dx).concat(x1); + return function(y) { + return x.map(function(x) { + return [ x, y ]; + }); + }; + } + function d3_source(d) { + return d.source; + } + function d3_target(d) { + return d.target; + } + d3.geo.greatArc = function() { + var source = d3_source, source_, target = d3_target, target_; + function greatArc() { + return { + type: "LineString", + coordinates: [ source_ || source.apply(this, arguments), target_ || target.apply(this, arguments) ] + }; + } + greatArc.distance = function() { + return d3.geo.distance(source_ || source.apply(this, arguments), target_ || target.apply(this, arguments)); + }; + greatArc.source = function(_) { + if (!arguments.length) return source; + source = _, source_ = typeof _ === "function" ? null : _; + return greatArc; + }; + greatArc.target = function(_) { + if (!arguments.length) return target; + target = _, target_ = typeof _ === "function" ? null : _; + return greatArc; + }; + greatArc.precision = function() { + return arguments.length ? greatArc : 0; + }; + return greatArc; + }; + d3.geo.interpolate = function(source, target) { + return d3_geo_interpolate(source[0] * d3_radians, source[1] * d3_radians, target[0] * d3_radians, target[1] * d3_radians); + }; + function d3_geo_interpolate(x0, y0, x1, y1) { + var cy0 = Math.cos(y0), sy0 = Math.sin(y0), cy1 = Math.cos(y1), sy1 = Math.sin(y1), kx0 = cy0 * Math.cos(x0), ky0 = cy0 * Math.sin(x0), kx1 = cy1 * Math.cos(x1), ky1 = cy1 * Math.sin(x1), d = 2 * Math.asin(Math.sqrt(d3_haversin(y1 - y0) + cy0 * cy1 * d3_haversin(x1 - x0))), k = 1 / Math.sin(d); + var interpolate = d ? function(t) { + var B = Math.sin(t *= d) * k, A = Math.sin(d - t) * k, x = A * kx0 + B * kx1, y = A * ky0 + B * ky1, z = A * sy0 + B * sy1; + return [ Math.atan2(y, x) * d3_degrees, Math.atan2(z, Math.sqrt(x * x + y * y)) * d3_degrees ]; + } : function() { + return [ x0 * d3_degrees, y0 * d3_degrees ]; + }; + interpolate.distance = d; + return interpolate; + } + d3.geo.length = function(object) { + d3_geo_lengthSum = 0; + d3.geo.stream(object, d3_geo_length); + return d3_geo_lengthSum; + }; + var d3_geo_lengthSum; + var d3_geo_length = { + sphere: d3_noop, + point: d3_noop, + lineStart: d3_geo_lengthLineStart, + lineEnd: d3_noop, + polygonStart: d3_noop, + polygonEnd: d3_noop + }; + function d3_geo_lengthLineStart() { + var λ0, sinφ0, cosφ0; + d3_geo_length.point = function(λ, φ) { + λ0 = λ * d3_radians, sinφ0 = Math.sin(φ *= d3_radians), cosφ0 = Math.cos(φ); + d3_geo_length.point = nextPoint; + }; + d3_geo_length.lineEnd = function() { + d3_geo_length.point = d3_geo_length.lineEnd = d3_noop; + }; + function nextPoint(λ, φ) { + var sinφ = Math.sin(φ *= d3_radians), cosφ = Math.cos(φ), t = Math.abs((λ *= d3_radians) - λ0), cosΔλ = Math.cos(t); + d3_geo_lengthSum += Math.atan2(Math.sqrt((t = cosφ * Math.sin(t)) * t + (t = cosφ0 * sinφ - sinφ0 * cosφ * cosΔλ) * t), sinφ0 * sinφ + cosφ0 * cosφ * cosΔλ); + λ0 = λ, sinφ0 = sinφ, cosφ0 = cosφ; + } + } + function d3_geo_conic(projectAt) { + var φ0 = 0, φ1 = π / 3, m = d3_geo_projectionMutator(projectAt), p = m(φ0, φ1); + p.parallels = function(_) { + if (!arguments.length) return [ φ0 / π * 180, φ1 / π * 180 ]; + return m(φ0 = _[0] * π / 180, φ1 = _[1] * π / 180); + }; + return p; + } + function d3_geo_conicEqualArea(φ0, φ1) { + var sinφ0 = Math.sin(φ0), n = (sinφ0 + Math.sin(φ1)) / 2, C = 1 + sinφ0 * (2 * n - sinφ0), ρ0 = Math.sqrt(C) / n; + function forward(λ, φ) { + var ρ = Math.sqrt(C - 2 * n * Math.sin(φ)) / n; + return [ ρ * Math.sin(λ *= n), ρ0 - ρ * Math.cos(λ) ]; + } + forward.invert = function(x, y) { + var ρ0_y = ρ0 - y; + return [ Math.atan2(x, ρ0_y) / n, Math.asin((C - (x * x + ρ0_y * ρ0_y) * n * n) / (2 * n)) ]; + }; + return forward; + } + (d3.geo.conicEqualArea = function() { + return d3_geo_conic(d3_geo_conicEqualArea); + }).raw = d3_geo_conicEqualArea; + d3.geo.albersUsa = function() { + var lower48 = d3.geo.conicEqualArea().rotate([ 98, 0 ]).center([ 0, 38 ]).parallels([ 29.5, 45.5 ]); + var alaska = d3.geo.conicEqualArea().rotate([ 160, 0 ]).center([ 0, 60 ]).parallels([ 55, 65 ]); + var hawaii = d3.geo.conicEqualArea().rotate([ 160, 0 ]).center([ 0, 20 ]).parallels([ 8, 18 ]); + var puertoRico = d3.geo.conicEqualArea().rotate([ 60, 0 ]).center([ 0, 10 ]).parallels([ 8, 18 ]); + var alaskaInvert, hawaiiInvert, puertoRicoInvert; + function albersUsa(coordinates) { + return projection(coordinates)(coordinates); + } + function projection(point) { + var lon = point[0], lat = point[1]; + return lat > 50 ? alaska : lon < -140 ? hawaii : lat < 21 ? puertoRico : lower48; + } + albersUsa.invert = function(coordinates) { + return alaskaInvert(coordinates) || hawaiiInvert(coordinates) || puertoRicoInvert(coordinates) || lower48.invert(coordinates); + }; + albersUsa.scale = function(x) { + if (!arguments.length) return lower48.scale(); + lower48.scale(x); + alaska.scale(x * .6); + hawaii.scale(x); + puertoRico.scale(x * 1.5); + return albersUsa.translate(lower48.translate()); + }; + albersUsa.translate = function(x) { + if (!arguments.length) return lower48.translate(); + var dz = lower48.scale(), dx = x[0], dy = x[1]; + lower48.translate(x); + alaska.translate([ dx - .4 * dz, dy + .17 * dz ]); + hawaii.translate([ dx - .19 * dz, dy + .2 * dz ]); + puertoRico.translate([ dx + .58 * dz, dy + .43 * dz ]); + alaskaInvert = d3_geo_albersUsaInvert(alaska, [ [ -180, 50 ], [ -130, 72 ] ]); + hawaiiInvert = d3_geo_albersUsaInvert(hawaii, [ [ -164, 18 ], [ -154, 24 ] ]); + puertoRicoInvert = d3_geo_albersUsaInvert(puertoRico, [ [ -67.5, 17.5 ], [ -65, 19 ] ]); + return albersUsa; + }; + return albersUsa.scale(1e3); + }; + function d3_geo_albersUsaInvert(projection, extent) { + var a = projection(extent[0]), b = projection([ .5 * (extent[0][0] + extent[1][0]), extent[0][1] ]), c = projection([ extent[1][0], extent[0][1] ]), d = projection(extent[1]); + var dya = b[1] - a[1], dxa = b[0] - a[0], dyb = c[1] - b[1], dxb = c[0] - b[0]; + var ma = dya / dxa, mb = dyb / dxb; + var cx = .5 * (ma * mb * (a[1] - c[1]) + mb * (a[0] + b[0]) - ma * (b[0] + c[0])) / (mb - ma), cy = (.5 * (a[0] + b[0]) - cx) / ma + .5 * (a[1] + b[1]); + var dx0 = d[0] - cx, dy0 = d[1] - cy, dx1 = a[0] - cx, dy1 = a[1] - cy, r0 = dx0 * dx0 + dy0 * dy0, r1 = dx1 * dx1 + dy1 * dy1; + var a0 = Math.atan2(dy0, dx0), a1 = Math.atan2(dy1, dx1); + return function(coordinates) { + var dx = coordinates[0] - cx, dy = coordinates[1] - cy, r = dx * dx + dy * dy, a = Math.atan2(dy, dx); + if (r0 < r && r < r1 && a0 < a && a < a1) return projection.invert(coordinates); + }; + } + var d3_geo_pathAreaSum, d3_geo_pathAreaPolygon, d3_geo_pathArea = { + point: d3_noop, + lineStart: d3_noop, + lineEnd: d3_noop, + polygonStart: function() { + d3_geo_pathAreaPolygon = 0; + d3_geo_pathArea.lineStart = d3_geo_pathAreaRingStart; + }, + polygonEnd: function() { + d3_geo_pathArea.lineStart = d3_geo_pathArea.lineEnd = d3_geo_pathArea.point = d3_noop; + d3_geo_pathAreaSum += Math.abs(d3_geo_pathAreaPolygon / 2); + } + }; + function d3_geo_pathAreaRingStart() { + var x00, y00, x0, y0; + d3_geo_pathArea.point = function(x, y) { + d3_geo_pathArea.point = nextPoint; + x00 = x0 = x, y00 = y0 = y; + }; + function nextPoint(x, y) { + d3_geo_pathAreaPolygon += y0 * x - x0 * y; + x0 = x, y0 = y; + } + d3_geo_pathArea.lineEnd = function() { + nextPoint(x00, y00); + }; + } + function d3_geo_pathBuffer() { + var pointCircle = d3_geo_pathCircle(4.5), buffer = []; + var stream = { + point: point, + lineStart: function() { + stream.point = pointLineStart; + }, + lineEnd: lineEnd, + polygonStart: function() { + stream.lineEnd = lineEndPolygon; + }, + polygonEnd: function() { + stream.lineEnd = lineEnd; + stream.point = point; + }, + pointRadius: function(_) { + pointCircle = d3_geo_pathCircle(_); + return stream; + }, + result: function() { + if (buffer.length) { + var result = buffer.join(""); + buffer = []; + return result; + } + } + }; + function point(x, y) { + buffer.push("M", x, ",", y, pointCircle); + } + function pointLineStart(x, y) { + buffer.push("M", x, ",", y); + stream.point = pointLine; + } + function pointLine(x, y) { + buffer.push("L", x, ",", y); + } + function lineEnd() { + stream.point = point; + } + function lineEndPolygon() { + buffer.push("Z"); + } + return stream; + } + var d3_geo_pathCentroid = { + point: d3_geo_pathCentroidPoint, + lineStart: d3_geo_pathCentroidLineStart, + lineEnd: d3_geo_pathCentroidLineEnd, + polygonStart: function() { + d3_geo_pathCentroid.lineStart = d3_geo_pathCentroidRingStart; + }, + polygonEnd: function() { + d3_geo_pathCentroid.point = d3_geo_pathCentroidPoint; + d3_geo_pathCentroid.lineStart = d3_geo_pathCentroidLineStart; + d3_geo_pathCentroid.lineEnd = d3_geo_pathCentroidLineEnd; + } + }; + function d3_geo_pathCentroidPoint(x, y) { + if (d3_geo_centroidDimension) return; + d3_geo_centroidX += x; + d3_geo_centroidY += y; + ++d3_geo_centroidZ; + } + function d3_geo_pathCentroidLineStart() { + var x0, y0; + if (d3_geo_centroidDimension !== 1) { + if (d3_geo_centroidDimension < 1) { + d3_geo_centroidDimension = 1; + d3_geo_centroidX = d3_geo_centroidY = d3_geo_centroidZ = 0; + } else return; + } + d3_geo_pathCentroid.point = function(x, y) { + d3_geo_pathCentroid.point = nextPoint; + x0 = x, y0 = y; + }; + function nextPoint(x, y) { + var dx = x - x0, dy = y - y0, z = Math.sqrt(dx * dx + dy * dy); + d3_geo_centroidX += z * (x0 + x) / 2; + d3_geo_centroidY += z * (y0 + y) / 2; + d3_geo_centroidZ += z; + x0 = x, y0 = y; + } + } + function d3_geo_pathCentroidLineEnd() { + d3_geo_pathCentroid.point = d3_geo_pathCentroidPoint; + } + function d3_geo_pathCentroidRingStart() { + var x00, y00, x0, y0; + if (d3_geo_centroidDimension < 2) { + d3_geo_centroidDimension = 2; + d3_geo_centroidX = d3_geo_centroidY = d3_geo_centroidZ = 0; + } + d3_geo_pathCentroid.point = function(x, y) { + d3_geo_pathCentroid.point = nextPoint; + x00 = x0 = x, y00 = y0 = y; + }; + function nextPoint(x, y) { + var z = y0 * x - x0 * y; + d3_geo_centroidX += z * (x0 + x); + d3_geo_centroidY += z * (y0 + y); + d3_geo_centroidZ += z * 3; + x0 = x, y0 = y; + } + d3_geo_pathCentroid.lineEnd = function() { + nextPoint(x00, y00); + }; + } + function d3_geo_pathContext(context) { + var pointRadius = 4.5; + var stream = { + point: point, + lineStart: function() { + stream.point = pointLineStart; + }, + lineEnd: lineEnd, + polygonStart: function() { + stream.lineEnd = lineEndPolygon; + }, + polygonEnd: function() { + stream.lineEnd = lineEnd; + stream.point = point; + }, + pointRadius: function(_) { + pointRadius = _; + return stream; + }, + result: d3_noop + }; + function point(x, y) { + context.moveTo(x, y); + context.arc(x, y, pointRadius, 0, 2 * π); + } + function pointLineStart(x, y) { + context.moveTo(x, y); + stream.point = pointLine; + } + function pointLine(x, y) { + context.lineTo(x, y); + } + function lineEnd() { + stream.point = point; + } + function lineEndPolygon() { + context.closePath(); + } + return stream; + } + d3.geo.path = function() { + var pointRadius = 4.5, projection, context, projectStream, contextStream; + function path(object) { + if (object) d3.geo.stream(object, projectStream(contextStream.pointRadius(typeof pointRadius === "function" ? +pointRadius.apply(this, arguments) : pointRadius))); + return contextStream.result(); + } + path.area = function(object) { + d3_geo_pathAreaSum = 0; + d3.geo.stream(object, projectStream(d3_geo_pathArea)); + return d3_geo_pathAreaSum; + }; + path.centroid = function(object) { + d3_geo_centroidDimension = d3_geo_centroidX = d3_geo_centroidY = d3_geo_centroidZ = 0; + d3.geo.stream(object, projectStream(d3_geo_pathCentroid)); + return d3_geo_centroidZ ? [ d3_geo_centroidX / d3_geo_centroidZ, d3_geo_centroidY / d3_geo_centroidZ ] : undefined; + }; + path.bounds = function(object) { + return d3_geo_bounds(projectStream)(object); + }; + path.projection = function(_) { + if (!arguments.length) return projection; + projectStream = (projection = _) ? _.stream || d3_geo_pathProjectStream(_) : d3_identity; + return path; + }; + path.context = function(_) { + if (!arguments.length) return context; + contextStream = (context = _) == null ? new d3_geo_pathBuffer() : new d3_geo_pathContext(_); + return path; + }; + path.pointRadius = function(_) { + if (!arguments.length) return pointRadius; + pointRadius = typeof _ === "function" ? _ : +_; + return path; + }; + return path.projection(d3.geo.albersUsa()).context(null); + }; + function d3_geo_pathCircle(radius) { + return "m0," + radius + "a" + radius + "," + radius + " 0 1,1 0," + -2 * radius + "a" + radius + "," + radius + " 0 1,1 0," + +2 * radius + "z"; + } + function d3_geo_pathProjectStream(project) { + var resample = d3_geo_resample(function(λ, φ) { + return project([ λ * d3_degrees, φ * d3_degrees ]); + }); + return function(stream) { + stream = resample(stream); + return { + point: function(λ, φ) { + stream.point(λ * d3_radians, φ * d3_radians); + }, + sphere: function() { + stream.sphere(); + }, + lineStart: function() { + stream.lineStart(); + }, + lineEnd: function() { + stream.lineEnd(); + }, + polygonStart: function() { + stream.polygonStart(); + }, + polygonEnd: function() { + stream.polygonEnd(); + } + }; + }; + } + d3.geo.albers = function() { + return d3.geo.conicEqualArea().parallels([ 29.5, 45.5 ]).rotate([ 98, 0 ]).center([ 0, 38 ]).scale(1e3); + }; + function d3_geo_azimuthal(scale, angle) { + function azimuthal(λ, φ) { + var cosλ = Math.cos(λ), cosφ = Math.cos(φ), k = scale(cosλ * cosφ); + return [ k * cosφ * Math.sin(λ), k * Math.sin(φ) ]; + } + azimuthal.invert = function(x, y) { + var ρ = Math.sqrt(x * x + y * y), c = angle(ρ), sinc = Math.sin(c), cosc = Math.cos(c); + return [ Math.atan2(x * sinc, ρ * cosc), Math.asin(ρ && y * sinc / ρ) ]; + }; + return azimuthal; + } + var d3_geo_azimuthalEqualArea = d3_geo_azimuthal(function(cosλcosφ) { + return Math.sqrt(2 / (1 + cosλcosφ)); + }, function(ρ) { + return 2 * Math.asin(ρ / 2); }); -}; - -d3.csv.parseRows = function(text, f) { - var EOL = {}, // sentinel value for end-of-line - EOF = {}, // sentinel value for end-of-file - rows = [], // output rows - re = /\r\n|[,\r\n]/g, // field separator regex - n = 0, // the current line number - t, // the current token - eol; // is the current token followed by EOL? - - re.lastIndex = 0; // work-around bug in FF 3.6 - - /** @private Returns the next token. */ - function token() { - if (re.lastIndex >= text.length) return EOF; // special case: end of file - if (eol) { eol = false; return EOL; } // special case: end of line - - // special case: quotes - var j = re.lastIndex; - if (text.charCodeAt(j) === 34) { - var i = j; - while (i++ < text.length) { - if (text.charCodeAt(i) === 34) { - if (text.charCodeAt(i + 1) !== 34) break; + (d3.geo.azimuthalEqualArea = function() { + return d3_geo_projection(d3_geo_azimuthalEqualArea); + }).raw = d3_geo_azimuthalEqualArea; + var d3_geo_azimuthalEquidistant = d3_geo_azimuthal(function(cosλcosφ) { + var c = Math.acos(cosλcosφ); + return c && c / Math.sin(c); + }, d3_identity); + (d3.geo.azimuthalEquidistant = function() { + return d3_geo_projection(d3_geo_azimuthalEquidistant); + }).raw = d3_geo_azimuthalEquidistant; + function d3_geo_conicConformal(φ0, φ1) { + var cosφ0 = Math.cos(φ0), t = function(φ) { + return Math.tan(π / 4 + φ / 2); + }, n = φ0 === φ1 ? Math.sin(φ0) : Math.log(cosφ0 / Math.cos(φ1)) / Math.log(t(φ1) / t(φ0)), F = cosφ0 * Math.pow(t(φ0), n) / n; + if (!n) return d3_geo_mercator; + function forward(λ, φ) { + var ρ = Math.abs(Math.abs(φ) - π / 2) < ε ? 0 : F / Math.pow(t(φ), n); + return [ ρ * Math.sin(n * λ), F - ρ * Math.cos(n * λ) ]; + } + forward.invert = function(x, y) { + var ρ0_y = F - y, ρ = d3_sgn(n) * Math.sqrt(x * x + ρ0_y * ρ0_y); + return [ Math.atan2(x, ρ0_y) / n, 2 * Math.atan(Math.pow(F / ρ, 1 / n)) - π / 2 ]; + }; + return forward; + } + (d3.geo.conicConformal = function() { + return d3_geo_conic(d3_geo_conicConformal); + }).raw = d3_geo_conicConformal; + function d3_geo_conicEquidistant(φ0, φ1) { + var cosφ0 = Math.cos(φ0), n = φ0 === φ1 ? Math.sin(φ0) : (cosφ0 - Math.cos(φ1)) / (φ1 - φ0), G = cosφ0 / n + φ0; + if (Math.abs(n) < ε) return d3_geo_equirectangular; + function forward(λ, φ) { + var ρ = G - φ; + return [ ρ * Math.sin(n * λ), G - ρ * Math.cos(n * λ) ]; + } + forward.invert = function(x, y) { + var ρ0_y = G - y; + return [ Math.atan2(x, ρ0_y) / n, G - d3_sgn(n) * Math.sqrt(x * x + ρ0_y * ρ0_y) ]; + }; + return forward; + } + (d3.geo.conicEquidistant = function() { + return d3_geo_conic(d3_geo_conicEquidistant); + }).raw = d3_geo_conicEquidistant; + var d3_geo_gnomonic = d3_geo_azimuthal(function(cosλcosφ) { + return 1 / cosλcosφ; + }, Math.atan); + (d3.geo.gnomonic = function() { + return d3_geo_projection(d3_geo_gnomonic); + }).raw = d3_geo_gnomonic; + function d3_geo_mercator(λ, φ) { + return [ λ, Math.log(Math.tan(π / 4 + φ / 2)) ]; + } + d3_geo_mercator.invert = function(x, y) { + return [ x, 2 * Math.atan(Math.exp(y)) - π / 2 ]; + }; + function d3_geo_mercatorProjection(project) { + var m = d3_geo_projection(project), scale = m.scale, translate = m.translate, clipExtent = m.clipExtent, clipAuto; + m.scale = function() { + var v = scale.apply(m, arguments); + return v === m ? clipAuto ? m.clipExtent(null) : m : v; + }; + m.translate = function() { + var v = translate.apply(m, arguments); + return v === m ? clipAuto ? m.clipExtent(null) : m : v; + }; + m.clipExtent = function(_) { + var v = clipExtent.apply(m, arguments); + if (v === m) { + if (clipAuto = _ == null) { + var k = π * scale(), t = translate(); + clipExtent([ [ t[0] - k, t[1] - k ], [ t[0] + k, t[1] + k ] ]); + } + } else if (clipAuto) { + v = null; + } + return v; + }; + return m.clipExtent(null); + } + (d3.geo.mercator = function() { + return d3_geo_mercatorProjection(d3_geo_mercator); + }).raw = d3_geo_mercator; + var d3_geo_orthographic = d3_geo_azimuthal(function() { + return 1; + }, Math.asin); + (d3.geo.orthographic = function() { + return d3_geo_projection(d3_geo_orthographic); + }).raw = d3_geo_orthographic; + var d3_geo_stereographic = d3_geo_azimuthal(function(cosλcosφ) { + return 1 / (1 + cosλcosφ); + }, function(ρ) { + return 2 * Math.atan(ρ); + }); + (d3.geo.stereographic = function() { + return d3_geo_projection(d3_geo_stereographic); + }).raw = d3_geo_stereographic; + function d3_geo_transverseMercator(λ, φ) { + var B = Math.cos(φ) * Math.sin(λ); + return [ Math.log((1 + B) / (1 - B)) / 2, Math.atan2(Math.tan(φ), Math.cos(λ)) ]; + } + d3_geo_transverseMercator.invert = function(x, y) { + return [ Math.atan2(d3_sinh(x), Math.cos(y)), d3_asin(Math.sin(y) / d3_cosh(x)) ]; + }; + (d3.geo.transverseMercator = function() { + return d3_geo_mercatorProjection(d3_geo_transverseMercator); + }).raw = d3_geo_transverseMercator; + d3.geom = {}; + d3.svg = {}; + function d3_svg_line(projection) { + var x = d3_svg_lineX, y = d3_svg_lineY, defined = d3_true, interpolate = d3_svg_lineLinear, interpolateKey = interpolate.key, tension = .7; + function line(data) { + var segments = [], points = [], i = -1, n = data.length, d, fx = d3_functor(x), fy = d3_functor(y); + function segment() { + segments.push("M", interpolate(projection(points), tension)); + } + while (++i < n) { + if (defined.call(this, d = data[i], i)) { + points.push([ +fx.call(this, d, i), +fy.call(this, d, i) ]); + } else if (points.length) { + segment(); + points = []; + } + } + if (points.length) segment(); + return segments.length ? segments.join("") : null; + } + line.x = function(_) { + if (!arguments.length) return x; + x = _; + return line; + }; + line.y = function(_) { + if (!arguments.length) return y; + y = _; + return line; + }; + line.defined = function(_) { + if (!arguments.length) return defined; + defined = _; + return line; + }; + line.interpolate = function(_) { + if (!arguments.length) return interpolateKey; + if (typeof _ === "function") interpolateKey = interpolate = _; else interpolateKey = (interpolate = d3_svg_lineInterpolators.get(_) || d3_svg_lineLinear).key; + return line; + }; + line.tension = function(_) { + if (!arguments.length) return tension; + tension = _; + return line; + }; + return line; + } + d3.svg.line = function() { + return d3_svg_line(d3_identity); + }; + function d3_svg_lineX(d) { + return d[0]; + } + function d3_svg_lineY(d) { + return d[1]; + } + var d3_svg_lineInterpolators = d3.map({ + linear: d3_svg_lineLinear, + "linear-closed": d3_svg_lineLinearClosed, + "step-before": d3_svg_lineStepBefore, + "step-after": d3_svg_lineStepAfter, + basis: d3_svg_lineBasis, + "basis-open": d3_svg_lineBasisOpen, + "basis-closed": d3_svg_lineBasisClosed, + bundle: d3_svg_lineBundle, + cardinal: d3_svg_lineCardinal, + "cardinal-open": d3_svg_lineCardinalOpen, + "cardinal-closed": d3_svg_lineCardinalClosed, + monotone: d3_svg_lineMonotone + }); + d3_svg_lineInterpolators.forEach(function(key, value) { + value.key = key; + value.closed = /-closed$/.test(key); + }); + function d3_svg_lineLinear(points) { + return points.join("L"); + } + function d3_svg_lineLinearClosed(points) { + return d3_svg_lineLinear(points) + "Z"; + } + function d3_svg_lineStepBefore(points) { + var i = 0, n = points.length, p = points[0], path = [ p[0], ",", p[1] ]; + while (++i < n) path.push("V", (p = points[i])[1], "H", p[0]); + return path.join(""); + } + function d3_svg_lineStepAfter(points) { + var i = 0, n = points.length, p = points[0], path = [ p[0], ",", p[1] ]; + while (++i < n) path.push("H", (p = points[i])[0], "V", p[1]); + return path.join(""); + } + function d3_svg_lineCardinalOpen(points, tension) { + return points.length < 4 ? d3_svg_lineLinear(points) : points[1] + d3_svg_lineHermite(points.slice(1, points.length - 1), d3_svg_lineCardinalTangents(points, tension)); + } + function d3_svg_lineCardinalClosed(points, tension) { + return points.length < 3 ? d3_svg_lineLinear(points) : points[0] + d3_svg_lineHermite((points.push(points[0]), + points), d3_svg_lineCardinalTangents([ points[points.length - 2] ].concat(points, [ points[1] ]), tension)); + } + function d3_svg_lineCardinal(points, tension) { + return points.length < 3 ? d3_svg_lineLinear(points) : points[0] + d3_svg_lineHermite(points, d3_svg_lineCardinalTangents(points, tension)); + } + function d3_svg_lineHermite(points, tangents) { + if (tangents.length < 1 || points.length != tangents.length && points.length != tangents.length + 2) { + return d3_svg_lineLinear(points); + } + var quad = points.length != tangents.length, path = "", p0 = points[0], p = points[1], t0 = tangents[0], t = t0, pi = 1; + if (quad) { + path += "Q" + (p[0] - t0[0] * 2 / 3) + "," + (p[1] - t0[1] * 2 / 3) + "," + p[0] + "," + p[1]; + p0 = points[1]; + pi = 2; + } + if (tangents.length > 1) { + t = tangents[1]; + p = points[pi]; + pi++; + path += "C" + (p0[0] + t0[0]) + "," + (p0[1] + t0[1]) + "," + (p[0] - t[0]) + "," + (p[1] - t[1]) + "," + p[0] + "," + p[1]; + for (var i = 2; i < tangents.length; i++, pi++) { + p = points[pi]; + t = tangents[i]; + path += "S" + (p[0] - t[0]) + "," + (p[1] - t[1]) + "," + p[0] + "," + p[1]; + } + } + if (quad) { + var lp = points[pi]; + path += "Q" + (p[0] + t[0] * 2 / 3) + "," + (p[1] + t[1] * 2 / 3) + "," + lp[0] + "," + lp[1]; + } + return path; + } + function d3_svg_lineCardinalTangents(points, tension) { + var tangents = [], a = (1 - tension) / 2, p0, p1 = points[0], p2 = points[1], i = 1, n = points.length; + while (++i < n) { + p0 = p1; + p1 = p2; + p2 = points[i]; + tangents.push([ a * (p2[0] - p0[0]), a * (p2[1] - p0[1]) ]); + } + return tangents; + } + function d3_svg_lineBasis(points) { + if (points.length < 3) return d3_svg_lineLinear(points); + var i = 1, n = points.length, pi = points[0], x0 = pi[0], y0 = pi[1], px = [ x0, x0, x0, (pi = points[1])[0] ], py = [ y0, y0, y0, pi[1] ], path = [ x0, ",", y0 ]; + d3_svg_lineBasisBezier(path, px, py); + while (++i < n) { + pi = points[i]; + px.shift(); + px.push(pi[0]); + py.shift(); + py.push(pi[1]); + d3_svg_lineBasisBezier(path, px, py); + } + i = -1; + while (++i < 2) { + px.shift(); + px.push(pi[0]); + py.shift(); + py.push(pi[1]); + d3_svg_lineBasisBezier(path, px, py); + } + return path.join(""); + } + function d3_svg_lineBasisOpen(points) { + if (points.length < 4) return d3_svg_lineLinear(points); + var path = [], i = -1, n = points.length, pi, px = [ 0 ], py = [ 0 ]; + while (++i < 3) { + pi = points[i]; + px.push(pi[0]); + py.push(pi[1]); + } + path.push(d3_svg_lineDot4(d3_svg_lineBasisBezier3, px) + "," + d3_svg_lineDot4(d3_svg_lineBasisBezier3, py)); + --i; + while (++i < n) { + pi = points[i]; + px.shift(); + px.push(pi[0]); + py.shift(); + py.push(pi[1]); + d3_svg_lineBasisBezier(path, px, py); + } + return path.join(""); + } + function d3_svg_lineBasisClosed(points) { + var path, i = -1, n = points.length, m = n + 4, pi, px = [], py = []; + while (++i < 4) { + pi = points[i % n]; + px.push(pi[0]); + py.push(pi[1]); + } + path = [ d3_svg_lineDot4(d3_svg_lineBasisBezier3, px), ",", d3_svg_lineDot4(d3_svg_lineBasisBezier3, py) ]; + --i; + while (++i < m) { + pi = points[i % n]; + px.shift(); + px.push(pi[0]); + py.shift(); + py.push(pi[1]); + d3_svg_lineBasisBezier(path, px, py); + } + return path.join(""); + } + function d3_svg_lineBundle(points, tension) { + var n = points.length - 1; + if (n) { + var x0 = points[0][0], y0 = points[0][1], dx = points[n][0] - x0, dy = points[n][1] - y0, i = -1, p, t; + while (++i <= n) { + p = points[i]; + t = i / n; + p[0] = tension * p[0] + (1 - tension) * (x0 + t * dx); + p[1] = tension * p[1] + (1 - tension) * (y0 + t * dy); + } + } + return d3_svg_lineBasis(points); + } + function d3_svg_lineDot4(a, b) { + return a[0] * b[0] + a[1] * b[1] + a[2] * b[2] + a[3] * b[3]; + } + var d3_svg_lineBasisBezier1 = [ 0, 2 / 3, 1 / 3, 0 ], d3_svg_lineBasisBezier2 = [ 0, 1 / 3, 2 / 3, 0 ], d3_svg_lineBasisBezier3 = [ 0, 1 / 6, 2 / 3, 1 / 6 ]; + function d3_svg_lineBasisBezier(path, x, y) { + path.push("C", d3_svg_lineDot4(d3_svg_lineBasisBezier1, x), ",", d3_svg_lineDot4(d3_svg_lineBasisBezier1, y), ",", d3_svg_lineDot4(d3_svg_lineBasisBezier2, x), ",", d3_svg_lineDot4(d3_svg_lineBasisBezier2, y), ",", d3_svg_lineDot4(d3_svg_lineBasisBezier3, x), ",", d3_svg_lineDot4(d3_svg_lineBasisBezier3, y)); + } + function d3_svg_lineSlope(p0, p1) { + return (p1[1] - p0[1]) / (p1[0] - p0[0]); + } + function d3_svg_lineFiniteDifferences(points) { + var i = 0, j = points.length - 1, m = [], p0 = points[0], p1 = points[1], d = m[0] = d3_svg_lineSlope(p0, p1); + while (++i < j) { + m[i] = (d + (d = d3_svg_lineSlope(p0 = p1, p1 = points[i + 1]))) / 2; + } + m[i] = d; + return m; + } + function d3_svg_lineMonotoneTangents(points) { + var tangents = [], d, a, b, s, m = d3_svg_lineFiniteDifferences(points), i = -1, j = points.length - 1; + while (++i < j) { + d = d3_svg_lineSlope(points[i], points[i + 1]); + if (Math.abs(d) < 1e-6) { + m[i] = m[i + 1] = 0; + } else { + a = m[i] / d; + b = m[i + 1] / d; + s = a * a + b * b; + if (s > 9) { + s = d * 3 / Math.sqrt(s); + m[i] = s * a; + m[i + 1] = s * b; + } + } + } + i = -1; + while (++i <= j) { + s = (points[Math.min(j, i + 1)][0] - points[Math.max(0, i - 1)][0]) / (6 * (1 + m[i] * m[i])); + tangents.push([ s || 0, m[i] * s || 0 ]); + } + return tangents; + } + function d3_svg_lineMonotone(points) { + return points.length < 3 ? d3_svg_lineLinear(points) : points[0] + d3_svg_lineHermite(points, d3_svg_lineMonotoneTangents(points)); + } + d3.geom.hull = function(vertices) { + var x = d3_svg_lineX, y = d3_svg_lineY; + if (arguments.length) return hull(vertices); + function hull(data) { + if (data.length < 3) return []; + var fx = d3_functor(x), fy = d3_functor(y), n = data.length, vertices, plen = n - 1, points = [], stack = [], d, i, j, h = 0, x1, y1, x2, y2, u, v, a, sp; + if (fx === d3_svg_lineX && y === d3_svg_lineY) vertices = data; else for (i = 0, + vertices = []; i < n; ++i) { + vertices.push([ +fx.call(this, d = data[i], i), +fy.call(this, d, i) ]); + } + for (i = 1; i < n; ++i) { + if (vertices[i][1] < vertices[h][1]) { + h = i; + } else if (vertices[i][1] == vertices[h][1]) { + h = vertices[i][0] < vertices[h][0] ? i : h; + } + } + for (i = 0; i < n; ++i) { + if (i === h) continue; + y1 = vertices[i][1] - vertices[h][1]; + x1 = vertices[i][0] - vertices[h][0]; + points.push({ + angle: Math.atan2(y1, x1), + index: i + }); + } + points.sort(function(a, b) { + return a.angle - b.angle; + }); + a = points[0].angle; + v = points[0].index; + u = 0; + for (i = 1; i < plen; ++i) { + j = points[i].index; + if (a == points[i].angle) { + x1 = vertices[v][0] - vertices[h][0]; + y1 = vertices[v][1] - vertices[h][1]; + x2 = vertices[j][0] - vertices[h][0]; + y2 = vertices[j][1] - vertices[h][1]; + if (x1 * x1 + y1 * y1 >= x2 * x2 + y2 * y2) { + points[i].index = -1; + } else { + points[u].index = -1; + a = points[i].angle; + u = i; + v = j; + } + } else { + a = points[i].angle; + u = i; + v = j; + } + } + stack.push(h); + for (i = 0, j = 0; i < 2; ++j) { + if (points[j].index !== -1) { + stack.push(points[j].index); i++; } } - re.lastIndex = i + 2; - var c = text.charCodeAt(i + 1); - if (c === 13) { - eol = true; - if (text.charCodeAt(i + 2) === 10) re.lastIndex++; - } else if (c === 10) { - eol = true; - } - return text.substring(j + 1, i).replace(/""/g, "\""); - } - - // common case - var m = re.exec(text); - if (m) { - eol = m[0].charCodeAt(0) !== 44; - return text.substring(j, m.index); - } - re.lastIndex = text.length; - return text.substring(j); - } - - while ((t = token()) !== EOF) { - var a = []; - while ((t !== EOL) && (t !== EOF)) { - a.push(t); - t = token(); - } - if (f && !(a = f(a, n++))) continue; - rows.push(a); - } - - return rows; -}; -d3.csv.format = function(rows) { - return rows.map(d3_csv_formatRow).join("\n"); -}; - -function d3_csv_formatRow(row) { - return row.map(d3_csv_formatValue).join(","); -} - -function d3_csv_formatValue(text) { - return /[",\n]/.test(text) - ? "\"" + text.replace(/\"/g, "\"\"") + "\"" - : text; -} -d3.geo = {}; - -var d3_geo_radians = Math.PI / 180; -// TODO clip input coordinates on opposite hemisphere -d3.geo.azimuthal = function() { - var mode = "orthographic", // or stereographic, gnomonic, equidistant or equalarea - origin, - scale = 200, - translate = [480, 250], - x0, - y0, - cy0, - sy0; - - function azimuthal(coordinates) { - var x1 = coordinates[0] * d3_geo_radians - x0, - y1 = coordinates[1] * d3_geo_radians, - cx1 = Math.cos(x1), - sx1 = Math.sin(x1), - cy1 = Math.cos(y1), - sy1 = Math.sin(y1), - cc = mode !== "orthographic" ? sy0 * sy1 + cy0 * cy1 * cx1 : null, - c, - k = mode === "stereographic" ? 1 / (1 + cc) - : mode === "gnomonic" ? 1 / cc - : mode === "equidistant" ? (c = Math.acos(cc), c ? c / Math.sin(c) : 0) - : mode === "equalarea" ? Math.sqrt(2 / (1 + cc)) - : 1, - x = k * cy1 * sx1, - y = k * (sy0 * cy1 * cx1 - cy0 * sy1); - return [ - scale * x + translate[0], - scale * y + translate[1] - ]; - } - - azimuthal.invert = function(coordinates) { - var x = (coordinates[0] - translate[0]) / scale, - y = (coordinates[1] - translate[1]) / scale, - p = Math.sqrt(x * x + y * y), - c = mode === "stereographic" ? 2 * Math.atan(p) - : mode === "gnomonic" ? Math.atan(p) - : mode === "equidistant" ? p - : mode === "equalarea" ? 2 * Math.asin(.5 * p) - : Math.asin(p), - sc = Math.sin(c), - cc = Math.cos(c); - return [ - (x0 + Math.atan2(x * sc, p * cy0 * cc + y * sy0 * sc)) / d3_geo_radians, - Math.asin(cc * sy0 - (p ? (y * sc * cy0) / p : 0)) / d3_geo_radians - ]; - }; - - azimuthal.mode = function(x) { - if (!arguments.length) return mode; - mode = x + ""; - return azimuthal; - }; - - azimuthal.origin = function(x) { - if (!arguments.length) return origin; - origin = x; - x0 = origin[0] * d3_geo_radians; - y0 = origin[1] * d3_geo_radians; - cy0 = Math.cos(y0); - sy0 = Math.sin(y0); - return azimuthal; - }; - - azimuthal.scale = function(x) { - if (!arguments.length) return scale; - scale = +x; - return azimuthal; - }; - - azimuthal.translate = function(x) { - if (!arguments.length) return translate; - translate = [+x[0], +x[1]]; - return azimuthal; - }; - - return azimuthal.origin([0, 0]); -}; -// Derived from Tom Carden's Albers implementation for Protovis. -// http://gist.github.com/476238 -// http://mathworld.wolfram.com/AlbersEqual-AreaConicProjection.html - -d3.geo.albers = function() { - var origin = [-98, 38], - parallels = [29.5, 45.5], - scale = 1000, - translate = [480, 250], - lng0, // d3_geo_radians * origin[0] - n, - C, - p0; - - function albers(coordinates) { - var t = n * (d3_geo_radians * coordinates[0] - lng0), - p = Math.sqrt(C - 2 * n * Math.sin(d3_geo_radians * coordinates[1])) / n; - return [ - scale * p * Math.sin(t) + translate[0], - scale * (p * Math.cos(t) - p0) + translate[1] - ]; - } - - albers.invert = function(coordinates) { - var x = (coordinates[0] - translate[0]) / scale, - y = (coordinates[1] - translate[1]) / scale, - p0y = p0 + y, - t = Math.atan2(x, p0y), - p = Math.sqrt(x * x + p0y * p0y); - return [ - (lng0 + t / n) / d3_geo_radians, - Math.asin((C - p * p * n * n) / (2 * n)) / d3_geo_radians - ]; - }; - - function reload() { - var phi1 = d3_geo_radians * parallels[0], - phi2 = d3_geo_radians * parallels[1], - lat0 = d3_geo_radians * origin[1], - s = Math.sin(phi1), - c = Math.cos(phi1); - lng0 = d3_geo_radians * origin[0]; - n = .5 * (s + Math.sin(phi2)); - C = c * c + 2 * n * s; - p0 = Math.sqrt(C - 2 * n * Math.sin(lat0)) / n; - return albers; - } - - albers.origin = function(x) { - if (!arguments.length) return origin; - origin = [+x[0], +x[1]]; - return reload(); - }; - - albers.parallels = function(x) { - if (!arguments.length) return parallels; - parallels = [+x[0], +x[1]]; - return reload(); - }; - - albers.scale = function(x) { - if (!arguments.length) return scale; - scale = +x; - return albers; - }; - - albers.translate = function(x) { - if (!arguments.length) return translate; - translate = [+x[0], +x[1]]; - return albers; - }; - - return reload(); -}; - -// A composite projection for the United States, 960x500. The set of standard -// parallels for each region comes from USGS, which is published here: -// http://egsc.usgs.gov/isb/pubs/MapProjections/projections.html#albers -// TODO allow the composite projection to be rescaled? -d3.geo.albersUsa = function() { - var lower48 = d3.geo.albers(); - - var alaska = d3.geo.albers() - .origin([-160, 60]) - .parallels([55, 65]); - - var hawaii = d3.geo.albers() - .origin([-160, 20]) - .parallels([8, 18]); - - var puertoRico = d3.geo.albers() - .origin([-60, 10]) - .parallels([8, 18]); - - function albersUsa(coordinates) { - var lon = coordinates[0], - lat = coordinates[1]; - return (lat > 50 ? alaska - : lon < -140 ? hawaii - : lat < 21 ? puertoRico - : lower48)(coordinates); - } - - albersUsa.scale = function(x) { - if (!arguments.length) return lower48.scale(); - lower48.scale(x); - alaska.scale(x * .6); - hawaii.scale(x); - puertoRico.scale(x * 1.5); - return albersUsa.translate(lower48.translate()); - }; - - albersUsa.translate = function(x) { - if (!arguments.length) return lower48.translate(); - var dz = lower48.scale() / 1000, - dx = x[0], - dy = x[1]; - lower48.translate(x); - alaska.translate([dx - 400 * dz, dy + 170 * dz]); - hawaii.translate([dx - 190 * dz, dy + 200 * dz]); - puertoRico.translate([dx + 580 * dz, dy + 430 * dz]); - return albersUsa; - }; - - return albersUsa.scale(lower48.scale()); -}; -d3.geo.bonne = function() { - var scale = 200, - translate = [480, 250], - x0, // origin longitude in radians - y0, // origin latitude in radians - y1, // parallel latitude in radians - c1; // cot(y1) - - function bonne(coordinates) { - var x = coordinates[0] * d3_geo_radians - x0, - y = coordinates[1] * d3_geo_radians - y0; - if (y1) { - var p = c1 + y1 - y, E = x * Math.cos(y) / p; - x = p * Math.sin(E); - y = p * Math.cos(E) - c1; - } else { - x *= Math.cos(y); - y *= -1; - } - return [ - scale * x + translate[0], - scale * y + translate[1] - ]; - } - - bonne.invert = function(coordinates) { - var x = (coordinates[0] - translate[0]) / scale, - y = (coordinates[1] - translate[1]) / scale; - if (y1) { - var c = c1 + y, p = Math.sqrt(x * x + c * c); - y = c1 + y1 - p; - x = x0 + p * Math.atan2(x, c) / Math.cos(y); - } else { - y *= -1; - x /= Math.cos(y); - } - return [ - x / d3_geo_radians, - y / d3_geo_radians - ]; - }; - - // 90° for Werner, 0° for Sinusoidal - bonne.parallel = function(x) { - if (!arguments.length) return y1 / d3_geo_radians; - c1 = 1 / Math.tan(y1 = x * d3_geo_radians); - return bonne; - }; - - bonne.origin = function(x) { - if (!arguments.length) return [x0 / d3_geo_radians, y0 / d3_geo_radians]; - x0 = x[0] * d3_geo_radians; - y0 = x[1] * d3_geo_radians; - return bonne; - }; - - bonne.scale = function(x) { - if (!arguments.length) return scale; - scale = +x; - return bonne; - }; - - bonne.translate = function(x) { - if (!arguments.length) return translate; - translate = [+x[0], +x[1]]; - return bonne; - }; - - return bonne.origin([0, 0]).parallel(45); -}; -d3.geo.equirectangular = function() { - var scale = 500, - translate = [480, 250]; - - function equirectangular(coordinates) { - var x = coordinates[0] / 360, - y = -coordinates[1] / 360; - return [ - scale * x + translate[0], - scale * y + translate[1] - ]; - } - - equirectangular.invert = function(coordinates) { - var x = (coordinates[0] - translate[0]) / scale, - y = (coordinates[1] - translate[1]) / scale; - return [ - 360 * x, - -360 * y - ]; - }; - - equirectangular.scale = function(x) { - if (!arguments.length) return scale; - scale = +x; - return equirectangular; - }; - - equirectangular.translate = function(x) { - if (!arguments.length) return translate; - translate = [+x[0], +x[1]]; - return equirectangular; - }; - - return equirectangular; -}; -d3.geo.mercator = function() { - var scale = 500, - translate = [480, 250]; - - function mercator(coordinates) { - var x = coordinates[0] / 360, - y = -(Math.log(Math.tan(Math.PI / 4 + coordinates[1] * d3_geo_radians / 2)) / d3_geo_radians) / 360; - return [ - scale * x + translate[0], - scale * Math.max(-.5, Math.min(.5, y)) + translate[1] - ]; - } - - mercator.invert = function(coordinates) { - var x = (coordinates[0] - translate[0]) / scale, - y = (coordinates[1] - translate[1]) / scale; - return [ - 360 * x, - 2 * Math.atan(Math.exp(-360 * y * d3_geo_radians)) / d3_geo_radians - 90 - ]; - }; - - mercator.scale = function(x) { - if (!arguments.length) return scale; - scale = +x; - return mercator; - }; - - mercator.translate = function(x) { - if (!arguments.length) return translate; - translate = [+x[0], +x[1]]; - return mercator; - }; - - return mercator; -}; -function d3_geo_type(types, defaultValue) { - return function(object) { - return object && types.hasOwnProperty(object.type) ? types[object.type](object) : defaultValue; - }; -} -/** - * Returns a function that, given a GeoJSON object (e.g., a feature), returns - * the corresponding SVG path. The function can be customized by overriding the - * projection. Point features are mapped to circles with a default radius of - * 4.5px; the radius can be specified either as a constant or a function that - * is evaluated per object. - */ -d3.geo.path = function() { - var pointRadius = 4.5, - pointCircle = d3_path_circle(pointRadius), - projection = d3.geo.albersUsa(); - - function path(d, i) { - if (typeof pointRadius === "function") { - pointCircle = d3_path_circle(pointRadius.apply(this, arguments)); - } - return pathType(d) || null; - } - - function project(coordinates) { - return projection(coordinates).join(","); - } - - var pathType = d3_geo_type({ - - FeatureCollection: function(o) { - var path = [], - features = o.features, - i = -1, // features.index - n = features.length; - while (++i < n) path.push(pathType(features[i].geometry)); - return path.join(""); - }, - - Feature: function(o) { - return pathType(o.geometry); - }, - - Point: function(o) { - return "M" + project(o.coordinates) + pointCircle; - }, - - MultiPoint: function(o) { - var path = [], - coordinates = o.coordinates, - i = -1, // coordinates.index - n = coordinates.length; - while (++i < n) path.push("M", project(coordinates[i]), pointCircle); - return path.join(""); - }, - - LineString: function(o) { - var path = ["M"], - coordinates = o.coordinates, - i = -1, // coordinates.index - n = coordinates.length; - while (++i < n) path.push(project(coordinates[i]), "L"); - path.pop(); - return path.join(""); - }, - - MultiLineString: function(o) { - var path = [], - coordinates = o.coordinates, - i = -1, // coordinates.index - n = coordinates.length, - subcoordinates, // coordinates[i] - j, // subcoordinates.index - m; // subcoordinates.length - while (++i < n) { - subcoordinates = coordinates[i]; - j = -1; - m = subcoordinates.length; - path.push("M"); - while (++j < m) path.push(project(subcoordinates[j]), "L"); - path.pop(); - } - return path.join(""); - }, - - Polygon: function(o) { - var path = [], - coordinates = o.coordinates, - i = -1, // coordinates.index - n = coordinates.length, - subcoordinates, // coordinates[i] - j, // subcoordinates.index - m; // subcoordinates.length - while (++i < n) { - subcoordinates = coordinates[i]; - j = -1; - if ((m = subcoordinates.length - 1) > 0) { - path.push("M"); - while (++j < m) path.push(project(subcoordinates[j]), "L"); - path[path.length - 1] = "Z"; + sp = stack.length; + for (;j < plen; ++j) { + if (points[j].index === -1) continue; + while (!d3_geom_hullCCW(stack[sp - 2], stack[sp - 1], points[j].index, vertices)) { + --sp; } + stack[sp++] = points[j].index; } - return path.join(""); - }, - - MultiPolygon: function(o) { - var path = [], - coordinates = o.coordinates, - i = -1, // coordinates index - n = coordinates.length, - subcoordinates, // coordinates[i] - j, // subcoordinates index - m, // subcoordinates.length - subsubcoordinates, // subcoordinates[j] - k, // subsubcoordinates index - p; // subsubcoordinates.length - while (++i < n) { - subcoordinates = coordinates[i]; - j = -1; - m = subcoordinates.length; - while (++j < m) { - subsubcoordinates = subcoordinates[j]; - k = -1; - if ((p = subsubcoordinates.length - 1) > 0) { - path.push("M"); - while (++k < p) path.push(project(subsubcoordinates[k]), "L"); - path[path.length - 1] = "Z"; - } - } + var poly = []; + for (i = 0; i < sp; ++i) { + poly.push(data[stack[i]]); } - return path.join(""); - }, - - GeometryCollection: function(o) { - var path = [], - geometries = o.geometries, - i = -1, // geometries index - n = geometries.length; - while (++i < n) path.push(pathType(geometries[i])); - return path.join(""); + return poly; } - - }); - - var areaType = path.area = d3_geo_type({ - - FeatureCollection: function(o) { - var area = 0, - features = o.features, - i = -1, // features.index - n = features.length; - while (++i < n) area += areaType(features[i]); - return area; - }, - - Feature: function(o) { - return areaType(o.geometry); - }, - - Polygon: function(o) { - return polygonArea(o.coordinates); - }, - - MultiPolygon: function(o) { - var sum = 0, - coordinates = o.coordinates, - i = -1, // coordinates index - n = coordinates.length; - while (++i < n) sum += polygonArea(coordinates[i]); - return sum; - }, - - GeometryCollection: function(o) { - var sum = 0, - geometries = o.geometries, - i = -1, // geometries index - n = geometries.length; - while (++i < n) sum += areaType(geometries[i]); - return sum; - } - - }, 0); - - function polygonArea(coordinates) { - var sum = area(coordinates[0]), // exterior ring - i = 0, // coordinates.index - n = coordinates.length; - while (++i < n) sum -= area(coordinates[i]); // holes - return sum; - } - - function polygonCentroid(coordinates) { - var polygon = d3.geom.polygon(coordinates[0].map(projection)), // exterior ring - area = polygon.area(), - centroid = polygon.centroid(area < 0 ? (area *= -1, 1) : -1), - x = centroid[0], - y = centroid[1], - z = area, - i = 0, // coordinates index - n = coordinates.length; - while (++i < n) { - polygon = d3.geom.polygon(coordinates[i].map(projection)); // holes - area = polygon.area(); - centroid = polygon.centroid(area < 0 ? (area *= -1, 1) : -1); - x -= centroid[0]; - y -= centroid[1]; - z -= area; - } - return [x, y, 6 * z]; // weighted centroid - } - - var centroidType = path.centroid = d3_geo_type({ - - // TODO FeatureCollection - // TODO Point - // TODO MultiPoint - // TODO LineString - // TODO MultiLineString - // TODO GeometryCollection - - Feature: function(o) { - return centroidType(o.geometry); - }, - - Polygon: function(o) { - var centroid = polygonCentroid(o.coordinates); - return [centroid[0] / centroid[2], centroid[1] / centroid[2]]; - }, - - MultiPolygon: function(o) { - var area = 0, - coordinates = o.coordinates, - centroid, - x = 0, - y = 0, - z = 0, - i = -1, // coordinates index - n = coordinates.length; - while (++i < n) { - centroid = polygonCentroid(coordinates[i]); - x += centroid[0]; - y += centroid[1]; - z += centroid[2]; - } - return [x / z, y / z]; - } - - }); - - function area(coordinates) { - return Math.abs(d3.geom.polygon(coordinates.map(projection)).area()); - } - - path.projection = function(x) { - projection = x; - return path; - }; - - path.pointRadius = function(x) { - if (typeof x === "function") pointRadius = x; - else { - pointRadius = +x; - pointCircle = d3_path_circle(pointRadius); - } - return path; - }; - - return path; -}; - -function d3_path_circle(radius) { - return "m0," + radius - + "a" + radius + "," + radius + " 0 1,1 0," + (-2 * radius) - + "a" + radius + "," + radius + " 0 1,1 0," + (+2 * radius) - + "z"; -} -/** - * Given a GeoJSON object, returns the corresponding bounding box. The bounding - * box is represented by a two-dimensional array: [[left, bottom], [right, - * top]], where left is the minimum longitude, bottom is the minimum latitude, - * right is maximum longitude, and top is the maximum latitude. - */ -d3.geo.bounds = function(feature) { - var left = Infinity, - bottom = Infinity, - right = -Infinity, - top = -Infinity; - d3_geo_bounds(feature, function(x, y) { - if (x < left) left = x; - if (x > right) right = x; - if (y < bottom) bottom = y; - if (y > top) top = y; - }); - return [[left, bottom], [right, top]]; -}; - -function d3_geo_bounds(o, f) { - if (d3_geo_boundsTypes.hasOwnProperty(o.type)) d3_geo_boundsTypes[o.type](o, f); -} - -var d3_geo_boundsTypes = { - Feature: d3_geo_boundsFeature, - FeatureCollection: d3_geo_boundsFeatureCollection, - GeometryCollection: d3_geo_boundsGeometryCollection, - LineString: d3_geo_boundsLineString, - MultiLineString: d3_geo_boundsMultiLineString, - MultiPoint: d3_geo_boundsLineString, - MultiPolygon: d3_geo_boundsMultiPolygon, - Point: d3_geo_boundsPoint, - Polygon: d3_geo_boundsPolygon -}; - -function d3_geo_boundsFeature(o, f) { - d3_geo_bounds(o.geometry, f); -} - -function d3_geo_boundsFeatureCollection(o, f) { - for (var a = o.features, i = 0, n = a.length; i < n; i++) { - d3_geo_bounds(a[i].geometry, f); - } -} - -function d3_geo_boundsGeometryCollection(o, f) { - for (var a = o.geometries, i = 0, n = a.length; i < n; i++) { - d3_geo_bounds(a[i], f); - } -} - -function d3_geo_boundsLineString(o, f) { - for (var a = o.coordinates, i = 0, n = a.length; i < n; i++) { - f.apply(null, a[i]); - } -} - -function d3_geo_boundsMultiLineString(o, f) { - for (var a = o.coordinates, i = 0, n = a.length; i < n; i++) { - for (var b = a[i], j = 0, m = b.length; j < m; j++) { - f.apply(null, b[j]); - } - } -} - -function d3_geo_boundsMultiPolygon(o, f) { - for (var a = o.coordinates, i = 0, n = a.length; i < n; i++) { - for (var b = a[i][0], j = 0, m = b.length; j < m; j++) { - f.apply(null, b[j]); - } - } -} - -function d3_geo_boundsPoint(o, f) { - f.apply(null, o.coordinates); -} - -function d3_geo_boundsPolygon(o, f) { - for (var a = o.coordinates[0], i = 0, n = a.length; i < n; i++) { - f.apply(null, a[i]); - } -} -// TODO breakAtDateLine? - -d3.geo.circle = function() { - var origin = [0, 0], - degrees = 90 - 1e-2, - radians = degrees * d3_geo_radians, - arc = d3.geo.greatArc().target(d3_identity); - - function circle() { - // TODO render a circle as a Polygon - } - - function visible(point) { - return arc.distance(point) < radians; - } - - circle.clip = function(d) { - arc.source(typeof origin === "function" ? origin.apply(this, arguments) : origin); - return clipType(d); - }; - - var clipType = d3_geo_type({ - - FeatureCollection: function(o) { - var features = o.features.map(clipType).filter(d3_identity); - return features && (o = Object.create(o), o.features = features, o); - }, - - Feature: function(o) { - var geometry = clipType(o.geometry); - return geometry && (o = Object.create(o), o.geometry = geometry, o); - }, - - Point: function(o) { - return visible(o.coordinates) && o; - }, - - MultiPoint: function(o) { - var coordinates = o.coordinates.filter(visible); - return coordinates.length && { - type: o.type, - coordinates: coordinates - }; - }, - - LineString: function(o) { - var coordinates = clip(o.coordinates); - return coordinates.length && (o = Object.create(o), o.coordinates = coordinates, o); - }, - - MultiLineString: function(o) { - var coordinates = o.coordinates.map(clip).filter(function(d) { return d.length; }); - return coordinates.length && (o = Object.create(o), o.coordinates = coordinates, o); - }, - - Polygon: function(o) { - var coordinates = o.coordinates.map(clip); - return coordinates[0].length && (o = Object.create(o), o.coordinates = coordinates, o); - }, - - MultiPolygon: function(o) { - var coordinates = o.coordinates.map(function(d) { return d.map(clip); }).filter(function(d) { return d[0].length; }); - return coordinates.length && (o = Object.create(o), o.coordinates = coordinates, o); - }, - - GeometryCollection: function(o) { - var geometries = o.geometries.map(clipType).filter(d3_identity); - return geometries.length && (o = Object.create(o), o.geometries = geometries, o); - } - - }); - - function clip(coordinates) { - var i = -1, - n = coordinates.length, - clipped = [], - p0, - p1, - p2, - d0, - d1; - - while (++i < n) { - d1 = arc.distance(p2 = coordinates[i]); - if (d1 < radians) { - if (p1) clipped.push(d3_geo_greatArcInterpolate(p1, p2)((d0 - radians) / (d0 - d1))); - clipped.push(p2); - p0 = p1 = null; - } else { - p1 = p2; - if (!p0 && clipped.length) { - clipped.push(d3_geo_greatArcInterpolate(clipped[clipped.length - 1], p1)((radians - d0) / (d1 - d0))); - p0 = p1; - } - } - d0 = d1; - } - - if (p1 && clipped.length) { - d1 = arc.distance(p2 = clipped[0]); - clipped.push(d3_geo_greatArcInterpolate(p1, p2)((d0 - radians) / (d0 - d1))); - } - - return resample(clipped); - } - - // Resample coordinates, creating great arcs between each. - function resample(coordinates) { - var i = 0, - n = coordinates.length, - j, - m, - resampled = n ? [coordinates[0]] : coordinates, - resamples, - origin = arc.source(); - - while (++i < n) { - resamples = arc.source(coordinates[i - 1])(coordinates[i]).coordinates; - for (j = 0, m = resamples.length; ++j < m;) resampled.push(resamples[j]); - } - - arc.source(origin); - return resampled; - } - - circle.origin = function(x) { - if (!arguments.length) return origin; - origin = x; - return circle; - }; - - circle.angle = function(x) { - if (!arguments.length) return degrees; - radians = (degrees = +x) * d3_geo_radians; - return circle; - }; - - // Precision is specified in degrees. - circle.precision = function(x) { - if (!arguments.length) return arc.precision(); - arc.precision(x); - return circle; - }; - - return circle; -} -d3.geo.greatArc = function() { - var source = d3_geo_greatArcSource, - target = d3_geo_greatArcTarget, - precision = 6 * d3_geo_radians; - - function greatArc() { - var a = typeof source === "function" ? source.apply(this, arguments) : source, - b = typeof target === "function" ? target.apply(this, arguments) : target, - i = d3_geo_greatArcInterpolate(a, b), - dt = precision / i.d, - t = 0, - coordinates = [a]; - while ((t += dt) < 1) coordinates.push(i(t)); - coordinates.push(b); - return { - type: "LineString", - coordinates: coordinates + hull.x = function(_) { + return arguments.length ? (x = _, hull) : x; }; - } - - // Length returned in radians; multiply by radius for distance. - greatArc.distance = function() { - var a = typeof source === "function" ? source.apply(this, arguments) : source, - b = typeof target === "function" ? target.apply(this, arguments) : target; - return d3_geo_greatArcInterpolate(a, b).d; + hull.y = function(_) { + return arguments.length ? (y = _, hull) : y; + }; + return hull; }; - - greatArc.source = function(x) { - if (!arguments.length) return source; - source = x; - return greatArc; - }; - - greatArc.target = function(x) { - if (!arguments.length) return target; - target = x; - return greatArc; - }; - - // Precision is specified in degrees. - greatArc.precision = function(x) { - if (!arguments.length) return precision / d3_geo_radians; - precision = x * d3_geo_radians; - return greatArc; - }; - - return greatArc; -}; - -function d3_geo_greatArcSource(d) { - return d.source; -} - -function d3_geo_greatArcTarget(d) { - return d.target; -} - -function d3_geo_greatArcInterpolate(a, b) { - var x0 = a[0] * d3_geo_radians, cx0 = Math.cos(x0), sx0 = Math.sin(x0), - y0 = a[1] * d3_geo_radians, cy0 = Math.cos(y0), sy0 = Math.sin(y0), - x1 = b[0] * d3_geo_radians, cx1 = Math.cos(x1), sx1 = Math.sin(x1), - y1 = b[1] * d3_geo_radians, cy1 = Math.cos(y1), sy1 = Math.sin(y1), - d = interpolate.d = Math.acos(Math.max(-1, Math.min(1, sy0 * sy1 + cy0 * cy1 * Math.cos(x1 - x0)))), - sd = Math.sin(d); - - // From http://williams.best.vwh.net/avform.htm#Intermediate - function interpolate(t) { - var A = Math.sin(d - (t *= d)) / sd, - B = Math.sin(t) / sd, - x = A * cy0 * cx0 + B * cy1 * cx1, - y = A * cy0 * sx0 + B * cy1 * sx1, - z = A * sy0 + B * sy1; - return [ - Math.atan2(y, x) / d3_geo_radians, - Math.atan2(z, Math.sqrt(x * x + y * y)) / d3_geo_radians - ]; + function d3_geom_hullCCW(i1, i2, i3, v) { + var t, a, b, c, d, e, f; + t = v[i1]; + a = t[0]; + b = t[1]; + t = v[i2]; + c = t[0]; + d = t[1]; + t = v[i3]; + e = t[0]; + f = t[1]; + return (f - b) * (c - a) - (d - b) * (e - a) > 0; } - - return interpolate; -} -d3.geo.greatCircle = d3.geo.circle; -d3.geom = {}; -/** - * Computes a contour for a given input grid function using the marching - * squares algorithm. Returns the contour polygon as an array of points. - * - * @param grid a two-input function(x, y) that returns true for values - * inside the contour and false for values outside the contour. - * @param start an optional starting point [x, y] on the grid. - * @returns polygon [[x1, y1], [x2, y2], …] - */ -d3.geom.contour = function(grid, start) { - var s = start || d3_geom_contourStart(grid), // starting point - c = [], // contour polygon - x = s[0], // current x position - y = s[1], // current y position - dx = 0, // next x direction - dy = 0, // next y direction - pdx = NaN, // previous x direction - pdy = NaN, // previous y direction - i = 0; - - do { - // determine marching squares index - i = 0; - if (grid(x-1, y-1)) i += 1; - if (grid(x, y-1)) i += 2; - if (grid(x-1, y )) i += 4; - if (grid(x, y )) i += 8; - - // determine next direction - if (i === 6) { - dx = pdy === -1 ? -1 : 1; - dy = 0; - } else if (i === 9) { - dx = 0; - dy = pdx === 1 ? -1 : 1; - } else { - dx = d3_geom_contourDx[i]; - dy = d3_geom_contourDy[i]; - } - - // update contour polygon - if (dx != pdx && dy != pdy) { - c.push([x, y]); - pdx = dx; - pdy = dy; - } - - x += dx; - y += dy; - } while (s[0] != x || s[1] != y); - - return c; -}; - -// lookup tables for marching directions -var d3_geom_contourDx = [1, 0, 1, 1,-1, 0,-1, 1,0, 0,0,0,-1, 0,-1,NaN], - d3_geom_contourDy = [0,-1, 0, 0, 0,-1, 0, 0,1,-1,1,1, 0,-1, 0,NaN]; - -function d3_geom_contourStart(grid) { - var x = 0, - y = 0; - - // search for a starting point; begin at origin - // and proceed along outward-expanding diagonals - while (true) { - if (grid(x,y)) { - return [x,y]; - } - if (x === 0) { - x = y + 1; - y = 0; - } else { - x = x - 1; - y = y + 1; - } - } -} -/** - * Computes the 2D convex hull of a set of points using Graham's scanning - * algorithm. The algorithm has been implemented as described in Cormen, - * Leiserson, and Rivest's Introduction to Algorithms. The running time of - * this algorithm is O(n log n), where n is the number of input points. - * - * @param vertices [[x1, y1], [x2, y2], …] - * @returns polygon [[x1, y1], [x2, y2], …] - */ -d3.geom.hull = function(vertices) { - if (vertices.length < 3) return []; - - var len = vertices.length, - plen = len - 1, - points = [], - stack = [], - i, j, h = 0, x1, y1, x2, y2, u, v, a, sp; - - // find the starting ref point: leftmost point with the minimum y coord - for (i=1; i= (x2*x2 + y2*y2)) { - points[i].index = -1; - } else { - points[u].index = -1; - a = points[i].angle; - u = i; - v = j; + d3.geom.polygon = function(coordinates) { + coordinates.area = function() { + var i = 0, n = coordinates.length, area = coordinates[n - 1][1] * coordinates[0][0] - coordinates[n - 1][0] * coordinates[0][1]; + while (++i < n) { + area += coordinates[i - 1][1] * coordinates[i][0] - coordinates[i - 1][0] * coordinates[i][1]; } - } else { - a = points[i].angle; - u = i; - v = j; - } - } - - // initialize the stack - stack.push(h); - for (i=0, j=0; i<2; ++j) { - if (points[j].index !== -1) { - stack.push(points[j].index); - i++; - } - } - sp = stack.length; - - // do graham's scan - for (; j 0; -} -// Note: requires coordinates to be counterclockwise and convex! -d3.geom.polygon = function(coordinates) { - - coordinates.area = function() { - var i = 0, - n = coordinates.length, - a = coordinates[n - 1][0] * coordinates[0][1], - b = coordinates[n - 1][1] * coordinates[0][0]; - while (++i < n) { - a += coordinates[i - 1][0] * coordinates[i][1]; - b += coordinates[i - 1][1] * coordinates[i][0]; - } - return (b - a) * .5; - }; - - coordinates.centroid = function(k) { - var i = -1, - n = coordinates.length, - x = 0, - y = 0, - a, - b = coordinates[n - 1], - c; - if (!arguments.length) k = -1 / (6 * coordinates.area()); - while (++i < n) { - a = b; - b = coordinates[i]; - c = a[0] * b[1] - b[0] * a[1]; - x += (a[0] + b[0]) * c; - y += (a[1] + b[1]) * c; - } - return [x * k, y * k]; - }; - - // The Sutherland-Hodgman clipping algorithm. - coordinates.clip = function(subject) { - var input, - i = -1, - n = coordinates.length, - j, - m, - a = coordinates[n - 1], - b, - c, - d; - while (++i < n) { - input = subject.slice(); - subject.length = 0; - b = coordinates[i]; - c = input[(m = input.length) - 1]; - j = -1; - while (++j < m) { - d = input[j]; - if (d3_geom_polygonInside(d, a, b)) { - if (!d3_geom_polygonInside(c, a, b)) { + return area * .5; + }; + coordinates.centroid = function(k) { + var i = -1, n = coordinates.length, x = 0, y = 0, a, b = coordinates[n - 1], c; + if (!arguments.length) k = -1 / (6 * coordinates.area()); + while (++i < n) { + a = b; + b = coordinates[i]; + c = a[0] * b[1] - b[0] * a[1]; + x += (a[0] + b[0]) * c; + y += (a[1] + b[1]) * c; + } + return [ x * k, y * k ]; + }; + coordinates.clip = function(subject) { + var input, i = -1, n = coordinates.length, j, m, a = coordinates[n - 1], b, c, d; + while (++i < n) { + input = subject.slice(); + subject.length = 0; + b = coordinates[i]; + c = input[(m = input.length) - 1]; + j = -1; + while (++j < m) { + d = input[j]; + if (d3_geom_polygonInside(d, a, b)) { + if (!d3_geom_polygonInside(c, a, b)) { + subject.push(d3_geom_polygonIntersect(c, d, a, b)); + } + subject.push(d); + } else if (d3_geom_polygonInside(c, a, b)) { subject.push(d3_geom_polygonIntersect(c, d, a, b)); } - subject.push(d); - } else if (d3_geom_polygonInside(c, a, b)) { - subject.push(d3_geom_polygonIntersect(c, d, a, b)); + c = d; } - c = d; + a = b; } - a = b; - } - return subject; + return subject; + }; + return coordinates; }; - - return coordinates; -}; - -function d3_geom_polygonInside(p, a, b) { - return (b[0] - a[0]) * (p[1] - a[1]) < (b[1] - a[1]) * (p[0] - a[0]); -} - -// Intersect two infinite lines cd and ab. -function d3_geom_polygonIntersect(c, d, a, b) { - var x1 = c[0], x2 = d[0], x3 = a[0], x4 = b[0], - y1 = c[1], y2 = d[1], y3 = a[1], y4 = b[1], - x13 = x1 - x3, - x21 = x2 - x1, - x43 = x4 - x3, - y13 = y1 - y3, - y21 = y2 - y1, - y43 = y4 - y3, - ua = (x43 * y13 - y43 * x13) / (y43 * x21 - x43 * y21); - return [x1 + ua * x21, y1 + ua * y21]; -} -// Adapted from Nicolas Garcia Belmonte's JIT implementation: -// http://blog.thejit.org/2010/02/12/voronoi-tessellation/ -// http://blog.thejit.org/assets/voronoijs/voronoi.js -// See lib/jit/LICENSE for details. - -// Notes: -// -// This implementation does not clip the returned polygons, so if you want to -// clip them to a particular shape you will need to do that either in SVG or by -// post-processing with d3.geom.polygon's clip method. -// -// If any vertices are coincident or have NaN positions, the behavior of this -// method is undefined. Most likely invalid polygons will be returned. You -// should filter invalid points, and consolidate coincident points, before -// computing the tessellation. - -/** - * @param vertices [[x1, y1], [x2, y2], …] - * @returns polygons [[[x1, y1], [x2, y2], …], …] - */ -d3.geom.voronoi = function(vertices) { - var polygons = vertices.map(function() { return []; }); - - d3_voronoi_tessellate(vertices, function(e) { - var s1, - s2, - x1, - x2, - y1, - y2; - if (e.a === 1 && e.b >= 0) { - s1 = e.ep.r; - s2 = e.ep.l; - } else { - s1 = e.ep.l; - s2 = e.ep.r; - } - if (e.a === 1) { - y1 = s1 ? s1.y : -1e6; - x1 = e.c - e.b * y1; - y2 = s2 ? s2.y : 1e6; - x2 = e.c - e.b * y2; - } else { - x1 = s1 ? s1.x : -1e6; - y1 = e.c - e.a * x1; - x2 = s2 ? s2.x : 1e6; - y2 = e.c - e.a * x2; - } - var v1 = [x1, y1], - v2 = [x2, y2]; - polygons[e.region.l.index].push(v1, v2); - polygons[e.region.r.index].push(v1, v2); - }); - - // Reconnect the polygon segments into counterclockwise loops. - return polygons.map(function(polygon, i) { - var cx = vertices[i][0], - cy = vertices[i][1]; - polygon.forEach(function(v) { - v.angle = Math.atan2(v[0] - cx, v[1] - cy); + function d3_geom_polygonInside(p, a, b) { + return (b[0] - a[0]) * (p[1] - a[1]) < (b[1] - a[1]) * (p[0] - a[0]); + } + function d3_geom_polygonIntersect(c, d, a, b) { + var x1 = c[0], x3 = a[0], x21 = d[0] - x1, x43 = b[0] - x3, y1 = c[1], y3 = a[1], y21 = d[1] - y1, y43 = b[1] - y3, ua = (x43 * (y1 - y3) - y43 * (x1 - x3)) / (y43 * x21 - x43 * y21); + return [ x1 + ua * x21, y1 + ua * y21 ]; + } + d3.geom.delaunay = function(vertices) { + var edges = vertices.map(function() { + return []; + }), triangles = []; + d3_geom_voronoiTessellate(vertices, function(e) { + edges[e.region.l.index].push(vertices[e.region.r.index]); }); - return polygon.sort(function(a, b) { - return a.angle - b.angle; - }).filter(function(d, i) { - return !i || (d.angle - polygon[i - 1].angle > 1e-10); + edges.forEach(function(edge, i) { + var v = vertices[i], cx = v[0], cy = v[1]; + edge.forEach(function(v) { + v.angle = Math.atan2(v[0] - cx, v[1] - cy); + }); + edge.sort(function(a, b) { + return a.angle - b.angle; + }); + for (var j = 0, m = edge.length - 1; j < m; j++) { + triangles.push([ v, edge[j], edge[j + 1] ]); + } }); - }); -}; - -var d3_voronoi_opposite = {"l": "r", "r": "l"}; - -function d3_voronoi_tessellate(vertices, callback) { - - var Sites = { - list: vertices - .map(function(v, i) { + return triangles; + }; + d3.geom.voronoi = function(points) { + var size = null, x = d3_svg_lineX, y = d3_svg_lineY, clip; + if (arguments.length) return voronoi(points); + function voronoi(data) { + var points, polygons = data.map(function() { + return []; + }), fx = d3_functor(x), fy = d3_functor(y), d, i, n = data.length, Z = 1e6; + if (fx === d3_svg_lineX && fy === d3_svg_lineY) points = data; else for (points = [], + i = 0; i < n; ++i) { + points.push([ +fx.call(this, d = data[i], i), +fy.call(this, d, i) ]); + } + d3_geom_voronoiTessellate(points, function(e) { + var s1, s2, x1, x2, y1, y2; + if (e.a === 1 && e.b >= 0) { + s1 = e.ep.r; + s2 = e.ep.l; + } else { + s1 = e.ep.l; + s2 = e.ep.r; + } + if (e.a === 1) { + y1 = s1 ? s1.y : -Z; + x1 = e.c - e.b * y1; + y2 = s2 ? s2.y : Z; + x2 = e.c - e.b * y2; + } else { + x1 = s1 ? s1.x : -Z; + y1 = e.c - e.a * x1; + x2 = s2 ? s2.x : Z; + y2 = e.c - e.a * x2; + } + var v1 = [ x1, y1 ], v2 = [ x2, y2 ]; + polygons[e.region.l.index].push(v1, v2); + polygons[e.region.r.index].push(v1, v2); + }); + polygons = polygons.map(function(polygon, i) { + var cx = points[i][0], cy = points[i][1], angle = polygon.map(function(v) { + return Math.atan2(v[0] - cx, v[1] - cy); + }), order = d3.range(polygon.length).sort(function(a, b) { + return angle[a] - angle[b]; + }); + return order.filter(function(d, i) { + return !i || angle[d] - angle[order[i - 1]] > ε; + }).map(function(d) { + return polygon[d]; + }); + }); + polygons.forEach(function(polygon, i) { + var n = polygon.length; + if (!n) return polygon.push([ -Z, -Z ], [ -Z, Z ], [ Z, Z ], [ Z, -Z ]); + if (n > 2) return; + var p0 = points[i], p1 = polygon[0], p2 = polygon[1], x0 = p0[0], y0 = p0[1], x1 = p1[0], y1 = p1[1], x2 = p2[0], y2 = p2[1], dx = Math.abs(x2 - x1), dy = y2 - y1; + if (Math.abs(dy) < ε) { + var y = y0 < y1 ? -Z : Z; + polygon.push([ -Z, y ], [ Z, y ]); + } else if (dx < ε) { + var x = x0 < x1 ? -Z : Z; + polygon.push([ x, -Z ], [ x, Z ]); + } else { + var y = (x2 - x1) * (y1 - y0) < (x1 - x0) * (y2 - y1) ? Z : -Z, z = Math.abs(dy) - dx; + if (Math.abs(z) < ε) { + polygon.push([ dy < 0 ? y : -y, y ]); + } else { + if (z > 0) y *= -1; + polygon.push([ -Z, y ], [ Z, y ]); + } + } + }); + if (clip) for (i = 0; i < n; ++i) clip(polygons[i]); + for (i = 0; i < n; ++i) polygons[i].point = data[i]; + return polygons; + } + voronoi.x = function(_) { + return arguments.length ? (x = _, voronoi) : x; + }; + voronoi.y = function(_) { + return arguments.length ? (y = _, voronoi) : y; + }; + voronoi.size = function(_) { + if (!arguments.length) return size; + if (_ == null) { + clip = null; + } else { + size = [ +_[0], +_[1] ]; + clip = d3.geom.polygon([ [ 0, 0 ], [ 0, size[1] ], size, [ size[0], 0 ] ]).clip; + } + return voronoi; + }; + voronoi.links = function(data) { + var points, graph = data.map(function() { + return []; + }), links = [], fx = d3_functor(x), fy = d3_functor(y), d, i, n = data.length; + if (fx === d3_svg_lineX && fy === d3_svg_lineY) points = data; else for (i = 0; i < n; ++i) { + points.push([ +fx.call(this, d = data[i], i), +fy.call(this, d, i) ]); + } + d3_geom_voronoiTessellate(points, function(e) { + var l = e.region.l.index, r = e.region.r.index; + if (graph[l][r]) return; + graph[l][r] = graph[r][l] = true; + links.push({ + source: data[l], + target: data[r] + }); + }); + return links; + }; + voronoi.triangles = function(data) { + if (x === d3_svg_lineX && y === d3_svg_lineY) return d3.geom.delaunay(data); + var points, point, fx = d3_functor(x), fy = d3_functor(y), d, i, n; + for (i = 0, points = [], n = data.length; i < n; ++i) { + point = [ +fx.call(this, d = data[i], i), +fy.call(this, d, i) ]; + point.data = d; + points.push(point); + } + return d3.geom.delaunay(points).map(function(triangle) { + return triangle.map(function(point) { + return point.data; + }); + }); + }; + return voronoi; + }; + var d3_geom_voronoiOpposite = { + l: "r", + r: "l" + }; + function d3_geom_voronoiTessellate(points, callback) { + var Sites = { + list: points.map(function(v, i) { return { index: i, x: v[0], y: v[1] }; - }) - .sort(function(a, b) { - return a.y < b.y ? -1 - : a.y > b.y ? 1 - : a.x < b.x ? -1 - : a.x > b.x ? 1 - : 0; + }).sort(function(a, b) { + return a.y < b.y ? -1 : a.y > b.y ? 1 : a.x < b.x ? -1 : a.x > b.x ? 1 : 0; }), - bottomSite: null - }; - - var EdgeList = { - list: [], - leftEnd: null, - rightEnd: null, - - init: function() { - EdgeList.leftEnd = EdgeList.createHalfEdge(null, "l"); - EdgeList.rightEnd = EdgeList.createHalfEdge(null, "l"); - EdgeList.leftEnd.r = EdgeList.rightEnd; - EdgeList.rightEnd.l = EdgeList.leftEnd; - EdgeList.list.unshift(EdgeList.leftEnd, EdgeList.rightEnd); - }, - - createHalfEdge: function(edge, side) { - return { - edge: edge, - side: side, - vertex: null, - "l": null, - "r": null - }; - }, - - insert: function(lb, he) { - he.l = lb; - he.r = lb.r; - lb.r.l = he; - lb.r = he; - }, - - leftBound: function(p) { - var he = EdgeList.leftEnd; - do { - he = he.r; - } while (he != EdgeList.rightEnd && Geom.rightOf(he, p)); - he = he.l; - return he; - }, - - del: function(he) { - he.l.r = he.r; - he.r.l = he.l; - he.edge = null; - }, - - right: function(he) { - return he.r; - }, - - left: function(he) { - return he.l; - }, - - leftRegion: function(he) { - return he.edge == null - ? Sites.bottomSite - : he.edge.region[he.side]; - }, - - rightRegion: function(he) { - return he.edge == null - ? Sites.bottomSite - : he.edge.region[d3_voronoi_opposite[he.side]]; - } - }; - - var Geom = { - - bisect: function(s1, s2) { - var newEdge = { - region: {"l": s1, "r": s2}, - ep: {"l": null, "r": null} - }; - - var dx = s2.x - s1.x, - dy = s2.y - s1.y, - adx = dx > 0 ? dx : -dx, - ady = dy > 0 ? dy : -dy; - - newEdge.c = s1.x * dx + s1.y * dy - + (dx * dx + dy * dy) * .5; - - if (adx > ady) { - newEdge.a = 1; - newEdge.b = dy / dx; - newEdge.c /= dx; - } else { - newEdge.b = 1; - newEdge.a = dx / dy; - newEdge.c /= dy; + bottomSite: null + }; + var EdgeList = { + list: [], + leftEnd: null, + rightEnd: null, + init: function() { + EdgeList.leftEnd = EdgeList.createHalfEdge(null, "l"); + EdgeList.rightEnd = EdgeList.createHalfEdge(null, "l"); + EdgeList.leftEnd.r = EdgeList.rightEnd; + EdgeList.rightEnd.l = EdgeList.leftEnd; + EdgeList.list.unshift(EdgeList.leftEnd, EdgeList.rightEnd); + }, + createHalfEdge: function(edge, side) { + return { + edge: edge, + side: side, + vertex: null, + l: null, + r: null + }; + }, + insert: function(lb, he) { + he.l = lb; + he.r = lb.r; + lb.r.l = he; + lb.r = he; + }, + leftBound: function(p) { + var he = EdgeList.leftEnd; + do { + he = he.r; + } while (he != EdgeList.rightEnd && Geom.rightOf(he, p)); + he = he.l; + return he; + }, + del: function(he) { + he.l.r = he.r; + he.r.l = he.l; + he.edge = null; + }, + right: function(he) { + return he.r; + }, + left: function(he) { + return he.l; + }, + leftRegion: function(he) { + return he.edge == null ? Sites.bottomSite : he.edge.region[he.side]; + }, + rightRegion: function(he) { + return he.edge == null ? Sites.bottomSite : he.edge.region[d3_geom_voronoiOpposite[he.side]]; } - - return newEdge; - }, - - intersect: function(el1, el2) { - var e1 = el1.edge, - e2 = el2.edge; - if (!e1 || !e2 || (e1.region.r == e2.region.r)) { - return null; - } - var d = (e1.a * e2.b) - (e1.b * e2.a); - if (Math.abs(d) < 1e-10) { - return null; - } - var xint = (e1.c * e2.b - e2.c * e1.b) / d, - yint = (e2.c * e1.a - e1.c * e2.a) / d, - e1r = e1.region.r, - e2r = e2.region.r, - el, - e; - if ((e1r.y < e2r.y) || - (e1r.y == e2r.y && e1r.x < e2r.x)) { - el = el1; - e = e1; - } else { - el = el2; - e = e2; - } - var rightOfSite = (xint >= e.region.r.x); - if ((rightOfSite && (el.side === "l")) || - (!rightOfSite && (el.side === "r"))) { - return null; - } - return { - x: xint, - y: yint - }; - }, - - rightOf: function(he, p) { - var e = he.edge, - topsite = e.region.r, - rightOfSite = (p.x > topsite.x); - - if (rightOfSite && (he.side === "l")) { - return 1; - } - if (!rightOfSite && (he.side === "r")) { - return 0; - } - if (e.a === 1) { - var dyp = p.y - topsite.y, - dxp = p.x - topsite.x, - fast = 0, - above = 0; - - if ((!rightOfSite && (e.b < 0)) || - (rightOfSite && (e.b >= 0))) { - above = fast = (dyp >= e.b * dxp); + }; + var Geom = { + bisect: function(s1, s2) { + var newEdge = { + region: { + l: s1, + r: s2 + }, + ep: { + l: null, + r: null + } + }; + var dx = s2.x - s1.x, dy = s2.y - s1.y, adx = dx > 0 ? dx : -dx, ady = dy > 0 ? dy : -dy; + newEdge.c = s1.x * dx + s1.y * dy + (dx * dx + dy * dy) * .5; + if (adx > ady) { + newEdge.a = 1; + newEdge.b = dy / dx; + newEdge.c /= dx; } else { - above = ((p.x + p.y * e.b) > e.c); - if (e.b < 0) { - above = !above; - } - if (!above) { - fast = 1; - } + newEdge.b = 1; + newEdge.a = dx / dy; + newEdge.c /= dy; } - if (!fast) { - var dxs = topsite.x - e.region.l.x; - above = (e.b * (dxp * dxp - dyp * dyp)) < - (dxs * dyp * (1 + 2 * dxp / dxs + e.b * e.b)); - - if (e.b < 0) { - above = !above; - } + return newEdge; + }, + intersect: function(el1, el2) { + var e1 = el1.edge, e2 = el2.edge; + if (!e1 || !e2 || e1.region.r == e2.region.r) { + return null; } - } else /* e.b == 1 */ { - var yl = e.c - e.a * p.x, - t1 = p.y - yl, - t2 = p.x - topsite.x, - t3 = yl - topsite.y; - - above = (t1 * t1) > (t2 * t2 + t3 * t3); - } - return he.side === "l" ? above : !above; - }, - - endPoint: function(edge, side, site) { - edge.ep[side] = site; - if (!edge.ep[d3_voronoi_opposite[side]]) return; - callback(edge); - }, - - distance: function(s, t) { - var dx = s.x - t.x, - dy = s.y - t.y; - return Math.sqrt(dx * dx + dy * dy); - } - }; - - var EventQueue = { - list: [], - - insert: function(he, site, offset) { - he.vertex = site; - he.ystar = site.y + offset; - for (var i=0, list=EventQueue.list, l=list.length; i next.ystar || - (he.ystar == next.ystar && - site.x > next.vertex.x)) { - continue; + var d = e1.a * e2.b - e1.b * e2.a; + if (Math.abs(d) < 1e-10) { + return null; + } + var xint = (e1.c * e2.b - e2.c * e1.b) / d, yint = (e2.c * e1.a - e1.c * e2.a) / d, e1r = e1.region.r, e2r = e2.region.r, el, e; + if (e1r.y < e2r.y || e1r.y == e2r.y && e1r.x < e2r.x) { + el = el1; + e = e1; } else { - break; + el = el2; + e = e2; } + var rightOfSite = xint >= e.region.r.x; + if (rightOfSite && el.side === "l" || !rightOfSite && el.side === "r") { + return null; + } + return { + x: xint, + y: yint + }; + }, + rightOf: function(he, p) { + var e = he.edge, topsite = e.region.r, rightOfSite = p.x > topsite.x; + if (rightOfSite && he.side === "l") { + return 1; + } + if (!rightOfSite && he.side === "r") { + return 0; + } + if (e.a === 1) { + var dyp = p.y - topsite.y, dxp = p.x - topsite.x, fast = 0, above = 0; + if (!rightOfSite && e.b < 0 || rightOfSite && e.b >= 0) { + above = fast = dyp >= e.b * dxp; + } else { + above = p.x + p.y * e.b > e.c; + if (e.b < 0) { + above = !above; + } + if (!above) { + fast = 1; + } + } + if (!fast) { + var dxs = topsite.x - e.region.l.x; + above = e.b * (dxp * dxp - dyp * dyp) < dxs * dyp * (1 + 2 * dxp / dxs + e.b * e.b); + if (e.b < 0) { + above = !above; + } + } + } else { + var yl = e.c - e.a * p.x, t1 = p.y - yl, t2 = p.x - topsite.x, t3 = yl - topsite.y; + above = t1 * t1 > t2 * t2 + t3 * t3; + } + return he.side === "l" ? above : !above; + }, + endPoint: function(edge, side, site) { + edge.ep[side] = site; + if (!edge.ep[d3_geom_voronoiOpposite[side]]) return; + callback(edge); + }, + distance: function(s, t) { + var dx = s.x - t.x, dy = s.y - t.y; + return Math.sqrt(dx * dx + dy * dy); } - list.splice(i, 0, he); - }, - - del: function(he) { - for (var i=0, ls=EventQueue.list, l=ls.length; i next.ystar || he.ystar == next.ystar && site.x > next.vertex.x) { + continue; + } else { + break; + } + } + list.splice(i, 0, he); + }, + del: function(he) { + for (var i = 0, ls = EventQueue.list, l = ls.length; i < l && ls[i] != he; ++i) {} + ls.splice(i, 1); + }, + empty: function() { + return EventQueue.list.length === 0; + }, + nextEvent: function(he) { + for (var i = 0, ls = EventQueue.list, l = ls.length; i < l; ++i) { + if (ls[i] == he) return ls[i + 1]; + } + return null; + }, + min: function() { + var elem = EventQueue.list[0]; + return { + x: elem.vertex.x, + y: elem.ystar + }; + }, + extractMin: function() { + return EventQueue.list.shift(); + } + }; + EdgeList.init(); + Sites.bottomSite = Sites.list.shift(); + var newSite = Sites.list.shift(), newIntStar; + var lbnd, rbnd, llbnd, rrbnd, bisector; + var bot, top, temp, p, v; + var e, pm; + while (true) { + if (!EventQueue.empty()) { + newIntStar = EventQueue.min(); + } + if (newSite && (EventQueue.empty() || newSite.y < newIntStar.y || newSite.y == newIntStar.y && newSite.x < newIntStar.x)) { + lbnd = EdgeList.leftBound(newSite); + rbnd = EdgeList.right(lbnd); + bot = EdgeList.rightRegion(lbnd); + e = Geom.bisect(bot, newSite); + bisector = EdgeList.createHalfEdge(e, "l"); + EdgeList.insert(lbnd, bisector); + p = Geom.intersect(lbnd, bisector); + if (p) { + EventQueue.del(lbnd); + EventQueue.insert(lbnd, p, Geom.distance(p, newSite)); + } + lbnd = bisector; + bisector = EdgeList.createHalfEdge(e, "r"); + EdgeList.insert(lbnd, bisector); + p = Geom.intersect(bisector, rbnd); + if (p) { + EventQueue.insert(bisector, p, Geom.distance(p, newSite)); + } + newSite = Sites.list.shift(); + } else if (!EventQueue.empty()) { + lbnd = EventQueue.extractMin(); + llbnd = EdgeList.left(lbnd); + rbnd = EdgeList.right(lbnd); + rrbnd = EdgeList.right(rbnd); + bot = EdgeList.leftRegion(lbnd); + top = EdgeList.rightRegion(rbnd); + v = lbnd.vertex; + Geom.endPoint(lbnd.edge, lbnd.side, v); + Geom.endPoint(rbnd.edge, rbnd.side, v); + EdgeList.del(lbnd); + EventQueue.del(rbnd); + EdgeList.del(rbnd); + pm = "l"; + if (bot.y > top.y) { + temp = bot; + bot = top; + top = temp; + pm = "r"; + } + e = Geom.bisect(bot, top); + bisector = EdgeList.createHalfEdge(e, pm); + EdgeList.insert(llbnd, bisector); + Geom.endPoint(e, d3_geom_voronoiOpposite[pm], v); + p = Geom.intersect(llbnd, bisector); + if (p) { + EventQueue.del(llbnd); + EventQueue.insert(llbnd, p, Geom.distance(p, bot)); + } + p = Geom.intersect(bisector, rrbnd); + if (p) { + EventQueue.insert(bisector, p, Geom.distance(p, bot)); + } + } else { + break; } - return null; - }, - - min: function() { - var elem = EventQueue.list[0]; - return { - x: elem.vertex.x, - y: elem.ystar - }; - }, - - extractMin: function() { - return EventQueue.list.shift(); } - }; - - EdgeList.init(); - Sites.bottomSite = Sites.list.shift(); - - var newSite = Sites.list.shift(), newIntStar; - var lbnd, rbnd, llbnd, rrbnd, bisector; - var bot, top, temp, p, v; - var e, pm; - - while (true) { - if (!EventQueue.empty()) { - newIntStar = EventQueue.min(); + for (lbnd = EdgeList.right(EdgeList.leftEnd); lbnd != EdgeList.rightEnd; lbnd = EdgeList.right(lbnd)) { + callback(lbnd.edge); } - if (newSite && (EventQueue.empty() - || newSite.y < newIntStar.y - || (newSite.y == newIntStar.y - && newSite.x < newIntStar.x))) { //new site is smallest - lbnd = EdgeList.leftBound(newSite); - rbnd = EdgeList.right(lbnd); - bot = EdgeList.rightRegion(lbnd); - e = Geom.bisect(bot, newSite); - bisector = EdgeList.createHalfEdge(e, "l"); - EdgeList.insert(lbnd, bisector); - p = Geom.intersect(lbnd, bisector); - if (p) { - EventQueue.del(lbnd); - EventQueue.insert(lbnd, p, Geom.distance(p, newSite)); - } - lbnd = bisector; - bisector = EdgeList.createHalfEdge(e, "r"); - EdgeList.insert(lbnd, bisector); - p = Geom.intersect(bisector, rbnd); - if (p) { - EventQueue.insert(bisector, p, Geom.distance(p, newSite)); - } - newSite = Sites.list.shift(); - } else if (!EventQueue.empty()) { //intersection is smallest - lbnd = EventQueue.extractMin(); - llbnd = EdgeList.left(lbnd); - rbnd = EdgeList.right(lbnd); - rrbnd = EdgeList.right(rbnd); - bot = EdgeList.leftRegion(lbnd); - top = EdgeList.rightRegion(rbnd); - v = lbnd.vertex; - Geom.endPoint(lbnd.edge, lbnd.side, v); - Geom.endPoint(rbnd.edge, rbnd.side, v); - EdgeList.del(lbnd); - EventQueue.del(rbnd); - EdgeList.del(rbnd); - pm = "l"; - if (bot.y > top.y) { - temp = bot; - bot = top; - top = temp; - pm = "r"; - } - e = Geom.bisect(bot, top); - bisector = EdgeList.createHalfEdge(e, pm); - EdgeList.insert(llbnd, bisector); - Geom.endPoint(e, d3_voronoi_opposite[pm], v); - p = Geom.intersect(llbnd, bisector); - if (p) { - EventQueue.del(llbnd); - EventQueue.insert(llbnd, p, Geom.distance(p, bot)); - } - p = Geom.intersect(bisector, rrbnd); - if (p) { - EventQueue.insert(bisector, p, Geom.distance(p, bot)); - } - } else { - break; - } - }//end while - - for (lbnd = EdgeList.right(EdgeList.leftEnd); - lbnd != EdgeList.rightEnd; - lbnd = EdgeList.right(lbnd)) { - callback(lbnd.edge); } -} -/** -* @param vertices [[x1, y1], [x2, y2], …] -* @returns triangles [[[x1, y1], [x2, y2], [x3, y3]], …] - */ -d3.geom.delaunay = function(vertices) { - var edges = vertices.map(function() { return []; }), - triangles = []; - - // Use the Voronoi tessellation to determine Delaunay edges. - d3_voronoi_tessellate(vertices, function(e) { - edges[e.region.l.index].push(vertices[e.region.r.index]); - }); - - // Reconnect the edges into counterclockwise triangles. - edges.forEach(function(edge, i) { - var v = vertices[i], - cx = v[0], - cy = v[1]; - edge.forEach(function(v) { - v.angle = Math.atan2(v[0] - cx, v[1] - cy); - }); - edge.sort(function(a, b) { - return a.angle - b.angle; - }); - for (var j = 0, m = edge.length - 1; j < m; j++) { - triangles.push([v, edge[j], edge[j + 1]]); + d3.geom.quadtree = function(points, x1, y1, x2, y2) { + var x = d3_svg_lineX, y = d3_svg_lineY, compat; + if (compat = arguments.length) { + x = d3_geom_quadtreeCompatX; + y = d3_geom_quadtreeCompatY; + if (compat === 3) { + y2 = y1; + x2 = x1; + y1 = x1 = 0; + } + return quadtree(points); + } + function quadtree(data) { + var d, fx = d3_functor(x), fy = d3_functor(y), xs, ys, i, n, x1_, y1_, x2_, y2_; + if (x1 != null) { + x1_ = x1, y1_ = y1, x2_ = x2, y2_ = y2; + } else { + x2_ = y2_ = -(x1_ = y1_ = Infinity); + xs = [], ys = []; + n = data.length; + if (compat) for (i = 0; i < n; ++i) { + d = data[i]; + if (d.x < x1_) x1_ = d.x; + if (d.y < y1_) y1_ = d.y; + if (d.x > x2_) x2_ = d.x; + if (d.y > y2_) y2_ = d.y; + xs.push(d.x); + ys.push(d.y); + } else for (i = 0; i < n; ++i) { + var x_ = +fx(d = data[i], i), y_ = +fy(d, i); + if (x_ < x1_) x1_ = x_; + if (y_ < y1_) y1_ = y_; + if (x_ > x2_) x2_ = x_; + if (y_ > y2_) y2_ = y_; + xs.push(x_); + ys.push(y_); + } + } + var dx = x2_ - x1_, dy = y2_ - y1_; + if (dx > dy) y2_ = y1_ + dx; else x2_ = x1_ + dy; + function insert(n, d, x, y, x1, y1, x2, y2) { + if (isNaN(x) || isNaN(y)) return; + if (n.leaf) { + var nx = n.x, ny = n.y; + if (nx != null) { + if (Math.abs(nx - x) + Math.abs(ny - y) < .01) { + insertChild(n, d, x, y, x1, y1, x2, y2); + } else { + var nPoint = n.point; + n.x = n.y = n.point = null; + insertChild(n, nPoint, nx, ny, x1, y1, x2, y2); + insertChild(n, d, x, y, x1, y1, x2, y2); + } + } else { + n.x = x, n.y = y, n.point = d; + } + } else { + insertChild(n, d, x, y, x1, y1, x2, y2); + } + } + function insertChild(n, d, x, y, x1, y1, x2, y2) { + var sx = (x1 + x2) * .5, sy = (y1 + y2) * .5, right = x >= sx, bottom = y >= sy, i = (bottom << 1) + right; + n.leaf = false; + n = n.nodes[i] || (n.nodes[i] = d3_geom_quadtreeNode()); + if (right) x1 = sx; else x2 = sx; + if (bottom) y1 = sy; else y2 = sy; + insert(n, d, x, y, x1, y1, x2, y2); + } + var root = d3_geom_quadtreeNode(); + root.add = function(d) { + insert(root, d, +fx(d, ++i), +fy(d, i), x1_, y1_, x2_, y2_); + }; + root.visit = function(f) { + d3_geom_quadtreeVisit(f, root, x1_, y1_, x2_, y2_); + }; + i = -1; + if (x1 == null) { + while (++i < n) { + insert(root, data[i], xs[i], ys[i], x1_, y1_, x2_, y2_); + } + --i; + } else data.forEach(root.add); + xs = ys = data = d = null; + return root; + } + quadtree.x = function(_) { + return arguments.length ? (x = _, quadtree) : x; + }; + quadtree.y = function(_) { + return arguments.length ? (y = _, quadtree) : y; + }; + quadtree.size = function(_) { + if (!arguments.length) return x1 == null ? null : [ x2, y2 ]; + if (_ == null) { + x1 = y1 = x2 = y2 = null; + } else { + x1 = y1 = 0; + x2 = +_[0], y2 = +_[1]; + } + return quadtree; + }; + return quadtree; + }; + function d3_geom_quadtreeCompatX(d) { + return d.x; + } + function d3_geom_quadtreeCompatY(d) { + return d.y; + } + function d3_geom_quadtreeNode() { + return { + leaf: true, + nodes: [], + point: null, + x: null, + y: null + }; + } + function d3_geom_quadtreeVisit(f, node, x1, y1, x2, y2) { + if (!f(node, x1, y1, x2, y2)) { + var sx = (x1 + x2) * .5, sy = (y1 + y2) * .5, children = node.nodes; + if (children[0]) d3_geom_quadtreeVisit(f, children[0], x1, y1, sx, sy); + if (children[1]) d3_geom_quadtreeVisit(f, children[1], sx, y1, x2, sy); + if (children[2]) d3_geom_quadtreeVisit(f, children[2], x1, sy, sx, y2); + if (children[3]) d3_geom_quadtreeVisit(f, children[3], sx, sy, x2, y2); + } + } + d3.interpolateRgb = d3_interpolateRgb; + function d3_interpolateRgb(a, b) { + a = d3.rgb(a); + b = d3.rgb(b); + var ar = a.r, ag = a.g, ab = a.b, br = b.r - ar, bg = b.g - ag, bb = b.b - ab; + return function(t) { + return "#" + d3_rgb_hex(Math.round(ar + br * t)) + d3_rgb_hex(Math.round(ag + bg * t)) + d3_rgb_hex(Math.round(ab + bb * t)); + }; + } + d3.transform = function(string) { + var g = d3_document.createElementNS(d3.ns.prefix.svg, "g"); + return (d3.transform = function(string) { + g.setAttribute("transform", string); + var t = g.transform.baseVal.consolidate(); + return new d3_transform(t ? t.matrix : d3_transformIdentity); + })(string); + }; + function d3_transform(m) { + var r0 = [ m.a, m.b ], r1 = [ m.c, m.d ], kx = d3_transformNormalize(r0), kz = d3_transformDot(r0, r1), ky = d3_transformNormalize(d3_transformCombine(r1, r0, -kz)) || 0; + if (r0[0] * r1[1] < r1[0] * r0[1]) { + r0[0] *= -1; + r0[1] *= -1; + kx *= -1; + kz *= -1; + } + this.rotate = (kx ? Math.atan2(r0[1], r0[0]) : Math.atan2(-r1[0], r1[1])) * d3_degrees; + this.translate = [ m.e, m.f ]; + this.scale = [ kx, ky ]; + this.skew = ky ? Math.atan2(kz, ky) * d3_degrees : 0; + } + d3_transform.prototype.toString = function() { + return "translate(" + this.translate + ")rotate(" + this.rotate + ")skewX(" + this.skew + ")scale(" + this.scale + ")"; + }; + function d3_transformDot(a, b) { + return a[0] * b[0] + a[1] * b[1]; + } + function d3_transformNormalize(a) { + var k = Math.sqrt(d3_transformDot(a, a)); + if (k) { + a[0] /= k; + a[1] /= k; + } + return k; + } + function d3_transformCombine(a, b, k) { + a[0] += k * b[0]; + a[1] += k * b[1]; + return a; + } + var d3_transformIdentity = { + a: 1, + b: 0, + c: 0, + d: 1, + e: 0, + f: 0 + }; + d3.interpolateNumber = d3_interpolateNumber; + function d3_interpolateNumber(a, b) { + b -= a; + return function(t) { + return a + b * t; + }; + } + d3.interpolateTransform = d3_interpolateTransform; + function d3_interpolateTransform(a, b) { + var s = [], q = [], n, A = d3.transform(a), B = d3.transform(b), ta = A.translate, tb = B.translate, ra = A.rotate, rb = B.rotate, wa = A.skew, wb = B.skew, ka = A.scale, kb = B.scale; + if (ta[0] != tb[0] || ta[1] != tb[1]) { + s.push("translate(", null, ",", null, ")"); + q.push({ + i: 1, + x: d3_interpolateNumber(ta[0], tb[0]) + }, { + i: 3, + x: d3_interpolateNumber(ta[1], tb[1]) + }); + } else if (tb[0] || tb[1]) { + s.push("translate(" + tb + ")"); + } else { + s.push(""); + } + if (ra != rb) { + if (ra - rb > 180) rb += 360; else if (rb - ra > 180) ra += 360; + q.push({ + i: s.push(s.pop() + "rotate(", null, ")") - 2, + x: d3_interpolateNumber(ra, rb) + }); + } else if (rb) { + s.push(s.pop() + "rotate(" + rb + ")"); + } + if (wa != wb) { + q.push({ + i: s.push(s.pop() + "skewX(", null, ")") - 2, + x: d3_interpolateNumber(wa, wb) + }); + } else if (wb) { + s.push(s.pop() + "skewX(" + wb + ")"); + } + if (ka[0] != kb[0] || ka[1] != kb[1]) { + n = s.push(s.pop() + "scale(", null, ",", null, ")"); + q.push({ + i: n - 4, + x: d3_interpolateNumber(ka[0], kb[0]) + }, { + i: n - 2, + x: d3_interpolateNumber(ka[1], kb[1]) + }); + } else if (kb[0] != 1 || kb[1] != 1) { + s.push(s.pop() + "scale(" + kb + ")"); + } + n = q.length; + return function(t) { + var i = -1, o; + while (++i < n) s[(o = q[i]).i] = o.x(t); + return s.join(""); + }; + } + d3.interpolateObject = d3_interpolateObject; + function d3_interpolateObject(a, b) { + var i = {}, c = {}, k; + for (k in a) { + if (k in b) { + i[k] = d3_interpolateByName(k)(a[k], b[k]); + } else { + c[k] = a[k]; + } + } + for (k in b) { + if (!(k in a)) { + c[k] = b[k]; + } + } + return function(t) { + for (k in i) c[k] = i[k](t); + return c; + }; + } + d3.interpolateString = d3_interpolateString; + function d3_interpolateString(a, b) { + var m, i, j, s0 = 0, s1 = 0, s = [], q = [], n, o; + d3_interpolate_number.lastIndex = 0; + for (i = 0; m = d3_interpolate_number.exec(b); ++i) { + if (m.index) s.push(b.substring(s0, s1 = m.index)); + q.push({ + i: s.length, + x: m[0] + }); + s.push(null); + s0 = d3_interpolate_number.lastIndex; + } + if (s0 < b.length) s.push(b.substring(s0)); + for (i = 0, n = q.length; (m = d3_interpolate_number.exec(a)) && i < n; ++i) { + o = q[i]; + if (o.x == m[0]) { + if (o.i) { + if (s[o.i + 1] == null) { + s[o.i - 1] += o.x; + s.splice(o.i, 1); + for (j = i + 1; j < n; ++j) q[j].i--; + } else { + s[o.i - 1] += o.x + s[o.i + 1]; + s.splice(o.i, 2); + for (j = i + 1; j < n; ++j) q[j].i -= 2; + } + } else { + if (s[o.i + 1] == null) { + s[o.i] = o.x; + } else { + s[o.i] = o.x + s[o.i + 1]; + s.splice(o.i + 1, 1); + for (j = i + 1; j < n; ++j) q[j].i--; + } + } + q.splice(i, 1); + n--; + i--; + } else { + o.x = d3_interpolateNumber(parseFloat(m[0]), parseFloat(o.x)); + } + } + while (i < n) { + o = q.pop(); + if (s[o.i + 1] == null) { + s[o.i] = o.x; + } else { + s[o.i] = o.x + s[o.i + 1]; + s.splice(o.i + 1, 1); + } + n--; + } + if (s.length === 1) { + return s[0] == null ? q[0].x : function() { + return b; + }; + } + return function(t) { + for (i = 0; i < n; ++i) s[(o = q[i]).i] = o.x(t); + return s.join(""); + }; + } + var d3_interpolate_number = /[-+]?(?:\d+\.?\d*|\.?\d+)(?:[eE][-+]?\d+)?/g; + d3.interpolate = d3_interpolate; + function d3_interpolate(a, b) { + var i = d3.interpolators.length, f; + while (--i >= 0 && !(f = d3.interpolators[i](a, b))) ; + return f; + } + function d3_interpolateByName(name) { + return name == "transform" ? d3_interpolateTransform : d3_interpolate; + } + d3.interpolators = [ d3_interpolateObject, function(a, b) { + return Array.isArray(b) && d3_interpolateArray(a, b); + }, function(a, b) { + return (typeof a === "string" || typeof b === "string") && d3_interpolateString(a + "", b + ""); + }, function(a, b) { + return (typeof b === "string" ? d3_rgb_names.has(b) || /^(#|rgb\(|hsl\()/.test(b) : b instanceof d3_Color) && d3_interpolateRgb(a, b); + }, function(a, b) { + return !isNaN(a = +a) && !isNaN(b = +b) && d3_interpolateNumber(a, b); + } ]; + d3.interpolateArray = d3_interpolateArray; + function d3_interpolateArray(a, b) { + var x = [], c = [], na = a.length, nb = b.length, n0 = Math.min(a.length, b.length), i; + for (i = 0; i < n0; ++i) x.push(d3_interpolate(a[i], b[i])); + for (;i < na; ++i) c[i] = a[i]; + for (;i < nb; ++i) c[i] = b[i]; + return function(t) { + for (i = 0; i < n0; ++i) c[i] = x[i](t); + return c; + }; + } + var d3_ease_default = function() { + return d3_identity; + }; + var d3_ease = d3.map({ + linear: d3_ease_default, + poly: d3_ease_poly, + quad: function() { + return d3_ease_quad; + }, + cubic: function() { + return d3_ease_cubic; + }, + sin: function() { + return d3_ease_sin; + }, + exp: function() { + return d3_ease_exp; + }, + circle: function() { + return d3_ease_circle; + }, + elastic: d3_ease_elastic, + back: d3_ease_back, + bounce: function() { + return d3_ease_bounce; } }); - - return triangles; -}; -// Constructs a new quadtree for the specified array of points. A quadtree is a -// two-dimensional recursive spatial subdivision. This implementation uses -// square partitions, dividing each square into four equally-sized squares. Each -// point exists in a unique node; if multiple points are in the same position, -// some points may be stored on internal nodes rather than leaf nodes. Quadtrees -// can be used to accelerate various spatial operations, such as the Barnes-Hut -// approximation for computing n-body forces, or collision detection. -d3.geom.quadtree = function(points, x1, y1, x2, y2) { - var p, - i = -1, - n = points.length; - - // Type conversion for deprecated API. - if (n && isNaN(points[0].x)) points = points.map(d3_geom_quadtreePoint); - - // Allow bounds to be specified explicitly. - if (arguments.length < 5) { - if (arguments.length === 3) { - y2 = x2 = y1; - y1 = x1; - } else { - x1 = y1 = Infinity; - x2 = y2 = -Infinity; - - // Compute bounds. + var d3_ease_mode = d3.map({ + "in": d3_identity, + out: d3_ease_reverse, + "in-out": d3_ease_reflect, + "out-in": function(f) { + return d3_ease_reflect(d3_ease_reverse(f)); + } + }); + d3.ease = function(name) { + var i = name.indexOf("-"), t = i >= 0 ? name.substring(0, i) : name, m = i >= 0 ? name.substring(i + 1) : "in"; + t = d3_ease.get(t) || d3_ease_default; + m = d3_ease_mode.get(m) || d3_identity; + return d3_ease_clamp(m(t.apply(null, Array.prototype.slice.call(arguments, 1)))); + }; + function d3_ease_clamp(f) { + return function(t) { + return t <= 0 ? 0 : t >= 1 ? 1 : f(t); + }; + } + function d3_ease_reverse(f) { + return function(t) { + return 1 - f(1 - t); + }; + } + function d3_ease_reflect(f) { + return function(t) { + return .5 * (t < .5 ? f(2 * t) : 2 - f(2 - 2 * t)); + }; + } + function d3_ease_quad(t) { + return t * t; + } + function d3_ease_cubic(t) { + return t * t * t; + } + function d3_ease_cubicInOut(t) { + if (t <= 0) return 0; + if (t >= 1) return 1; + var t2 = t * t, t3 = t2 * t; + return 4 * (t < .5 ? t3 : 3 * (t - t2) + t3 - .75); + } + function d3_ease_poly(e) { + return function(t) { + return Math.pow(t, e); + }; + } + function d3_ease_sin(t) { + return 1 - Math.cos(t * π / 2); + } + function d3_ease_exp(t) { + return Math.pow(2, 10 * (t - 1)); + } + function d3_ease_circle(t) { + return 1 - Math.sqrt(1 - t * t); + } + function d3_ease_elastic(a, p) { + var s; + if (arguments.length < 2) p = .45; + if (arguments.length) s = p / (2 * π) * Math.asin(1 / a); else a = 1, s = p / 4; + return function(t) { + return 1 + a * Math.pow(2, 10 * -t) * Math.sin((t - s) * 2 * π / p); + }; + } + function d3_ease_back(s) { + if (!s) s = 1.70158; + return function(t) { + return t * t * ((s + 1) * t - s); + }; + } + function d3_ease_bounce(t) { + return t < 1 / 2.75 ? 7.5625 * t * t : t < 2 / 2.75 ? 7.5625 * (t -= 1.5 / 2.75) * t + .75 : t < 2.5 / 2.75 ? 7.5625 * (t -= 2.25 / 2.75) * t + .9375 : 7.5625 * (t -= 2.625 / 2.75) * t + .984375; + } + d3.interpolateHcl = d3_interpolateHcl; + function d3_interpolateHcl(a, b) { + a = d3.hcl(a); + b = d3.hcl(b); + var ah = a.h, ac = a.c, al = a.l, bh = b.h - ah, bc = b.c - ac, bl = b.l - al; + if (bh > 180) bh -= 360; else if (bh < -180) bh += 360; + return function(t) { + return d3_hcl_lab(ah + bh * t, ac + bc * t, al + bl * t) + ""; + }; + } + d3.interpolateHsl = d3_interpolateHsl; + function d3_interpolateHsl(a, b) { + a = d3.hsl(a); + b = d3.hsl(b); + var h0 = a.h, s0 = a.s, l0 = a.l, h1 = b.h - h0, s1 = b.s - s0, l1 = b.l - l0; + if (h1 > 180) h1 -= 360; else if (h1 < -180) h1 += 360; + return function(t) { + return d3_hsl_rgb(h0 + h1 * t, s0 + s1 * t, l0 + l1 * t) + ""; + }; + } + d3.interpolateLab = d3_interpolateLab; + function d3_interpolateLab(a, b) { + a = d3.lab(a); + b = d3.lab(b); + var al = a.l, aa = a.a, ab = a.b, bl = b.l - al, ba = b.a - aa, bb = b.b - ab; + return function(t) { + return d3_lab_rgb(al + bl * t, aa + ba * t, ab + bb * t) + ""; + }; + } + d3.interpolateRound = d3_interpolateRound; + function d3_interpolateRound(a, b) { + b -= a; + return function(t) { + return Math.round(a + b * t); + }; + } + function d3_uninterpolateNumber(a, b) { + b = b - (a = +a) ? 1 / (b - a) : 0; + return function(x) { + return (x - a) * b; + }; + } + function d3_uninterpolateClamp(a, b) { + b = b - (a = +a) ? 1 / (b - a) : 0; + return function(x) { + return Math.max(0, Math.min(1, (x - a) * b)); + }; + } + d3.layout = {}; + d3.layout.bundle = function() { + return function(links) { + var paths = [], i = -1, n = links.length; + while (++i < n) paths.push(d3_layout_bundlePath(links[i])); + return paths; + }; + }; + function d3_layout_bundlePath(link) { + var start = link.source, end = link.target, lca = d3_layout_bundleLeastCommonAncestor(start, end), points = [ start ]; + while (start !== lca) { + start = start.parent; + points.push(start); + } + var k = points.length; + while (end !== lca) { + points.splice(k, 0, end); + end = end.parent; + } + return points; + } + function d3_layout_bundleAncestors(node) { + var ancestors = [], parent = node.parent; + while (parent != null) { + ancestors.push(node); + node = parent; + parent = parent.parent; + } + ancestors.push(node); + return ancestors; + } + function d3_layout_bundleLeastCommonAncestor(a, b) { + if (a === b) return a; + var aNodes = d3_layout_bundleAncestors(a), bNodes = d3_layout_bundleAncestors(b), aNode = aNodes.pop(), bNode = bNodes.pop(), sharedNode = null; + while (aNode === bNode) { + sharedNode = aNode; + aNode = aNodes.pop(); + bNode = bNodes.pop(); + } + return sharedNode; + } + d3.layout.chord = function() { + var chord = {}, chords, groups, matrix, n, padding = 0, sortGroups, sortSubgroups, sortChords; + function relayout() { + var subgroups = {}, groupSums = [], groupIndex = d3.range(n), subgroupIndex = [], k, x, x0, i, j; + chords = []; + groups = []; + k = 0, i = -1; while (++i < n) { - p = points[i]; - if (p.x < x1) x1 = p.x; - if (p.y < y1) y1 = p.y; - if (p.x > x2) x2 = p.x; - if (p.y > y2) y2 = p.y; + x = 0, j = -1; + while (++j < n) { + x += matrix[i][j]; + } + groupSums.push(x); + subgroupIndex.push(d3.range(n)); + k += x; } - - // Squarify the bounds. - var dx = x2 - x1, - dy = y2 - y1; - if (dx > dy) y2 = y1 + dx; - else x2 = x1 + dy; + if (sortGroups) { + groupIndex.sort(function(a, b) { + return sortGroups(groupSums[a], groupSums[b]); + }); + } + if (sortSubgroups) { + subgroupIndex.forEach(function(d, i) { + d.sort(function(a, b) { + return sortSubgroups(matrix[i][a], matrix[i][b]); + }); + }); + } + k = (2 * π - padding * n) / k; + x = 0, i = -1; + while (++i < n) { + x0 = x, j = -1; + while (++j < n) { + var di = groupIndex[i], dj = subgroupIndex[di][j], v = matrix[di][dj], a0 = x, a1 = x += v * k; + subgroups[di + "-" + dj] = { + index: di, + subindex: dj, + startAngle: a0, + endAngle: a1, + value: v + }; + } + groups[di] = { + index: di, + startAngle: x0, + endAngle: x, + value: (x - x0) / k + }; + x += padding; + } + i = -1; + while (++i < n) { + j = i - 1; + while (++j < n) { + var source = subgroups[i + "-" + j], target = subgroups[j + "-" + i]; + if (source.value || target.value) { + chords.push(source.value < target.value ? { + source: target, + target: source + } : { + source: source, + target: target + }); + } + } + } + if (sortChords) resort(); + } + function resort() { + chords.sort(function(a, b) { + return sortChords((a.source.value + a.target.value) / 2, (b.source.value + b.target.value) / 2); + }); + } + chord.matrix = function(x) { + if (!arguments.length) return matrix; + n = (matrix = x) && matrix.length; + chords = groups = null; + return chord; + }; + chord.padding = function(x) { + if (!arguments.length) return padding; + padding = x; + chords = groups = null; + return chord; + }; + chord.sortGroups = function(x) { + if (!arguments.length) return sortGroups; + sortGroups = x; + chords = groups = null; + return chord; + }; + chord.sortSubgroups = function(x) { + if (!arguments.length) return sortSubgroups; + sortSubgroups = x; + chords = null; + return chord; + }; + chord.sortChords = function(x) { + if (!arguments.length) return sortChords; + sortChords = x; + if (chords) resort(); + return chord; + }; + chord.chords = function() { + if (!chords) relayout(); + return chords; + }; + chord.groups = function() { + if (!groups) relayout(); + return groups; + }; + return chord; + }; + d3.layout.force = function() { + var force = {}, event = d3.dispatch("start", "tick", "end"), size = [ 1, 1 ], drag, alpha, friction = .9, linkDistance = d3_layout_forceLinkDistance, linkStrength = d3_layout_forceLinkStrength, charge = -30, gravity = .1, theta = .8, nodes = [], links = [], distances, strengths, charges; + function repulse(node) { + return function(quad, x1, _, x2) { + if (quad.point !== node) { + var dx = quad.cx - node.x, dy = quad.cy - node.y, dn = 1 / Math.sqrt(dx * dx + dy * dy); + if ((x2 - x1) * dn < theta) { + var k = quad.charge * dn * dn; + node.px -= dx * k; + node.py -= dy * k; + return true; + } + if (quad.point && isFinite(dn)) { + var k = quad.pointCharge * dn * dn; + node.px -= dx * k; + node.py -= dy * k; + } + } + return !quad.charge; + }; + } + force.tick = function() { + if ((alpha *= .99) < .005) { + event.end({ + type: "end", + alpha: alpha = 0 + }); + return true; + } + var n = nodes.length, m = links.length, q, i, o, s, t, l, k, x, y; + for (i = 0; i < m; ++i) { + o = links[i]; + s = o.source; + t = o.target; + x = t.x - s.x; + y = t.y - s.y; + if (l = x * x + y * y) { + l = alpha * strengths[i] * ((l = Math.sqrt(l)) - distances[i]) / l; + x *= l; + y *= l; + t.x -= x * (k = s.weight / (t.weight + s.weight)); + t.y -= y * k; + s.x += x * (k = 1 - k); + s.y += y * k; + } + } + if (k = alpha * gravity) { + x = size[0] / 2; + y = size[1] / 2; + i = -1; + if (k) while (++i < n) { + o = nodes[i]; + o.x += (x - o.x) * k; + o.y += (y - o.y) * k; + } + } + if (charge) { + d3_layout_forceAccumulate(q = d3.geom.quadtree(nodes), alpha, charges); + i = -1; + while (++i < n) { + if (!(o = nodes[i]).fixed) { + q.visit(repulse(o)); + } + } + } + i = -1; + while (++i < n) { + o = nodes[i]; + if (o.fixed) { + o.x = o.px; + o.y = o.py; + } else { + o.x -= (o.px - (o.px = o.x)) * friction; + o.y -= (o.py - (o.py = o.y)) * friction; + } + } + event.tick({ + type: "tick", + alpha: alpha + }); + }; + force.nodes = function(x) { + if (!arguments.length) return nodes; + nodes = x; + return force; + }; + force.links = function(x) { + if (!arguments.length) return links; + links = x; + return force; + }; + force.size = function(x) { + if (!arguments.length) return size; + size = x; + return force; + }; + force.linkDistance = function(x) { + if (!arguments.length) return linkDistance; + linkDistance = typeof x === "function" ? x : +x; + return force; + }; + force.distance = force.linkDistance; + force.linkStrength = function(x) { + if (!arguments.length) return linkStrength; + linkStrength = typeof x === "function" ? x : +x; + return force; + }; + force.friction = function(x) { + if (!arguments.length) return friction; + friction = +x; + return force; + }; + force.charge = function(x) { + if (!arguments.length) return charge; + charge = typeof x === "function" ? x : +x; + return force; + }; + force.gravity = function(x) { + if (!arguments.length) return gravity; + gravity = +x; + return force; + }; + force.theta = function(x) { + if (!arguments.length) return theta; + theta = +x; + return force; + }; + force.alpha = function(x) { + if (!arguments.length) return alpha; + x = +x; + if (alpha) { + if (x > 0) alpha = x; else alpha = 0; + } else if (x > 0) { + event.start({ + type: "start", + alpha: alpha = x + }); + d3.timer(force.tick); + } + return force; + }; + force.start = function() { + var i, j, n = nodes.length, m = links.length, w = size[0], h = size[1], neighbors, o; + for (i = 0; i < n; ++i) { + (o = nodes[i]).index = i; + o.weight = 0; + } + for (i = 0; i < m; ++i) { + o = links[i]; + if (typeof o.source == "number") o.source = nodes[o.source]; + if (typeof o.target == "number") o.target = nodes[o.target]; + ++o.source.weight; + ++o.target.weight; + } + for (i = 0; i < n; ++i) { + o = nodes[i]; + if (isNaN(o.x)) o.x = position("x", w); + if (isNaN(o.y)) o.y = position("y", h); + if (isNaN(o.px)) o.px = o.x; + if (isNaN(o.py)) o.py = o.y; + } + distances = []; + if (typeof linkDistance === "function") for (i = 0; i < m; ++i) distances[i] = +linkDistance.call(this, links[i], i); else for (i = 0; i < m; ++i) distances[i] = linkDistance; + strengths = []; + if (typeof linkStrength === "function") for (i = 0; i < m; ++i) strengths[i] = +linkStrength.call(this, links[i], i); else for (i = 0; i < m; ++i) strengths[i] = linkStrength; + charges = []; + if (typeof charge === "function") for (i = 0; i < n; ++i) charges[i] = +charge.call(this, nodes[i], i); else for (i = 0; i < n; ++i) charges[i] = charge; + function position(dimension, size) { + var neighbors = neighbor(i), j = -1, m = neighbors.length, x; + while (++j < m) if (!isNaN(x = neighbors[j][dimension])) return x; + return Math.random() * size; + } + function neighbor() { + if (!neighbors) { + neighbors = []; + for (j = 0; j < n; ++j) { + neighbors[j] = []; + } + for (j = 0; j < m; ++j) { + var o = links[j]; + neighbors[o.source.index].push(o.target); + neighbors[o.target.index].push(o.source); + } + } + return neighbors[i]; + } + return force.resume(); + }; + force.resume = function() { + return force.alpha(.1); + }; + force.stop = function() { + return force.alpha(0); + }; + force.drag = function() { + if (!drag) drag = d3.behavior.drag().origin(d3_identity).on("dragstart.force", d3_layout_forceDragstart).on("drag.force", dragmove).on("dragend.force", d3_layout_forceDragend); + if (!arguments.length) return drag; + this.on("mouseover.force", d3_layout_forceMouseover).on("mouseout.force", d3_layout_forceMouseout).call(drag); + }; + function dragmove(d) { + d.px = d3.event.x, d.py = d3.event.y; + force.resume(); + } + return d3.rebind(force, event, "on"); + }; + function d3_layout_forceDragstart(d) { + d.fixed |= 2; + } + function d3_layout_forceDragend(d) { + d.fixed &= ~6; + } + function d3_layout_forceMouseover(d) { + d.fixed |= 4; + d.px = d.x, d.py = d.y; + } + function d3_layout_forceMouseout(d) { + d.fixed &= ~4; + } + function d3_layout_forceAccumulate(quad, alpha, charges) { + var cx = 0, cy = 0; + quad.charge = 0; + if (!quad.leaf) { + var nodes = quad.nodes, n = nodes.length, i = -1, c; + while (++i < n) { + c = nodes[i]; + if (c == null) continue; + d3_layout_forceAccumulate(c, alpha, charges); + quad.charge += c.charge; + cx += c.charge * c.cx; + cy += c.charge * c.cy; + } + } + if (quad.point) { + if (!quad.leaf) { + quad.point.x += Math.random() - .5; + quad.point.y += Math.random() - .5; + } + var k = alpha * charges[quad.point.index]; + quad.charge += quad.pointCharge = k; + cx += k * quad.point.x; + cy += k * quad.point.y; + } + quad.cx = cx / quad.charge; + quad.cy = cy / quad.charge; + } + var d3_layout_forceLinkDistance = 20, d3_layout_forceLinkStrength = 1; + d3.layout.hierarchy = function() { + var sort = d3_layout_hierarchySort, children = d3_layout_hierarchyChildren, value = d3_layout_hierarchyValue; + function recurse(node, depth, nodes) { + var childs = children.call(hierarchy, node, depth); + node.depth = depth; + nodes.push(node); + if (childs && (n = childs.length)) { + var i = -1, n, c = node.children = [], v = 0, j = depth + 1, d; + while (++i < n) { + d = recurse(childs[i], j, nodes); + d.parent = node; + c.push(d); + v += d.value; + } + if (sort) c.sort(sort); + if (value) node.value = v; + } else if (value) { + node.value = +value.call(hierarchy, node, depth) || 0; + } + return node; + } + function revalue(node, depth) { + var children = node.children, v = 0; + if (children && (n = children.length)) { + var i = -1, n, j = depth + 1; + while (++i < n) v += revalue(children[i], j); + } else if (value) { + v = +value.call(hierarchy, node, depth) || 0; + } + if (value) node.value = v; + return v; + } + function hierarchy(d) { + var nodes = []; + recurse(d, 0, nodes); + return nodes; + } + hierarchy.sort = function(x) { + if (!arguments.length) return sort; + sort = x; + return hierarchy; + }; + hierarchy.children = function(x) { + if (!arguments.length) return children; + children = x; + return hierarchy; + }; + hierarchy.value = function(x) { + if (!arguments.length) return value; + value = x; + return hierarchy; + }; + hierarchy.revalue = function(root) { + revalue(root, 0); + return root; + }; + return hierarchy; + }; + function d3_layout_hierarchyRebind(object, hierarchy) { + d3.rebind(object, hierarchy, "sort", "children", "value"); + object.nodes = object; + object.links = d3_layout_hierarchyLinks; + return object; + } + function d3_layout_hierarchyChildren(d) { + return d.children; + } + function d3_layout_hierarchyValue(d) { + return d.value; + } + function d3_layout_hierarchySort(a, b) { + return b.value - a.value; + } + function d3_layout_hierarchyLinks(nodes) { + return d3.merge(nodes.map(function(parent) { + return (parent.children || []).map(function(child) { + return { + source: parent, + target: child + }; + }); + })); + } + d3.layout.partition = function() { + var hierarchy = d3.layout.hierarchy(), size = [ 1, 1 ]; + function position(node, x, dx, dy) { + var children = node.children; + node.x = x; + node.y = node.depth * dy; + node.dx = dx; + node.dy = dy; + if (children && (n = children.length)) { + var i = -1, n, c, d; + dx = node.value ? dx / node.value : 0; + while (++i < n) { + position(c = children[i], x, d = c.value * dx, dy); + x += d; + } + } + } + function depth(node) { + var children = node.children, d = 0; + if (children && (n = children.length)) { + var i = -1, n; + while (++i < n) d = Math.max(d, depth(children[i])); + } + return 1 + d; + } + function partition(d, i) { + var nodes = hierarchy.call(this, d, i); + position(nodes[0], 0, size[0], size[1] / depth(nodes[0])); + return nodes; + } + partition.size = function(x) { + if (!arguments.length) return size; + size = x; + return partition; + }; + return d3_layout_hierarchyRebind(partition, hierarchy); + }; + d3.layout.pie = function() { + var value = Number, sort = d3_layout_pieSortByValue, startAngle = 0, endAngle = 2 * π; + function pie(data) { + var values = data.map(function(d, i) { + return +value.call(pie, d, i); + }); + var a = +(typeof startAngle === "function" ? startAngle.apply(this, arguments) : startAngle); + var k = ((typeof endAngle === "function" ? endAngle.apply(this, arguments) : endAngle) - a) / d3.sum(values); + var index = d3.range(data.length); + if (sort != null) index.sort(sort === d3_layout_pieSortByValue ? function(i, j) { + return values[j] - values[i]; + } : function(i, j) { + return sort(data[i], data[j]); + }); + var arcs = []; + index.forEach(function(i) { + var d; + arcs[i] = { + data: data[i], + value: d = values[i], + startAngle: a, + endAngle: a += d * k + }; + }); + return arcs; + } + pie.value = function(x) { + if (!arguments.length) return value; + value = x; + return pie; + }; + pie.sort = function(x) { + if (!arguments.length) return sort; + sort = x; + return pie; + }; + pie.startAngle = function(x) { + if (!arguments.length) return startAngle; + startAngle = x; + return pie; + }; + pie.endAngle = function(x) { + if (!arguments.length) return endAngle; + endAngle = x; + return pie; + }; + return pie; + }; + var d3_layout_pieSortByValue = {}; + d3.layout.stack = function() { + var values = d3_identity, order = d3_layout_stackOrderDefault, offset = d3_layout_stackOffsetZero, out = d3_layout_stackOut, x = d3_layout_stackX, y = d3_layout_stackY; + function stack(data, index) { + var series = data.map(function(d, i) { + return values.call(stack, d, i); + }); + var points = series.map(function(d) { + return d.map(function(v, i) { + return [ x.call(stack, v, i), y.call(stack, v, i) ]; + }); + }); + var orders = order.call(stack, points, index); + series = d3.permute(series, orders); + points = d3.permute(points, orders); + var offsets = offset.call(stack, points, index); + var n = series.length, m = series[0].length, i, j, o; + for (j = 0; j < m; ++j) { + out.call(stack, series[0][j], o = offsets[j], points[0][j][1]); + for (i = 1; i < n; ++i) { + out.call(stack, series[i][j], o += points[i - 1][j][1], points[i][j][1]); + } + } + return data; + } + stack.values = function(x) { + if (!arguments.length) return values; + values = x; + return stack; + }; + stack.order = function(x) { + if (!arguments.length) return order; + order = typeof x === "function" ? x : d3_layout_stackOrders.get(x) || d3_layout_stackOrderDefault; + return stack; + }; + stack.offset = function(x) { + if (!arguments.length) return offset; + offset = typeof x === "function" ? x : d3_layout_stackOffsets.get(x) || d3_layout_stackOffsetZero; + return stack; + }; + stack.x = function(z) { + if (!arguments.length) return x; + x = z; + return stack; + }; + stack.y = function(z) { + if (!arguments.length) return y; + y = z; + return stack; + }; + stack.out = function(z) { + if (!arguments.length) return out; + out = z; + return stack; + }; + return stack; + }; + function d3_layout_stackX(d) { + return d.x; + } + function d3_layout_stackY(d) { + return d.y; + } + function d3_layout_stackOut(d, y0, y) { + d.y0 = y0; + d.y = y; + } + var d3_layout_stackOrders = d3.map({ + "inside-out": function(data) { + var n = data.length, i, j, max = data.map(d3_layout_stackMaxIndex), sums = data.map(d3_layout_stackReduceSum), index = d3.range(n).sort(function(a, b) { + return max[a] - max[b]; + }), top = 0, bottom = 0, tops = [], bottoms = []; + for (i = 0; i < n; ++i) { + j = index[i]; + if (top < bottom) { + top += sums[j]; + tops.push(j); + } else { + bottom += sums[j]; + bottoms.push(j); + } + } + return bottoms.reverse().concat(tops); + }, + reverse: function(data) { + return d3.range(data.length).reverse(); + }, + "default": d3_layout_stackOrderDefault + }); + var d3_layout_stackOffsets = d3.map({ + silhouette: function(data) { + var n = data.length, m = data[0].length, sums = [], max = 0, i, j, o, y0 = []; + for (j = 0; j < m; ++j) { + for (i = 0, o = 0; i < n; i++) o += data[i][j][1]; + if (o > max) max = o; + sums.push(o); + } + for (j = 0; j < m; ++j) { + y0[j] = (max - sums[j]) / 2; + } + return y0; + }, + wiggle: function(data) { + var n = data.length, x = data[0], m = x.length, i, j, k, s1, s2, s3, dx, o, o0, y0 = []; + y0[0] = o = o0 = 0; + for (j = 1; j < m; ++j) { + for (i = 0, s1 = 0; i < n; ++i) s1 += data[i][j][1]; + for (i = 0, s2 = 0, dx = x[j][0] - x[j - 1][0]; i < n; ++i) { + for (k = 0, s3 = (data[i][j][1] - data[i][j - 1][1]) / (2 * dx); k < i; ++k) { + s3 += (data[k][j][1] - data[k][j - 1][1]) / dx; + } + s2 += s3 * data[i][j][1]; + } + y0[j] = o -= s1 ? s2 / s1 * dx : 0; + if (o < o0) o0 = o; + } + for (j = 0; j < m; ++j) y0[j] -= o0; + return y0; + }, + expand: function(data) { + var n = data.length, m = data[0].length, k = 1 / n, i, j, o, y0 = []; + for (j = 0; j < m; ++j) { + for (i = 0, o = 0; i < n; i++) o += data[i][j][1]; + if (o) for (i = 0; i < n; i++) data[i][j][1] /= o; else for (i = 0; i < n; i++) data[i][j][1] = k; + } + for (j = 0; j < m; ++j) y0[j] = 0; + return y0; + }, + zero: d3_layout_stackOffsetZero + }); + function d3_layout_stackOrderDefault(data) { + return d3.range(data.length); + } + function d3_layout_stackOffsetZero(data) { + var j = -1, m = data[0].length, y0 = []; + while (++j < m) y0[j] = 0; + return y0; + } + function d3_layout_stackMaxIndex(array) { + var i = 1, j = 0, v = array[0][1], k, n = array.length; + for (;i < n; ++i) { + if ((k = array[i][1]) > v) { + j = i; + v = k; + } + } + return j; + } + function d3_layout_stackReduceSum(d) { + return d.reduce(d3_layout_stackSum, 0); + } + function d3_layout_stackSum(p, d) { + return p + d[1]; + } + d3.layout.histogram = function() { + var frequency = true, valuer = Number, ranger = d3_layout_histogramRange, binner = d3_layout_histogramBinSturges; + function histogram(data, i) { + var bins = [], values = data.map(valuer, this), range = ranger.call(this, values, i), thresholds = binner.call(this, range, values, i), bin, i = -1, n = values.length, m = thresholds.length - 1, k = frequency ? 1 : 1 / n, x; + while (++i < m) { + bin = bins[i] = []; + bin.dx = thresholds[i + 1] - (bin.x = thresholds[i]); + bin.y = 0; + } + if (m > 0) { + i = -1; + while (++i < n) { + x = values[i]; + if (x >= range[0] && x <= range[1]) { + bin = bins[d3.bisect(thresholds, x, 1, m) - 1]; + bin.y += k; + bin.push(data[i]); + } + } + } + return bins; + } + histogram.value = function(x) { + if (!arguments.length) return valuer; + valuer = x; + return histogram; + }; + histogram.range = function(x) { + if (!arguments.length) return ranger; + ranger = d3_functor(x); + return histogram; + }; + histogram.bins = function(x) { + if (!arguments.length) return binner; + binner = typeof x === "number" ? function(range) { + return d3_layout_histogramBinFixed(range, x); + } : d3_functor(x); + return histogram; + }; + histogram.frequency = function(x) { + if (!arguments.length) return frequency; + frequency = !!x; + return histogram; + }; + return histogram; + }; + function d3_layout_histogramBinSturges(range, values) { + return d3_layout_histogramBinFixed(range, Math.ceil(Math.log(values.length) / Math.LN2 + 1)); + } + function d3_layout_histogramBinFixed(range, n) { + var x = -1, b = +range[0], m = (range[1] - b) / n, f = []; + while (++x <= n) f[x] = m * x + b; + return f; + } + function d3_layout_histogramRange(values) { + return [ d3.min(values), d3.max(values) ]; + } + d3.layout.tree = function() { + var hierarchy = d3.layout.hierarchy().sort(null).value(null), separation = d3_layout_treeSeparation, size = [ 1, 1 ]; + function tree(d, i) { + var nodes = hierarchy.call(this, d, i), root = nodes[0]; + function firstWalk(node, previousSibling) { + var children = node.children, layout = node._tree; + if (children && (n = children.length)) { + var n, firstChild = children[0], previousChild, ancestor = firstChild, child, i = -1; + while (++i < n) { + child = children[i]; + firstWalk(child, previousChild); + ancestor = apportion(child, previousChild, ancestor); + previousChild = child; + } + d3_layout_treeShift(node); + var midpoint = .5 * (firstChild._tree.prelim + child._tree.prelim); + if (previousSibling) { + layout.prelim = previousSibling._tree.prelim + separation(node, previousSibling); + layout.mod = layout.prelim - midpoint; + } else { + layout.prelim = midpoint; + } + } else { + if (previousSibling) { + layout.prelim = previousSibling._tree.prelim + separation(node, previousSibling); + } + } + } + function secondWalk(node, x) { + node.x = node._tree.prelim + x; + var children = node.children; + if (children && (n = children.length)) { + var i = -1, n; + x += node._tree.mod; + while (++i < n) { + secondWalk(children[i], x); + } + } + } + function apportion(node, previousSibling, ancestor) { + if (previousSibling) { + var vip = node, vop = node, vim = previousSibling, vom = node.parent.children[0], sip = vip._tree.mod, sop = vop._tree.mod, sim = vim._tree.mod, som = vom._tree.mod, shift; + while (vim = d3_layout_treeRight(vim), vip = d3_layout_treeLeft(vip), vim && vip) { + vom = d3_layout_treeLeft(vom); + vop = d3_layout_treeRight(vop); + vop._tree.ancestor = node; + shift = vim._tree.prelim + sim - vip._tree.prelim - sip + separation(vim, vip); + if (shift > 0) { + d3_layout_treeMove(d3_layout_treeAncestor(vim, node, ancestor), node, shift); + sip += shift; + sop += shift; + } + sim += vim._tree.mod; + sip += vip._tree.mod; + som += vom._tree.mod; + sop += vop._tree.mod; + } + if (vim && !d3_layout_treeRight(vop)) { + vop._tree.thread = vim; + vop._tree.mod += sim - sop; + } + if (vip && !d3_layout_treeLeft(vom)) { + vom._tree.thread = vip; + vom._tree.mod += sip - som; + ancestor = node; + } + } + return ancestor; + } + d3_layout_treeVisitAfter(root, function(node, previousSibling) { + node._tree = { + ancestor: node, + prelim: 0, + mod: 0, + change: 0, + shift: 0, + number: previousSibling ? previousSibling._tree.number + 1 : 0 + }; + }); + firstWalk(root); + secondWalk(root, -root._tree.prelim); + var left = d3_layout_treeSearch(root, d3_layout_treeLeftmost), right = d3_layout_treeSearch(root, d3_layout_treeRightmost), deep = d3_layout_treeSearch(root, d3_layout_treeDeepest), x0 = left.x - separation(left, right) / 2, x1 = right.x + separation(right, left) / 2, y1 = deep.depth || 1; + d3_layout_treeVisitAfter(root, function(node) { + node.x = (node.x - x0) / (x1 - x0) * size[0]; + node.y = node.depth / y1 * size[1]; + delete node._tree; + }); + return nodes; + } + tree.separation = function(x) { + if (!arguments.length) return separation; + separation = x; + return tree; + }; + tree.size = function(x) { + if (!arguments.length) return size; + size = x; + return tree; + }; + return d3_layout_hierarchyRebind(tree, hierarchy); + }; + function d3_layout_treeSeparation(a, b) { + return a.parent == b.parent ? 1 : 2; + } + function d3_layout_treeLeft(node) { + var children = node.children; + return children && children.length ? children[0] : node._tree.thread; + } + function d3_layout_treeRight(node) { + var children = node.children, n; + return children && (n = children.length) ? children[n - 1] : node._tree.thread; + } + function d3_layout_treeSearch(node, compare) { + var children = node.children; + if (children && (n = children.length)) { + var child, n, i = -1; + while (++i < n) { + if (compare(child = d3_layout_treeSearch(children[i], compare), node) > 0) { + node = child; + } + } + } + return node; + } + function d3_layout_treeRightmost(a, b) { + return a.x - b.x; + } + function d3_layout_treeLeftmost(a, b) { + return b.x - a.x; + } + function d3_layout_treeDeepest(a, b) { + return a.depth - b.depth; + } + function d3_layout_treeVisitAfter(node, callback) { + function visit(node, previousSibling) { + var children = node.children; + if (children && (n = children.length)) { + var child, previousChild = null, i = -1, n; + while (++i < n) { + child = children[i]; + visit(child, previousChild); + previousChild = child; + } + } + callback(node, previousSibling); + } + visit(node, null); + } + function d3_layout_treeShift(node) { + var shift = 0, change = 0, children = node.children, i = children.length, child; + while (--i >= 0) { + child = children[i]._tree; + child.prelim += shift; + child.mod += shift; + shift += child.shift + (change += child.change); } } - - // Recursively inserts the specified point p at the node n or one of its - // descendants. The bounds are defined by [x1, x2] and [y1, y2]. - function insert(n, p, x1, y1, x2, y2) { - if (isNaN(p.x) || isNaN(p.y)) return; // ignore invalid points - if (n.leaf) { - var v = n.point; - if (v) { - // If the point at this leaf node is at the same position as the new - // point we are adding, we leave the point associated with the - // internal node while adding the new point to a child node. This - // avoids infinite recursion. - if ((Math.abs(v.x - p.x) + Math.abs(v.y - p.y)) < .01) { - insertChild(n, p, x1, y1, x2, y2); + function d3_layout_treeMove(ancestor, node, shift) { + ancestor = ancestor._tree; + node = node._tree; + var change = shift / (node.number - ancestor.number); + ancestor.change += change; + node.change -= change; + node.shift += shift; + node.prelim += shift; + node.mod += shift; + } + function d3_layout_treeAncestor(vim, node, ancestor) { + return vim._tree.ancestor.parent == node.parent ? vim._tree.ancestor : ancestor; + } + d3.layout.pack = function() { + var hierarchy = d3.layout.hierarchy().sort(d3_layout_packSort), padding = 0, size = [ 1, 1 ]; + function pack(d, i) { + var nodes = hierarchy.call(this, d, i), root = nodes[0]; + root.x = 0; + root.y = 0; + d3_layout_treeVisitAfter(root, function(d) { + d.r = Math.sqrt(d.value); + }); + d3_layout_treeVisitAfter(root, d3_layout_packSiblings); + var w = size[0], h = size[1], k = Math.max(2 * root.r / w, 2 * root.r / h); + if (padding > 0) { + var dr = padding * k / 2; + d3_layout_treeVisitAfter(root, function(d) { + d.r += dr; + }); + d3_layout_treeVisitAfter(root, d3_layout_packSiblings); + d3_layout_treeVisitAfter(root, function(d) { + d.r -= dr; + }); + k = Math.max(2 * root.r / w, 2 * root.r / h); + } + d3_layout_packTransform(root, w / 2, h / 2, 1 / k); + return nodes; + } + pack.size = function(x) { + if (!arguments.length) return size; + size = x; + return pack; + }; + pack.padding = function(_) { + if (!arguments.length) return padding; + padding = +_; + return pack; + }; + return d3_layout_hierarchyRebind(pack, hierarchy); + }; + function d3_layout_packSort(a, b) { + return a.value - b.value; + } + function d3_layout_packInsert(a, b) { + var c = a._pack_next; + a._pack_next = b; + b._pack_prev = a; + b._pack_next = c; + c._pack_prev = b; + } + function d3_layout_packSplice(a, b) { + a._pack_next = b; + b._pack_prev = a; + } + function d3_layout_packIntersects(a, b) { + var dx = b.x - a.x, dy = b.y - a.y, dr = a.r + b.r; + return dr * dr - dx * dx - dy * dy > .001; + } + function d3_layout_packSiblings(node) { + if (!(nodes = node.children) || !(n = nodes.length)) return; + var nodes, xMin = Infinity, xMax = -Infinity, yMin = Infinity, yMax = -Infinity, a, b, c, i, j, k, n; + function bound(node) { + xMin = Math.min(node.x - node.r, xMin); + xMax = Math.max(node.x + node.r, xMax); + yMin = Math.min(node.y - node.r, yMin); + yMax = Math.max(node.y + node.r, yMax); + } + nodes.forEach(d3_layout_packLink); + a = nodes[0]; + a.x = -a.r; + a.y = 0; + bound(a); + if (n > 1) { + b = nodes[1]; + b.x = b.r; + b.y = 0; + bound(b); + if (n > 2) { + c = nodes[2]; + d3_layout_packPlace(a, b, c); + bound(c); + d3_layout_packInsert(a, c); + a._pack_prev = c; + d3_layout_packInsert(c, b); + b = a._pack_next; + for (i = 3; i < n; i++) { + d3_layout_packPlace(a, b, c = nodes[i]); + var isect = 0, s1 = 1, s2 = 1; + for (j = b._pack_next; j !== b; j = j._pack_next, s1++) { + if (d3_layout_packIntersects(j, c)) { + isect = 1; + break; + } + } + if (isect == 1) { + for (k = a._pack_prev; k !== j._pack_prev; k = k._pack_prev, s2++) { + if (d3_layout_packIntersects(k, c)) { + break; + } + } + } + if (isect) { + if (s1 < s2 || s1 == s2 && b.r < a.r) d3_layout_packSplice(a, b = j); else d3_layout_packSplice(a = k, b); + i--; + } else { + d3_layout_packInsert(a, c); + b = c; + bound(c); + } + } + } + } + var cx = (xMin + xMax) / 2, cy = (yMin + yMax) / 2, cr = 0; + for (i = 0; i < n; i++) { + c = nodes[i]; + c.x -= cx; + c.y -= cy; + cr = Math.max(cr, c.r + Math.sqrt(c.x * c.x + c.y * c.y)); + } + node.r = cr; + nodes.forEach(d3_layout_packUnlink); + } + function d3_layout_packLink(node) { + node._pack_next = node._pack_prev = node; + } + function d3_layout_packUnlink(node) { + delete node._pack_next; + delete node._pack_prev; + } + function d3_layout_packTransform(node, x, y, k) { + var children = node.children; + node.x = x += k * node.x; + node.y = y += k * node.y; + node.r *= k; + if (children) { + var i = -1, n = children.length; + while (++i < n) d3_layout_packTransform(children[i], x, y, k); + } + } + function d3_layout_packPlace(a, b, c) { + var db = a.r + c.r, dx = b.x - a.x, dy = b.y - a.y; + if (db && (dx || dy)) { + var da = b.r + c.r, dc = dx * dx + dy * dy; + da *= da; + db *= db; + var x = .5 + (db - da) / (2 * dc), y = Math.sqrt(Math.max(0, 2 * da * (db + dc) - (db -= dc) * db - da * da)) / (2 * dc); + c.x = a.x + x * dx + y * dy; + c.y = a.y + x * dy - y * dx; + } else { + c.x = a.x + db; + c.y = a.y; + } + } + d3.layout.cluster = function() { + var hierarchy = d3.layout.hierarchy().sort(null).value(null), separation = d3_layout_treeSeparation, size = [ 1, 1 ]; + function cluster(d, i) { + var nodes = hierarchy.call(this, d, i), root = nodes[0], previousNode, x = 0; + d3_layout_treeVisitAfter(root, function(node) { + var children = node.children; + if (children && children.length) { + node.x = d3_layout_clusterX(children); + node.y = d3_layout_clusterY(children); } else { - n.point = null; - insertChild(n, v, x1, y1, x2, y2); - insertChild(n, p, x1, y1, x2, y2); + node.x = previousNode ? x += separation(node, previousNode) : 0; + node.y = 0; + previousNode = node; + } + }); + var left = d3_layout_clusterLeft(root), right = d3_layout_clusterRight(root), x0 = left.x - separation(left, right) / 2, x1 = right.x + separation(right, left) / 2; + d3_layout_treeVisitAfter(root, function(node) { + node.x = (node.x - x0) / (x1 - x0) * size[0]; + node.y = (1 - (root.y ? node.y / root.y : 1)) * size[1]; + }); + return nodes; + } + cluster.separation = function(x) { + if (!arguments.length) return separation; + separation = x; + return cluster; + }; + cluster.size = function(x) { + if (!arguments.length) return size; + size = x; + return cluster; + }; + return d3_layout_hierarchyRebind(cluster, hierarchy); + }; + function d3_layout_clusterY(children) { + return 1 + d3.max(children, function(child) { + return child.y; + }); + } + function d3_layout_clusterX(children) { + return children.reduce(function(x, child) { + return x + child.x; + }, 0) / children.length; + } + function d3_layout_clusterLeft(node) { + var children = node.children; + return children && children.length ? d3_layout_clusterLeft(children[0]) : node; + } + function d3_layout_clusterRight(node) { + var children = node.children, n; + return children && (n = children.length) ? d3_layout_clusterRight(children[n - 1]) : node; + } + d3.layout.treemap = function() { + var hierarchy = d3.layout.hierarchy(), round = Math.round, size = [ 1, 1 ], padding = null, pad = d3_layout_treemapPadNull, sticky = false, stickies, mode = "squarify", ratio = .5 * (1 + Math.sqrt(5)); + function scale(children, k) { + var i = -1, n = children.length, child, area; + while (++i < n) { + area = (child = children[i]).value * (k < 0 ? 0 : k); + child.area = isNaN(area) || area <= 0 ? 0 : area; + } + } + function squarify(node) { + var children = node.children; + if (children && children.length) { + var rect = pad(node), row = [], remaining = children.slice(), child, best = Infinity, score, u = mode === "slice" ? rect.dx : mode === "dice" ? rect.dy : mode === "slice-dice" ? node.depth & 1 ? rect.dy : rect.dx : Math.min(rect.dx, rect.dy), n; + scale(remaining, rect.dx * rect.dy / node.value); + row.area = 0; + while ((n = remaining.length) > 0) { + row.push(child = remaining[n - 1]); + row.area += child.area; + if (mode !== "squarify" || (score = worst(row, u)) <= best) { + remaining.pop(); + best = score; + } else { + row.area -= row.pop().area; + position(row, u, rect, false); + u = Math.min(rect.dx, rect.dy); + row.length = row.area = 0; + best = Infinity; + } + } + if (row.length) { + position(row, u, rect, true); + row.length = row.area = 0; + } + children.forEach(squarify); + } + } + function stickify(node) { + var children = node.children; + if (children && children.length) { + var rect = pad(node), remaining = children.slice(), child, row = []; + scale(remaining, rect.dx * rect.dy / node.value); + row.area = 0; + while (child = remaining.pop()) { + row.push(child); + row.area += child.area; + if (child.z != null) { + position(row, child.z ? rect.dx : rect.dy, rect, !remaining.length); + row.length = row.area = 0; + } + } + children.forEach(stickify); + } + } + function worst(row, u) { + var s = row.area, r, rmax = 0, rmin = Infinity, i = -1, n = row.length; + while (++i < n) { + if (!(r = row[i].area)) continue; + if (r < rmin) rmin = r; + if (r > rmax) rmax = r; + } + s *= s; + u *= u; + return s ? Math.max(u * rmax * ratio / s, s / (u * rmin * ratio)) : Infinity; + } + function position(row, u, rect, flush) { + var i = -1, n = row.length, x = rect.x, y = rect.y, v = u ? round(row.area / u) : 0, o; + if (u == rect.dx) { + if (flush || v > rect.dy) v = rect.dy; + while (++i < n) { + o = row[i]; + o.x = x; + o.y = y; + o.dy = v; + x += o.dx = Math.min(rect.x + rect.dx - x, v ? round(o.area / v) : 0); + } + o.z = true; + o.dx += rect.x + rect.dx - x; + rect.y += v; + rect.dy -= v; + } else { + if (flush || v > rect.dx) v = rect.dx; + while (++i < n) { + o = row[i]; + o.x = x; + o.y = y; + o.dx = v; + y += o.dy = Math.min(rect.y + rect.dy - y, v ? round(o.area / v) : 0); + } + o.z = false; + o.dy += rect.y + rect.dy - y; + rect.x += v; + rect.dx -= v; + } + } + function treemap(d) { + var nodes = stickies || hierarchy(d), root = nodes[0]; + root.x = 0; + root.y = 0; + root.dx = size[0]; + root.dy = size[1]; + if (stickies) hierarchy.revalue(root); + scale([ root ], root.dx * root.dy / root.value); + (stickies ? stickify : squarify)(root); + if (sticky) stickies = nodes; + return nodes; + } + treemap.size = function(x) { + if (!arguments.length) return size; + size = x; + return treemap; + }; + treemap.padding = function(x) { + if (!arguments.length) return padding; + function padFunction(node) { + var p = x.call(treemap, node, node.depth); + return p == null ? d3_layout_treemapPadNull(node) : d3_layout_treemapPad(node, typeof p === "number" ? [ p, p, p, p ] : p); + } + function padConstant(node) { + return d3_layout_treemapPad(node, x); + } + var type; + pad = (padding = x) == null ? d3_layout_treemapPadNull : (type = typeof x) === "function" ? padFunction : type === "number" ? (x = [ x, x, x, x ], + padConstant) : padConstant; + return treemap; + }; + treemap.round = function(x) { + if (!arguments.length) return round != Number; + round = x ? Math.round : Number; + return treemap; + }; + treemap.sticky = function(x) { + if (!arguments.length) return sticky; + sticky = x; + stickies = null; + return treemap; + }; + treemap.ratio = function(x) { + if (!arguments.length) return ratio; + ratio = x; + return treemap; + }; + treemap.mode = function(x) { + if (!arguments.length) return mode; + mode = x + ""; + return treemap; + }; + return d3_layout_hierarchyRebind(treemap, hierarchy); + }; + function d3_layout_treemapPadNull(node) { + return { + x: node.x, + y: node.y, + dx: node.dx, + dy: node.dy + }; + } + function d3_layout_treemapPad(node, padding) { + var x = node.x + padding[3], y = node.y + padding[0], dx = node.dx - padding[1] - padding[3], dy = node.dy - padding[0] - padding[2]; + if (dx < 0) { + x += dx / 2; + dx = 0; + } + if (dy < 0) { + y += dy / 2; + dy = 0; + } + return { + x: x, + y: y, + dx: dx, + dy: dy + }; + } + d3.random = { + normal: function(µ, σ) { + var n = arguments.length; + if (n < 2) σ = 1; + if (n < 1) µ = 0; + return function() { + var x, y, r; + do { + x = Math.random() * 2 - 1; + y = Math.random() * 2 - 1; + r = x * x + y * y; + } while (!r || r > 1); + return µ + σ * x * Math.sqrt(-2 * Math.log(r) / r); + }; + }, + logNormal: function() { + var random = d3.random.normal.apply(d3, arguments); + return function() { + return Math.exp(random()); + }; + }, + irwinHall: function(m) { + return function() { + for (var s = 0, j = 0; j < m; j++) s += Math.random(); + return s / m; + }; + } + }; + d3.scale = {}; + function d3_scaleExtent(domain) { + var start = domain[0], stop = domain[domain.length - 1]; + return start < stop ? [ start, stop ] : [ stop, start ]; + } + function d3_scaleRange(scale) { + return scale.rangeExtent ? scale.rangeExtent() : d3_scaleExtent(scale.range()); + } + function d3_scale_bilinear(domain, range, uninterpolate, interpolate) { + var u = uninterpolate(domain[0], domain[1]), i = interpolate(range[0], range[1]); + return function(x) { + return i(u(x)); + }; + } + function d3_scale_nice(domain, nice) { + var i0 = 0, i1 = domain.length - 1, x0 = domain[i0], x1 = domain[i1], dx; + if (x1 < x0) { + dx = i0, i0 = i1, i1 = dx; + dx = x0, x0 = x1, x1 = dx; + } + if (nice = nice(x1 - x0)) { + domain[i0] = nice.floor(x0); + domain[i1] = nice.ceil(x1); + } + return domain; + } + function d3_scale_polylinear(domain, range, uninterpolate, interpolate) { + var u = [], i = [], j = 0, k = Math.min(domain.length, range.length) - 1; + if (domain[k] < domain[0]) { + domain = domain.slice().reverse(); + range = range.slice().reverse(); + } + while (++j <= k) { + u.push(uninterpolate(domain[j - 1], domain[j])); + i.push(interpolate(range[j - 1], range[j])); + } + return function(x) { + var j = d3.bisect(domain, x, 1, k) - 1; + return i[j](u[j](x)); + }; + } + d3.scale.linear = function() { + return d3_scale_linear([ 0, 1 ], [ 0, 1 ], d3_interpolate, false); + }; + function d3_scale_linear(domain, range, interpolate, clamp) { + var output, input; + function rescale() { + var linear = Math.min(domain.length, range.length) > 2 ? d3_scale_polylinear : d3_scale_bilinear, uninterpolate = clamp ? d3_uninterpolateClamp : d3_uninterpolateNumber; + output = linear(domain, range, uninterpolate, interpolate); + input = linear(range, domain, uninterpolate, d3_interpolate); + return scale; + } + function scale(x) { + return output(x); + } + scale.invert = function(y) { + return input(y); + }; + scale.domain = function(x) { + if (!arguments.length) return domain; + domain = x.map(Number); + return rescale(); + }; + scale.range = function(x) { + if (!arguments.length) return range; + range = x; + return rescale(); + }; + scale.rangeRound = function(x) { + return scale.range(x).interpolate(d3_interpolateRound); + }; + scale.clamp = function(x) { + if (!arguments.length) return clamp; + clamp = x; + return rescale(); + }; + scale.interpolate = function(x) { + if (!arguments.length) return interpolate; + interpolate = x; + return rescale(); + }; + scale.ticks = function(m) { + return d3_scale_linearTicks(domain, m); + }; + scale.tickFormat = function(m, format) { + return d3_scale_linearTickFormat(domain, m, format); + }; + scale.nice = function() { + d3_scale_nice(domain, d3_scale_linearNice); + return rescale(); + }; + scale.copy = function() { + return d3_scale_linear(domain, range, interpolate, clamp); + }; + return rescale(); + } + function d3_scale_linearRebind(scale, linear) { + return d3.rebind(scale, linear, "range", "rangeRound", "interpolate", "clamp"); + } + function d3_scale_linearNice(dx) { + dx = Math.pow(10, Math.round(Math.log(dx) / Math.LN10) - 1); + return dx && { + floor: function(x) { + return Math.floor(x / dx) * dx; + }, + ceil: function(x) { + return Math.ceil(x / dx) * dx; + } + }; + } + function d3_scale_linearTickRange(domain, m) { + var extent = d3_scaleExtent(domain), span = extent[1] - extent[0], step = Math.pow(10, Math.floor(Math.log(span / m) / Math.LN10)), err = m / span * step; + if (err <= .15) step *= 10; else if (err <= .35) step *= 5; else if (err <= .75) step *= 2; + extent[0] = Math.ceil(extent[0] / step) * step; + extent[1] = Math.floor(extent[1] / step) * step + step * .5; + extent[2] = step; + return extent; + } + function d3_scale_linearTicks(domain, m) { + return d3.range.apply(d3, d3_scale_linearTickRange(domain, m)); + } + function d3_scale_linearTickFormat(domain, m, format) { + var precision = -Math.floor(Math.log(d3_scale_linearTickRange(domain, m)[2]) / Math.LN10 + .01); + return d3.format(format ? format.replace(d3_format_re, function(a, b, c, d, e, f, g, h, i, j) { + return [ b, c, d, e, f, g, h, i || "." + (precision - (j === "%") * 2), j ].join(""); + }) : ",." + precision + "f"); + } + d3.scale.log = function() { + return d3_scale_log(d3.scale.linear().domain([ 0, Math.LN10 ]), 10, d3_scale_logp, d3_scale_powp); + }; + function d3_scale_log(linear, base, log, pow) { + function scale(x) { + return linear(log(x)); + } + scale.invert = function(x) { + return pow(linear.invert(x)); + }; + scale.domain = function(x) { + if (!arguments.length) return linear.domain().map(pow); + if (x[0] < 0) log = d3_scale_logn, pow = d3_scale_pown; else log = d3_scale_logp, + pow = d3_scale_powp; + linear.domain(x.map(log)); + return scale; + }; + scale.base = function(_) { + if (!arguments.length) return base; + base = +_; + return scale; + }; + scale.nice = function() { + linear.domain(d3_scale_nice(linear.domain(), d3_scale_logNice(base))); + return scale; + }; + scale.ticks = function() { + var extent = d3_scaleExtent(linear.domain()), ticks = []; + if (extent.every(isFinite)) { + var b = Math.log(base), i = Math.floor(extent[0] / b), j = Math.ceil(extent[1] / b), u = pow(extent[0]), v = pow(extent[1]), n = base % 1 ? 2 : base; + if (log === d3_scale_logn) { + ticks.push(-Math.pow(base, -i)); + for (;i++ < j; ) for (var k = n - 1; k > 0; k--) ticks.push(-Math.pow(base, -i) * k); + } else { + for (;i < j; i++) for (var k = 1; k < n; k++) ticks.push(Math.pow(base, i) * k); + ticks.push(Math.pow(base, i)); + } + for (i = 0; ticks[i] < u; i++) {} + for (j = ticks.length; ticks[j - 1] > v; j--) {} + ticks = ticks.slice(i, j); + } + return ticks; + }; + scale.tickFormat = function(n, format) { + if (arguments.length < 2) format = d3_scale_logFormat; + if (!arguments.length) return format; + var b = Math.log(base), k = Math.max(.1, n / scale.ticks().length), f = log === d3_scale_logn ? (e = -1e-12, + Math.floor) : (e = 1e-12, Math.ceil), e; + return function(d) { + return d / pow(b * f(log(d) / b + e)) <= k ? format(d) : ""; + }; + }; + scale.copy = function() { + return d3_scale_log(linear.copy(), base, log, pow); + }; + return d3_scale_linearRebind(scale, linear); + } + var d3_scale_logFormat = d3.format(".0e"); + function d3_scale_logp(x) { + return Math.log(x < 0 ? 0 : x); + } + function d3_scale_powp(x) { + return Math.exp(x); + } + function d3_scale_logn(x) { + return -Math.log(x > 0 ? 0 : -x); + } + function d3_scale_pown(x) { + return -Math.exp(-x); + } + function d3_scale_logNice(base) { + base = Math.log(base); + var nice = { + floor: function(x) { + return Math.floor(x / base) * base; + }, + ceil: function(x) { + return Math.ceil(x / base) * base; + } + }; + return function() { + return nice; + }; + } + d3.scale.pow = function() { + return d3_scale_pow(d3.scale.linear(), 1); + }; + function d3_scale_pow(linear, exponent) { + var powp = d3_scale_powPow(exponent), powb = d3_scale_powPow(1 / exponent); + function scale(x) { + return linear(powp(x)); + } + scale.invert = function(x) { + return powb(linear.invert(x)); + }; + scale.domain = function(x) { + if (!arguments.length) return linear.domain().map(powb); + linear.domain(x.map(powp)); + return scale; + }; + scale.ticks = function(m) { + return d3_scale_linearTicks(scale.domain(), m); + }; + scale.tickFormat = function(m, format) { + return d3_scale_linearTickFormat(scale.domain(), m, format); + }; + scale.nice = function() { + return scale.domain(d3_scale_nice(scale.domain(), d3_scale_linearNice)); + }; + scale.exponent = function(x) { + if (!arguments.length) return exponent; + var domain = scale.domain(); + powp = d3_scale_powPow(exponent = x); + powb = d3_scale_powPow(1 / exponent); + return scale.domain(domain); + }; + scale.copy = function() { + return d3_scale_pow(linear.copy(), exponent); + }; + return d3_scale_linearRebind(scale, linear); + } + function d3_scale_powPow(e) { + return function(x) { + return x < 0 ? -Math.pow(-x, e) : Math.pow(x, e); + }; + } + d3.scale.sqrt = function() { + return d3.scale.pow().exponent(.5); + }; + d3.scale.ordinal = function() { + return d3_scale_ordinal([], { + t: "range", + a: [ [] ] + }); + }; + function d3_scale_ordinal(domain, ranger) { + var index, range, rangeBand; + function scale(x) { + return range[((index.get(x) || index.set(x, domain.push(x))) - 1) % range.length]; + } + function steps(start, step) { + return d3.range(domain.length).map(function(i) { + return start + step * i; + }); + } + scale.domain = function(x) { + if (!arguments.length) return domain; + domain = []; + index = new d3_Map(); + var i = -1, n = x.length, xi; + while (++i < n) if (!index.has(xi = x[i])) index.set(xi, domain.push(xi)); + return scale[ranger.t].apply(scale, ranger.a); + }; + scale.range = function(x) { + if (!arguments.length) return range; + range = x; + rangeBand = 0; + ranger = { + t: "range", + a: arguments + }; + return scale; + }; + scale.rangePoints = function(x, padding) { + if (arguments.length < 2) padding = 0; + var start = x[0], stop = x[1], step = (stop - start) / (Math.max(1, domain.length - 1) + padding); + range = steps(domain.length < 2 ? (start + stop) / 2 : start + step * padding / 2, step); + rangeBand = 0; + ranger = { + t: "rangePoints", + a: arguments + }; + return scale; + }; + scale.rangeBands = function(x, padding, outerPadding) { + if (arguments.length < 2) padding = 0; + if (arguments.length < 3) outerPadding = padding; + var reverse = x[1] < x[0], start = x[reverse - 0], stop = x[1 - reverse], step = (stop - start) / (domain.length - padding + 2 * outerPadding); + range = steps(start + step * outerPadding, step); + if (reverse) range.reverse(); + rangeBand = step * (1 - padding); + ranger = { + t: "rangeBands", + a: arguments + }; + return scale; + }; + scale.rangeRoundBands = function(x, padding, outerPadding) { + if (arguments.length < 2) padding = 0; + if (arguments.length < 3) outerPadding = padding; + var reverse = x[1] < x[0], start = x[reverse - 0], stop = x[1 - reverse], step = Math.floor((stop - start) / (domain.length - padding + 2 * outerPadding)), error = stop - start - (domain.length - padding) * step; + range = steps(start + Math.round(error / 2), step); + if (reverse) range.reverse(); + rangeBand = Math.round(step * (1 - padding)); + ranger = { + t: "rangeRoundBands", + a: arguments + }; + return scale; + }; + scale.rangeBand = function() { + return rangeBand; + }; + scale.rangeExtent = function() { + return d3_scaleExtent(ranger.a[0]); + }; + scale.copy = function() { + return d3_scale_ordinal(domain, ranger); + }; + return scale.domain(domain); + } + d3.scale.category10 = function() { + return d3.scale.ordinal().range(d3_category10); + }; + d3.scale.category20 = function() { + return d3.scale.ordinal().range(d3_category20); + }; + d3.scale.category20b = function() { + return d3.scale.ordinal().range(d3_category20b); + }; + d3.scale.category20c = function() { + return d3.scale.ordinal().range(d3_category20c); + }; + var d3_category10 = [ "#1f77b4", "#ff7f0e", "#2ca02c", "#d62728", "#9467bd", "#8c564b", "#e377c2", "#7f7f7f", "#bcbd22", "#17becf" ]; + var d3_category20 = [ "#1f77b4", "#aec7e8", "#ff7f0e", "#ffbb78", "#2ca02c", "#98df8a", "#d62728", "#ff9896", "#9467bd", "#c5b0d5", "#8c564b", "#c49c94", "#e377c2", "#f7b6d2", "#7f7f7f", "#c7c7c7", "#bcbd22", "#dbdb8d", "#17becf", "#9edae5" ]; + var d3_category20b = [ "#393b79", "#5254a3", "#6b6ecf", "#9c9ede", "#637939", "#8ca252", "#b5cf6b", "#cedb9c", "#8c6d31", "#bd9e39", "#e7ba52", "#e7cb94", "#843c39", "#ad494a", "#d6616b", "#e7969c", "#7b4173", "#a55194", "#ce6dbd", "#de9ed6" ]; + var d3_category20c = [ "#3182bd", "#6baed6", "#9ecae1", "#c6dbef", "#e6550d", "#fd8d3c", "#fdae6b", "#fdd0a2", "#31a354", "#74c476", "#a1d99b", "#c7e9c0", "#756bb1", "#9e9ac8", "#bcbddc", "#dadaeb", "#636363", "#969696", "#bdbdbd", "#d9d9d9" ]; + d3.scale.quantile = function() { + return d3_scale_quantile([], []); + }; + function d3_scale_quantile(domain, range) { + var thresholds; + function rescale() { + var k = 0, q = range.length; + thresholds = []; + while (++k < q) thresholds[k - 1] = d3.quantile(domain, k / q); + return scale; + } + function scale(x) { + if (isNaN(x = +x)) return NaN; + return range[d3.bisect(thresholds, x)]; + } + scale.domain = function(x) { + if (!arguments.length) return domain; + domain = x.filter(function(d) { + return !isNaN(d); + }).sort(d3.ascending); + return rescale(); + }; + scale.range = function(x) { + if (!arguments.length) return range; + range = x; + return rescale(); + }; + scale.quantiles = function() { + return thresholds; + }; + scale.copy = function() { + return d3_scale_quantile(domain, range); + }; + return rescale(); + } + d3.scale.quantize = function() { + return d3_scale_quantize(0, 1, [ 0, 1 ]); + }; + function d3_scale_quantize(x0, x1, range) { + var kx, i; + function scale(x) { + return range[Math.max(0, Math.min(i, Math.floor(kx * (x - x0))))]; + } + function rescale() { + kx = range.length / (x1 - x0); + i = range.length - 1; + return scale; + } + scale.domain = function(x) { + if (!arguments.length) return [ x0, x1 ]; + x0 = +x[0]; + x1 = +x[x.length - 1]; + return rescale(); + }; + scale.range = function(x) { + if (!arguments.length) return range; + range = x; + return rescale(); + }; + scale.copy = function() { + return d3_scale_quantize(x0, x1, range); + }; + return rescale(); + } + d3.scale.threshold = function() { + return d3_scale_threshold([ .5 ], [ 0, 1 ]); + }; + function d3_scale_threshold(domain, range) { + function scale(x) { + return range[d3.bisect(domain, x)]; + } + scale.domain = function(_) { + if (!arguments.length) return domain; + domain = _; + return scale; + }; + scale.range = function(_) { + if (!arguments.length) return range; + range = _; + return scale; + }; + scale.copy = function() { + return d3_scale_threshold(domain, range); + }; + return scale; + } + d3.scale.identity = function() { + return d3_scale_identity([ 0, 1 ]); + }; + function d3_scale_identity(domain) { + function identity(x) { + return +x; + } + identity.invert = identity; + identity.domain = identity.range = function(x) { + if (!arguments.length) return domain; + domain = x.map(identity); + return identity; + }; + identity.ticks = function(m) { + return d3_scale_linearTicks(domain, m); + }; + identity.tickFormat = function(m, format) { + return d3_scale_linearTickFormat(domain, m, format); + }; + identity.copy = function() { + return d3_scale_identity(domain); + }; + return identity; + } + d3.svg.arc = function() { + var innerRadius = d3_svg_arcInnerRadius, outerRadius = d3_svg_arcOuterRadius, startAngle = d3_svg_arcStartAngle, endAngle = d3_svg_arcEndAngle; + function arc() { + var r0 = innerRadius.apply(this, arguments), r1 = outerRadius.apply(this, arguments), a0 = startAngle.apply(this, arguments) + d3_svg_arcOffset, a1 = endAngle.apply(this, arguments) + d3_svg_arcOffset, da = (a1 < a0 && (da = a0, + a0 = a1, a1 = da), a1 - a0), df = da < π ? "0" : "1", c0 = Math.cos(a0), s0 = Math.sin(a0), c1 = Math.cos(a1), s1 = Math.sin(a1); + return da >= d3_svg_arcMax ? r0 ? "M0," + r1 + "A" + r1 + "," + r1 + " 0 1,1 0," + -r1 + "A" + r1 + "," + r1 + " 0 1,1 0," + r1 + "M0," + r0 + "A" + r0 + "," + r0 + " 0 1,0 0," + -r0 + "A" + r0 + "," + r0 + " 0 1,0 0," + r0 + "Z" : "M0," + r1 + "A" + r1 + "," + r1 + " 0 1,1 0," + -r1 + "A" + r1 + "," + r1 + " 0 1,1 0," + r1 + "Z" : r0 ? "M" + r1 * c0 + "," + r1 * s0 + "A" + r1 + "," + r1 + " 0 " + df + ",1 " + r1 * c1 + "," + r1 * s1 + "L" + r0 * c1 + "," + r0 * s1 + "A" + r0 + "," + r0 + " 0 " + df + ",0 " + r0 * c0 + "," + r0 * s0 + "Z" : "M" + r1 * c0 + "," + r1 * s0 + "A" + r1 + "," + r1 + " 0 " + df + ",1 " + r1 * c1 + "," + r1 * s1 + "L0,0" + "Z"; + } + arc.innerRadius = function(v) { + if (!arguments.length) return innerRadius; + innerRadius = d3_functor(v); + return arc; + }; + arc.outerRadius = function(v) { + if (!arguments.length) return outerRadius; + outerRadius = d3_functor(v); + return arc; + }; + arc.startAngle = function(v) { + if (!arguments.length) return startAngle; + startAngle = d3_functor(v); + return arc; + }; + arc.endAngle = function(v) { + if (!arguments.length) return endAngle; + endAngle = d3_functor(v); + return arc; + }; + arc.centroid = function() { + var r = (innerRadius.apply(this, arguments) + outerRadius.apply(this, arguments)) / 2, a = (startAngle.apply(this, arguments) + endAngle.apply(this, arguments)) / 2 + d3_svg_arcOffset; + return [ Math.cos(a) * r, Math.sin(a) * r ]; + }; + return arc; + }; + var d3_svg_arcOffset = -π / 2, d3_svg_arcMax = 2 * π - 1e-6; + function d3_svg_arcInnerRadius(d) { + return d.innerRadius; + } + function d3_svg_arcOuterRadius(d) { + return d.outerRadius; + } + function d3_svg_arcStartAngle(d) { + return d.startAngle; + } + function d3_svg_arcEndAngle(d) { + return d.endAngle; + } + d3.svg.line.radial = function() { + var line = d3_svg_line(d3_svg_lineRadial); + line.radius = line.x, delete line.x; + line.angle = line.y, delete line.y; + return line; + }; + function d3_svg_lineRadial(points) { + var point, i = -1, n = points.length, r, a; + while (++i < n) { + point = points[i]; + r = point[0]; + a = point[1] + d3_svg_arcOffset; + point[0] = r * Math.cos(a); + point[1] = r * Math.sin(a); + } + return points; + } + function d3_svg_area(projection) { + var x0 = d3_svg_lineX, x1 = d3_svg_lineX, y0 = 0, y1 = d3_svg_lineY, defined = d3_true, interpolate = d3_svg_lineLinear, interpolateKey = interpolate.key, interpolateReverse = interpolate, L = "L", tension = .7; + function area(data) { + var segments = [], points0 = [], points1 = [], i = -1, n = data.length, d, fx0 = d3_functor(x0), fy0 = d3_functor(y0), fx1 = x0 === x1 ? function() { + return x; + } : d3_functor(x1), fy1 = y0 === y1 ? function() { + return y; + } : d3_functor(y1), x, y; + function segment() { + segments.push("M", interpolate(projection(points1), tension), L, interpolateReverse(projection(points0.reverse()), tension), "Z"); + } + while (++i < n) { + if (defined.call(this, d = data[i], i)) { + points0.push([ x = +fx0.call(this, d, i), y = +fy0.call(this, d, i) ]); + points1.push([ +fx1.call(this, d, i), +fy1.call(this, d, i) ]); + } else if (points0.length) { + segment(); + points0 = []; + points1 = []; + } + } + if (points0.length) segment(); + return segments.length ? segments.join("") : null; + } + area.x = function(_) { + if (!arguments.length) return x1; + x0 = x1 = _; + return area; + }; + area.x0 = function(_) { + if (!arguments.length) return x0; + x0 = _; + return area; + }; + area.x1 = function(_) { + if (!arguments.length) return x1; + x1 = _; + return area; + }; + area.y = function(_) { + if (!arguments.length) return y1; + y0 = y1 = _; + return area; + }; + area.y0 = function(_) { + if (!arguments.length) return y0; + y0 = _; + return area; + }; + area.y1 = function(_) { + if (!arguments.length) return y1; + y1 = _; + return area; + }; + area.defined = function(_) { + if (!arguments.length) return defined; + defined = _; + return area; + }; + area.interpolate = function(_) { + if (!arguments.length) return interpolateKey; + if (typeof _ === "function") interpolateKey = interpolate = _; else interpolateKey = (interpolate = d3_svg_lineInterpolators.get(_) || d3_svg_lineLinear).key; + interpolateReverse = interpolate.reverse || interpolate; + L = interpolate.closed ? "M" : "L"; + return area; + }; + area.tension = function(_) { + if (!arguments.length) return tension; + tension = _; + return area; + }; + return area; + } + d3_svg_lineStepBefore.reverse = d3_svg_lineStepAfter; + d3_svg_lineStepAfter.reverse = d3_svg_lineStepBefore; + d3.svg.area = function() { + return d3_svg_area(d3_identity); + }; + d3.svg.area.radial = function() { + var area = d3_svg_area(d3_svg_lineRadial); + area.radius = area.x, delete area.x; + area.innerRadius = area.x0, delete area.x0; + area.outerRadius = area.x1, delete area.x1; + area.angle = area.y, delete area.y; + area.startAngle = area.y0, delete area.y0; + area.endAngle = area.y1, delete area.y1; + return area; + }; + d3.svg.chord = function() { + var source = d3_source, target = d3_target, radius = d3_svg_chordRadius, startAngle = d3_svg_arcStartAngle, endAngle = d3_svg_arcEndAngle; + function chord(d, i) { + var s = subgroup(this, source, d, i), t = subgroup(this, target, d, i); + return "M" + s.p0 + arc(s.r, s.p1, s.a1 - s.a0) + (equals(s, t) ? curve(s.r, s.p1, s.r, s.p0) : curve(s.r, s.p1, t.r, t.p0) + arc(t.r, t.p1, t.a1 - t.a0) + curve(t.r, t.p1, s.r, s.p0)) + "Z"; + } + function subgroup(self, f, d, i) { + var subgroup = f.call(self, d, i), r = radius.call(self, subgroup, i), a0 = startAngle.call(self, subgroup, i) + d3_svg_arcOffset, a1 = endAngle.call(self, subgroup, i) + d3_svg_arcOffset; + return { + r: r, + a0: a0, + a1: a1, + p0: [ r * Math.cos(a0), r * Math.sin(a0) ], + p1: [ r * Math.cos(a1), r * Math.sin(a1) ] + }; + } + function equals(a, b) { + return a.a0 == b.a0 && a.a1 == b.a1; + } + function arc(r, p, a) { + return "A" + r + "," + r + " 0 " + +(a > π) + ",1 " + p; + } + function curve(r0, p0, r1, p1) { + return "Q 0,0 " + p1; + } + chord.radius = function(v) { + if (!arguments.length) return radius; + radius = d3_functor(v); + return chord; + }; + chord.source = function(v) { + if (!arguments.length) return source; + source = d3_functor(v); + return chord; + }; + chord.target = function(v) { + if (!arguments.length) return target; + target = d3_functor(v); + return chord; + }; + chord.startAngle = function(v) { + if (!arguments.length) return startAngle; + startAngle = d3_functor(v); + return chord; + }; + chord.endAngle = function(v) { + if (!arguments.length) return endAngle; + endAngle = d3_functor(v); + return chord; + }; + return chord; + }; + function d3_svg_chordRadius(d) { + return d.radius; + } + d3.svg.diagonal = function() { + var source = d3_source, target = d3_target, projection = d3_svg_diagonalProjection; + function diagonal(d, i) { + var p0 = source.call(this, d, i), p3 = target.call(this, d, i), m = (p0.y + p3.y) / 2, p = [ p0, { + x: p0.x, + y: m + }, { + x: p3.x, + y: m + }, p3 ]; + p = p.map(projection); + return "M" + p[0] + "C" + p[1] + " " + p[2] + " " + p[3]; + } + diagonal.source = function(x) { + if (!arguments.length) return source; + source = d3_functor(x); + return diagonal; + }; + diagonal.target = function(x) { + if (!arguments.length) return target; + target = d3_functor(x); + return diagonal; + }; + diagonal.projection = function(x) { + if (!arguments.length) return projection; + projection = x; + return diagonal; + }; + return diagonal; + }; + function d3_svg_diagonalProjection(d) { + return [ d.x, d.y ]; + } + d3.svg.diagonal.radial = function() { + var diagonal = d3.svg.diagonal(), projection = d3_svg_diagonalProjection, projection_ = diagonal.projection; + diagonal.projection = function(x) { + return arguments.length ? projection_(d3_svg_diagonalRadialProjection(projection = x)) : projection; + }; + return diagonal; + }; + function d3_svg_diagonalRadialProjection(projection) { + return function() { + var d = projection.apply(this, arguments), r = d[0], a = d[1] + d3_svg_arcOffset; + return [ r * Math.cos(a), r * Math.sin(a) ]; + }; + } + d3.svg.symbol = function() { + var type = d3_svg_symbolType, size = d3_svg_symbolSize; + function symbol(d, i) { + return (d3_svg_symbols.get(type.call(this, d, i)) || d3_svg_symbolCircle)(size.call(this, d, i)); + } + symbol.type = function(x) { + if (!arguments.length) return type; + type = d3_functor(x); + return symbol; + }; + symbol.size = function(x) { + if (!arguments.length) return size; + size = d3_functor(x); + return symbol; + }; + return symbol; + }; + function d3_svg_symbolSize() { + return 64; + } + function d3_svg_symbolType() { + return "circle"; + } + function d3_svg_symbolCircle(size) { + var r = Math.sqrt(size / π); + return "M0," + r + "A" + r + "," + r + " 0 1,1 0," + -r + "A" + r + "," + r + " 0 1,1 0," + r + "Z"; + } + var d3_svg_symbols = d3.map({ + circle: d3_svg_symbolCircle, + cross: function(size) { + var r = Math.sqrt(size / 5) / 2; + return "M" + -3 * r + "," + -r + "H" + -r + "V" + -3 * r + "H" + r + "V" + -r + "H" + 3 * r + "V" + r + "H" + r + "V" + 3 * r + "H" + -r + "V" + r + "H" + -3 * r + "Z"; + }, + diamond: function(size) { + var ry = Math.sqrt(size / (2 * d3_svg_symbolTan30)), rx = ry * d3_svg_symbolTan30; + return "M0," + -ry + "L" + rx + ",0" + " 0," + ry + " " + -rx + ",0" + "Z"; + }, + square: function(size) { + var r = Math.sqrt(size) / 2; + return "M" + -r + "," + -r + "L" + r + "," + -r + " " + r + "," + r + " " + -r + "," + r + "Z"; + }, + "triangle-down": function(size) { + var rx = Math.sqrt(size / d3_svg_symbolSqrt3), ry = rx * d3_svg_symbolSqrt3 / 2; + return "M0," + ry + "L" + rx + "," + -ry + " " + -rx + "," + -ry + "Z"; + }, + "triangle-up": function(size) { + var rx = Math.sqrt(size / d3_svg_symbolSqrt3), ry = rx * d3_svg_symbolSqrt3 / 2; + return "M0," + -ry + "L" + rx + "," + ry + " " + -rx + "," + ry + "Z"; + } + }); + d3.svg.symbolTypes = d3_svg_symbols.keys(); + var d3_svg_symbolSqrt3 = Math.sqrt(3), d3_svg_symbolTan30 = Math.tan(30 * d3_radians); + function d3_transition(groups, id) { + d3_arraySubclass(groups, d3_transitionPrototype); + groups.id = id; + return groups; + } + var d3_transitionPrototype = [], d3_transitionId = 0, d3_transitionInheritId, d3_transitionInherit = { + ease: d3_ease_cubicInOut, + delay: 0, + duration: 250 + }; + d3_transitionPrototype.call = d3_selectionPrototype.call; + d3_transitionPrototype.empty = d3_selectionPrototype.empty; + d3_transitionPrototype.node = d3_selectionPrototype.node; + d3.transition = function(selection) { + return arguments.length ? d3_transitionInheritId ? selection.transition() : selection : d3_selectionRoot.transition(); + }; + d3.transition.prototype = d3_transitionPrototype; + d3_transitionPrototype.select = function(selector) { + var id = this.id, subgroups = [], subgroup, subnode, node; + if (typeof selector !== "function") selector = d3_selection_selector(selector); + for (var j = -1, m = this.length; ++j < m; ) { + subgroups.push(subgroup = []); + for (var group = this[j], i = -1, n = group.length; ++i < n; ) { + if ((node = group[i]) && (subnode = selector.call(node, node.__data__, i))) { + if ("__data__" in node) subnode.__data__ = node.__data__; + d3_transitionNode(subnode, i, id, node.__transition__[id]); + subgroup.push(subnode); + } else { + subgroup.push(null); + } + } + } + return d3_transition(subgroups, id); + }; + d3_transitionPrototype.selectAll = function(selector) { + var id = this.id, subgroups = [], subgroup, subnodes, node, subnode, transition; + if (typeof selector !== "function") selector = d3_selection_selectorAll(selector); + for (var j = -1, m = this.length; ++j < m; ) { + for (var group = this[j], i = -1, n = group.length; ++i < n; ) { + if (node = group[i]) { + transition = node.__transition__[id]; + subnodes = selector.call(node, node.__data__, i); + subgroups.push(subgroup = []); + for (var k = -1, o = subnodes.length; ++k < o; ) { + d3_transitionNode(subnode = subnodes[k], k, id, transition); + subgroup.push(subnode); + } + } + } + } + return d3_transition(subgroups, id); + }; + d3_transitionPrototype.filter = function(filter) { + var subgroups = [], subgroup, group, node; + if (typeof filter !== "function") filter = d3_selection_filter(filter); + for (var j = 0, m = this.length; j < m; j++) { + subgroups.push(subgroup = []); + for (var group = this[j], i = 0, n = group.length; i < n; i++) { + if ((node = group[i]) && filter.call(node, node.__data__, i)) { + subgroup.push(node); + } + } + } + return d3_transition(subgroups, this.id, this.time).ease(this.ease()); + }; + d3_transitionPrototype.tween = function(name, tween) { + var id = this.id; + if (arguments.length < 2) return this.node().__transition__[id].tween.get(name); + return d3_selection_each(this, tween == null ? function(node) { + node.__transition__[id].tween.remove(name); + } : function(node) { + node.__transition__[id].tween.set(name, tween); + }); + }; + function d3_transition_tween(groups, name, value, tween) { + var id = groups.id; + return d3_selection_each(groups, typeof value === "function" ? function(node, i, j) { + node.__transition__[id].tween.set(name, tween(value.call(node, node.__data__, i, j))); + } : (value = tween(value), function(node) { + node.__transition__[id].tween.set(name, value); + })); + } + d3_transitionPrototype.attr = function(nameNS, value) { + if (arguments.length < 2) { + for (value in nameNS) this.attr(value, nameNS[value]); + return this; + } + var interpolate = d3_interpolateByName(nameNS), name = d3.ns.qualify(nameNS); + function attrNull() { + this.removeAttribute(name); + } + function attrNullNS() { + this.removeAttributeNS(name.space, name.local); + } + return d3_transition_tween(this, "attr." + nameNS, value, function(b) { + function attrString() { + var a = this.getAttribute(name), i; + return a !== b && (i = interpolate(a, b), function(t) { + this.setAttribute(name, i(t)); + }); + } + function attrStringNS() { + var a = this.getAttributeNS(name.space, name.local), i; + return a !== b && (i = interpolate(a, b), function(t) { + this.setAttributeNS(name.space, name.local, i(t)); + }); + } + return b == null ? name.local ? attrNullNS : attrNull : (b += "", name.local ? attrStringNS : attrString); + }); + }; + d3_transitionPrototype.attrTween = function(nameNS, tween) { + var name = d3.ns.qualify(nameNS); + function attrTween(d, i) { + var f = tween.call(this, d, i, this.getAttribute(name)); + return f && function(t) { + this.setAttribute(name, f(t)); + }; + } + function attrTweenNS(d, i) { + var f = tween.call(this, d, i, this.getAttributeNS(name.space, name.local)); + return f && function(t) { + this.setAttributeNS(name.space, name.local, f(t)); + }; + } + return this.tween("attr." + nameNS, name.local ? attrTweenNS : attrTween); + }; + d3_transitionPrototype.style = function(name, value, priority) { + var n = arguments.length; + if (n < 3) { + if (typeof name !== "string") { + if (n < 2) value = ""; + for (priority in name) this.style(priority, name[priority], value); + return this; + } + priority = ""; + } + var interpolate = d3_interpolateByName(name); + function styleNull() { + this.style.removeProperty(name); + } + return d3_transition_tween(this, "style." + name, value, function(b) { + function styleString() { + var a = d3_window.getComputedStyle(this, null).getPropertyValue(name), i; + return a !== b && (i = interpolate(a, b), function(t) { + this.style.setProperty(name, i(t), priority); + }); + } + return b == null ? styleNull : (b += "", styleString); + }); + }; + d3_transitionPrototype.styleTween = function(name, tween, priority) { + if (arguments.length < 3) priority = ""; + return this.tween("style." + name, function(d, i) { + var f = tween.call(this, d, i, d3_window.getComputedStyle(this, null).getPropertyValue(name)); + return f && function(t) { + this.style.setProperty(name, f(t), priority); + }; + }); + }; + d3_transitionPrototype.text = function(value) { + return d3_transition_tween(this, "text", value, d3_transition_text); + }; + function d3_transition_text(b) { + if (b == null) b = ""; + return function() { + this.textContent = b; + }; + } + d3_transitionPrototype.remove = function() { + return this.each("end.transition", function() { + var p; + if (!this.__transition__ && (p = this.parentNode)) p.removeChild(this); + }); + }; + d3_transitionPrototype.ease = function(value) { + var id = this.id; + if (arguments.length < 1) return this.node().__transition__[id].ease; + if (typeof value !== "function") value = d3.ease.apply(d3, arguments); + return d3_selection_each(this, function(node) { + node.__transition__[id].ease = value; + }); + }; + d3_transitionPrototype.delay = function(value) { + var id = this.id; + return d3_selection_each(this, typeof value === "function" ? function(node, i, j) { + node.__transition__[id].delay = value.call(node, node.__data__, i, j) | 0; + } : (value |= 0, function(node) { + node.__transition__[id].delay = value; + })); + }; + d3_transitionPrototype.duration = function(value) { + var id = this.id; + return d3_selection_each(this, typeof value === "function" ? function(node, i, j) { + node.__transition__[id].duration = Math.max(1, value.call(node, node.__data__, i, j) | 0); + } : (value = Math.max(1, value | 0), function(node) { + node.__transition__[id].duration = value; + })); + }; + d3_transitionPrototype.each = function(type, listener) { + var id = this.id; + if (arguments.length < 2) { + var inherit = d3_transitionInherit, inheritId = d3_transitionInheritId; + d3_transitionInheritId = id; + d3_selection_each(this, function(node, i, j) { + d3_transitionInherit = node.__transition__[id]; + type.call(node, node.__data__, i, j); + }); + d3_transitionInherit = inherit; + d3_transitionInheritId = inheritId; + } else { + d3_selection_each(this, function(node) { + node.__transition__[id].event.on(type, listener); + }); + } + return this; + }; + d3_transitionPrototype.transition = function() { + var id0 = this.id, id1 = ++d3_transitionId, subgroups = [], subgroup, group, node, transition; + for (var j = 0, m = this.length; j < m; j++) { + subgroups.push(subgroup = []); + for (var group = this[j], i = 0, n = group.length; i < n; i++) { + if (node = group[i]) { + transition = Object.create(node.__transition__[id0]); + transition.delay += transition.duration; + d3_transitionNode(node, i, id1, transition); + } + subgroup.push(node); + } + } + return d3_transition(subgroups, id1); + }; + function d3_transitionNode(node, i, id, inherit) { + var lock = node.__transition__ || (node.__transition__ = { + active: 0, + count: 0 + }), transition = lock[id]; + if (!transition) { + var time = inherit.time; + transition = lock[id] = { + tween: new d3_Map(), + event: d3.dispatch("start", "end"), + time: time, + ease: inherit.ease, + delay: inherit.delay, + duration: inherit.duration + }; + ++lock.count; + d3.timer(function(elapsed) { + var d = node.__data__, ease = transition.ease, event = transition.event, delay = transition.delay, duration = transition.duration, tweened = []; + return delay <= elapsed ? start(elapsed) : d3.timer(start, delay, time), 1; + function start(elapsed) { + if (lock.active > id) return stop(); + lock.active = id; + event.start.call(node, d, i); + transition.tween.forEach(function(key, value) { + if (value = value.call(node, d, i)) { + tweened.push(value); + } + }); + if (!tick(elapsed)) d3.timer(tick, 0, time); + return 1; + } + function tick(elapsed) { + if (lock.active !== id) return stop(); + var t = (elapsed - delay) / duration, e = ease(t), n = tweened.length; + while (n > 0) { + tweened[--n].call(node, e); + } + if (t >= 1) { + stop(); + event.end.call(node, d, i); + return 1; + } + } + function stop() { + if (--lock.count) delete lock[id]; else delete node.__transition__; + return 1; + } + }, 0, time); + return transition; + } + } + d3.svg.axis = function() { + var scale = d3.scale.linear(), orient = d3_svg_axisDefaultOrient, tickMajorSize = 6, tickMinorSize = 6, tickEndSize = 6, tickPadding = 3, tickArguments_ = [ 10 ], tickValues = null, tickFormat_, tickSubdivide = 0; + function axis(g) { + g.each(function() { + var g = d3.select(this); + var ticks = tickValues == null ? scale.ticks ? scale.ticks.apply(scale, tickArguments_) : scale.domain() : tickValues, tickFormat = tickFormat_ == null ? scale.tickFormat ? scale.tickFormat.apply(scale, tickArguments_) : String : tickFormat_; + var subticks = d3_svg_axisSubdivide(scale, ticks, tickSubdivide), subtick = g.selectAll(".tick.minor").data(subticks, String), subtickEnter = subtick.enter().insert("line", ".tick").attr("class", "tick minor").style("opacity", 1e-6), subtickExit = d3.transition(subtick.exit()).style("opacity", 1e-6).remove(), subtickUpdate = d3.transition(subtick).style("opacity", 1); + var tick = g.selectAll(".tick.major").data(ticks, String), tickEnter = tick.enter().insert("g", "path").attr("class", "tick major").style("opacity", 1e-6), tickExit = d3.transition(tick.exit()).style("opacity", 1e-6).remove(), tickUpdate = d3.transition(tick).style("opacity", 1), tickTransform; + var range = d3_scaleRange(scale), path = g.selectAll(".domain").data([ 0 ]), pathUpdate = (path.enter().append("path").attr("class", "domain"), + d3.transition(path)); + var scale1 = scale.copy(), scale0 = this.__chart__ || scale1; + this.__chart__ = scale1; + tickEnter.append("line"); + tickEnter.append("text"); + var lineEnter = tickEnter.select("line"), lineUpdate = tickUpdate.select("line"), text = tick.select("text").text(tickFormat), textEnter = tickEnter.select("text"), textUpdate = tickUpdate.select("text"); + switch (orient) { + case "bottom": + { + tickTransform = d3_svg_axisX; + subtickEnter.attr("y2", tickMinorSize); + subtickUpdate.attr("x2", 0).attr("y2", tickMinorSize); + lineEnter.attr("y2", tickMajorSize); + textEnter.attr("y", Math.max(tickMajorSize, 0) + tickPadding); + lineUpdate.attr("x2", 0).attr("y2", tickMajorSize); + textUpdate.attr("x", 0).attr("y", Math.max(tickMajorSize, 0) + tickPadding); + text.attr("dy", ".71em").style("text-anchor", "middle"); + pathUpdate.attr("d", "M" + range[0] + "," + tickEndSize + "V0H" + range[1] + "V" + tickEndSize); + break; + } + + case "top": + { + tickTransform = d3_svg_axisX; + subtickEnter.attr("y2", -tickMinorSize); + subtickUpdate.attr("x2", 0).attr("y2", -tickMinorSize); + lineEnter.attr("y2", -tickMajorSize); + textEnter.attr("y", -(Math.max(tickMajorSize, 0) + tickPadding)); + lineUpdate.attr("x2", 0).attr("y2", -tickMajorSize); + textUpdate.attr("x", 0).attr("y", -(Math.max(tickMajorSize, 0) + tickPadding)); + text.attr("dy", "0em").style("text-anchor", "middle"); + pathUpdate.attr("d", "M" + range[0] + "," + -tickEndSize + "V0H" + range[1] + "V" + -tickEndSize); + break; + } + + case "left": + { + tickTransform = d3_svg_axisY; + subtickEnter.attr("x2", -tickMinorSize); + subtickUpdate.attr("x2", -tickMinorSize).attr("y2", 0); + lineEnter.attr("x2", -tickMajorSize); + textEnter.attr("x", -(Math.max(tickMajorSize, 0) + tickPadding)); + lineUpdate.attr("x2", -tickMajorSize).attr("y2", 0); + textUpdate.attr("x", -(Math.max(tickMajorSize, 0) + tickPadding)).attr("y", 0); + text.attr("dy", ".32em").style("text-anchor", "end"); + pathUpdate.attr("d", "M" + -tickEndSize + "," + range[0] + "H0V" + range[1] + "H" + -tickEndSize); + break; + } + + case "right": + { + tickTransform = d3_svg_axisY; + subtickEnter.attr("x2", tickMinorSize); + subtickUpdate.attr("x2", tickMinorSize).attr("y2", 0); + lineEnter.attr("x2", tickMajorSize); + textEnter.attr("x", Math.max(tickMajorSize, 0) + tickPadding); + lineUpdate.attr("x2", tickMajorSize).attr("y2", 0); + textUpdate.attr("x", Math.max(tickMajorSize, 0) + tickPadding).attr("y", 0); + text.attr("dy", ".32em").style("text-anchor", "start"); + pathUpdate.attr("d", "M" + tickEndSize + "," + range[0] + "H0V" + range[1] + "H" + tickEndSize); + break; + } + } + if (scale.ticks) { + tickEnter.call(tickTransform, scale0); + tickUpdate.call(tickTransform, scale1); + tickExit.call(tickTransform, scale1); + subtickEnter.call(tickTransform, scale0); + subtickUpdate.call(tickTransform, scale1); + subtickExit.call(tickTransform, scale1); + } else { + var dx = scale1.rangeBand() / 2, x = function(d) { + return scale1(d) + dx; + }; + tickEnter.call(tickTransform, x); + tickUpdate.call(tickTransform, x); + } + }); + } + axis.scale = function(x) { + if (!arguments.length) return scale; + scale = x; + return axis; + }; + axis.orient = function(x) { + if (!arguments.length) return orient; + orient = x in d3_svg_axisOrients ? x + "" : d3_svg_axisDefaultOrient; + return axis; + }; + axis.ticks = function() { + if (!arguments.length) return tickArguments_; + tickArguments_ = arguments; + return axis; + }; + axis.tickValues = function(x) { + if (!arguments.length) return tickValues; + tickValues = x; + return axis; + }; + axis.tickFormat = function(x) { + if (!arguments.length) return tickFormat_; + tickFormat_ = x; + return axis; + }; + axis.tickSize = function(x, y) { + if (!arguments.length) return tickMajorSize; + var n = arguments.length - 1; + tickMajorSize = +x; + tickMinorSize = n > 1 ? +y : tickMajorSize; + tickEndSize = n > 0 ? +arguments[n] : tickMajorSize; + return axis; + }; + axis.tickPadding = function(x) { + if (!arguments.length) return tickPadding; + tickPadding = +x; + return axis; + }; + axis.tickSubdivide = function(x) { + if (!arguments.length) return tickSubdivide; + tickSubdivide = +x; + return axis; + }; + return axis; + }; + var d3_svg_axisDefaultOrient = "bottom", d3_svg_axisOrients = { + top: 1, + right: 1, + bottom: 1, + left: 1 + }; + function d3_svg_axisX(selection, x) { + selection.attr("transform", function(d) { + return "translate(" + x(d) + ",0)"; + }); + } + function d3_svg_axisY(selection, y) { + selection.attr("transform", function(d) { + return "translate(0," + y(d) + ")"; + }); + } + function d3_svg_axisSubdivide(scale, ticks, m) { + subticks = []; + if (m && ticks.length > 1) { + var extent = d3_scaleExtent(scale.domain()), subticks, i = -1, n = ticks.length, d = (ticks[1] - ticks[0]) / ++m, j, v; + while (++i < n) { + for (j = m; --j > 0; ) { + if ((v = +ticks[i] - j * d) >= extent[0]) { + subticks.push(v); + } + } + } + for (--i, j = 0; ++j < m && (v = +ticks[i] + j * d) < extent[1]; ) { + subticks.push(v); + } + } + return subticks; + } + d3.svg.brush = function() { + var event = d3_eventDispatch(brush, "brushstart", "brush", "brushend"), x = null, y = null, resizes = d3_svg_brushResizes[0], extent = [ [ 0, 0 ], [ 0, 0 ] ], extentDomain; + function brush(g) { + g.each(function() { + var g = d3.select(this), bg = g.selectAll(".background").data([ 0 ]), fg = g.selectAll(".extent").data([ 0 ]), tz = g.selectAll(".resize").data(resizes, String), e; + g.style("pointer-events", "all").on("mousedown.brush", brushstart).on("touchstart.brush", brushstart); + bg.enter().append("rect").attr("class", "background").style("visibility", "hidden").style("cursor", "crosshair"); + fg.enter().append("rect").attr("class", "extent").style("cursor", "move"); + tz.enter().append("g").attr("class", function(d) { + return "resize " + d; + }).style("cursor", function(d) { + return d3_svg_brushCursor[d]; + }).append("rect").attr("x", function(d) { + return /[ew]$/.test(d) ? -3 : null; + }).attr("y", function(d) { + return /^[ns]/.test(d) ? -3 : null; + }).attr("width", 6).attr("height", 6).style("visibility", "hidden"); + tz.style("display", brush.empty() ? "none" : null); + tz.exit().remove(); + if (x) { + e = d3_scaleRange(x); + bg.attr("x", e[0]).attr("width", e[1] - e[0]); + redrawX(g); + } + if (y) { + e = d3_scaleRange(y); + bg.attr("y", e[0]).attr("height", e[1] - e[0]); + redrawY(g); + } + redraw(g); + }); + } + function redraw(g) { + g.selectAll(".resize").attr("transform", function(d) { + return "translate(" + extent[+/e$/.test(d)][0] + "," + extent[+/^s/.test(d)][1] + ")"; + }); + } + function redrawX(g) { + g.select(".extent").attr("x", extent[0][0]); + g.selectAll(".extent,.n>rect,.s>rect").attr("width", extent[1][0] - extent[0][0]); + } + function redrawY(g) { + g.select(".extent").attr("y", extent[0][1]); + g.selectAll(".extent,.e>rect,.w>rect").attr("height", extent[1][1] - extent[0][1]); + } + function brushstart() { + var target = this, eventTarget = d3.select(d3.event.target), event_ = event.of(target, arguments), g = d3.select(target), resizing = eventTarget.datum(), resizingX = !/^(n|s)$/.test(resizing) && x, resizingY = !/^(e|w)$/.test(resizing) && y, dragging = eventTarget.classed("extent"), center, origin = mouse(), offset; + var w = d3.select(d3_window).on("mousemove.brush", brushmove).on("mouseup.brush", brushend).on("touchmove.brush", brushmove).on("touchend.brush", brushend).on("keydown.brush", keydown).on("keyup.brush", keyup); + if (dragging) { + origin[0] = extent[0][0] - origin[0]; + origin[1] = extent[0][1] - origin[1]; + } else if (resizing) { + var ex = +/w$/.test(resizing), ey = +/^n/.test(resizing); + offset = [ extent[1 - ex][0] - origin[0], extent[1 - ey][1] - origin[1] ]; + origin[0] = extent[ex][0]; + origin[1] = extent[ey][1]; + } else if (d3.event.altKey) center = origin.slice(); + g.style("pointer-events", "none").selectAll(".resize").style("display", null); + d3.select("body").style("cursor", eventTarget.style("cursor")); + event_({ + type: "brushstart" + }); + brushmove(); + d3_eventCancel(); + function mouse() { + var touches = d3.event.changedTouches; + return touches ? d3.touches(target, touches)[0] : d3.mouse(target); + } + function keydown() { + if (d3.event.keyCode == 32) { + if (!dragging) { + center = null; + origin[0] -= extent[1][0]; + origin[1] -= extent[1][1]; + dragging = 2; + } + d3_eventCancel(); + } + } + function keyup() { + if (d3.event.keyCode == 32 && dragging == 2) { + origin[0] += extent[1][0]; + origin[1] += extent[1][1]; + dragging = 0; + d3_eventCancel(); + } + } + function brushmove() { + var point = mouse(), moved = false; + if (offset) { + point[0] += offset[0]; + point[1] += offset[1]; + } + if (!dragging) { + if (d3.event.altKey) { + if (!center) center = [ (extent[0][0] + extent[1][0]) / 2, (extent[0][1] + extent[1][1]) / 2 ]; + origin[0] = extent[+(point[0] < center[0])][0]; + origin[1] = extent[+(point[1] < center[1])][1]; + } else center = null; + } + if (resizingX && move1(point, x, 0)) { + redrawX(g); + moved = true; + } + if (resizingY && move1(point, y, 1)) { + redrawY(g); + moved = true; + } + if (moved) { + redraw(g); + event_({ + type: "brush", + mode: dragging ? "move" : "resize" + }); + } + } + function move1(point, scale, i) { + var range = d3_scaleRange(scale), r0 = range[0], r1 = range[1], position = origin[i], size = extent[1][i] - extent[0][i], min, max; + if (dragging) { + r0 -= position; + r1 -= size + position; + } + min = Math.max(r0, Math.min(r1, point[i])); + if (dragging) { + max = (min += position) + size; + } else { + if (center) position = Math.max(r0, Math.min(r1, 2 * center[i] - min)); + if (position < min) { + max = min; + min = position; + } else { + max = position; + } + } + if (extent[0][i] !== min || extent[1][i] !== max) { + extentDomain = null; + extent[0][i] = min; + extent[1][i] = max; + return true; + } + } + function brushend() { + brushmove(); + g.style("pointer-events", "all").selectAll(".resize").style("display", brush.empty() ? "none" : null); + d3.select("body").style("cursor", null); + w.on("mousemove.brush", null).on("mouseup.brush", null).on("touchmove.brush", null).on("touchend.brush", null).on("keydown.brush", null).on("keyup.brush", null); + event_({ + type: "brushend" + }); + d3_eventCancel(); + } + } + brush.x = function(z) { + if (!arguments.length) return x; + x = z; + resizes = d3_svg_brushResizes[!x << 1 | !y]; + return brush; + }; + brush.y = function(z) { + if (!arguments.length) return y; + y = z; + resizes = d3_svg_brushResizes[!x << 1 | !y]; + return brush; + }; + brush.extent = function(z) { + var x0, x1, y0, y1, t; + if (!arguments.length) { + z = extentDomain || extent; + if (x) { + x0 = z[0][0], x1 = z[1][0]; + if (!extentDomain) { + x0 = extent[0][0], x1 = extent[1][0]; + if (x.invert) x0 = x.invert(x0), x1 = x.invert(x1); + if (x1 < x0) t = x0, x0 = x1, x1 = t; + } + } + if (y) { + y0 = z[0][1], y1 = z[1][1]; + if (!extentDomain) { + y0 = extent[0][1], y1 = extent[1][1]; + if (y.invert) y0 = y.invert(y0), y1 = y.invert(y1); + if (y1 < y0) t = y0, y0 = y1, y1 = t; + } + } + return x && y ? [ [ x0, y0 ], [ x1, y1 ] ] : x ? [ x0, x1 ] : y && [ y0, y1 ]; + } + extentDomain = [ [ 0, 0 ], [ 0, 0 ] ]; + if (x) { + x0 = z[0], x1 = z[1]; + if (y) x0 = x0[0], x1 = x1[0]; + extentDomain[0][0] = x0, extentDomain[1][0] = x1; + if (x.invert) x0 = x(x0), x1 = x(x1); + if (x1 < x0) t = x0, x0 = x1, x1 = t; + extent[0][0] = x0 | 0, extent[1][0] = x1 | 0; + } + if (y) { + y0 = z[0], y1 = z[1]; + if (x) y0 = y0[1], y1 = y1[1]; + extentDomain[0][1] = y0, extentDomain[1][1] = y1; + if (y.invert) y0 = y(y0), y1 = y(y1); + if (y1 < y0) t = y0, y0 = y1, y1 = t; + extent[0][1] = y0 | 0, extent[1][1] = y1 | 0; + } + return brush; + }; + brush.clear = function() { + extentDomain = null; + extent[0][0] = extent[0][1] = extent[1][0] = extent[1][1] = 0; + return brush; + }; + brush.empty = function() { + return x && extent[0][0] === extent[1][0] || y && extent[0][1] === extent[1][1]; + }; + return d3.rebind(brush, event, "on"); + }; + var d3_svg_brushCursor = { + n: "ns-resize", + e: "ew-resize", + s: "ns-resize", + w: "ew-resize", + nw: "nwse-resize", + ne: "nesw-resize", + se: "nwse-resize", + sw: "nesw-resize" + }; + var d3_svg_brushResizes = [ [ "n", "e", "s", "w", "nw", "ne", "se", "sw" ], [ "e", "w" ], [ "n", "s" ], [] ]; + d3.time = {}; + var d3_time = Date, d3_time_daySymbols = [ "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" ]; + function d3_time_utc() { + this._ = new Date(arguments.length > 1 ? Date.UTC.apply(this, arguments) : arguments[0]); + } + d3_time_utc.prototype = { + getDate: function() { + return this._.getUTCDate(); + }, + getDay: function() { + return this._.getUTCDay(); + }, + getFullYear: function() { + return this._.getUTCFullYear(); + }, + getHours: function() { + return this._.getUTCHours(); + }, + getMilliseconds: function() { + return this._.getUTCMilliseconds(); + }, + getMinutes: function() { + return this._.getUTCMinutes(); + }, + getMonth: function() { + return this._.getUTCMonth(); + }, + getSeconds: function() { + return this._.getUTCSeconds(); + }, + getTime: function() { + return this._.getTime(); + }, + getTimezoneOffset: function() { + return 0; + }, + valueOf: function() { + return this._.valueOf(); + }, + setDate: function() { + d3_time_prototype.setUTCDate.apply(this._, arguments); + }, + setDay: function() { + d3_time_prototype.setUTCDay.apply(this._, arguments); + }, + setFullYear: function() { + d3_time_prototype.setUTCFullYear.apply(this._, arguments); + }, + setHours: function() { + d3_time_prototype.setUTCHours.apply(this._, arguments); + }, + setMilliseconds: function() { + d3_time_prototype.setUTCMilliseconds.apply(this._, arguments); + }, + setMinutes: function() { + d3_time_prototype.setUTCMinutes.apply(this._, arguments); + }, + setMonth: function() { + d3_time_prototype.setUTCMonth.apply(this._, arguments); + }, + setSeconds: function() { + d3_time_prototype.setUTCSeconds.apply(this._, arguments); + }, + setTime: function() { + d3_time_prototype.setTime.apply(this._, arguments); + } + }; + var d3_time_prototype = Date.prototype; + var d3_time_formatDateTime = "%a %b %e %X %Y", d3_time_formatDate = "%m/%d/%Y", d3_time_formatTime = "%H:%M:%S"; + var d3_time_days = [ "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" ], d3_time_dayAbbreviations = [ "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" ], d3_time_months = [ "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" ], d3_time_monthAbbreviations = [ "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" ]; + function d3_time_interval(local, step, number) { + function round(date) { + var d0 = local(date), d1 = offset(d0, 1); + return date - d0 < d1 - date ? d0 : d1; + } + function ceil(date) { + step(date = local(new d3_time(date - 1)), 1); + return date; + } + function offset(date, k) { + step(date = new d3_time(+date), k); + return date; + } + function range(t0, t1, dt) { + var time = ceil(t0), times = []; + if (dt > 1) { + while (time < t1) { + if (!(number(time) % dt)) times.push(new Date(+time)); + step(time, 1); } } else { - n.point = p; + while (time < t1) times.push(new Date(+time)), step(time, 1); } - } else { - insertChild(n, p, x1, y1, x2, y2); + return times; } - } - - // Recursively inserts the specified point p into a descendant of node n. The - // bounds are defined by [x1, x2] and [y1, y2]. - function insertChild(n, p, x1, y1, x2, y2) { - // Compute the split point, and the quadrant in which to insert p. - var sx = (x1 + x2) * .5, - sy = (y1 + y2) * .5, - right = p.x >= sx, - bottom = p.y >= sy, - i = (bottom << 1) + right; - - // Recursively insert into the child node. - n.leaf = false; - n = n.nodes[i] || (n.nodes[i] = d3_geom_quadtreeNode()); - - // Update the bounds as we recurse. - if (right) x1 = sx; else x2 = sx; - if (bottom) y1 = sy; else y2 = sy; - insert(n, p, x1, y1, x2, y2); - } - - // Create the root node. - var root = d3_geom_quadtreeNode(); - - root.add = function(p) { - insert(root, p, x1, y1, x2, y2); - }; - - root.visit = function(f) { - d3_geom_quadtreeVisit(f, root, x1, y1, x2, y2); - }; - - // Insert all points. - points.forEach(root.add); - return root; -}; - -function d3_geom_quadtreeNode() { - return { - leaf: true, - nodes: [], - point: null - }; -} - -function d3_geom_quadtreeVisit(f, node, x1, y1, x2, y2) { - if (!f(node, x1, y1, x2, y2)) { - var sx = (x1 + x2) * .5, - sy = (y1 + y2) * .5, - children = node.nodes; - if (children[0]) d3_geom_quadtreeVisit(f, children[0], x1, y1, sx, sy); - if (children[1]) d3_geom_quadtreeVisit(f, children[1], sx, y1, x2, sy); - if (children[2]) d3_geom_quadtreeVisit(f, children[2], x1, sy, sx, y2); - if (children[3]) d3_geom_quadtreeVisit(f, children[3], sx, sy, x2, y2); - } -} - -function d3_geom_quadtreePoint(p) { - return { - x: p[0], - y: p[1] - }; -} -d3.time = {}; - -var d3_time = Date; - -function d3_time_utc() { - this._ = new Date(arguments.length > 1 - ? Date.UTC.apply(this, arguments) - : arguments[0]); -} - -d3_time_utc.prototype = { - getDate: function() { return this._.getUTCDate(); }, - getDay: function() { return this._.getUTCDay(); }, - getFullYear: function() { return this._.getUTCFullYear(); }, - getHours: function() { return this._.getUTCHours(); }, - getMilliseconds: function() { return this._.getUTCMilliseconds(); }, - getMinutes: function() { return this._.getUTCMinutes(); }, - getMonth: function() { return this._.getUTCMonth(); }, - getSeconds: function() { return this._.getUTCSeconds(); }, - getTime: function() { return this._.getTime(); }, - getTimezoneOffset: function() { return 0; }, - valueOf: function() { return this._.valueOf(); }, - setDate: function() { d3_time_prototype.setUTCDate.apply(this._, arguments); }, - setDay: function() { d3_time_prototype.setUTCDay.apply(this._, arguments); }, - setFullYear: function() { d3_time_prototype.setUTCFullYear.apply(this._, arguments); }, - setHours: function() { d3_time_prototype.setUTCHours.apply(this._, arguments); }, - setMilliseconds: function() { d3_time_prototype.setUTCMilliseconds.apply(this._, arguments); }, - setMinutes: function() { d3_time_prototype.setUTCMinutes.apply(this._, arguments); }, - setMonth: function() { d3_time_prototype.setUTCMonth.apply(this._, arguments); }, - setSeconds: function() { d3_time_prototype.setUTCSeconds.apply(this._, arguments); }, - setTime: function() { d3_time_prototype.setTime.apply(this._, arguments); } -}; - -var d3_time_prototype = Date.prototype; -d3.time.format = function(template) { - var n = template.length; - - function format(date) { - var string = [], - i = -1, - j = 0, - c, - f; - while (++i < n) { - if (template.charCodeAt(i) == 37) { - string.push( - template.substring(j, i), - (f = d3_time_formats[c = template.charAt(++i)]) - ? f(date) : c); - j = i + 1; + function range_utc(t0, t1, dt) { + try { + d3_time = d3_time_utc; + var utc = new d3_time_utc(); + utc._ = t0; + return range(utc, t1, dt); + } finally { + d3_time = Date; } } - string.push(template.substring(j, i)); - return string.join(""); + local.floor = local; + local.round = round; + local.ceil = ceil; + local.offset = offset; + local.range = range; + var utc = local.utc = d3_time_interval_utc(local); + utc.floor = utc; + utc.round = d3_time_interval_utc(round); + utc.ceil = d3_time_interval_utc(ceil); + utc.offset = d3_time_interval_utc(offset); + utc.range = range_utc; + return local; } - - format.parse = function(string) { - var d = {y: 1900, m: 0, d: 1, H: 0, M: 0, S: 0, L: 0}, - i = d3_time_parse(d, template, string, 0); - if (i != string.length) return null; - - // The am-pm flag is 0 for AM, and 1 for PM. - if ("p" in d) d.H = d.H % 12 + d.p * 12; - - var date = new d3_time(); - date.setFullYear(d.y, d.m, d.d); - date.setHours(d.H, d.M, d.S, d.L); - return date; - }; - - format.toString = function() { - return template; - }; - - return format; -}; - -function d3_time_parse(date, template, string, j) { - var c, - p, - i = 0, - n = template.length, - m = string.length; - while (i < n) { - if (j >= m) return -1; - c = template.charCodeAt(i++); - if (c == 37) { - p = d3_time_parsers[template.charAt(i++)]; - if (!p || ((j = p(date, string, j)) < 0)) return -1; - } else if (c != string.charCodeAt(j++)) { - return -1; - } - } - return j; -} - -var d3_time_zfill2 = d3.format("02d"), - d3_time_zfill3 = d3.format("03d"), - d3_time_zfill4 = d3.format("04d"), - d3_time_sfill2 = d3.format("2d"); - -var d3_time_formats = { - a: function(d) { return d3_time_weekdays[d.getDay()].substring(0, 3); }, - A: function(d) { return d3_time_weekdays[d.getDay()]; }, - b: function(d) { return d3_time_months[d.getMonth()].substring(0, 3); }, - B: function(d) { return d3_time_months[d.getMonth()]; }, - c: d3.time.format("%a %b %e %H:%M:%S %Y"), - d: function(d) { return d3_time_zfill2(d.getDate()); }, - e: function(d) { return d3_time_sfill2(d.getDate()); }, - H: function(d) { return d3_time_zfill2(d.getHours()); }, - I: function(d) { return d3_time_zfill2(d.getHours() % 12 || 12); }, - j: function(d) { return d3_time_zfill3(1 + d3.time.dayOfYear(d)); }, - L: function(d) { return d3_time_zfill3(d.getMilliseconds()); }, - m: function(d) { return d3_time_zfill2(d.getMonth() + 1); }, - M: function(d) { return d3_time_zfill2(d.getMinutes()); }, - p: function(d) { return d.getHours() >= 12 ? "PM" : "AM"; }, - S: function(d) { return d3_time_zfill2(d.getSeconds()); }, - U: function(d) { return d3_time_zfill2(d3.time.sundayOfYear(d)); }, - w: function(d) { return d.getDay(); }, - W: function(d) { return d3_time_zfill2(d3.time.mondayOfYear(d)); }, - x: d3.time.format("%m/%d/%y"), - X: d3.time.format("%H:%M:%S"), - y: function(d) { return d3_time_zfill2(d.getFullYear() % 100); }, - Y: function(d) { return d3_time_zfill4(d.getFullYear() % 10000); }, - Z: d3_time_zone, - "%": function(d) { return "%"; } -}; - -var d3_time_parsers = { - a: d3_time_parseWeekdayAbbrev, - A: d3_time_parseWeekday, - b: d3_time_parseMonthAbbrev, - B: d3_time_parseMonth, - c: d3_time_parseLocaleFull, - d: d3_time_parseDay, - e: d3_time_parseDay, - H: d3_time_parseHour24, - I: d3_time_parseHour24, - // j: function(d, s, i) { /*TODO day of year [001,366] */ return i; }, - L: d3_time_parseMilliseconds, - m: d3_time_parseMonthNumber, - M: d3_time_parseMinutes, - p: d3_time_parseAmPm, - S: d3_time_parseSeconds, - // U: function(d, s, i) { /*TODO week number (sunday) [00,53] */ return i; }, - // w: function(d, s, i) { /*TODO weekday [0,6] */ return i; }, - // W: function(d, s, i) { /*TODO week number (monday) [00,53] */ return i; }, - x: d3_time_parseLocaleDate, - X: d3_time_parseLocaleTime, - y: d3_time_parseYear, - Y: d3_time_parseFullYear - // , - // Z: function(d, s, i) { /*TODO time zone */ return i; }, - // "%": function(d, s, i) { /*TODO literal % */ return i; } -}; - -// Note: weekday is validated, but does not set the date. -function d3_time_parseWeekdayAbbrev(date, string, i) { - return d3_time_weekdayAbbrevRe.test(string.substring(i, i += 3)) ? i : -1; -} - -// Note: weekday is validated, but does not set the date. -function d3_time_parseWeekday(date, string, i) { - d3_time_weekdayRe.lastIndex = 0; - var n = d3_time_weekdayRe.exec(string.substring(i, i + 10)); - return n ? i += n[0].length : -1; -} - -var d3_time_weekdayAbbrevRe = /^(?:sun|mon|tue|wed|thu|fri|sat)/i, - d3_time_weekdayRe = /^(?:Sunday|Monday|Tuesday|Wednesday|Thursday|Friday|Saturday)/i, - d3_time_weekdays = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"]; - -function d3_time_parseMonthAbbrev(date, string, i) { - var n = d3_time_monthAbbrevLookup.get(string.substring(i, i += 3).toLowerCase()); - return n == null ? -1 : (date.m = n, i); -} - -var d3_time_monthAbbrevLookup = d3.map({ - jan: 0, - feb: 1, - mar: 2, - apr: 3, - may: 4, - jun: 5, - jul: 6, - aug: 7, - sep: 8, - oct: 9, - nov: 10, - dec: 11 -}); - -function d3_time_parseMonth(date, string, i) { - d3_time_monthRe.lastIndex = 0; - var n = d3_time_monthRe.exec(string.substring(i, i + 12)); - return n ? (date.m = d3_time_monthLookup.get(n[0].toLowerCase()), i += n[0].length) : -1; -} - -var d3_time_monthRe = /^(?:January|February|March|April|May|June|July|August|September|October|November|December)/ig; - -var d3_time_monthLookup = d3.map({ - january: 0, - february: 1, - march: 2, - april: 3, - may: 4, - june: 5, - july: 6, - august: 7, - september: 8, - october: 9, - november: 10, - december: 11 -}); - -var d3_time_months = [ - "January", - "February", - "March", - "April", - "May", - "June", - "July", - "August", - "September", - "October", - "November", - "December" -]; - -function d3_time_parseLocaleFull(date, string, i) { - return d3_time_parse(date, d3_time_formats.c.toString(), string, i); -} - -function d3_time_parseLocaleDate(date, string, i) { - return d3_time_parse(date, d3_time_formats.x.toString(), string, i); -} - -function d3_time_parseLocaleTime(date, string, i) { - return d3_time_parse(date, d3_time_formats.X.toString(), string, i); -} - -function d3_time_parseFullYear(date, string, i) { - d3_time_numberRe.lastIndex = 0; - var n = d3_time_numberRe.exec(string.substring(i, i + 4)); - return n ? (date.y = +n[0], i += n[0].length) : -1; -} - -function d3_time_parseYear(date, string, i) { - d3_time_numberRe.lastIndex = 0; - var n = d3_time_numberRe.exec(string.substring(i, i + 2)); - return n ? (date.y = d3_time_century() + +n[0], i += n[0].length) : -1; -} - -function d3_time_century() { - return ~~(new Date().getFullYear() / 1000) * 1000; -} - -function d3_time_parseMonthNumber(date, string, i) { - d3_time_numberRe.lastIndex = 0; - var n = d3_time_numberRe.exec(string.substring(i, i + 2)); - return n ? (date.m = n[0] - 1, i += n[0].length) : -1; -} - -function d3_time_parseDay(date, string, i) { - d3_time_numberRe.lastIndex = 0; - var n = d3_time_numberRe.exec(string.substring(i, i + 2)); - return n ? (date.d = +n[0], i += n[0].length) : -1; -} - -// Note: we don't validate that the hour is in the range [0,23] or [1,12]. -function d3_time_parseHour24(date, string, i) { - d3_time_numberRe.lastIndex = 0; - var n = d3_time_numberRe.exec(string.substring(i, i + 2)); - return n ? (date.H = +n[0], i += n[0].length) : -1; -} - -function d3_time_parseMinutes(date, string, i) { - d3_time_numberRe.lastIndex = 0; - var n = d3_time_numberRe.exec(string.substring(i, i + 2)); - return n ? (date.M = +n[0], i += n[0].length) : -1; -} - -function d3_time_parseSeconds(date, string, i) { - d3_time_numberRe.lastIndex = 0; - var n = d3_time_numberRe.exec(string.substring(i, i + 2)); - return n ? (date.S = +n[0], i += n[0].length) : -1; -} - -function d3_time_parseMilliseconds(date, string, i) { - d3_time_numberRe.lastIndex = 0; - var n = d3_time_numberRe.exec(string.substring(i, i + 3)); - return n ? (date.L = +n[0], i += n[0].length) : -1; -} - -// Note: we don't look at the next directive. -var d3_time_numberRe = /\s*\d+/; - -function d3_time_parseAmPm(date, string, i) { - var n = d3_time_amPmLookup.get(string.substring(i, i += 2).toLowerCase()); - return n == null ? -1 : (date.p = n, i); -} - -var d3_time_amPmLookup = d3.map({ - am: 0, - pm: 1 -}); - -// TODO table of time zone offset names? -function d3_time_zone(d) { - var z = d.getTimezoneOffset(), - zs = z > 0 ? "-" : "+", - zh = ~~(Math.abs(z) / 60), - zm = Math.abs(z) % 60; - return zs + d3_time_zfill2(zh) + d3_time_zfill2(zm); -} -d3.time.format.utc = function(template) { - var local = d3.time.format(template); - - function format(date) { - try { - d3_time = d3_time_utc; - var utc = new d3_time(); - utc._ = date; - return local(utc); - } finally { - d3_time = Date; - } - } - - format.parse = function(string) { - try { - d3_time = d3_time_utc; - var date = local.parse(string); - return date && date._; - } finally { - d3_time = Date; - } - }; - - format.toString = local.toString; - - return format; -}; -var d3_time_formatIso = d3.time.format.utc("%Y-%m-%dT%H:%M:%S.%LZ"); - -d3.time.format.iso = Date.prototype.toISOString ? d3_time_formatIsoNative : d3_time_formatIso; - -function d3_time_formatIsoNative(date) { - return date.toISOString(); -} - -d3_time_formatIsoNative.parse = function(string) { - return new Date(string); -}; - -d3_time_formatIsoNative.toString = d3_time_formatIso.toString; -function d3_time_interval(local, step, number) { - - function round(date) { - var d0 = local(date), d1 = offset(d0, 1); - return date - d0 < d1 - date ? d0 : d1; - } - - function ceil(date) { - step(date = local(new d3_time(date - 1)), 1); - return date; - } - - function offset(date, k) { - step(date = new d3_time(+date), k); - return date; - } - - function range(t0, t1, dt) { - var time = ceil(t0), times = []; - if (dt > 1) { - while (time < t1) { - if (!(number(time) % dt)) times.push(new Date(+time)); - step(time, 1); + function d3_time_interval_utc(method) { + return function(date, k) { + try { + d3_time = d3_time_utc; + var utc = new d3_time_utc(); + utc._ = date; + return method(utc, k)._; + } finally { + d3_time = Date; } - } else { - while (time < t1) times.push(new Date(+time)), step(time, 1); - } - return times; + }; } - - function range_utc(t0, t1, dt) { - try { - d3_time = d3_time_utc; - var utc = new d3_time_utc(); - utc._ = t0; - return range(utc, t1, dt); - } finally { - d3_time = Date; - } - } - - local.floor = local; - local.round = round; - local.ceil = ceil; - local.offset = offset; - local.range = range; - - var utc = local.utc = d3_time_interval_utc(local); - utc.floor = utc; - utc.round = d3_time_interval_utc(round); - utc.ceil = d3_time_interval_utc(ceil); - utc.offset = d3_time_interval_utc(offset); - utc.range = range_utc; - - return local; -} - -function d3_time_interval_utc(method) { - return function(date, k) { - try { - d3_time = d3_time_utc; - var utc = new d3_time_utc(); - utc._ = date; - return method(utc, k)._; - } finally { - d3_time = Date; - } - }; -} -d3.time.second = d3_time_interval(function(date) { - return new d3_time(Math.floor(date / 1e3) * 1e3); -}, function(date, offset) { - date.setTime(date.getTime() + Math.floor(offset) * 1e3); // DST breaks setSeconds -}, function(date) { - return date.getSeconds(); -}); - -d3.time.seconds = d3.time.second.range; -d3.time.seconds.utc = d3.time.second.utc.range; -d3.time.minute = d3_time_interval(function(date) { - return new d3_time(Math.floor(date / 6e4) * 6e4); -}, function(date, offset) { - date.setTime(date.getTime() + Math.floor(offset) * 6e4); // DST breaks setMinutes -}, function(date) { - return date.getMinutes(); -}); - -d3.time.minutes = d3.time.minute.range; -d3.time.minutes.utc = d3.time.minute.utc.range; -d3.time.hour = d3_time_interval(function(date) { - var timezone = date.getTimezoneOffset() / 60; - return new d3_time((Math.floor(date / 36e5 - timezone) + timezone) * 36e5); -}, function(date, offset) { - date.setTime(date.getTime() + Math.floor(offset) * 36e5); // DST breaks setHours -}, function(date) { - return date.getHours(); -}); - -d3.time.hours = d3.time.hour.range; -d3.time.hours.utc = d3.time.hour.utc.range; -d3.time.day = d3_time_interval(function(date) { - return new d3_time(date.getFullYear(), date.getMonth(), date.getDate()); -}, function(date, offset) { - date.setDate(date.getDate() + offset); -}, function(date) { - return date.getDate() - 1; -}); - -d3.time.days = d3.time.day.range; -d3.time.days.utc = d3.time.day.utc.range; - -d3.time.dayOfYear = function(date) { - var year = d3.time.year(date); - return Math.floor((date - year) / 864e5 - (date.getTimezoneOffset() - year.getTimezoneOffset()) / 1440); -}; -d3_time_weekdays.forEach(function(day, i) { - day = day.toLowerCase(); - i = 7 - i; - - var interval = d3.time[day] = d3_time_interval(function(date) { - (date = d3.time.day(date)).setDate(date.getDate() - (date.getDay() + i) % 7); + d3.time.year = d3_time_interval(function(date) { + date = d3.time.day(date); + date.setMonth(0, 1); return date; }, function(date, offset) { - date.setDate(date.getDate() + Math.floor(offset) * 7); + date.setFullYear(date.getFullYear() + offset); }, function(date) { - var day = d3.time.year(date).getDay(); - return Math.floor((d3.time.dayOfYear(date) + (day + i) % 7) / 7) - (day !== i); + return date.getFullYear(); }); - - d3.time[day + "s"] = interval.range; - d3.time[day + "s"].utc = interval.utc.range; - - d3.time[day + "OfYear"] = function(date) { - var day = d3.time.year(date).getDay(); - return Math.floor((d3.time.dayOfYear(date) + (day + i) % 7) / 7); + d3.time.years = d3.time.year.range; + d3.time.years.utc = d3.time.year.utc.range; + d3.time.day = d3_time_interval(function(date) { + var day = new d3_time(1970, 0); + day.setFullYear(date.getFullYear(), date.getMonth(), date.getDate()); + return day; + }, function(date, offset) { + date.setDate(date.getDate() + offset); + }, function(date) { + return date.getDate() - 1; + }); + d3.time.days = d3.time.day.range; + d3.time.days.utc = d3.time.day.utc.range; + d3.time.dayOfYear = function(date) { + var year = d3.time.year(date); + return Math.floor((date - year - (date.getTimezoneOffset() - year.getTimezoneOffset()) * 6e4) / 864e5); }; -}); - -d3.time.week = d3.time.sunday; -d3.time.weeks = d3.time.sunday.range; -d3.time.weeks.utc = d3.time.sunday.utc.range; -d3.time.weekOfYear = d3.time.sundayOfYear; -d3.time.month = d3_time_interval(function(date) { - return new d3_time(date.getFullYear(), date.getMonth(), 1); -}, function(date, offset) { - date.setMonth(date.getMonth() + offset); -}, function(date) { - return date.getMonth(); -}); - -d3.time.months = d3.time.month.range; -d3.time.months.utc = d3.time.month.utc.range; -d3.time.year = d3_time_interval(function(date) { - return new d3_time(date.getFullYear(), 0, 1); -}, function(date, offset) { - date.setFullYear(date.getFullYear() + offset); -}, function(date) { - return date.getFullYear(); -}); - -d3.time.years = d3.time.year.range; -d3.time.years.utc = d3.time.year.utc.range; -function d3_time_scale(linear, methods, format) { - - function scale(x) { - return linear(x); - } - - scale.invert = function(x) { - return d3_time_scaleDate(linear.invert(x)); - }; - - scale.domain = function(x) { - if (!arguments.length) return linear.domain().map(d3_time_scaleDate); - linear.domain(x); - return scale; - }; - - scale.nice = function(m) { - var extent = d3_time_scaleExtent(scale.domain()); - return scale.domain([m.floor(extent[0]), m.ceil(extent[1])]); - }; - - scale.ticks = function(m, k) { - var extent = d3_time_scaleExtent(scale.domain()); - if (typeof m !== "function") { - var span = extent[1] - extent[0], - target = span / m, - i = d3.bisect(d3_time_scaleSteps, target); - if (i == d3_time_scaleSteps.length) return methods.year(extent, m); - if (!i) return linear.ticks(m).map(d3_time_scaleDate); - if (Math.log(target / d3_time_scaleSteps[i - 1]) < Math.log(d3_time_scaleSteps[i] / target)) --i; - m = methods[i]; - k = m[1]; - m = m[0].range; + d3_time_daySymbols.forEach(function(day, i) { + day = day.toLowerCase(); + i = 7 - i; + var interval = d3.time[day] = d3_time_interval(function(date) { + (date = d3.time.day(date)).setDate(date.getDate() - (date.getDay() + i) % 7); + return date; + }, function(date, offset) { + date.setDate(date.getDate() + Math.floor(offset) * 7); + }, function(date) { + var day = d3.time.year(date).getDay(); + return Math.floor((d3.time.dayOfYear(date) + (day + i) % 7) / 7) - (day !== i); + }); + d3.time[day + "s"] = interval.range; + d3.time[day + "s"].utc = interval.utc.range; + d3.time[day + "OfYear"] = function(date) { + var day = d3.time.year(date).getDay(); + return Math.floor((d3.time.dayOfYear(date) + (day + i) % 7) / 7); + }; + }); + d3.time.week = d3.time.sunday; + d3.time.weeks = d3.time.sunday.range; + d3.time.weeks.utc = d3.time.sunday.utc.range; + d3.time.weekOfYear = d3.time.sundayOfYear; + d3.time.format = function(template) { + var n = template.length; + function format(date) { + var string = [], i = -1, j = 0, c, p, f; + while (++i < n) { + if (template.charCodeAt(i) === 37) { + string.push(template.substring(j, i)); + if ((p = d3_time_formatPads[c = template.charAt(++i)]) != null) c = template.charAt(++i); + if (f = d3_time_formats[c]) c = f(date, p == null ? c === "e" ? " " : "0" : p); + string.push(c); + j = i + 1; + } + } + string.push(template.substring(j, i)); + return string.join(""); } - return m(extent[0], new Date(+extent[1] + 1), k); // inclusive upper bound - }; - - scale.tickFormat = function() { + format.parse = function(string) { + var d = { + y: 1900, + m: 0, + d: 1, + H: 0, + M: 0, + S: 0, + L: 0 + }, i = d3_time_parse(d, template, string, 0); + if (i != string.length) return null; + if ("p" in d) d.H = d.H % 12 + d.p * 12; + var date = new d3_time(); + date.setFullYear(d.y, d.m, d.d); + date.setHours(d.H, d.M, d.S, d.L); + return date; + }; + format.toString = function() { + return template; + }; return format; }; - - scale.copy = function() { - return d3_time_scale(linear.copy(), methods, format); + function d3_time_parse(date, template, string, j) { + var c, p, i = 0, n = template.length, m = string.length; + while (i < n) { + if (j >= m) return -1; + c = template.charCodeAt(i++); + if (c === 37) { + p = d3_time_parsers[template.charAt(i++)]; + if (!p || (j = p(date, string, j)) < 0) return -1; + } else if (c != string.charCodeAt(j++)) { + return -1; + } + } + return j; + } + function d3_time_formatRe(names) { + return new RegExp("^(?:" + names.map(d3.requote).join("|") + ")", "i"); + } + function d3_time_formatLookup(names) { + var map = new d3_Map(), i = -1, n = names.length; + while (++i < n) map.set(names[i].toLowerCase(), i); + return map; + } + function d3_time_formatPad(value, fill, width) { + value += ""; + var length = value.length; + return length < width ? new Array(width - length + 1).join(fill) + value : value; + } + var d3_time_dayRe = d3_time_formatRe(d3_time_days), d3_time_dayAbbrevRe = d3_time_formatRe(d3_time_dayAbbreviations), d3_time_monthRe = d3_time_formatRe(d3_time_months), d3_time_monthLookup = d3_time_formatLookup(d3_time_months), d3_time_monthAbbrevRe = d3_time_formatRe(d3_time_monthAbbreviations), d3_time_monthAbbrevLookup = d3_time_formatLookup(d3_time_monthAbbreviations); + var d3_time_formatPads = { + "-": "", + _: " ", + "0": "0" }; - - // TOOD expose d3_scale_linear_rebind? - return d3.rebind(scale, linear, "range", "rangeRound", "interpolate", "clamp"); -} - -// TODO expose d3_scaleExtent? -function d3_time_scaleExtent(domain) { - var start = domain[0], stop = domain[domain.length - 1]; - return start < stop ? [start, stop] : [stop, start]; -} - -function d3_time_scaleDate(t) { - return new Date(t); -} - -function d3_time_scaleFormat(formats) { - return function(date) { - var i = formats.length - 1, f = formats[i]; - while (!f[1](date)) f = formats[--i]; - return f[0](date); + var d3_time_formats = { + a: function(d) { + return d3_time_dayAbbreviations[d.getDay()]; + }, + A: function(d) { + return d3_time_days[d.getDay()]; + }, + b: function(d) { + return d3_time_monthAbbreviations[d.getMonth()]; + }, + B: function(d) { + return d3_time_months[d.getMonth()]; + }, + c: d3.time.format(d3_time_formatDateTime), + d: function(d, p) { + return d3_time_formatPad(d.getDate(), p, 2); + }, + e: function(d, p) { + return d3_time_formatPad(d.getDate(), p, 2); + }, + H: function(d, p) { + return d3_time_formatPad(d.getHours(), p, 2); + }, + I: function(d, p) { + return d3_time_formatPad(d.getHours() % 12 || 12, p, 2); + }, + j: function(d, p) { + return d3_time_formatPad(1 + d3.time.dayOfYear(d), p, 3); + }, + L: function(d, p) { + return d3_time_formatPad(d.getMilliseconds(), p, 3); + }, + m: function(d, p) { + return d3_time_formatPad(d.getMonth() + 1, p, 2); + }, + M: function(d, p) { + return d3_time_formatPad(d.getMinutes(), p, 2); + }, + p: function(d) { + return d.getHours() >= 12 ? "PM" : "AM"; + }, + S: function(d, p) { + return d3_time_formatPad(d.getSeconds(), p, 2); + }, + U: function(d, p) { + return d3_time_formatPad(d3.time.sundayOfYear(d), p, 2); + }, + w: function(d) { + return d.getDay(); + }, + W: function(d, p) { + return d3_time_formatPad(d3.time.mondayOfYear(d), p, 2); + }, + x: d3.time.format(d3_time_formatDate), + X: d3.time.format(d3_time_formatTime), + y: function(d, p) { + return d3_time_formatPad(d.getFullYear() % 100, p, 2); + }, + Y: function(d, p) { + return d3_time_formatPad(d.getFullYear() % 1e4, p, 4); + }, + Z: d3_time_zone, + "%": function() { + return "%"; + } }; -} - -function d3_time_scaleSetYear(y) { - var d = new Date(y, 0, 1); - d.setFullYear(y); // Y2K fail - return d; -} - -function d3_time_scaleGetYear(d) { - var y = d.getFullYear(), - d0 = d3_time_scaleSetYear(y), - d1 = d3_time_scaleSetYear(y + 1); - return y + (d - d0) / (d1 - d0); -} - -var d3_time_scaleSteps = [ - 1e3, // 1-second - 5e3, // 5-second - 15e3, // 15-second - 3e4, // 30-second - 6e4, // 1-minute - 3e5, // 5-minute - 9e5, // 15-minute - 18e5, // 30-minute - 36e5, // 1-hour - 108e5, // 3-hour - 216e5, // 6-hour - 432e5, // 12-hour - 864e5, // 1-day - 1728e5, // 2-day - 6048e5, // 1-week - 2592e6, // 1-month - 7776e6, // 3-month - 31536e6 // 1-year -]; - -var d3_time_scaleLocalMethods = [ - [d3.time.second, 1], - [d3.time.second, 5], - [d3.time.second, 15], - [d3.time.second, 30], - [d3.time.minute, 1], - [d3.time.minute, 5], - [d3.time.minute, 15], - [d3.time.minute, 30], - [d3.time.hour, 1], - [d3.time.hour, 3], - [d3.time.hour, 6], - [d3.time.hour, 12], - [d3.time.day, 1], - [d3.time.day, 2], - [d3.time.week, 1], - [d3.time.month, 1], - [d3.time.month, 3], - [d3.time.year, 1] -]; - -var d3_time_scaleLocalFormats = [ - [d3.time.format("%Y"), function(d) { return true; }], - [d3.time.format("%B"), function(d) { return d.getMonth(); }], - [d3.time.format("%b %d"), function(d) { return d.getDate() != 1; }], - [d3.time.format("%a %d"), function(d) { return d.getDay() && d.getDate() != 1; }], - [d3.time.format("%I %p"), function(d) { return d.getHours(); }], - [d3.time.format("%I:%M"), function(d) { return d.getMinutes(); }], - [d3.time.format(":%S"), function(d) { return d.getSeconds(); }], - [d3.time.format(".%L"), function(d) { return d.getMilliseconds(); }] -]; - -var d3_time_scaleLinear = d3.scale.linear(), - d3_time_scaleLocalFormat = d3_time_scaleFormat(d3_time_scaleLocalFormats); - -d3_time_scaleLocalMethods.year = function(extent, m) { - return d3_time_scaleLinear.domain(extent.map(d3_time_scaleGetYear)).ticks(m).map(d3_time_scaleSetYear); -}; - -d3.time.scale = function() { - return d3_time_scale(d3.scale.linear(), d3_time_scaleLocalMethods, d3_time_scaleLocalFormat); -}; -var d3_time_scaleUTCMethods = d3_time_scaleLocalMethods.map(function(m) { - return [m[0].utc, m[1]]; -}); - -var d3_time_scaleUTCFormats = [ - [d3.time.format.utc("%Y"), function(d) { return true; }], - [d3.time.format.utc("%B"), function(d) { return d.getUTCMonth(); }], - [d3.time.format.utc("%b %d"), function(d) { return d.getUTCDate() != 1; }], - [d3.time.format.utc("%a %d"), function(d) { return d.getUTCDay() && d.getUTCDate() != 1; }], - [d3.time.format.utc("%I %p"), function(d) { return d.getUTCHours(); }], - [d3.time.format.utc("%I:%M"), function(d) { return d.getUTCMinutes(); }], - [d3.time.format.utc(":%S"), function(d) { return d.getUTCSeconds(); }], - [d3.time.format.utc(".%L"), function(d) { return d.getUTCMilliseconds(); }] -]; - -var d3_time_scaleUTCFormat = d3_time_scaleFormat(d3_time_scaleUTCFormats); - -function d3_time_scaleUTCSetYear(y) { - var d = new Date(Date.UTC(y, 0, 1)); - d.setUTCFullYear(y); // Y2K fail - return d; -} - -function d3_time_scaleUTCGetYear(d) { - var y = d.getUTCFullYear(), - d0 = d3_time_scaleUTCSetYear(y), - d1 = d3_time_scaleUTCSetYear(y + 1); - return y + (d - d0) / (d1 - d0); -} - -d3_time_scaleUTCMethods.year = function(extent, m) { - return d3_time_scaleLinear.domain(extent.map(d3_time_scaleUTCGetYear)).ticks(m).map(d3_time_scaleUTCSetYear); -}; - -d3.time.scale.utc = function() { - return d3_time_scale(d3.scale.linear(), d3_time_scaleUTCMethods, d3_time_scaleUTCFormat); -}; -})(); + var d3_time_parsers = { + a: d3_time_parseWeekdayAbbrev, + A: d3_time_parseWeekday, + b: d3_time_parseMonthAbbrev, + B: d3_time_parseMonth, + c: d3_time_parseLocaleFull, + d: d3_time_parseDay, + e: d3_time_parseDay, + H: d3_time_parseHour24, + I: d3_time_parseHour24, + L: d3_time_parseMilliseconds, + m: d3_time_parseMonthNumber, + M: d3_time_parseMinutes, + p: d3_time_parseAmPm, + S: d3_time_parseSeconds, + x: d3_time_parseLocaleDate, + X: d3_time_parseLocaleTime, + y: d3_time_parseYear, + Y: d3_time_parseFullYear + }; + function d3_time_parseWeekdayAbbrev(date, string, i) { + d3_time_dayAbbrevRe.lastIndex = 0; + var n = d3_time_dayAbbrevRe.exec(string.substring(i)); + return n ? i += n[0].length : -1; + } + function d3_time_parseWeekday(date, string, i) { + d3_time_dayRe.lastIndex = 0; + var n = d3_time_dayRe.exec(string.substring(i)); + return n ? i += n[0].length : -1; + } + function d3_time_parseMonthAbbrev(date, string, i) { + d3_time_monthAbbrevRe.lastIndex = 0; + var n = d3_time_monthAbbrevRe.exec(string.substring(i)); + return n ? (date.m = d3_time_monthAbbrevLookup.get(n[0].toLowerCase()), i += n[0].length) : -1; + } + function d3_time_parseMonth(date, string, i) { + d3_time_monthRe.lastIndex = 0; + var n = d3_time_monthRe.exec(string.substring(i)); + return n ? (date.m = d3_time_monthLookup.get(n[0].toLowerCase()), i += n[0].length) : -1; + } + function d3_time_parseLocaleFull(date, string, i) { + return d3_time_parse(date, d3_time_formats.c.toString(), string, i); + } + function d3_time_parseLocaleDate(date, string, i) { + return d3_time_parse(date, d3_time_formats.x.toString(), string, i); + } + function d3_time_parseLocaleTime(date, string, i) { + return d3_time_parse(date, d3_time_formats.X.toString(), string, i); + } + function d3_time_parseFullYear(date, string, i) { + d3_time_numberRe.lastIndex = 0; + var n = d3_time_numberRe.exec(string.substring(i, i + 4)); + return n ? (date.y = +n[0], i += n[0].length) : -1; + } + function d3_time_parseYear(date, string, i) { + d3_time_numberRe.lastIndex = 0; + var n = d3_time_numberRe.exec(string.substring(i, i + 2)); + return n ? (date.y = d3_time_expandYear(+n[0]), i += n[0].length) : -1; + } + function d3_time_expandYear(d) { + return d + (d > 68 ? 1900 : 2e3); + } + function d3_time_parseMonthNumber(date, string, i) { + d3_time_numberRe.lastIndex = 0; + var n = d3_time_numberRe.exec(string.substring(i, i + 2)); + return n ? (date.m = n[0] - 1, i += n[0].length) : -1; + } + function d3_time_parseDay(date, string, i) { + d3_time_numberRe.lastIndex = 0; + var n = d3_time_numberRe.exec(string.substring(i, i + 2)); + return n ? (date.d = +n[0], i += n[0].length) : -1; + } + function d3_time_parseHour24(date, string, i) { + d3_time_numberRe.lastIndex = 0; + var n = d3_time_numberRe.exec(string.substring(i, i + 2)); + return n ? (date.H = +n[0], i += n[0].length) : -1; + } + function d3_time_parseMinutes(date, string, i) { + d3_time_numberRe.lastIndex = 0; + var n = d3_time_numberRe.exec(string.substring(i, i + 2)); + return n ? (date.M = +n[0], i += n[0].length) : -1; + } + function d3_time_parseSeconds(date, string, i) { + d3_time_numberRe.lastIndex = 0; + var n = d3_time_numberRe.exec(string.substring(i, i + 2)); + return n ? (date.S = +n[0], i += n[0].length) : -1; + } + function d3_time_parseMilliseconds(date, string, i) { + d3_time_numberRe.lastIndex = 0; + var n = d3_time_numberRe.exec(string.substring(i, i + 3)); + return n ? (date.L = +n[0], i += n[0].length) : -1; + } + var d3_time_numberRe = /^\s*\d+/; + function d3_time_parseAmPm(date, string, i) { + var n = d3_time_amPmLookup.get(string.substring(i, i += 2).toLowerCase()); + return n == null ? -1 : (date.p = n, i); + } + var d3_time_amPmLookup = d3.map({ + am: 0, + pm: 1 + }); + function d3_time_zone(d) { + var z = d.getTimezoneOffset(), zs = z > 0 ? "-" : "+", zh = ~~(Math.abs(z) / 60), zm = Math.abs(z) % 60; + return zs + d3_time_formatPad(zh, "0", 2) + d3_time_formatPad(zm, "0", 2); + } + d3.time.format.utc = function(template) { + var local = d3.time.format(template); + function format(date) { + try { + d3_time = d3_time_utc; + var utc = new d3_time(); + utc._ = date; + return local(utc); + } finally { + d3_time = Date; + } + } + format.parse = function(string) { + try { + d3_time = d3_time_utc; + var date = local.parse(string); + return date && date._; + } finally { + d3_time = Date; + } + }; + format.toString = local.toString; + return format; + }; + var d3_time_formatIso = d3.time.format.utc("%Y-%m-%dT%H:%M:%S.%LZ"); + d3.time.format.iso = Date.prototype.toISOString && +new Date("2000-01-01T00:00:00.000Z") ? d3_time_formatIsoNative : d3_time_formatIso; + function d3_time_formatIsoNative(date) { + return date.toISOString(); + } + d3_time_formatIsoNative.parse = function(string) { + var date = new Date(string); + return isNaN(date) ? null : date; + }; + d3_time_formatIsoNative.toString = d3_time_formatIso.toString; + d3.time.second = d3_time_interval(function(date) { + return new d3_time(Math.floor(date / 1e3) * 1e3); + }, function(date, offset) { + date.setTime(date.getTime() + Math.floor(offset) * 1e3); + }, function(date) { + return date.getSeconds(); + }); + d3.time.seconds = d3.time.second.range; + d3.time.seconds.utc = d3.time.second.utc.range; + d3.time.minute = d3_time_interval(function(date) { + return new d3_time(Math.floor(date / 6e4) * 6e4); + }, function(date, offset) { + date.setTime(date.getTime() + Math.floor(offset) * 6e4); + }, function(date) { + return date.getMinutes(); + }); + d3.time.minutes = d3.time.minute.range; + d3.time.minutes.utc = d3.time.minute.utc.range; + d3.time.hour = d3_time_interval(function(date) { + var timezone = date.getTimezoneOffset() / 60; + return new d3_time((Math.floor(date / 36e5 - timezone) + timezone) * 36e5); + }, function(date, offset) { + date.setTime(date.getTime() + Math.floor(offset) * 36e5); + }, function(date) { + return date.getHours(); + }); + d3.time.hours = d3.time.hour.range; + d3.time.hours.utc = d3.time.hour.utc.range; + d3.time.month = d3_time_interval(function(date) { + date = d3.time.day(date); + date.setDate(1); + return date; + }, function(date, offset) { + date.setMonth(date.getMonth() + offset); + }, function(date) { + return date.getMonth(); + }); + d3.time.months = d3.time.month.range; + d3.time.months.utc = d3.time.month.utc.range; + function d3_time_scale(linear, methods, format) { + function scale(x) { + return linear(x); + } + scale.invert = function(x) { + return d3_time_scaleDate(linear.invert(x)); + }; + scale.domain = function(x) { + if (!arguments.length) return linear.domain().map(d3_time_scaleDate); + linear.domain(x); + return scale; + }; + scale.nice = function(m) { + return scale.domain(d3_scale_nice(scale.domain(), function() { + return m; + })); + }; + scale.ticks = function(m, k) { + var extent = d3_time_scaleExtent(scale.domain()); + if (typeof m !== "function") { + var span = extent[1] - extent[0], target = span / m, i = d3.bisect(d3_time_scaleSteps, target); + if (i == d3_time_scaleSteps.length) return methods.year(extent, m); + if (!i) return linear.ticks(m).map(d3_time_scaleDate); + if (Math.log(target / d3_time_scaleSteps[i - 1]) < Math.log(d3_time_scaleSteps[i] / target)) --i; + m = methods[i]; + k = m[1]; + m = m[0].range; + } + return m(extent[0], new Date(+extent[1] + 1), k); + }; + scale.tickFormat = function() { + return format; + }; + scale.copy = function() { + return d3_time_scale(linear.copy(), methods, format); + }; + return d3.rebind(scale, linear, "range", "rangeRound", "interpolate", "clamp"); + } + function d3_time_scaleExtent(domain) { + var start = domain[0], stop = domain[domain.length - 1]; + return start < stop ? [ start, stop ] : [ stop, start ]; + } + function d3_time_scaleDate(t) { + return new Date(t); + } + function d3_time_scaleFormat(formats) { + return function(date) { + var i = formats.length - 1, f = formats[i]; + while (!f[1](date)) f = formats[--i]; + return f[0](date); + }; + } + function d3_time_scaleSetYear(y) { + var d = new Date(y, 0, 1); + d.setFullYear(y); + return d; + } + function d3_time_scaleGetYear(d) { + var y = d.getFullYear(), d0 = d3_time_scaleSetYear(y), d1 = d3_time_scaleSetYear(y + 1); + return y + (d - d0) / (d1 - d0); + } + var d3_time_scaleSteps = [ 1e3, 5e3, 15e3, 3e4, 6e4, 3e5, 9e5, 18e5, 36e5, 108e5, 216e5, 432e5, 864e5, 1728e5, 6048e5, 2592e6, 7776e6, 31536e6 ]; + var d3_time_scaleLocalMethods = [ [ d3.time.second, 1 ], [ d3.time.second, 5 ], [ d3.time.second, 15 ], [ d3.time.second, 30 ], [ d3.time.minute, 1 ], [ d3.time.minute, 5 ], [ d3.time.minute, 15 ], [ d3.time.minute, 30 ], [ d3.time.hour, 1 ], [ d3.time.hour, 3 ], [ d3.time.hour, 6 ], [ d3.time.hour, 12 ], [ d3.time.day, 1 ], [ d3.time.day, 2 ], [ d3.time.week, 1 ], [ d3.time.month, 1 ], [ d3.time.month, 3 ], [ d3.time.year, 1 ] ]; + var d3_time_scaleLocalFormats = [ [ d3.time.format("%Y"), d3_true ], [ d3.time.format("%B"), function(d) { + return d.getMonth(); + } ], [ d3.time.format("%b %d"), function(d) { + return d.getDate() != 1; + } ], [ d3.time.format("%a %d"), function(d) { + return d.getDay() && d.getDate() != 1; + } ], [ d3.time.format("%I %p"), function(d) { + return d.getHours(); + } ], [ d3.time.format("%I:%M"), function(d) { + return d.getMinutes(); + } ], [ d3.time.format(":%S"), function(d) { + return d.getSeconds(); + } ], [ d3.time.format(".%L"), function(d) { + return d.getMilliseconds(); + } ] ]; + var d3_time_scaleLinear = d3.scale.linear(), d3_time_scaleLocalFormat = d3_time_scaleFormat(d3_time_scaleLocalFormats); + d3_time_scaleLocalMethods.year = function(extent, m) { + return d3_time_scaleLinear.domain(extent.map(d3_time_scaleGetYear)).ticks(m).map(d3_time_scaleSetYear); + }; + d3.time.scale = function() { + return d3_time_scale(d3.scale.linear(), d3_time_scaleLocalMethods, d3_time_scaleLocalFormat); + }; + var d3_time_scaleUTCMethods = d3_time_scaleLocalMethods.map(function(m) { + return [ m[0].utc, m[1] ]; + }); + var d3_time_scaleUTCFormats = [ [ d3.time.format.utc("%Y"), d3_true ], [ d3.time.format.utc("%B"), function(d) { + return d.getUTCMonth(); + } ], [ d3.time.format.utc("%b %d"), function(d) { + return d.getUTCDate() != 1; + } ], [ d3.time.format.utc("%a %d"), function(d) { + return d.getUTCDay() && d.getUTCDate() != 1; + } ], [ d3.time.format.utc("%I %p"), function(d) { + return d.getUTCHours(); + } ], [ d3.time.format.utc("%I:%M"), function(d) { + return d.getUTCMinutes(); + } ], [ d3.time.format.utc(":%S"), function(d) { + return d.getUTCSeconds(); + } ], [ d3.time.format.utc(".%L"), function(d) { + return d.getUTCMilliseconds(); + } ] ]; + var d3_time_scaleUTCFormat = d3_time_scaleFormat(d3_time_scaleUTCFormats); + function d3_time_scaleUTCSetYear(y) { + var d = new Date(Date.UTC(y, 0, 1)); + d.setUTCFullYear(y); + return d; + } + function d3_time_scaleUTCGetYear(d) { + var y = d.getUTCFullYear(), d0 = d3_time_scaleUTCSetYear(y), d1 = d3_time_scaleUTCSetYear(y + 1); + return y + (d - d0) / (d1 - d0); + } + d3_time_scaleUTCMethods.year = function(extent, m) { + return d3_time_scaleLinear.domain(extent.map(d3_time_scaleUTCGetYear)).ticks(m).map(d3_time_scaleUTCSetYear); + }; + d3.time.scale.utc = function() { + return d3_time_scale(d3.scale.linear(), d3_time_scaleUTCMethods, d3_time_scaleUTCFormat); + }; + d3.text = function() { + return d3.xhr.apply(d3, arguments).response(d3_text); + }; + function d3_text(request) { + return request.responseText; + } + d3.json = function(url, callback) { + return d3.xhr(url, "application/json", callback).response(d3_json); + }; + function d3_json(request) { + return JSON.parse(request.responseText); + } + d3.html = function(url, callback) { + return d3.xhr(url, "text/html", callback).response(d3_html); + }; + function d3_html(request) { + var range = d3_document.createRange(); + range.selectNode(d3_document.body); + return range.createContextualFragment(request.responseText); + } + d3.xml = function() { + return d3.xhr.apply(d3, arguments).response(d3_xml); + }; + function d3_xml(request) { + return request.responseXML; + } + return d3; +}(); \ No newline at end of file diff --git a/static/scripts/pack_scripts.py b/static/scripts/pack_scripts.py index 0544c933c47..5ca85222636 100755 --- a/static/scripts/pack_scripts.py +++ b/static/scripts/pack_scripts.py @@ -10,7 +10,7 @@ from os import path # Scripts that should not be packed -- just copied do_not_pack = set() -cmd = "java -jar ../../scripts/yuicompressor.jar --type js %(fname)s -o packed/%(fname)s" +cmd = "java -jar ../../scripts/yuicompressor.jar --charset utf-8 --type js %(fname)s -o packed/%(fname)s" # cmd = "java -jar ../../scripts/compiler.jar --compilation_level SIMPLE_OPTIMIZATIONS --js %(fname)s --js_output_file packed/%(fname)s" # If specific scripts specified on command line, just pack them, otherwise pack diff --git a/static/scripts/packed/libs/d3.js b/static/scripts/packed/libs/d3.js index ce0a5600592..7cca181ab7e 100644 --- a/static/scripts/packed/libs/d3.js +++ b/static/scripts/packed/libs/d3.js @@ -1 +1 @@ -(function(){if(!Date.now){Date.now=function(){return +new Date}}try{document.createElement("div").style.setProperty("opacity",0,"")}catch(c7){var bN=CSSStyleDeclaration.prototype,dY=bN.setProperty;bN.setProperty=function(e,eR,eQ){dY.call(this,e,eR+"",eQ)}}d3={version:"2.9.2"};function eK(eS,eR){try{for(var eQ in eR){Object.defineProperty(eS.prototype,eQ,{value:eR[eQ],enumerable:false})}}catch(eT){eS.prototype=eR}}var bU=aN;function eI(eQ){var e=-1,eS=eQ.length,eR=[];while(++ee?1:eQ>=e?0:NaN};d3.descending=function(eQ,e){return eeQ?1:e>=eQ?0:NaN};d3.mean=function(eV,eT){var eU=eV.length,eQ,e=0,eS=-1,eR=0;if(arguments.length===1){while(++eS1){eQ=eQ.map(e)}eQ=eQ.filter(eo);return eQ.length?d3.quantile(eQ.sort(d3.ascending),0.5):undefined};d3.min=function(eU,eS){var eR=-1,eT=eU.length,eQ,e;if(arguments.length===1){while(++eRe){eQ=e}}}else{while(++eRe){eQ=e}}}return eQ};d3.max=function(eU,eS){var eR=-1,eT=eU.length,eQ,e;if(arguments.length===1){while(++eReQ){eQ=e}}}else{while(++eReQ){eQ=e}}}return eQ};d3.extent=function(eV,eS){var eR=-1,eU=eV.length,eQ,e,eT;if(arguments.length===1){while(++eRe){eQ=e}if(eTe){eQ=e}if(eT1);return e+eQ*eR*Math.sqrt(-2*Math.log(eS)/eS)}}};function eo(e){return e!=null&&!isNaN(e)}d3.sum=function(eU,eS){var eR=0,eT=eU.length,e,eQ=-1;if(arguments.length===1){while(++eQ>1;if(e.call(eR,eR[eS],eS)>1;if(eQ0){eQ=e}}return eQ};d3.last=function(eU,eS){var eR=0,eT=eU.length,eQ=eU[0],e;if(arguments.length===1){eS=d3.ascending}while(++eR=eR.length){return eT?eT.call(eS,e3):(eQ?e3.sort(eQ):e3)}var e2=-1,eX=e3.length,e5=eR[e1++],e0,eZ,eY=new ad,e4,eW={};while(++e2=eR.length){return eZ}var eW=[],eX=eV[e0++],eY;for(eY in eZ){eW.push({key:eY,values:e(eZ[eY],e0)})}if(eX){eW.sort(function(e2,e1){return eX(e2.key,e1.key)})}return eW}eS.map=function(eW){return eU(eW,0)};eS.entries=function(eW){return e(eU(eW,0),0)};eS.key=function(eW){eR.push(eW);return eS};eS.sortKeys=function(eW){eV[eR.length-1]=eW;return eS};eS.sortValues=function(eW){eQ=eW;return eS};eS.rollup=function(eW){eT=eW;return eS};return eS};d3.keys=function(eR){var eQ=[];for(var e in eR){eQ.push(e)}return eQ};d3.values=function(eR){var e=[];for(var eQ in eR){e.push(eR[eQ])}return e};d3.entries=function(eR){var e=[];for(var eQ in eR){e.push({key:eQ,value:eR[eQ]})}return e};d3.permute=function(eT,eQ){var e=[],eR=-1,eS=eQ.length;while(++eReT){eQ.push(eR/e)}}else{while((eR=eV+eU*++eS)=200&&eT<300||eT===304?eQ:null)}};eQ.send(null)};d3.text=function(e,eR,eS){function eQ(eT){eS(eT&&eT.responseText)}if(arguments.length<3){eS=eR;eR=null}d3.xhr(e,eR,eQ)};d3.json=function(e,eQ){d3.text(e,"application/json",function(eR){eQ(eR?JSON.parse(eR):null)})};d3.html=function(e,eQ){d3.text(e,"text/html",function(eS){if(eS!=null){var eR=document.createRange();eR.selectNode(document.body);eS=eR.createContextualFragment(eS)}eQ(eS)})};d3.xml=function(e,eR,eS){function eQ(eT){eS(eT&&eT.responseXML)}if(arguments.length<3){eS=eR;eR=null}d3.xhr(e,eR,eQ)};var cb={svg:"http://www.w3.org/2000/svg",xhtml:"http://www.w3.org/1999/xhtml",xlink:"http://www.w3.org/1999/xlink",xml:"http://www.w3.org/XML/1998/namespace",xmlns:"http://www.w3.org/2000/xmlns/"};d3.ns={prefix:cb,qualify:function(e){var eQ=e.indexOf(":"),eR=e;if(eQ>=0){eR=e.substring(0,eQ);e=e.substring(eQ+1)}return cb.hasOwnProperty(eR)?{space:cb[eR],local:e}:e}};d3.dispatch=function(){var e=new cw,eQ=-1,eR=arguments.length;while(++eQ0){e=eR.substring(eQ+1);eR=eR.substring(0,eQ)}return arguments.length<2?this[eR].on(e):this[eR].on(e,eS)};function cU(e){var eR=[],eQ=new ad;function eS(){var eV=eR,eU=-1,eW=eV.length,eT;while(++eU=^]))?([+\- ])?(#)?(0)?([0-9]+)?(,)?(\.[0-9]+)?([a-zA-Z%])?/;var s=d3.map({g:function(e,eQ){return e.toPrecision(eQ)},e:function(e,eQ){return e.toExponential(eQ)},f:function(e,eQ){return e.toFixed(eQ)},r:function(e,eQ){return d3.round(e,eQ=cd(e,eQ)).toFixed(Math.max(0,Math.min(20,eQ)))}});function cd(e,eQ){return eQ-(e?1+Math.floor(Math.log(e+Math.pow(10,1+Math.floor(Math.log(e)/Math.LN10)-eQ))/Math.LN10):1)}function aA(e){return e+""}function bD(eS){var eQ=eS.lastIndexOf("."),eR=eQ>=0?eS.substring(eQ):(eQ=eS.length,""),e=[];while(eQ>0){e.push(eS.substring(eQ-=3,eQ+3))}return e.reverse().join(",")+eR}var bb=["y","z","a","f","p","n","μ","m","","k","M","G","T","P","E","Z","Y"].map(bT);d3.formatPrefix=function(eR,e){var eQ=0;if(eR){if(eR<0){eR*=-1}if(e){eR=d3.round(eR,cd(eR,e))}eQ=1+Math.floor(1e-12+Math.log(eR)/Math.LN10);eQ=Math.max(-24,Math.min(24,Math.floor((eQ<=0?eQ+1:eQ-1)/3)*3))}return bb[8+eQ/3]};function bT(eR,eQ){var e=Math.pow(10,Math.abs(8-eQ)*3);return{scale:eQ>8?function(eS){return eS/e}:function(eS){return eS*e},symbol:eR}}var aW=ai(2),z=ai(3),ej=function(){return bC};var L=d3.map({linear:ej,poly:ai,quad:function(){return aW},cubic:function(){return z},sin:function(){return dy},exp:function(){return c4},circle:function(){return b9},elastic:K,back:d8,bounce:function(){return a5}});var j=d3.map({"in":bC,out:v,"in-out":ap,"out-in":function(e){return ap(v(e))}});d3.ease=function(eQ){var eS=eQ.indexOf("-"),eR=eS>=0?eQ.substring(0,eS):eQ,e=eS>=0?eQ.substring(eS+1):"in";eR=L.get(eR)||ej;e=j.get(e)||bC;return db(e(eR.apply(null,Array.prototype.slice.call(arguments,1))))};function db(e){return function(eQ){return eQ<=0?0:eQ>=1?1:e(eQ)}}function v(e){return function(eQ){return 1-e(1-eQ)}}function ap(e){return function(eQ){return 0.5*(eQ<0.5?e(2*eQ):(2-e(2-2*eQ)))}}function bC(e){return e}function ai(eQ){return function(e){return Math.pow(e,eQ)}}function dy(e){return 1-Math.cos(e*Math.PI/2)}function c4(e){return Math.pow(2,10*(e-1))}function b9(e){return 1-Math.sqrt(1-e*e)}function K(e,eR){var eQ;if(arguments.length<2){eR=0.45}if(arguments.length<1){e=1;eQ=eR/4}else{eQ=eR/(2*Math.PI)*Math.asin(1/e)}return function(eS){return 1+e*Math.pow(2,10*-eS)*Math.sin((eS-eQ)*2*Math.PI/eR)}}function d8(e){if(!e){e=1.70158}return function(eQ){return eQ*eQ*((e+1)*eQ-e)}}function a5(e){return e<1/2.75?7.5625*e*e:e<2/2.75?7.5625*(e-=1.5/2.75)*e+0.75:e<2.5/2.75?7.5625*(e-=2.25/2.75)*e+0.9375:7.5625*(e-=2.625/2.75)*e+0.984375}d3.event=null;function ez(){d3.event.stopPropagation();d3.event.preventDefault()}function N(){var eR=d3.event,eQ;while(eQ=eR.sourceEvent){eR=eQ}return eR}function ei(eR){var e=new cw,eQ=0,eS=arguments.length;while(++eQ=0&&!(eS=d3.interpolators[eR](eQ,e))){}return eS};d3.interpolateNumber=function(eQ,e){e-=eQ;return function(eR){return eQ+e*eR}};d3.interpolateRound=function(eQ,e){e-=eQ;return function(eR){return Math.round(eQ+e*eR)}};d3.interpolateString=function(eW,eV){var eS,eU,eT,eY=0,eX=0,eZ=[],e=[],eR,eQ;dk.lastIndex=0;for(eU=0;eS=dk.exec(eV);++eU){if(eS.index){eZ.push(eV.substring(eY,eX=eS.index))}e.push({i:eZ.length,x:eS[0]});eZ.push(null);eY=dk.lastIndex}if(eY1?1:eT;e=e<0?0:e>1?1:e;eR=e<=0.5?e*(1+eT):e+eT-e*eT;eS=2*e-eR;function eQ(eW){if(eW>360){eW-=360}else{if(eW<0){eW+=360}}if(eW<60){return eS+(eR-eS)*eW/60}if(eW<180){return eR}if(eW<240){return eS+(eR-eS)*(240-eW)/60}return eS}function eV(eW){return Math.round(eQ(eW)*255)}return cg(eV(eU+120),eV(eU),eV(eU-120))}function b2(e){cA(e,b1);return e}var bH=function(e,eQ){return eQ.querySelector(e)},du=function(e,eQ){return eQ.querySelectorAll(e)},co=document.documentElement,b=co.matchesSelector||co.webkitMatchesSelector||co.mozMatchesSelector||co.msMatchesSelector||co.oMatchesSelector,a=function(eQ,e){return b.call(eQ,e)};if(typeof Sizzle==="function"){bH=function(e,eQ){return Sizzle(e,eQ)[0]};du=function(e,eQ){return Sizzle.uniqueSort(Sizzle(e,eQ))};a=Sizzle.matchesSelector}var b1=[];d3.selection=function(){return d1};d3.selection.prototype=b1;b1.select=function(eU){var eR=[],eY,e,eX,eS;if(typeof eU!=="function"){eU=t(eU)}for(var eV=-1,eT=this.length;++eV1){while(++eQ=0;){if(eT=eU[eR]){if(eS&&eS!==eT.nextSibling){eS.parentNode.insertBefore(eT,eS)}eS=eT}}}return this};b1.sort=function(eQ){eQ=dX.apply(this,arguments);for(var eR=-1,e=this.length;++eR0){eS=eS.substring(0,eR)}if(arguments.length<2){return(eR=this.node()[eQ])&&eR._}return this.each(function(eY,eV){var eW=this,eX=eW[eQ];if(eX){eW.removeEventListener(eS,eX,eX.$);delete eW[eQ]}if(eT){eW.addEventListener(eS,eW[eQ]=eU,eU.$=e);eU._=eT}function eU(eZ){var e0=d3.event;d3.event=eZ;try{eT.call(eW,eW.__data__,eV)}finally{d3.event=e0}}})};b1.each=function(eV){for(var eQ=-1,e=this.length;++eQeU){return e4()}e6.active=eU;eR.forEach(function(e8,e9){if(e9=e9.call(eX,e3,e0)){e5.push(e9)}});eQ.start.call(eX,e3,e0);if(!e2(e7)){d3.timer(e2,0,eS)}return 1}function e2(e7){if(e6.active!==eU){return e4()}var e8=(e7-e1)/eY,e9=eT(e8),fa=e5.length;while(fa>0){e5[--fa].call(eX,e9)}if(e8>=1){e4();eg=eU;eQ.end.call(eX,e3,e0);eg=0;return 1}}function e4(){if(!--e6.count){delete eX.__transition__}return 1}});return 1},0,eS);return e}var r={};function d7(eR,eQ,e){return e!=""&&r}function f(eS,e){var eR=c5(eS);function eQ(eX,eW,eU){var eV=e.call(this,eX,eW);return eV==null?eU!=""&&r:eU!=eV&&eR(eU,eV)}function eT(eW,eV,eU){return eU!=e&&eR(eU,e)}return typeof e==="function"?eQ:e==null?d7:(e+="",eT)}var cj=[],eu=0,eg=0,d=0,d5=250,cV=d3.ease("cubic-in-out"),cF=d,d9=d5,ax=cV;cj.call=b1.call;d3.transition=function(e){return arguments.length?(eg?e.transition():e):d1.transition()};d3.transition.prototype=cj;cj.select=function(eU){var eR=[],eY,e,eS;if(typeof eU!=="function"){eU=t(eU)}for(var eV=-1,eT=this.length;++eV=eS.delay){eS.flush=eS.callback(e)}eS=eS.next}var eQ=cK()-eR;if(eQ>24){if(isFinite(eQ)){clearTimeout(cT);cT=setTimeout(eq,eQ)}a8=0}else{a8=1;ep(eq)}}d3.timer.flush=function(){var e,eQ=Date.now(),eR=a0;while(eR){e=eQ-eR.then;if(!eR.delay){eR.flush=eR.callback(e)}eR=eR.next}cK()};function cK(){var eQ=null,e=a0,eR=Infinity;while(e){if(e.flush){e=eQ?eQ.next=e.next:a0=e.next}else{eR=Math.min(eR,e.then+e.delay);e=(eQ=e).next}}return eR}var ep=window.requestAnimationFrame||window.webkitRequestAnimationFrame||window.mozRequestAnimationFrame||window.oRequestAnimationFrame||window.msRequestAnimationFrame||function(e){setTimeout(e,17)};d3.transform=function(eQ){var eR=document.createElementNS(d3.ns.prefix.svg,"g"),e={a:1,b:0,c:0,d:1,e:0,f:0};return(d3.transform=function(eS){eR.setAttribute("transform",eS);var eT=eR.transform.baseVal.consolidate();return new o(eT?eT.matrix:e)})(eQ)};function o(e){var eS=[e.a,e.b],eQ=[e.c,e.d],eU=c2(eS),eR=cB(eS,eQ),eT=c2(aR(eQ,eS,-eR))||0;if(eS[0]*eQ[1]2?C:ab,eY=eW?Y:er;eR=eX(eU,eS,eY,eT);eQ=eX(eS,eU,eY,d3.interpolate);return eV}function eV(eX){return eR(eX)}eV.invert=function(eX){return eQ(eX)};eV.domain=function(eX){if(!arguments.length){return eU}eU=eX.map(Number);return e()};eV.range=function(eX){if(!arguments.length){return eS}eS=eX;return e()};eV.rangeRound=function(eX){return eV.range(eX).interpolate(d3.interpolateRound)};eV.clamp=function(eX){if(!arguments.length){return eW}eW=eX;return e()};eV.interpolate=function(eX){if(!arguments.length){return eT}eT=eX;return e()};eV.ticks=function(eX){return cE(eU,eX)};eV.tickFormat=function(eX){return cO(eU,eX)};eV.nice=function(){dD(eU,af);return e()};eV.copy=function(){return ce(eU,eS,eT,eW)};return e()}function dv(eQ,e){return d3.rebind(eQ,e,"range","rangeRound","interpolate","clamp")}function af(e){e=Math.pow(10,Math.round(Math.log(e)/Math.LN10)-1);return{floor:function(eQ){return Math.floor(eQ/e)*e},ceil:function(eQ){return Math.ceil(eQ/e)*e}}}function p(eU,e){var eR=ch(eU),eQ=eR[1]-eR[0],eT=Math.pow(10,Math.floor(Math.log(eQ/e)/Math.LN10)),eS=e/eQ*eT;if(eS<=0.15){eT*=10}else{if(eS<=0.35){eT*=5}else{if(eS<=0.75){eT*=2}}}eR[0]=Math.ceil(eR[0]/eT)*eT;eR[1]=Math.floor(eR[1]/eT)*eT+eT*0.5;eR[2]=eT;return eR}function cE(eQ,e){return d3.range.apply(d3,p(eQ,e))}function cO(eQ,e){return d3.format(",."+Math.max(0,-Math.floor(Math.log(p(eQ,e)[2])/Math.LN10+0.01))+"f")}function ab(eT,e,eU,eR){var eQ=eU(eT[0],eT[1]),eS=eR(e[0],e[1]);return function(eV){return eS(eQ(eV))}}function C(eV,eQ,eW,eT){var eS=[],eU=[],eR=0,e=Math.min(eV.length,eQ.length)-1;if(eV[e]0;eT--){eZ.push(eR(eX)*eT)}}}else{for(;eXeU;eW--){}eZ=eZ.slice(eX,eW)}return eZ};eS.tickFormat=function(eX,eW){if(arguments.length<2){eW=bI}if(arguments.length<1){return eW}var eT=eX/eS.ticks().length,eU=eQ===dz?(eV=-1e-12,Math.floor):(eV=1e-12,Math.ceil),eV;return function(eY){return eY/eR(eU(eQ(eY)+eV))0?0:-e)/Math.LN10}dw.pow=function(e){return Math.pow(10,e)};dz.pow=function(e){return -Math.pow(10,-e)};d3.scale.pow=function(){return dJ(d3.scale.linear(),1)};function dJ(e,eR){var eQ=dm(eR),eS=dm(1/eR);function eT(eU){return e(eQ(eU))}eT.invert=function(eU){return eS(e.invert(eU))};eT.domain=function(eU){if(!arguments.length){return e.domain().map(eS)}e.domain(eU.map(eQ));return eT};eT.ticks=function(eU){return cE(eT.domain(),eU)};eT.tickFormat=function(eU){return cO(eT.domain(),eU)};eT.nice=function(){return eT.domain(dD(eT.domain(),af))};eT.exponent=function(eU){if(!arguments.length){return eR}var eV=eT.domain();eQ=dm(eR=eU);eS=dm(1/eR);return eT.domain(eV)};eT.copy=function(){return dJ(e.copy(),eR)};return dv(eT,e)}function dm(eQ){return function(e){return e<0?-Math.pow(-e,eQ):Math.pow(e,eQ)}}d3.scale.sqrt=function(){return d3.scale.pow().exponent(0.5)};d3.scale.ordinal=function(){return a9([],{t:"range",x:[]})};function a9(eU,eR){var eT,eQ,eS;function eV(eW){return eQ[((eT.get(eW)||eT.set(eW,eU.push(eW)))-1)%eQ.length]}function e(eX,eW){return d3.range(eU.length).map(function(eY){return eX+eW*eY})}eV.domain=function(eW){if(!arguments.length){return eU}eU=[];eT=new ad;var eY=-1,eZ=eW.length,eX;while(++eY=eC?(eX?"M0,"+eW+"A"+eW+","+eW+" 0 1,1 0,"+(-eW)+"A"+eW+","+eW+" 0 1,1 0,"+eW+"M0,"+eX+"A"+eX+","+eX+" 0 1,0 0,"+(-eX)+"A"+eX+","+eX+" 0 1,0 0,"+eX+"Z":"M0,"+eW+"A"+eW+","+eW+" 0 1,1 0,"+(-eW)+"A"+eW+","+eW+" 0 1,1 0,"+eW+"Z"):(eX?"M"+eW*eZ+","+eW*e2+"A"+eW+","+eW+" 0 "+e0+",1 "+eW*eY+","+eW*e1+"L"+eX*eY+","+eX*e1+"A"+eX+","+eX+" 0 "+e0+",0 "+eX*eZ+","+eX*e2+"Z":"M"+eW*eZ+","+eW*e2+"A"+eW+","+eW+" 0 "+e0+",1 "+eW*eY+","+eW*e1+"L0,0Z")}eQ.innerRadius=function(eU){if(!arguments.length){return eT}eT=H(eU);return eQ};eQ.outerRadius=function(eU){if(!arguments.length){return eS}eS=H(eU);return eQ};eQ.startAngle=function(eU){if(!arguments.length){return eR}eR=H(eU);return eQ};eQ.endAngle=function(eU){if(!arguments.length){return e}e=H(eU);return eQ};eQ.centroid=function(){var eV=(eT.apply(this,arguments)+eS.apply(this,arguments))/2,eU=(eR.apply(this,arguments)+e.apply(this,arguments))/2+ex;return[Math.cos(eU)*eV,Math.sin(eU)*eV]};return eQ};var ex=-Math.PI/2,eC=2*Math.PI-0.000001;function aH(e){return e.innerRadius}function bp(e){return e.outerRadius}function dA(e){return e.startAngle}function T(e){return e.endAngle}function cN(eQ){var e=d4,eW=d2,eV=P,eS=b6,eU=eF,eT=0.7;function eR(e0){var e2=[],e5=[],e1=-1,eX=e0.length,e4,eZ=H(e),eY=H(eW);function e3(){e2.push("M",eU(eQ(e5),eT))}while(++e11){eY=eU[1];e=eV[eR];eR++;eZ+="C"+(eX[0]+eT[0])+","+(eX[1]+eT[1])+","+(e[0]-eY[0])+","+(e[1]-eY[1])+","+e[0]+","+e[1];for(var eQ=2;eQ9){eX=eS*3/Math.sqrt(eX);e[eR]=eX*eV;e[eR+1]=eX*eU}}}eR=-1;while(++eR<=eQ){eX=(eW[Math.min(eQ,eR+1)][0]-eW[Math.max(0,eR-1)][0])/(6*(1+e[eR]*e[eR]));eT.push([eX||0,e[eR]*eX||0])}return eT}function bF(e){return e.length<3?eF(e):e[0]+bt(e,V(e))}d3.svg.line.radial=function(){var e=cN(E);e.radius=e.x,delete e.x;e.angle=e.y,delete e.y;return e};function E(eS){var e,eR=-1,eU=eS.length,eT,eQ;while(++eRMath.PI)+",1 "+e1}function eS(e0,e2,eZ,e1){return"Q 0,0 "+e1}eU.radius=function(eZ){if(!arguments.length){return eV}eV=H(eZ);return eU};eU.source=function(eZ){if(!arguments.length){return e}e=H(eZ);return eU};eU.target=function(eZ){if(!arguments.length){return eW}eW=H(eZ);return eU};eU.startAngle=function(eZ){if(!arguments.length){return eX}eX=H(eZ);return eU};eU.endAngle=function(eZ){if(!arguments.length){return eT}eT=H(eZ);return eU};return eU};function U(e){return e.source}function ae(e){return e.target}function y(e){return e.radius}function dR(e){return e.startAngle}function ci(e){return e.endAngle}d3.svg.diagonal=function(){var eR=U,eS=ae,e=m;function eQ(eX,eU){var eY=eR.call(this,eX,eU),eV=eS.call(this,eX,eU),eT=(eY.y+eV.y)/2,eW=[eY,{x:eY.x,y:eT},{x:eV.x,y:eT},eV];eW=eW.map(e);return"M"+eW[0]+"C"+eW[1]+" "+eW[2]+" "+eW[3]}eQ.source=function(eT){if(!arguments.length){return eR}eR=H(eT);return eQ};eQ.target=function(eT){if(!arguments.length){return eS}eS=H(eT);return eQ};eQ.projection=function(eT){if(!arguments.length){return e}e=eT;return eQ};return eQ};function m(e){return[e.x,e.y]}d3.svg.diagonal.radial=function(){var eR=d3.svg.diagonal(),e=m,eQ=eR.projection;eR.projection=function(eS){return arguments.length?eQ(n(e=eS)):e};return eR};function n(e){return function(){var eS=e.apply(this,arguments),eR=eS[0],eQ=eS[1]+ex;return[eR*Math.cos(eQ),eR*Math.sin(eQ)]}}d3.svg.mouse=d3.mouse;d3.svg.touches=d3.touches;d3.svg.symbol=function(){var eQ=aj,e=cJ;function eR(eT,eS){return(eD.get(eQ.call(this,eT,eS))||a2)(e.call(this,eT,eS))}eR.type=function(eS){if(!arguments.length){return eQ}eQ=H(eS);return eR};eR.size=function(eS){if(!arguments.length){return e}e=H(eS);return eR};return eR};function cJ(){return 64}function aj(){return"circle"}function a2(e){var eQ=Math.sqrt(e/Math.PI);return"M0,"+eQ+"A"+eQ+","+eQ+" 0 1,1 0,"+(-eQ)+"A"+eQ+","+eQ+" 0 1,1 0,"+eQ+"Z"}var eD=d3.map({circle:a2,cross:function(e){var eQ=Math.sqrt(e/5)/2;return"M"+-3*eQ+","+-eQ+"H"+-eQ+"V"+-3*eQ+"H"+eQ+"V"+-eQ+"H"+3*eQ+"V"+eQ+"H"+eQ+"V"+3*eQ+"H"+-eQ+"V"+eQ+"H"+-3*eQ+"Z"},diamond:function(e){var eQ=Math.sqrt(e/(2*a6)),eR=eQ*a6;return"M0,"+-eQ+"L"+eR+",0 0,"+eQ+" "+-eR+",0Z"},square:function(e){var eQ=Math.sqrt(e)/2;return"M"+-eQ+","+-eQ+"L"+eQ+","+-eQ+" "+eQ+","+eQ+" "+-eQ+","+eQ+"Z"},"triangle-down":function(e){var eR=Math.sqrt(e/aZ),eQ=eR*aZ/2;return"M0,"+eQ+"L"+eR+","+-eQ+" "+-eR+","+-eQ+"Z"},"triangle-up":function(e){var eR=Math.sqrt(e/aZ),eQ=eR*aZ/2;return"M0,"+-eQ+"L"+eR+","+eQ+" "+-eR+","+eQ+"Z"}});d3.svg.symbolTypes=eD.keys();var aZ=Math.sqrt(3),a6=Math.tan(30*Math.PI/180);d3.svg.axis=function(){var eS=d3.scale.linear(),eV="bottom",eT=6,e=6,eQ=6,eZ=3,eY=[10],eU=null,eX,eW=0;function eR(e0){e0.each(function(){var fj=d3.select(this);var fk=eU==null?(eS.ticks?eS.ticks.apply(eS,eY):eS.domain()):eU,fd=eX==null?(eS.tickFormat?eS.tickFormat.apply(eS,eY):String):eX;var e5=bq(eS,fk,eW),e4=fj.selectAll(".minor").data(e5,String),fg=e4.enter().insert("line","g").attr("class","tick minor").style("opacity",0.000001),fe=d3.transition(e4.exit()).style("opacity",0.000001).remove(),e2=d3.transition(e4).style("opacity",1);var fi=fj.selectAll("g").data(fk,String),fa=fi.enter().insert("g","path").style("opacity",0.000001),fc=d3.transition(fi.exit()).style("opacity",0.000001).remove(),e6=d3.transition(fi).style("opacity",1),e1;var e9=am(eS),fb=fj.selectAll(".domain").data([0]),e3=fb.enter().append("path").attr("class","domain"),fl=d3.transition(fb);var ff=eS.copy(),fh=this.__chart__||ff;this.__chart__=ff;fa.append("line").attr("class","tick");fa.append("text");e6.select("text").text(fd);switch(eV){case"bottom":e1=cZ;fg.attr("y2",e);e2.attr("x2",0).attr("y2",e);fa.select("line").attr("y2",eT);fa.select("text").attr("y",Math.max(eT,0)+eZ);e6.select("line").attr("x2",0).attr("y2",eT);e6.select("text").attr("x",0).attr("y",Math.max(eT,0)+eZ).attr("dy",".71em").attr("text-anchor","middle");fl.attr("d","M"+e9[0]+","+eQ+"V0H"+e9[1]+"V"+eQ);break;case"top":e1=cZ;fg.attr("y2",-e);e2.attr("x2",0).attr("y2",-e);fa.select("line").attr("y2",-eT);fa.select("text").attr("y",-(Math.max(eT,0)+eZ));e6.select("line").attr("x2",0).attr("y2",-eT);e6.select("text").attr("x",0).attr("y",-(Math.max(eT,0)+eZ)).attr("dy","0em").attr("text-anchor","middle");fl.attr("d","M"+e9[0]+","+-eQ+"V0H"+e9[1]+"V"+-eQ);break;case"left":e1=cX;fg.attr("x2",-e);e2.attr("x2",-e).attr("y2",0);fa.select("line").attr("x2",-eT);fa.select("text").attr("x",-(Math.max(eT,0)+eZ));e6.select("line").attr("x2",-eT).attr("y2",0);e6.select("text").attr("x",-(Math.max(eT,0)+eZ)).attr("y",0).attr("dy",".32em").attr("text-anchor","end");fl.attr("d","M"+-eQ+","+e9[0]+"H0V"+e9[1]+"H"+-eQ);break;case"right":e1=cX;fg.attr("x2",e);e2.attr("x2",e).attr("y2",0);fa.select("line").attr("x2",eT);fa.select("text").attr("x",Math.max(eT,0)+eZ);e6.select("line").attr("x2",eT).attr("y2",0);e6.select("text").attr("x",Math.max(eT,0)+eZ).attr("y",0).attr("dy",".32em").attr("text-anchor","start");fl.attr("d","M"+eQ+","+e9[0]+"H0V"+e9[1]+"H"+eQ);break}if(eS.ticks){fa.call(e1,fh);e6.call(e1,ff);fc.call(e1,ff);fg.call(e1,fh);e2.call(e1,ff);fe.call(e1,ff)}else{var e8=ff.rangeBand()/2,e7=function(fm){return ff(fm)+e8};fa.call(e1,e7);e6.call(e1,e7)}})}eR.scale=function(e0){if(!arguments.length){return eS}eS=e0;return eR};eR.orient=function(e0){if(!arguments.length){return eV}eV=e0;return eR};eR.ticks=function(){if(!arguments.length){return eY}eY=arguments;return eR};eR.tickValues=function(e0){if(!arguments.length){return eU}eU=e0;return eR};eR.tickFormat=function(e0){if(!arguments.length){return eX}eX=e0;return eR};eR.tickSize=function(e0,e3,e1){if(!arguments.length){return eT}var e2=arguments.length-1;eT=+e0;e=e2>1?+e3:eT;eQ=e2>0?+arguments[e2]:eT;return eR};eR.tickPadding=function(e0){if(!arguments.length){return eZ}eZ=+e0;return eR};eR.tickSubdivide=function(e0){if(!arguments.length){return eW}eW=+e0;return eR};return eR};function cZ(eQ,e){eQ.attr("transform",function(eR){return"translate("+e(eR)+",0)"})}function cX(e,eQ){e.attr("transform",function(eR){return"translate(0,"+eQ(eR)+")"})}function bq(eR,eV,eQ){eW=[];if(eQ&&eV.length>1){var eY=ch(eR.domain()),eW,eT=-1,e=eV.length,eU=(eV[1]-eV[0])/++eQ,eS,eX;while(++eT0;){if((eX=+eV[eT]-eS*eU)>=eY[0]){eW.push(eX)}}}for(--eT,eS=0;++eSrect,.s>rect").attr("width",eZ[1][0]-eZ[0][0])}function eR(e0){e0.select(".extent").attr("y",eZ[0][1]);e0.selectAll(".extent,.e>rect,.w>rect").attr("height",eZ[1][1]-eZ[0][1])}function eX(){var fj=this,e1=d3.select(d3.event.target),e7=e.of(fj,arguments),fe=d3.select(fj),fg=e1.datum(),fc=!/^(n|s)$/.test(fg)&&eV,fa=!/^(e|w)$/.test(fg)&&eU,fb=e1.classed("extent"),fh,fi=e8(),e2;var e5=d3.select(window).on("mousemove.brush",e6).on("mouseup.brush",e0).on("touchmove.brush",e6).on("touchend.brush",e0).on("keydown.brush",e9).on("keyup.brush",e4);if(fb){fi[0]=eZ[0][0]-fi[0];fi[1]=eZ[0][1]-fi[1]}else{if(fg){var ff=+/w$/.test(fg),fd=+/^n/.test(fg);e2=[eZ[1-ff][0]-fi[0],eZ[1-fd][1]-fi[1]];fi[0]=eZ[ff][0];fi[1]=eZ[fd][1]}else{if(d3.event.altKey){fh=fi.slice()}}}fe.style("pointer-events","none").selectAll(".resize").style("display",null);d3.select("body").style("cursor",e1.style("cursor"));e7({type:"brushstart"});e6();ez();function e8(){var fk=d3.event.changedTouches;return fk?d3.touches(fj,fk)[0]:d3.mouse(fj)}function e9(){if(d3.event.keyCode==32){if(!fb){fh=null;fi[0]-=eZ[1][0];fi[1]-=eZ[1][1];fb=2}ez()}}function e4(){if(d3.event.keyCode==32&&fb==2){fi[0]+=eZ[1][0];fi[1]+=eZ[1][1];fb=0;ez()}}function e6(){var fk=e8(),fl=false;if(e2){fk[0]+=e2[0];fk[1]+=e2[1]}if(!fb){if(d3.event.altKey){if(!fh){fh=[(eZ[0][0]+eZ[1][0])/2,(eZ[0][1]+eZ[1][1])/2]}fi[0]=eZ[+(fk[0]0){eT=e8}else{eT=0}}else{if(e8>0){e3.start({type:"start",alpha:eT=e8});d3.timer(eQ.tick)}}return eQ};eQ.start=function(){var fc,fb,e9=e1.length,fa=eR.length,ff=eZ[0],fe=eZ[1],fh,e8;for(fc=0;fceW){eW=e}eV.push(e)}for(eS=0;eSeQ){eR=eS;eQ=e}}return eR}function bJ(e){return e.reduce(dd,0)}function dd(e,eQ){return e+eQ[1]}d3.layout.histogram=function(){var eT=true,eQ=Number,e=az,eR=es;function eS(eX,eY){var e3=[],e1=eX.map(eQ,this),eZ=e.call(this,e1,eY),e2=eR.call(this,eZ,e1,eY),e4,eY=-1,eU=e1.length,eV=e2.length-1,eW=eT?1:1/eU,e0;while(++eY0){eY=-1;while(++eY=eZ[0])&&(e0<=eZ[1])){e4=e3[d3.bisect(e2,e0,1,eV)-1];e4.y+=eW;e4.push(eX[eY])}}}return e3}eS.value=function(eU){if(!arguments.length){return eQ}eQ=eU;return eS};eS.range=function(eU){if(!arguments.length){return e}e=H(eU);return eS};eS.bins=function(eU){if(!arguments.length){return eR}eR=typeof eU==="number"?function(eV){return g(eV,eU)}:H(eU);return eS};eS.frequency=function(eU){if(!arguments.length){return eT}eT=!!eU;return eS};return eS};function es(eQ,e){return g(eQ,Math.ceil(Math.log(e.length)/Math.LN2+1))}function g(eS,eU){var eR=-1,eQ=+eS[0],e=(eS[1]-eQ)/eU,eT=[];while(++eR<=eU){eT[eR]=e*eR+eQ}return eT}function az(e){return[d3.min(e),d3.max(e)]}d3.layout.hierarchy=function(){var eR=bX,eQ=at,eT=eB;function eS(e0,eZ,eV){var e3=eQ.call(e,e0,eZ),eX=i?e0:{data:e0};eX.depth=eZ;eV.push(eX);if(e3&&(eW=e3.length)){var e1=-1,eW,e4=eX.children=[],e5=0,eY=eZ+1,e2;while(++e10.001}function F(eZ){var e4=Infinity,e8=-Infinity,e=Infinity,eT=-Infinity,eY=eZ.length,e7,e6,e5,e2,e1;function eR(e9){e4=Math.min(e9.x-e9.r,e4);e8=Math.max(e9.x+e9.r,e8);e=Math.min(e9.y-e9.r,e);eT=Math.max(e9.y+e9.r,eT)}eZ.forEach(ah);e7=eZ[0];e7.x=-e7.r;e7.y=0;eR(e7);if(eY>1){e6=eZ[1];e6.x=e6.r;e6.y=0;eR(e6);if(eY>2){e5=eZ[2];au(e7,e6,e5);eR(e5);aQ(e7,e5);e7._pack_prev=e5;aQ(e5,e6);e6=e7._pack_next;for(var e3=3;e30){eJ(dq(fc,fa,fd),fa,e7);ff+=e7;fe+=e7}fh+=fc._tree.mod;ff+=e9._tree.mod;fg+=fb._tree.mod;fe+=e8._tree.mod}if(fc&&!Z(e8)){e8._tree.thread=fc;e8._tree.mod+=fh-fe}if(e9&&!eH(fb)){fb._tree.thread=e9;fb._tree.mod+=ff-fg;fd=fa}}return fd}ew(e2,function(e7,e6){e7._tree={ancestor:e7,prelim:0,mod:0,change:0,shift:0,number:e6?e6._tree.number+1:0}});eZ(e2);e1(e2,-e2._tree.prelim);var eX=b4(e2,aJ),e5=b4(e2,bv),e4=b4(e2,x),eW=eX.x-eS(eX,e5)/2,eV=e5.x+eS(e5,eX)/2,e3=e4.depth||1;ew(e2,function(e6){e6.x=(e6.x-eW)/(eV-eW)*eR[0];e6.y=e6.depth/e3*eR[1];delete e6._tree});return eU}e.separation=function(eT){if(!arguments.length){return eS}eS=eT;return e};e.size=function(eT){if(!arguments.length){return eR}eR=eT;return e};return dK(e,eQ)};function bM(eQ,e){return eQ.parent==e.parent?1:2}function eH(eQ){var e=eQ.children;return e&&e.length?e[0]:eQ._tree.thread}function Z(eQ){var e=eQ.children,eR;return e&&(eR=e.length)?e[eR-1]:eQ._tree.thread}function b4(eR,eS){var eQ=eR.children;if(eQ&&(eT=eQ.length)){var eU,eT,e=-1;while(++e0){eR=eU}}}return eR}function bv(eQ,e){return eQ.x-e.x}function aJ(eQ,e){return e.x-eQ.x}function x(eQ,e){return eQ.depth-e.depth}function ew(eQ,eR){function e(eW,eV){var eU=eW.children;if(eU&&(eX=eU.length)){var eY,eT=null,eS=-1,eX;while(++eS=0){eT=eR[eQ]._tree;eT.prelim+=e;eT.mod+=e;e+=eT.shift+(eU+=eT.change)}}function eJ(eQ,eR,e){eQ=eQ._tree;eR=eR._tree;var eS=e/(eR.number-eQ.number);eQ.change+=eS;eR.change-=eS;eR.shift+=e;eR.prelim+=e;eR.mod+=e}function dq(e,eR,eQ){return e._tree.ancestor.parent==eR.parent?e._tree.ancestor:eQ}d3.layout.treemap=function(){var eW=d3.layout.hierarchy(),e0=Math.round,e2=[1,1],eX=null,e=cM,eY=false,eU,eV=0.5*(1+Math.sqrt(5));function eQ(e5,e3){var e4=-1,e8=e5.length,e7,e6;while(++e40){fc.push(e3=e9[e5-1]);fc.area+=e3.area;if((e6=eR(fc,fb))<=e8){e9.pop();e8=e6}else{fc.area-=fc.pop().area;eT(fc,fb,fa,false);fb=Math.min(fa.dx,fa.dy);fc.length=fc.area=0;e8=Infinity}}if(fc.length){eT(fc,fb,fa,true);fc.length=fc.area=0}e4.forEach(eS)}}function e1(e6){var e3=e6.children;if(e3&&e3.length){var e5=e(e6),e4=e3.slice(),e8,e7=[];eQ(e4,e5.dx*e5.dy/e6.value);e7.area=0;while(e8=e4.pop()){e7.push(e8);e7.area+=e8.area;if(e8.z!=null){eT(e7,e8.z?e5.dx:e5.dy,e5,!e4.length);e7.length=e7.area=0}}e3.forEach(e1)}}function eR(e8,e4){var e6=e8.area,e7,fa=0,e3=Infinity,e5=-1,e9=e8.length;while(++e5fa){fa=e7}}e6*=e6;e4*=e4;return e6?Math.max((e4*fa*eV)/e6,e6/(e4*e3*eV)):Infinity}function eT(fc,fb,e7,fa){var e5=-1,e4=fc.length,e8=e7.x,e6=e7.y,e9=fb?e0(fc.area/fb):0,e3;if(fb==e7.dx){if(fa||e9>e7.dy){e9=e7.dy}while(++e5e7.dx){e9=e7.dx}while(++e5=eW.length){return eS}if(eU){eU=false;return eQ}var e1=eX.lastIndex;if(eW.charCodeAt(e1)===34){var e2=e1;while(e2++50?eT:eV<-140?eS:eU<21?eR:e)(eW)}eQ.scale=function(eU){if(!arguments.length){return e.scale()}e.scale(eU);eT.scale(eU*0.6);eS.scale(eU);eR.scale(eU*1.5);return eQ.translate(e.translate())};eQ.translate=function(eV){if(!arguments.length){return e.translate()}var eU=e.scale()/1000,eX=eV[0],eW=eV[1];e.translate(eV);eT.translate([eX-400*eU,eW+170*eU]);eS.translate([eX-190*eU,eW+200*eU]);eR.translate([eX+580*eU,eW+430*eU]);return eQ};return eQ.scale(e.scale())};d3.geo.bonne=function(){var eU=200,eT=[480,250],e,eS,eR,eQ;function eV(eZ){var eW=eZ[0]*ct-e,e0=eZ[1]*ct-eS;if(eR){var eY=eQ+eR-e0,eX=eW*Math.cos(e0)/eY;eW=eY*Math.sin(eX);e0=eY*Math.cos(eX)-eQ}else{eW*=Math.cos(e0);e0*=-1}return[eU*eW+eT[0],eU*e0+eT[1]]}eV.invert=function(eY){var eW=(eY[0]-eT[0])/eU,e0=(eY[1]-eT[1])/eU;if(eR){var eZ=eQ+e0,eX=Math.sqrt(eW*eW+eZ*eZ);e0=eQ+eR-eX;eW=e+eX*Math.atan2(eW,eZ)/Math.cos(e0)}else{e0*=-1;eW/=Math.cos(e0)}return[eW/ct,e0/ct]};eV.parallel=function(eW){if(!arguments.length){return eR/ct}eQ=1/Math.tan(eR=eW*ct);return eV};eV.origin=function(eW){if(!arguments.length){return[e/ct,eS/ct]}e=eW[0]*ct;eS=eW[1]*ct;return eV};eV.scale=function(eW){if(!arguments.length){return eU}eU=+eW;return eV};eV.translate=function(eW){if(!arguments.length){return eT}eT=[+eW[0],+eW[1]];return eV};return eV.origin([0,0]).parallel(45)};d3.geo.equirectangular=function(){var eR=500,eQ=[480,250];function e(eT){var eS=eT[0]/360,eU=-eT[1]/360;return[eR*eS+eQ[0],eR*eU+eQ[1]]}e.invert=function(eT){var eS=(eT[0]-eQ[0])/eR,eU=(eT[1]-eQ[1])/eR;return[360*eS,-360*eU]};e.scale=function(eS){if(!arguments.length){return eR}eR=+eS;return e};e.translate=function(eS){if(!arguments.length){return eQ}eQ=[+eS[0],+eS[1]];return e};return e};d3.geo.mercator=function(){var eR=500,eQ=[480,250];function e(eT){var eS=eT[0]/360,eU=-(Math.log(Math.tan(Math.PI/4+eT[1]*ct/2))/ct)/360;return[eR*eS+eQ[0],eR*Math.max(-0.5,Math.min(0.5,eU))+eQ[1]]}e.invert=function(eT){var eS=(eT[0]-eQ[0])/eR,eU=(eT[1]-eQ[1])/eR;return[360*eS,2*Math.atan(Math.exp(-360*eU*ct))/ct-90]};e.scale=function(eS){if(!arguments.length){return eR}eR=+eS;return e};e.translate=function(eS){if(!arguments.length){return eQ}eQ=[+eS[0],+eS[1]];return e};return e};function da(eQ,e){return function(eR){return eR&&eQ.hasOwnProperty(eR.type)?eQ[eR.type](eR):e}}d3.geo.path=function(){var eV=4.5,eZ=A(eV),eU=d3.geo.albersUsa();function eY(e1,e0){if(typeof eV==="function"){eZ=A(eV.apply(this,arguments))}return eQ(e1)||null}function eW(e0){return eU(e0).join(",")}var eQ=da({FeatureCollection:function(e3){var e2=[],e1=e3.features,e0=-1,e4=e1.length;while(++e00){e4.push("M");while(++e20){fa.push("M");while(++e4eQ){eQ=eU}if(eVeS){eS=eV}});return[[eT,e],[eQ,eS]]};function bE(eQ,e){if(dU.hasOwnProperty(eQ.type)){dU[eQ.type](eQ,e)}}var dU={Feature:eA,FeatureCollection:dx,GeometryCollection:cQ,LineString:dC,MultiLineString:dQ,MultiPoint:dC,MultiPolygon:ey,Point:em,Polygon:bk};function eA(eQ,e){bE(eQ.geometry,e)}function dx(eS,eR){for(var e=eS.features,eQ=0,eT=e.length;eQ=(eQ*eQ+eZ*eZ)){e4[eV].index=-1}else{e4[e5].index=-1;e1=e4[eV].angle;e5=eV;e3=eU}}else{e1=e4[eV].angle;e5=eV;e3=eU}}e0.push(eW);for(eV=0,eU=0;eV<2;++eU){if(e4[eU].index!==-1){e0.push(e4[eU].index);eV++}}eR=e0.length;for(;eU0}d3.geom.polygon=function(e){e.area=function(){var eS=0,eT=e.length,eR=e[eT-1][0]*e[0][1],eQ=e[eT-1][1]*e[0][0];while(++eS=0){eZ=eT.ep.r;eY=eT.ep.l}else{eZ=eT.ep.l;eY=eT.ep.r}if(eT.a===1){eW=eZ?eZ.y:-1000000;eS=eT.c-eT.b*eW;eU=eY?eY.y:1000000;eR=eT.c-eT.b*eU}else{eS=eZ?eZ.x:-1000000;eW=eT.c-eT.a*eS;eR=eY?eY.x:1000000;eU=eT.c-eT.a*eR}var eX=[eS,eW],eV=[eR,eU];e[eT.region.l.index].push(eX,eV);e[eT.region.r.index].push(eX,eV)});return e.map(function(eT,eS){var eR=eQ[eS][0],eU=eQ[eS][1];eT.forEach(function(eV){eV.angle=Math.atan2(eV[0]-eR,eV[1]-eU)});return eT.sort(function(eW,eV){return eW.angle-eV.angle}).filter(function(eW,eV){return !eV||(eW.angle-eT[eV-1].angle>1e-10)})})};var bK={l:"r",r:"l"};function d0(eU,eS){var e8={list:eU.map(function(e,fa){return{index:fa,x:e[0],y:e[1]}}).sort(function(fa,e){return fa.ye.y?1:fa.xe.x?1:0}),bottomSite:null};var eR={list:[],leftEnd:null,rightEnd:null,init:function(){eR.leftEnd=eR.createHalfEdge(null,"l");eR.rightEnd=eR.createHalfEdge(null,"l");eR.leftEnd.r=eR.rightEnd;eR.rightEnd.l=eR.leftEnd;eR.list.unshift(eR.leftEnd,eR.rightEnd)},createHalfEdge:function(fa,e){return{edge:fa,side:e,vertex:null,l:null,r:null}},insert:function(fa,e){e.l=fa;e.r=fa.r;fa.r.l=e;fa.r=e},leftBound:function(fa){var e=eR.leftEnd;do{e=e.r}while(e!=eR.rightEnd&&e7.rightOf(e,fa));e=e.l;return e},del:function(e){e.l.r=e.r;e.r.l=e.l;e.edge=null},right:function(e){return e.r},left:function(e){return e.l},leftRegion:function(e){return e.edge==null?e8.bottomSite:e.edge.region[e.side]},rightRegion:function(e){return e.edge==null?e8.bottomSite:e.edge.region[bK[e.side]]}};var e7={bisect:function(fd,fb){var fc={region:{l:fd,r:fb},ep:{l:null,r:null}};var fa=fb.x-fd.x,e=fb.y-fd.y,ff=fa>0?fa:-fa,fe=e>0?e:-e;fc.c=fd.x*fa+fd.y*e+(fa*fa+e*e)*0.5;if(ff>fe){fc.a=1;fc.b=e/fa;fc.c/=fa}else{fc.b=1;fc.a=fa/e;fc.c/=e}return fc},intersect:function(fd,fc){var fi=fd.edge,fh=fc.edge;if(!fi||!fh||(fi.region.r==fh.region.r)){return null}var fg=(fi.a*fh.b)-(fi.b*fh.a);if(Math.abs(fg)<1e-10){return null}var fb=(fi.c*fh.b-fh.c*fi.b)/fg,fl=(fh.c*fi.a-fi.c*fh.a)/fg,fk=fi.region.r,ff=fh.region.r,fa,fe;if((fk.y=fe.region.r.x);if((fj&&(fa.side==="l"))||(!fj&&(fa.side==="r"))){return null}return{x:fb,y:fl}},rightOf:function(ff,fa){var fj=ff.edge,fd=fj.region.r,fm=(fa.x>fd.x);if(fm&&(ff.side==="l")){return 1}if(!fm&&(ff.side==="r")){return 0}if(fj.a===1){var fl=fa.y-fd.y,fb=fa.x-fd.x,fg=0,fk=0;if((!fm&&(fj.b<0))||(fm&&(fj.b>=0))){fk=fg=(fl>=fj.b*fb)}else{fk=((fa.x+fa.y*fj.b)>fj.c);if(fj.b<0){fk=!fk}if(!fk){fg=1}}if(!fg){var fn=fd.x-fj.region.l.x;fk=(fj.b*(fb*fb-fl*fl))<(fn*fl*(1+2*fb/fn+fj.b*fj.b));if(fj.b<0){fk=!fk}}}else{var fc=fj.c-fj.a*fa.x,fi=fa.y-fc,fh=fa.x-fd.x,fe=fc-fd.y;fk=(fi*fi)>(fh*fh+fe*fe)}return ff.side==="l"?fk:!fk},endPoint:function(fb,fa,e){fb.ep[fa]=e;if(!fb.ep[bK[fa]]){return}eS(fb)},distance:function(fc,fb){var fa=fc.x-fb.x,e=fc.y-fb.y;return Math.sqrt(fa*fa+e*e)}};var eQ={list:[],insert:function(fe,fa,ff){fe.vertex=fa;fe.ystar=fa.y+ff;for(var fb=0,fd=eQ.list,e=fd.length;fbfc.ystar||(fe.ystar==fc.ystar&&fa.x>fc.vertex.x)){continue}else{break}}fd.splice(fb,0,fe)},del:function(fc){for(var fb=0,fa=eQ.list,e=fa.length;fbe0.y){e6=eZ;eZ=e0;e0=e6;e3="r"}e4=e7.bisect(eZ,e0);eV=eR.createHalfEdge(e4,e3);eR.insert(eT,eV);e7.endPoint(e4,bK[e3],eX);e2=e7.intersect(eT,eV);if(e2){eQ.del(eT);eQ.insert(eT,e2,e7.distance(e2,eZ))}e2=e7.intersect(eV,e5);if(e2){eQ.insert(eV,e2,e7.distance(e2,eZ))}}else{break}}}for(e1=eR.right(eR.leftEnd);e1!=eR.rightEnd;e1=eR.right(e1)){eS(e1.edge)}}d3.geom.delaunay=function(eQ){var e=eQ.map(function(){return[]}),eR=[];d0(eQ,function(eS){e[eS.region.l.index].push(eQ[eS.region.r.index])});e.forEach(function(eX,eW){var eU=eQ[eW],eT=eU[0],eY=eU[1];eX.forEach(function(eZ){eZ.angle=Math.atan2(eZ[0]-eT,eZ[1]-eY)});eX.sort(function(e0,eZ){return e0.angle-eZ.angle});for(var eV=0,eS=eX.length-1;eVe){e=eQ.x}if(eQ.y>eW){eW=eQ.y}}var e1=e-eR,e0=eW-eX;if(e1>e0){eW=eX+e1}else{e=eR+e0}}}function eZ(e8,e7,e4,e6,e3,e5){if(isNaN(e7.x)||isNaN(e7.y)){return}if(e8.leaf){var e2=e8.point;if(e2){if((Math.abs(e2.x-e7.x)+Math.abs(e2.y-e7.y))<0.01){eU(e8,e7,e4,e6,e3,e5)}else{e8.point=null;eU(e8,e2,e4,e6,e3,e5);eU(e8,e7,e4,e6,e3,e5)}}else{e8.point=e7}}else{eU(e8,e7,e4,e6,e3,e5)}}function eU(e6,e4,e5,fb,e3,e9){var fa=(e5+e3)*0.5,e8=(fb+e9)*0.5,fc=e4.x>=fa,e2=e4.y>=e8,e7=(e2<<1)+fc;e6.leaf=false;e6=e6.nodes[e7]||(e6.nodes[e7]=dh());if(fc){e5=fa}else{e3=fa}if(e2){fb=e8}else{e9=e8}eZ(e6,e4,e5,fb,e3,e9)}var eV=dh();eV.add=function(e2){eZ(eV,e2,eR,eX,e,eW)};eV.visit=function(e2){bm(e2,eV,eR,eX,e,eW)};eY.forEach(eV.add);return eV};function dh(){return{leaf:true,nodes:[],point:null}}function bm(eT,eS,eQ,eX,e,eV){if(!eT(eS,eQ,eX,e,eV)){var eW=(eQ+e)*0.5,eU=(eX+eV)*0.5,eR=eS.nodes;if(eR[0]){bm(eT,eR[0],eQ,eX,eW,eU)}if(eR[1]){bm(eT,eR[1],eW,eX,e,eU)}if(eR[2]){bm(eT,eR[2],eQ,eU,eW,eV)}if(eR[3]){bm(eT,eR[3],eW,eU,e,eV)}}}function G(e){return{x:e[0],y:e[1]}}d3.time={};var dg=Date;function eb(){this._=new Date(arguments.length>1?Date.UTC.apply(this,arguments):arguments[0])}eb.prototype={getDate:function(){return this._.getUTCDate()},getDay:function(){return this._.getUTCDay()},getFullYear:function(){return this._.getUTCFullYear()},getHours:function(){return this._.getUTCHours()},getMilliseconds:function(){return this._.getUTCMilliseconds()},getMinutes:function(){return this._.getUTCMinutes()},getMonth:function(){return this._.getUTCMonth()},getSeconds:function(){return this._.getUTCSeconds()},getTime:function(){return this._.getTime()},getTimezoneOffset:function(){return 0},valueOf:function(){return this._.valueOf()},setDate:function(){ds.setUTCDate.apply(this._,arguments)},setDay:function(){ds.setUTCDay.apply(this._,arguments)},setFullYear:function(){ds.setUTCFullYear.apply(this._,arguments)},setHours:function(){ds.setUTCHours.apply(this._,arguments)},setMilliseconds:function(){ds.setUTCMilliseconds.apply(this._,arguments)},setMinutes:function(){ds.setUTCMinutes.apply(this._,arguments)},setMonth:function(){ds.setUTCMonth.apply(this._,arguments)},setSeconds:function(){ds.setUTCSeconds.apply(this._,arguments)},setTime:function(){ds.setTime.apply(this._,arguments)}};var ds=Date.prototype;d3.time.format=function(e){var eR=e.length;function eQ(eU){var eT=[],eV=-1,eS=0,eX,eW;while(++eV=eR){return -1}eW=eX.charCodeAt(eU++);if(eW==37){e=eE[eX.charAt(eU++)];if(!e||((eT=e(eS,eV,eT))<0)){return -1}}else{if(eW!=eV.charCodeAt(eT++)){return -1}}}return eT}var aM=d3.format("02d"),aL=d3.format("03d"),aK=d3.format("04d"),aI=d3.format("2d");var dN={a:function(e){return aa[e.getDay()].substring(0,3)},A:function(e){return aa[e.getDay()]},b:function(e){return a7[e.getMonth()].substring(0,3)},B:function(e){return a7[e.getMonth()]},c:d3.time.format("%a %b %e %H:%M:%S %Y"),d:function(e){return aM(e.getDate())},e:function(e){return aI(e.getDate())},H:function(e){return aM(e.getHours())},I:function(e){return aM(e.getHours()%12||12)},j:function(e){return aL(1+d3.time.dayOfYear(e))},L:function(e){return aL(e.getMilliseconds())},m:function(e){return aM(e.getMonth()+1)},M:function(e){return aM(e.getMinutes())},p:function(e){return e.getHours()>=12?"PM":"AM"},S:function(e){return aM(e.getSeconds())},U:function(e){return aM(d3.time.sundayOfYear(e))},w:function(e){return e.getDay()},W:function(e){return aM(d3.time.mondayOfYear(e))},x:d3.time.format("%m/%d/%y"),X:d3.time.format("%H:%M:%S"),y:function(e){return aM(e.getFullYear()%100)},Y:function(e){return aK(e.getFullYear()%10000)},Z:dp,"%":function(e){return"%"}};var eE={a:ea,A:bi,b:ao,B:cD,c:ck,d:aX,e:aX,H:ag,I:ag,L:d6,m:dW,M:bs,p:ev,S:b0,x:eO,X:dT,y:D,Y:aG};function ea(eQ,e,eR){return cW.test(e.substring(eR,eR+=3))?eR:-1}function bi(eQ,e,eR){aS.lastIndex=0;var eS=aS.exec(e.substring(eR,eR+10));return eS?eR+=eS[0].length:-1}var cW=/^(?:sun|mon|tue|wed|thu|fri|sat)/i,aS=/^(?:Sunday|Monday|Tuesday|Wednesday|Thursday|Friday|Saturday)/i,aa=["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"];function ao(eQ,e,eR){var eS=cH.get(e.substring(eR,eR+=3).toLowerCase());return eS==null?-1:(eQ.m=eS,eR)}var cH=d3.map({jan:0,feb:1,mar:2,apr:3,may:4,jun:5,jul:6,aug:7,sep:8,oct:9,nov:10,dec:11});function cD(eQ,e,eR){ed.lastIndex=0;var eS=ed.exec(e.substring(eR,eR+12));return eS?(eQ.m=c8.get(eS[0].toLowerCase()),eR+=eS[0].length):-1}var ed=/^(?:January|February|March|April|May|June|July|August|September|October|November|December)/ig;var c8=d3.map({january:0,february:1,march:2,april:3,may:4,june:5,july:6,august:7,september:8,october:9,november:10,december:11});var a7=["January","February","March","April","May","June","July","August","September","October","November","December"];function ck(eQ,e,eR){return b8(eQ,dN.c.toString(),e,eR)}function eO(eQ,e,eR){return b8(eQ,dN.x.toString(),e,eR)}function dT(eQ,e,eR){return b8(eQ,dN.X.toString(),e,eR)}function aG(eQ,e,eR){dt.lastIndex=0;var eS=dt.exec(e.substring(eR,eR+4));return eS?(eQ.y=+eS[0],eR+=eS[0].length):-1}function D(eQ,e,eR){dt.lastIndex=0;var eS=dt.exec(e.substring(eR,eR+2));return eS?(eQ.y=aD()+ +eS[0],eR+=eS[0].length):-1}function aD(){return ~~(new Date().getFullYear()/1000)*1000}function dW(eQ,e,eR){dt.lastIndex=0;var eS=dt.exec(e.substring(eR,eR+2));return eS?(eQ.m=eS[0]-1,eR+=eS[0].length):-1}function aX(eQ,e,eR){dt.lastIndex=0;var eS=dt.exec(e.substring(eR,eR+2));return eS?(eQ.d=+eS[0],eR+=eS[0].length):-1}function ag(eQ,e,eR){dt.lastIndex=0;var eS=dt.exec(e.substring(eR,eR+2));return eS?(eQ.H=+eS[0],eR+=eS[0].length):-1}function bs(eQ,e,eR){dt.lastIndex=0;var eS=dt.exec(e.substring(eR,eR+2));return eS?(eQ.M=+eS[0],eR+=eS[0].length):-1}function b0(eQ,e,eR){dt.lastIndex=0;var eS=dt.exec(e.substring(eR,eR+2));return eS?(eQ.S=+eS[0],eR+=eS[0].length):-1}function d6(eQ,e,eR){dt.lastIndex=0;var eS=dt.exec(e.substring(eR,eR+3));return eS?(eQ.L=+eS[0],eR+=eS[0].length):-1}var dt=/\s*\d+/;function ev(eQ,e,eR){var eS=c.get(e.substring(eR,eR+=2).toLowerCase());return eS==null?-1:(eQ.p=eS,eR)}var c=d3.map({am:0,pm:1});function dp(eT){var eS=eT.getTimezoneOffset(),eR=eS>0?"-":"+",eQ=~~(Math.abs(eS)/60),e=Math.abs(eS)%60;return eR+aM(eQ)+aM(e)}d3.time.format.utc=function(eQ){var e=d3.time.format(eQ);function eR(eS){try{dg=eb;var eT=new dg();eT._=eS;return e(eT)}finally{dg=Date}}eR.parse=function(eT){try{dg=eb;var eS=e.parse(eT);return eS&&eS._}finally{dg=Date}};eR.toString=e.toString;return eR};var bW=d3.time.format.utc("%Y-%m-%dT%H:%M:%S.%LZ");d3.time.format.iso=Date.prototype.toISOString?bL:bW;function bL(e){return e.toISOString()}bL.parse=function(e){return new Date(e)};bL.toString=bW.toString;function bu(eU,e,eQ){function eX(eY){var e0=eU(eY),eZ=eS(e0,1);return eY-e01){while(e1e?1:g1>=e?0:NaN};gg.descending=function(g1,e){return eg1?1:e>=g1?0:NaN};gg.min=function(g5,g3){var g2=-1,g4=g5.length,g1,e;if(arguments.length===1){while(++g2e){g1=e}}}else{while(++g2e){g1=e}}}return g1};gg.max=function(g5,g3){var g2=-1,g4=g5.length,g1,e;if(arguments.length===1){while(++g2g1){g1=e}}}else{while(++g2g1){g1=e}}}return g1};gg.extent=function(g6,g3){var g2=-1,g5=g6.length,g1,e,g4;if(arguments.length===1){while(++g2e){g1=e}if(g4e){g1=e}if(g41){g1=g1.map(e)}g1=g1.filter(gk);return g1.length?gg.quantile(g1.sort(gg.ascending),0.5):undefined};gg.bisector=function(e){return{left:function(g2,g1,g5,g4){if(arguments.length<3){g5=0}if(arguments.length<4){g4=g2.length}while(g5>>1;if(e.call(g2,g2[g3],g3)>>1;if(g1g4){g1.push(g2/e)}}else{while((g2=g6+g5*++g3)=g2.length){return g4?g4.call(g3,hf):g1?hf.sort(g1):hf}var he=-1,g7=hf.length,hh=g2[hd++],hc,hb,ha,g9=new aw(),hg;while(++he=g2.length){return g8}var ha=[],g7=g6[g9++];g8.forEach(function(hb,hc){ha.push({key:hb,values:e(hc,g9)})});return g7?ha.sort(function(hc,hb){return g7(hc.key,hb.key)}):ha}g3.map=function(g8,g7){return g5(g7,g8,0)};g3.entries=function(g7){return e(g5(gg.map,g7,0),0)};g3.key=function(g7){g2.push(g7);return g3};g3.sortKeys=function(g7){g6[g2.length-1]=g7;return g3};g3.sortValues=function(g7){g1=g7;return g3};g3.rollup=function(g7){g4=g7;return g3};return g3};gg.set=function(g2){var g1=new cO();if(g2){for(var e=0;e=0){e=g2.substring(g1+1);g2=g2.substring(0,g1)}if(g2){return arguments.length<2?this[g2].on(e):this[g2].on(e,g3)}if(arguments.length===2){if(g3==null){for(g2 in this){if(this.hasOwnProperty(g2)){this[g2].on(e,null)}}}return this}};function eq(e){var g2=[],g1=new aw();function g3(){var g6=g2,g5=-1,g7=g6.length,g4;while(++g5=0){g2=e.substring(0,g1);e=e.substring(g1+1)}return dr.hasOwnProperty(g2)?{space:dr[g2],local:e}:e}};df.attr=function(e,g2){if(arguments.length<2){if(typeof e==="string"){var g1=this.node();e=gg.ns.qualify(e);return e.local?g1.getAttributeNS(e.space,e.local):g1.getAttribute(e)}for(g2 in e){this.each(a8(g2,e[g2]))}return this}return this.each(a8(e,g2))};function a8(g2,g6){g2=gg.ns.qualify(g2);function g1(){this.removeAttribute(g2)}function g7(){this.removeAttributeNS(g2.space,g2.local)}function g5(){this.setAttribute(g2,g6)}function g4(){this.setAttributeNS(g2.space,g2.local,g6)}function e(){var g8=g6.apply(this,arguments);if(g8==null){this.removeAttribute(g2)}else{this.setAttribute(g2,g8)}}function g3(){var g8=g6.apply(this,arguments);if(g8==null){this.removeAttributeNS(g2.space,g2.local)}else{this.setAttributeNS(g2.space,g2.local,g8)}}return g6==null?g2.local?g7:g1:typeof g6==="function"?g2.local?g3:e:g2.local?g4:g5}function ci(e){return e.trim().replace(/\s+/g," ")}gg.requote=function(e){return e.replace(cH,"\\$&")};var cH=/[\\\^\$\*\+\?\|\[\]\(\)\.\{\}]/g;df.classed=function(e,g3){if(arguments.length<2){if(typeof e==="string"){var g2=this.node(),g4=(e=e.trim().split(/^|\s+/g)).length,g1=-1;if(g3=g2.classList){while(++g1=0;){if(g4=g5[g2]){if(g3&&g3!==g4.nextSibling){g3.parentNode.insertBefore(g4,g3)}g3=g4}}}return this};df.sort=function(g1){g1=fL.apply(this,arguments);for(var g2=-1,e=this.length;++g20){g8=g8.substring(0,g5)}var g2=bm.get(g8);if(g2){g8=g2,g1=ag}function g4(){var ha=this[e];if(ha){this.removeEventListener(g8,ha,ha.$);delete this[e]}}function g7(){var ha=g1(g3,c6(arguments));g4.call(this);this.addEventListener(g8,this[e]=ha,ha.$=g9);ha._=g3}function g6(){var hd=new RegExp("^__on([^.]+)"+gg.requote(g8)+"$"),hc;for(var hb in this){if(hc=hb.match(hd)){var ha=this[hb];this.removeEventListener(hc[1],ha,ha.$);delete this[hb]}}}return g5?g3?g7:g4:g3?av:g6}var bm=gg.map({mouseenter:"mouseover",mouseleave:"mouseout"});bm.forEach(function(e){if("on"+e in bp){bm.remove(e)}});function ey(g1,e){return function(g2){var g3=gg.event;gg.event=g2;e[0]=this.__data__;try{g1.apply(this,e)}finally{gg.event=g3}}}function ag(g2,e){var g1=ey(g2,e);return function(g5){var g4=this,g3=g5.relatedTarget;if(!g3||g3!==g4&&!(g3.compareDocumentPosition(g4)&8)){g1.call(g4,g5)}}}df.each=function(e){return cj(this,function(g3,g2,g1){e.call(g3,g3.__data__,g2,g1)})};function cj(g1,g7){for(var g2=0,e=g1.length;g21?1:g4;e=e<0?0:e>1?1:e;g2=e<=0.5?e*(1+g4):e+g4-e*g4;g3=2*e-g2;function g1(g7){if(g7>360){g7-=360}else{if(g7<0){g7+=360}}if(g7<60){return g3+(g2-g3)*g7/60}if(g7<180){return g2}if(g7<240){return g3+(g2-g3)*(240-g7)/60}return g3}function g6(g7){return Math.round(g1(g7)*255)}return dy(g6(g5+120),g6(g5),g6(g5-120))}var fj=Math.PI,fu=0.000001,F=fj/180,gu=180/fj;function bK(e){return e>0?1:e<0?-1:0}function X(e){return Math.acos(Math.max(-1,Math.min(1,e)))}function bP(e){return e>1?fj/2:e<-1?-fj/2:Math.asin(e)}function dd(e){return(Math.exp(e)-Math.exp(-e))/2}function bi(e){return(Math.exp(e)+Math.exp(-e))/2}function ar(e){return(e=Math.sin(e/2))*e}gg.hcl=function(g1,g2,e){return arguments.length===1?g1 instanceof b4?bg(g1.h,g1.c,g1.l):g1 instanceof cG?fJ(g1.l,g1.a,g1.b):fJ((g1=di((g1=gg.rgb(g1)).r,g1.g,g1.b)).l,g1.a,g1.b):bg(+g1,+g2,+e)};function bg(g1,g2,e){return new b4(g1,g2,e)}function b4(g1,g2,e){this.h=g1;this.c=g2;this.l=e}var ga=b4.prototype=new D();ga.brighter=function(e){return bg(this.h,this.c,Math.min(100,this.l+a7*(arguments.length?e:1)))};ga.darker=function(e){return bg(this.h,this.c,Math.max(0,this.l-a7*(arguments.length?e:1)))};ga.rgb=function(){return bh(this.h,this.c,this.l).rgb()};function bh(g1,g2,e){return bX(e,Math.cos(g1*=F)*g2,Math.sin(g1)*g2)}gg.lab=function(g2,g1,e){return arguments.length===1?g2 instanceof cG?bX(g2.l,g2.a,g2.b):g2 instanceof b4?bh(g2.l,g2.c,g2.h):di((g2=gg.rgb(g2)).r,g2.g,g2.b):bX(+g2,+g1,+e)};function bX(g2,g1,e){return new cG(g2,g1,e)}function cG(g2,g1,e){this.l=g2;this.a=g1;this.b=e}var a7=18;var a0=0.95047,aY=1,aV=1.08883;var fE=cG.prototype=new D();fE.brighter=function(e){return bX(Math.min(100,this.l+a7*(arguments.length?e:1)),this.a,this.b)};fE.darker=function(e){return bX(Math.max(0,this.l-a7*(arguments.length?e:1)),this.a,this.b)};fE.rgb=function(){return aq(this.l,this.a,this.b)};function aq(g3,g2,g1){var g5=(g3+16)/116,e=g5+g2/500,g4=g5-g1/200;e=fY(e)*a0;g5=fY(g5)*aY;g4=fY(g4)*aV;return dy(d(3.2404542*e-1.5371385*g5-0.4985314*g4),d(-0.969266*e+1.8760108*g5+0.041556*g4),d(0.0556434*e-0.2040259*g5+1.0572252*g4))}function fJ(g2,g1,e){return bg(Math.atan2(e,g1)*gu,Math.sqrt(g1*g1+e*e),g2)}function fY(e){return e>0.206893034?e*e*e:(e-4/29)/7.787037}function fx(e){return e>0.008856?Math.pow(e,1/3):7.787037*e+4/29}function d(e){return Math.round(255*(e<=0.00304?12.92*e:1.055*Math.pow(e,1/2.4)-0.055))}gg.rgb=function(g2,g1,e){return arguments.length===1?g2 instanceof ee?dy(g2.r,g2.g,g2.b):aK(""+g2,dy,aZ):dy(~~g2,~~g1,~~e)};function dy(g2,g1,e){return new ee(g2,g1,e)}function ee(g2,g1,e){this.r=g2;this.g=g1;this.b=e}var dM=ee.prototype=new D();dM.brighter=function(g1){g1=Math.pow(0.7,arguments.length?g1:1);var g4=this.r,g3=this.g,e=this.b,g2=30;if(!g4&&!g3&&!e){return dy(g2,g2,g2)}if(g4&&g43&&g1()};function g1(){var g9=g5.status;!g9&&g5.responseText||g9>=200&&g9<300||g9===304?g6.load.call(g8,g4.call(g8,g5)):g6.error.call(g8,g5)}g5.onprogress=function(g9){var ha=gg.event;gg.event=g9;try{g6.progress.call(g8,g5)}finally{gg.event=ha}};g8.header=function(g9,ha){g9=(g9+"").toLowerCase();if(arguments.length<2){return g3[g9]}if(ha==null){delete g3[g9]}else{g3[g9]=ha+""}return g8};g8.mimeType=function(g9){if(!arguments.length){return g2}g2=g9==null?null:g9+"";return g8};g8.response=function(g9){g4=g9;return g8};["get","post"].forEach(function(g9){g8[g9]=function(){return g8.send.apply(g8,[g9].concat(c6(arguments)))}});g8.send=function(hc,ha,hb){if(arguments.length===2&&typeof ha==="function"){hb=ha,ha=null}g5.open(hc,e,true);if(g2!=null&&!("accept" in g3)){g3.accept=g2+",*/*"}if(g5.setRequestHeader){for(var g9 in g3){g5.setRequestHeader(g9,g3[g9])}}if(g2!=null&&g5.overrideMimeType){g5.overrideMimeType(g2)}if(hb!=null){g8.on("error",hb).on("load",function(hd){hb(null,hd)})}g5.send(ha==null?null:ha);return g8};g8.abort=function(){g5.abort();return g8};gg.rebind(g8,g6,"on");if(arguments.length===2&&typeof g2==="function"){g7=g2,g2=null}return g7==null?g8:g8.get(dm(g7))};function dm(e){return e.length===1?function(g1,g2){e(g1==null?g2:null)}:e}function dK(e,g1){var g3=new RegExp('["'+e+"\n]"),g5=e.charCodeAt(0);function g7(g9,hb,hc){if(arguments.length<3){hc=hb,hb=null}var ha=gg.xhr(g9,g1,hc);ha.row=function(hd){return arguments.length?ha.response((hb=hd)==null?g4:g2(hd)):hb};return ha.row(hb)}function g4(g9){return g7.parse(g9.responseText)}function g2(g9){return function(ha){return g7.parse(ha.responseText,g9)}}g7.parse=function(hb,g9){var ha;return g7.parseRows(hb,function(he,hd){if(ha){return ha(he,hd-1)}var hc=new Function("d","return {"+he.map(function(hf,hg){return JSON.stringify(hf)+": d["+hg+"]"}).join(",")+"}");ha=g9?function(hg,hf){return g9(hc(hg),hf)}:hc})};g7.parseRows=function(hi,hd){var ha={},hc={},hk=[],he=hi.length,hh=0,g9=0,hj,hf;function hb(){if(hh>=he){return hc}if(hf){return hf=false,ha}var hm=hh;if(hi.charCodeAt(hm)===34){var hn=hm;while(hn++=g3.delay){g3.flush=g3.callback(e)}g3=g3.next}var g1=ec()-g2;if(g1>24){if(isFinite(g1)){clearTimeout(ep);ep=setTimeout(gm,g1)}b0=0}else{b0=1;gl(gm)}}gg.timer.flush=function(){var e,g1=Date.now(),g2=bL;while(g2){e=g1-g2.then;if(!g2.delay){g2.flush=g2.callback(e)}g2=g2.next}ec()};function ec(){var g1=null,e=bL,g2=Infinity;while(e){if(e.flush){delete T[e.callback.id];e=g1?g1.next=e.next:bL=e.next}else{g2=Math.min(g2,e.then+e.delay);e=(g1=e).next}}return g2}var gl=eH.requestAnimationFrame||eH.webkitRequestAnimationFrame||eH.mozRequestAnimationFrame||eH.oRequestAnimationFrame||eH.msRequestAnimationFrame||function(e){setTimeout(e,17)};var fV=".",d6=",",j=[3,3];var b5=["y","z","a","f","p","n","µ","m","","k","M","G","T","P","E","Z","Y"].map(c5);gg.formatPrefix=function(g2,e){var g1=0;if(g2){if(g2<0){g2*=-1}if(e){g2=gg.round(g2,ds(g2,e))}g1=1+Math.floor(1e-12+Math.log(g2)/Math.LN10);g1=Math.max(-24,Math.min(24,Math.floor((g1<=0?g1+1:g1-1)/3)*3))}return b5[8+g1/3]};function c5(g2,g1){var e=Math.pow(10,Math.abs(8-g1)*3);return{scale:g1>8?function(g3){return g3/e}:function(g3){return g3*e},symbol:g2}}gg.round=function(e,g1){return g1?Math.round(e*(g1=Math.pow(10,g1)))/g1:Math.round(e)};gg.format=function(hb){var g8=b9.exec(hb),hd=g8[1]||" ",g9=g8[2]||">",g2=g8[3]||"",e=g8[4]||"",g4=g8[5],g1=+g8[6],he=g8[7],g7=g8[8],ha=g8[9],g3=1,hc="",g6=false;if(g7){g7=+g7.substring(1)}if(g4||hd==="0"&&g9==="="){g4=hd="0";g9="=";if(he){g1-=Math.floor((g1-1)/4)}}switch(ha){case"n":he=true;ha="g";break;case"%":g3=100;hc="%";ha="f";break;case"p":g3=100;hc="%";ha="r";break;case"b":case"o":case"x":case"X":if(e){e="0"+ha.toLowerCase()}case"c":case"d":g6=true;g7=0;break;case"s":g3=-1;ha="r";break}if(e==="#"){e=""}if(ha=="r"&&!g7){ha="g"}if(g7!=null){if(ha=="g"){g7=Math.max(1,Math.min(21,g7))}else{if(ha=="e"||ha=="f"){g7=Math.max(0,Math.min(20,g7))}}}ha=v.get(ha)||a3;var g5=g4&&he;return function(hi){if(g6&&hi%1){return""}var hf=hi<0||hi===0&&1/hi<0?(hi=-hi,"-"):g2;if(g3<0){var hh=gg.formatPrefix(hi,g7);hi=hh.scale(hi);hc=hh.symbol}else{hi*=g3}hi=ha(hi,g7);if(!g4&&he){hi=cK(hi)}var hg=e.length+hi.length+(g5?0:hf.length),hj=hg"?hj+hf+hi:g9==="^"?hj.substring(0,hg>>=1)+hf+hi+hj.substring(hg):hf+(g5?hi:hj+hi))+hc}};var b9=/(?:([^{])?([<>=^]))?([+\- ])?(#)?(0)?(\d+)?(,)?(\.-?\d+)?([a-z%])?/i;var v=gg.map({b:function(e){return e.toString(2)},c:function(e){return String.fromCharCode(e)},o:function(e){return e.toString(8)},x:function(e){return e.toString(16)},X:function(e){return e.toString(16).toUpperCase()},g:function(e,g1){return e.toPrecision(g1)},e:function(e,g1){return e.toExponential(g1)},f:function(e,g1){return e.toFixed(g1)},r:function(e,g1){return(e=gg.round(e,ds(e,g1))).toFixed(Math.max(0,Math.min(20,ds(e*(1+1e-15),g1))))}});function ds(e,g1){return g1-(e?Math.ceil(Math.log(e)/Math.LN10):1)}function a3(e){return e+""}var cK=fs;if(j){var bJ=j.length;cK=function(g5){var g2=g5.lastIndexOf("."),g4=g2>=0?"."+g5.substring(g2+1):(g2=g5.length,""),g1=[],e=0,g3=j[0];while(g2>0&&g3>0){g1.push(g5.substring(g2-=g3,g2+g3));g3=j[e=(e+1)%bJ]}return g1.reverse().join(d6||"")+g4}}gg.geo={};gg.geo.stream=function(e,g1){if(x.hasOwnProperty(e.type)){x[e.type](e,g1)}else{aF(e,g1)}};function aF(g1,e){if(dR.hasOwnProperty(g1.type)){dR[g1.type](g1,e)}}var x={Feature:function(e,g1){aF(e.geometry,g1)},FeatureCollection:function(e,g3){var g2=e.features,g1=-1,g4=g2.length;while(++g1g1){g1=g8}if(g9g4){g4=g9}}function g7(){g3.point=g3.lineEnd=av}return function(g8){g4=g1=-(g2=g6=Infinity);gg.geo.stream(g8,g5(g3));return[[g2,g6],[g1,g4]]}}gg.geo.centroid=function(g1){c3=en=em=ek=ej=0;gg.geo.stream(g1,fo);var e;if(en&&Math.abs(e=Math.sqrt(em*em+ek*ek+ej*ej))>fu){return[Math.atan2(ek,em)*gu,Math.asin(Math.max(-1,Math.min(1,ej/e)))*gu]}};var c3,en,em,ek,ej;var fo={sphere:function(){if(c3<2){c3=2;en=em=ek=ej=0}},point:bM,lineStart:es,lineEnd:at,polygonStart:function(){if(c3<2){c3=2;en=em=ek=ej=0}fo.lineStart=a6},polygonEnd:function(){fo.lineStart=es}};function bM(g1,e){if(c3){return}++en;g1*=F;var g2=Math.cos(e*=F);em+=(g2*Math.cos(g1)-em)/en;ek+=(g2*Math.sin(g1)-ek)/en;ej+=(Math.sin(e)-ej)/en}function a6(){var g1,e;c3=1;es();c3=2;var g2=fo.point;fo.point=function(g4,g3){g2(g1=g4,e=g3)};fo.lineEnd=function(){fo.point(g1,e);at();fo.lineEnd=at}}function es(){var e,g1,g3;if(c3>1){return}if(c3<1){c3=1;en=em=ek=ej=0}fo.point=function(g5,g4){g5*=F;var g6=Math.cos(g4*=F);e=g6*Math.cos(g5);g1=g6*Math.sin(g5);g3=Math.sin(g4);fo.point=g2};function g2(g7,g6){g7*=F;var g8=Math.cos(g6*=F),g4=g8*Math.cos(g7),ha=g8*Math.sin(g7),g9=Math.sin(g6),g5=Math.atan2(Math.sqrt((g5=g1*g9-g3*ha)*g5+(g5=g3*g4-e*g9)*g5+(g5=e*ha-g1*g4)*g5),e*g4+g1*ha+g3*g9);en+=g5;em+=g5*(e+(e=g4));ek+=g5*(g1+(g1=ha));ej+=g5*(g3+(g3=g9))}}function at(){fo.point=bM}function ak(e){var g2=e[0],g1=e[1],g3=Math.cos(g1);return[g3*Math.cos(g2),g3*Math.sin(g2),Math.sin(g1)]}function an(g1,e){return g1[0]*e[0]+g1[1]*e[1]+g1[2]*e[2]}function cy(g1,e){return[g1[1]*e[2]-g1[2]*e[1],g1[2]*e[0]-g1[0]*e[2],g1[0]*e[1]-g1[1]*e[0]]}function cc(g1,e){g1[0]+=e[0];g1[1]+=e[1];g1[2]+=e[2]}function fm(e,g1){return[e[0]*g1,e[1]*g1,e[2]*g1]}function et(g1){var e=Math.sqrt(g1[0]*g1[0]+g1[1]*g1[1]+g1[2]*g1[2]);g1[0]/=e;g1[1]/=e;g1[2]/=e}function aa(){return true}function gY(e){return[Math.atan2(e[1],e[0]),Math.asin(Math.max(-1,Math.min(1,e[2])))]}function dS(g1,e){return Math.abs(g1[0]-e[0])=0;){g4.point((hd=he[g7])[0],hd[1])}}else{hb(g9.point,g9.prev.point,-1,g4)}g9=g9.prev}g9=g9.other;he=g9.points}while(!g9.visited);g4.lineEnd()}}function gz(g4){if(!(g3=g4.length)){return}var g3,g2=0,g1=g4[0],e;while(++g21&&hm&2){ho.push(ho.pop().concat(ho.shift()))}hc.push(ho.filter(dO))}return g7}}function dO(e){return e.length>1}function M(){var g1=[],e;return{lineStart:function(){g1.push(e=[])},point:function(g3,g2){e.push([g3,g2])},lineEnd:av,buffer:function(){var g2=g1;g1=[];e=null;return g2},rejoin:function(){if(g1.length>1){g1.push(g1.pop().concat(g1.shift()))}}}}function eb(g6,hc){if(!(g4=g6.length)){return 0}var g4,g7=0,g2=0,g1=g6[0],g8=g1[0],hd=g1[1],g5=Math.cos(hd),g3=Math.atan2(hc*Math.sin(g8)*g5,Math.sin(hd)),hb=1-hc*Math.cos(g8)*g5,e=g3,ha,g9;while(++g72){g2+=4*(ha-g3)}}else{if(Math.abs(hb-2)0?fj:-fj,g7=Math.abs(g6-g1);if(Math.abs(g7-fj)0?fj/2:-fj/2);g4.point(g3,e);g4.lineEnd();g4.lineStart();g4.point(g8,e);g4.point(g6,e);g2=0}else{if(g3!==g8&&g7>=fj){if(Math.abs(g1-g3)fu?Math.atan((Math.sin(g4)*(e=Math.cos(g2))*Math.sin(g3)-Math.sin(g2)*(g1=Math.cos(g4))*Math.sin(g5))/(g1*e*g6)):(g4+g2)/2}function cJ(g5,g4,g3,g2){var e;if(g5==null){e=g3*fj/2;g2.point(-fj,e);g2.point(0,e);g2.point(fj,e);g2.point(fj,0);g2.point(fj,-e);g2.point(0,-e);g2.point(-fj,-e);g2.point(-fj,0);g2.point(-fj,e)}else{if(Math.abs(g5[0]-g4[0])>fu){var g1=(g5[0]0,g6=Math.abs(g5)>fu,g7=bE(g4,6*F);return du(g2,g8,g7);function g2(ha,g9){return Math.cos(ha)*Math.cos(g9)>g5}function g8(he){var hd,hc,g9,hb,ha;return{lineStart:function(){hb=g9=false;ha=1},point:function(hg,hf){var hk=[hg,hf],hj,hh=g2(hg,hf),hl=g3?hh?0:e(hg,hf):hh?e(hg+(hg<0?fj:-fj),hf):0;if(!hd&&(hb=g9=hh)){he.lineStart()}if(hh!==g9){hj=g1(hd,hk);if(dS(hd,hj)||dS(hk,hj)){hk[0]+=fu;hk[1]+=fu;hh=g2(hk[0],hk[1])}}if(hh!==g9){ha=0;if(hh){he.lineStart();hj=g1(hk,hd);he.point(hj[0],hj[1])}else{hj=g1(hd,hk);he.point(hj[0],hj[1]);he.lineEnd()}hd=hj}else{if(g6&&hd&&g3^hh){var hi;if(!(hl&hc)&&(hi=g1(hk,hd,true))){ha=0;if(g3){he.lineStart();he.point(hi[0][0],hi[0][1]);he.point(hi[1][0],hi[1][1]);he.lineEnd()}else{he.point(hi[1][0],hi[1][1]);he.lineEnd();he.lineStart();he.point(hi[0][0],hi[0][1])}}}}if(hh&&(!hd||!dS(hd,hk))){he.point(hk[0],hk[1])}hd=hk,g9=hh,hc=hl},lineEnd:function(){if(g9){he.lineEnd()}hd=null},clean:function(){return ha|(hb&&g9)<<1}}}function g1(hA,hz,hd){var hb=ak(hA),g9=ak(hz);var hi=[1,0,0],hf=cy(hb,g9),hl=an(hf,hf),hy=hf[0],hr=hl-hy*hy;if(!hr){return !hd&&hA}var hu=g5*hl/hr,ht=-g5*hy/hr,hB=cy(hi,hf),hs=fm(hi,hu),hq=fm(hf,ht);cc(hs,hq);var hn=hB,hm=an(hs,hn),he=an(hn,hn),hk=hm*hm-he*(an(hs,hs)-1);if(hk<0){return}var ho=Math.sqrt(hk),hp=fm(hn,(-hm-ho)/he);cc(hp,hs);hp=gY(hp);if(!hd){return hp}var hc=hA[0],ha=hz[0],hw=hA[1],hv=hz[1],hh;if(ha0^hp[1]<(Math.abs(hp[0]-hc)fj^(hc<=hp[0]&&hp[0]<=ha)){var hj=fm(hn,(-hm+ho)/he);cc(hj,hs);return[hp,gY(hj)]}}function e(ha,g9){var hc=g3?g4:fj-g4,hb=0;if(ha<-hc){hb|=1}else{if(ha>hc){hb|=2}}if(g9<-hc){hb|=4}else{if(g9>hc){hb|=8}}return hb}}var gQ=1000000000;function g(g1,g4,e,g2){return function(hd){var g9=hd,hj=M(),hq,hk,hh;var hu={point:hp,lineStart:hm,lineEnd:hr,polygonStart:function(){hd=hj;hq=[];hk=[]},polygonEnd:function(){hd=g9;if((hq=gg.merge(hq)).length){hd.polygonStart();b1(hq,g5,hs,hi,hd);hd.polygonEnd()}else{if(hl([g1,g4])){hd.polygonStart(),hd.lineStart();hi(null,null,1,hd);hd.lineEnd(),hd.polygonEnd()}}hq=hk=hh=null}};function hs(hv){var hw=g3(hv,-1),hx=hl([hw===0||hw===3?g1:e,hw>1?g2:g4]);return hx}function hl(hv){var hC=0,hw=hk.length,hA=hv[1];for(var hz=0;hzhA&&he(hB,b,hv)>0){++hC}}else{if(b[1]<=hA&&he(hB,b,hv)<0){--hC}}hB=b}}return hC!==0}function he(hw,hv,hx){return(hv[0]-hw[0])*(hx[1]-hw[1])-(hx[0]-hw[0])*(hv[1]-hw[1])}function hi(hA,hz,hy,hx){var hw=0,hv=0;if(hA==null||(hw=g3(hA,hy))!==(hv=g3(hz,hy))||g6(hA,hz)<0^hy>0){do{hx.point(hw===0||hw===3?g1:e,hw>1?g2:g4)}while((hw=(hw+hy+4)%4)!==hv)}else{hx.point(hz[0],hz[1])}}function hb(hv,hw){return g1<=hv&&hv<=e&&g4<=hw&&hw<=g2}function hp(hv,hw){if(hb(hv,hw)){hd.point(hv,hw)}}var ho,hf,hn,ht,hc,ha,hg;function hm(){hu.point=g8;if(hk){hk.push(hh=[])}hg=true;ha=false;ht=hc=NaN}function hr(){if(hq){g8(ho,hf);if(hn&&ha){hj.rejoin()}hq.push(hj.buffer())}hu.point=hp;if(ha){hd.lineEnd()}}function g8(hw,hz){hw=Math.max(-gQ,Math.min(gQ,hw));hz=Math.max(-gQ,Math.min(gQ,hz));var hy=hb(hw,hz);if(hk){hh.push([hw,hz])}if(hg){ho=hw,hf=hz,hn=hy;hg=false;if(hy){hd.lineStart();hd.point(hw,hz)}}else{if(hy&&ha){hd.point(hw,hz)}else{var hx=[ht,hc],hv=[hw,hz];if(g7(hx,hv)){if(!ha){hd.lineStart();hd.point(hx[0],hx[1])}hd.point(hv[0],hv[1]);if(!hy){hd.lineEnd()}}else{hd.lineStart();hd.point(hw,hz)}}}ht=hw,hc=hz,ha=hy}return hu};function g3(g9,g8){return Math.abs(g9[0]-g1)0?0:3:Math.abs(g9[0]-e)0?2:1:Math.abs(g9[1]-g4)0?1:0:g8>0?3:2}function g5(g9,g8){return g6(g9.point,g8.point)}function g6(hb,g9){var ha=g3(hb,1),g8=g3(g9,1);return ha!==g8?ha-g8:ha===0?g9[1]-hb[1]:ha===1?hb[0]-g9[0]:ha===2?hb[1]-g9[1]:g9[0]-hb[0]}function g7(ha,g8){var hb=g8[0]-ha[0],g9=g8[1]-ha[1],hc=[0,1];if(Math.abs(hb)0){ha[0]+=hc[0]*hb;ha[1]+=hc[0]*g9}return true}return false}}function ah(g1,g3,g2){if(Math.abs(g3)0){if(e>g2[1]){return false}if(e>g2[0]){g2[0]=e}}else{if(e4*g2&&hu--){var ht=hx+hv,hs=he+hd,hr=hm+hk,hl=Math.sqrt(ht*ht+hs*hs+hr*hr),ho=Math.asin(hr/=hl),g5=Math.abs(Math.abs(hr)-1)g2||Math.abs((hi*g8+hh*hg)/hw-0.5)>0.3){e(hq,hb,g7,hx,he,hm,hn,g9,g5,ht/=hl,hs/=hl,hr,hu,hc);hc.point(hn,g9);e(hn,g9,g5,ht,hs,hr,hp,ha,g6,hv,hd,hk,hu,hc)}}}g1.precision=function(g5){if(!arguments.length){return Math.sqrt(g2)}g4=(g2=g5*g5)>0&&16;return g1};return g1}gg.geo.projection=eJ;gg.geo.projectionMutator=er;function eJ(e){return er(function(){return e})()}function er(hc){var g3,hh,g6,he=eW(function(hm,hn){hm=g3(hm,hn);return[hm[0]*hg+g5,g2-hm[1]*hg]}),hg=150,ha=480,g8=250,hf=0,g9=0,g7=0,g4=0,hd=0,g5,g2,hl=l,hi=fs,e=null,g1=null;function hk(hm){hm=g6(hm[0]*F,hm[1]*F);return[hm[0]*hg+g5,g2-hm[1]*hg]}function hb(hm){hm=g6.invert((hm[0]-g5)/hg,(g2-hm[1])/hg);return hm&&[hm[0]*gu,hm[1]*gu]}hk.stream=function(hm){return S(hh,hl(he(hi(hm))))};hk.clipAngle=function(hm){if(!arguments.length){return e}hl=hm==null?(e=hm,l):aA((e=+hm)*F);return hk};hk.clipExtent=function(hm){if(!arguments.length){return g1}g1=hm;hi=hm==null?fs:g(hm[0][0],hm[0][1],hm[1][0],hm[1][1]);return hk};hk.scale=function(hm){if(!arguments.length){return hg}hg=+hm;return hj()};hk.translate=function(hm){if(!arguments.length){return[ha,g8]}ha=+hm[0];g8=+hm[1];return hj()};hk.center=function(hm){if(!arguments.length){return[hf*gu,g9*gu]}hf=hm[0]%360*F;g9=hm[1]%360*F;return hj()};hk.rotate=function(hm){if(!arguments.length){return[g7*gu,g4*gu,hd*gu]}g7=hm[0]%360*F;g4=hm[1]%360*F;hd=hm.length>2?hm[2]%360*F:0;return hj()};gg.rebind(hk,he,"precision");function hj(){g6=ed(hh=c1(g7,g4,hd),g3);var hm=g3(hf,g9);g5=ha-hm[0]*hg;g2=g8+hm[1]*hg;return hk}return function(){g3=hc.apply(this,arguments);hk.invert=g3.invert&&hb;return hj()}}function S(e,g1){return{point:function(g2,g3){g3=e(g2*F,g3*F),g2=g3[0];g1.point(g2>fj?g2-2*fj:g2<-fj?g2+2*fj:g2,g3[1])},sphere:function(){g1.sphere()},lineStart:function(){g1.lineStart()},lineEnd:function(){g1.lineEnd()},polygonStart:function(){g1.polygonStart()},polygonEnd:function(){g1.polygonEnd()}}}function bd(g1,e){return[g1,e]}(gg.geo.equirectangular=function(){return eJ(bd)}).raw=bd.invert=bd;gg.geo.rotation=function(g1){g1=c1(g1[0]%360*F,g1[1]*F,g1.length>2?g1[2]*F:0);function e(g2){g2=g1(g2[0]*F,g2[1]*F);return g2[0]*=gu,g2[1]*=gu,g2}e.invert=function(g2){g2=g1.invert(g2[0]*F,g2[1]*F);return g2[0]*=gu,g2[1]*=gu,g2};return e};function c1(g2,g1,e){return g2?g1||e?ed(d2(g2),bv(g1,e)):d2(g2):g1||e?bv(g1,e):bd}function fW(e){return function(g2,g1){return g2+=e,[g2>fj?g2-2*fj:g2<-fj?g2+2*fj:g2,g1]}}function d2(g1){var e=fW(g1);e.invert=fW(-g1);return e}function bv(g5,g1){var g4=Math.cos(g5),g6=Math.sin(g5),e=Math.cos(g1),g2=Math.sin(g1);function g3(g9,g8){var hb=Math.cos(g8),g7=Math.cos(g9)*hb,hd=Math.sin(g9)*hb,hc=Math.sin(g8),ha=hc*g4+g7*g6;return[Math.atan2(hd*e-ha*g2,g7*g4-hc*g6),Math.asin(Math.max(-1,Math.min(1,ha*e+hd*g2)))]}g3.invert=function(g9,g8){var hb=Math.cos(g8),g7=Math.cos(g9)*hb,hd=Math.sin(g9)*hb,hc=Math.sin(g8),ha=hc*e-hd*g2;return[Math.atan2(hd*e+hc*g2,g7*g4+ha*g6),Math.asin(Math.max(-1,Math.min(1,ha*g4-g7*g6)))]};return g3}gg.geo.circle=function(){var g1=[0,0],g4,e=6,g2;function g3(){var g5=typeof g1==="function"?g1.apply(this,arguments):g1,g7=c1(-g5[0]*F,-g5[1]*F,0).invert,g6=[];g2(null,null,1,{point:function(g8,g9){g6.push(g8=g7(g8,g9));g8[0]*=gu,g8[1]*=gu}});return{type:"Polygon",coordinates:[g6]}}g3.origin=function(g5){if(!arguments.length){return g1}g1=g5;return g3};g3.angle=function(g5){if(!arguments.length){return g4}g2=bE((g4=+g5)*F,e*F);return g3};g3.precision=function(g5){if(!arguments.length){return e}g2=bE(g4*F,(e=+g5)*F);return g3};return g3.angle(90)};function bE(e,g1){var g3=Math.cos(e),g2=Math.sin(e);return function(ha,g9,g8,g7){if(ha!=null){ha=gL(g3,ha);g9=gL(g3,g9);if(g8>0?hag9){ha+=g8*2*fj}}else{ha=e+g8*2*fj;g9=e}var g4;for(var g6=g8*g1,g5=ha;g8>0?g5>g9:g5fu}).map(ha)).concat(gg.range(Math.ceil(g4/hb)*hb,g3,hb).filter(function(hj){return Math.abs(hj%g6)>fu}).map(g9))}hf.lines=function(){return e().map(function(hj){return{type:"LineString",coordinates:hj}})};hf.outline=function(){return{type:"Polygon",coordinates:[g2(hi).concat(g1(g5).slice(1),g2(hh).reverse().slice(1),g1(g7).reverse().slice(1))]}};hf.extent=function(hj){if(!arguments.length){return hf.minorExtent()}return hf.majorExtent(hj).minorExtent(hj)};hf.majorExtent=function(hj){if(!arguments.length){return[[hi,g7],[hh,g5]]}hi=+hj[0][0],hh=+hj[1][0];g7=+hj[0][1],g5=+hj[1][1];if(hi>hh){hj=hi,hi=hh,hh=hj}if(g7>g5){hj=g7,g7=g5,g5=hj}return hf.precision(hd)};hf.minorExtent=function(hj){if(!arguments.length){return[[hg,g4],[he,g3]]}hg=+hj[0][0],he=+hj[1][0];g4=+hj[0][1],g3=+hj[1][1];if(hg>he){hj=hg,hg=he,he=hj}if(g4>g3){hj=g4,g4=g3,g3=hj}return hf.precision(hd)};hf.step=function(hj){if(!arguments.length){return hf.minorStep()}return hf.majorStep(hj).minorStep(hj)};hf.majorStep=function(hj){if(!arguments.length){return[g8,g6]}g8=+hj[0],g6=+hj[1];return hf};hf.minorStep=function(hj){if(!arguments.length){return[hc,hb]}hc=+hj[0],hb=+hj[1];return hf};hf.precision=function(hj){if(!arguments.length){return hd}hd=+hj;ha=az(g4,g3,90);g9=ax(hg,he,hd);g2=az(g7,g5,90);g1=ax(hi,hh,hd);return hf};return hf.majorExtent([[-180,-90+fu],[180,90-fu]]).minorExtent([[-180,-80-fu],[180,80+fu]])};function az(g2,g1,e){var g3=gg.range(g2,g1-fu,e).concat(g1);return function(g4){return g3.map(function(g5){return[g4,g5]})}}function ax(g3,g2,g1){var e=gg.range(g3,g2-fu,g1).concat(g2);return function(g4){return e.map(function(g5){return[g5,g4]})}}function gy(e){return e.source}function gJ(e){return e.target}gg.geo.greatArc=function(){var g2=gy,g1,g3=gJ,g4;function e(){return{type:"LineString",coordinates:[g1||g2.apply(this,arguments),g4||g3.apply(this,arguments)]}}e.distance=function(){return gg.geo.distance(g1||g2.apply(this,arguments),g4||g3.apply(this,arguments))};e.source=function(g5){if(!arguments.length){return g2}g2=g5,g1=typeof g5==="function"?null:g5;return e};e.target=function(g5){if(!arguments.length){return g3}g3=g5,g4=typeof g5==="function"?null:g5;return e};e.precision=function(){return arguments.length?e:0};return e};gg.geo.interpolate=function(e,g1){return h(e[0]*F,e[1]*F,g1[0]*F,g1[1]*F)};function h(g3,he,g2,hc){var g6=Math.cos(he),hd=Math.sin(he),g4=Math.cos(hc),hb=Math.sin(hc),g8=g6*Math.cos(g3),g1=g6*Math.sin(g3),g7=g4*Math.cos(g2),e=g4*Math.sin(g2),ha=2*Math.asin(Math.sqrt(ar(hc-he)+g6*g4*ar(g2-g3))),g5=1/Math.sin(ha);var g9=ha?function(hh){var hk=Math.sin(hh*=ha)*g5,hg=Math.sin(ha-hh)*g5,hf=hg*g8+hk*g7,hj=hg*g1+hk*e,hi=hg*hd+hk*hb;return[Math.atan2(hj,hf)*gu,Math.atan2(hi,Math.sqrt(hf*hf+hj*hj))*gu]}:function(){return[g3*gu,he*gu]};g9.distance=ha;return g9}gg.geo.length=function(e){db=0;gg.geo.stream(e,e4);return db};var db;var e4={sphere:av,point:av,lineStart:gE,lineEnd:av,polygonStart:av,polygonEnd:av};function gE(){var g1,g2,e;e4.point=function(g5,g4){g1=g5*F,g2=Math.sin(g4*=F),e=Math.cos(g4);e4.point=g3};e4.lineEnd=function(){e4.point=e4.lineEnd=av};function g3(g5,g4){var g8=Math.sin(g4*=F),g9=Math.cos(g4),g6=Math.abs((g5*=F)-g1),g7=Math.cos(g6);db+=Math.atan2(Math.sqrt((g6=g9*Math.sin(g6))*g6+(g6=e*g8-g2*g9*g7)*g6),g2*g8+e*g9*g7);g1=g5,g2=g8,e=g9}}function bH(g3){var g2=0,g1=fj/3,e=er(g3),g4=e(g2,g1);g4.parallels=function(g5){if(!arguments.length){return[g2/fj*180,g1/fj*180]}return e(g2=g5[0]*fj/180,g1=g5[1]*fj/180)};return g4}function J(g1,e){var g4=Math.sin(g1),g6=(g4+Math.sin(e))/2,g5=1+g4*(2*g6-g4),g3=Math.sqrt(g5)/g6;function g2(g8,g7){var g9=Math.sqrt(g5-2*g6*Math.sin(g7))/g6;return[g9*Math.sin(g8*=g6),g3-g9*Math.cos(g8)]}g2.invert=function(g7,g9){var g8=g3-g9;return[Math.atan2(g7,g8)/g6,Math.asin((g5-(g7*g7+g8*g8)*g6*g6)/(2*g6))]};return g2}(gg.geo.conicEqualArea=function(){return bH(J)}).raw=J;gg.geo.albersUsa=function(){var g1=gg.geo.conicEqualArea().rotate([98,0]).center([0,38]).parallels([29.5,45.5]);var g5=gg.geo.conicEqualArea().rotate([160,0]).center([0,60]).parallels([55,65]);var g3=gg.geo.conicEqualArea().rotate([160,0]).center([0,20]).parallels([8,18]);var g2=gg.geo.conicEqualArea().rotate([60,0]).center([0,10]).parallels([8,18]);var g6,g8,e;function g7(g9){return g4(g9)(g9)}function g4(g9){var hb=g9[0],ha=g9[1];return ha>50?g5:hb<-140?g3:ha<21?g2:g1}g7.invert=function(g9){return g6(g9)||g8(g9)||e(g9)||g1.invert(g9)};g7.scale=function(g9){if(!arguments.length){return g1.scale()}g1.scale(g9);g5.scale(g9*0.6);g3.scale(g9);g2.scale(g9*1.5);return g7.translate(g1.translate())};g7.translate=function(ha){if(!arguments.length){return g1.translate()}var g9=g1.scale(),hc=ha[0],hb=ha[1];g1.translate(ha);g5.translate([hc-0.4*g9,hb+0.17*g9]);g3.translate([hc-0.19*g9,hb+0.2*g9]);g2.translate([hc+0.58*g9,hb+0.43*g9]);g6=cr(g5,[[-180,50],[-130,72]]);g8=cr(g3,[[-164,18],[-154,24]]);e=cr(g2,[[-67.5,17.5],[-65,19]]);return g7};return g7.scale(1000)};function cr(hj,hc){var hi=hj(hc[0]),hh=hj([0.5*(hc[0][0]+hc[1][0]),hc[0][1]]),hg=hj([hc[1][0],hc[0][1]]),hf=hj(hc[1]);var g9=hh[1]-hi[1],g3=hh[0]-hi[0],g8=hg[1]-hh[1],g2=hg[0]-hh[0];var g1=g9/g3,e=g8/g2;var g7=0.5*(g1*e*(hi[1]-hg[1])+e*(hi[0]+hh[0])-g1*(hh[0]+hg[0]))/(e-g1),g5=(0.5*(hi[0]+hh[0])-g7)/g1+0.5*(hi[1]+hh[1]);var g6=hf[0]-g7,hb=hf[1]-g5,g4=hi[0]-g7,ha=hi[1]-g5,he=g6*g6+hb*hb,hd=g4*g4+ha*ha;var hl=Math.atan2(hb,g6),hk=Math.atan2(ha,g4);return function(hq){var ho=hq[0]-g7,hn=hq[1]-g5,hp=ho*ho+hn*hn,hm=Math.atan2(hn,ho);if(he1){g9=g5[1];e=g6[g2];g2++;ha+="C"+(g8[0]+g4[0])+","+(g8[1]+g4[1])+","+(e[0]-g9[0])+","+(e[1]-g9[1])+","+e[0]+","+e[1];for(var g1=2;g19){g8=g3*3/Math.sqrt(g8);e[g2]=g8*g6;e[g2+1]=g8*g5}}}g2=-1;while(++g2<=g1){g8=(g7[Math.min(g1,g2+1)][0]-g7[Math.max(0,g2-1)][0])/(6*(1+e[g2]*e[g2]));g4.push([g8||0,e[g2]*g8||0])}return g4}function cM(e){return e.length<3?gK(e):e[0]+cz(e,al(e))}gg.geom.hull=function(g1){var e=fS,g3=fR;if(arguments.length){return g2(g1)}function g2(ho){if(ho.length<3){return[]}var g7=O(e),g6=O(g3),he=ho.length,g9,hb=he-1,hh=[],ha=[],hm,hi,hf,hj=0,hl,g5,hk,g4,hd,hc,hn,hg;if(g7===fS&&g3===fR){g9=ho}else{for(hi=0,g9=[];hi=hk*hk+g4*g4){hh[hi].index=-1}else{hh[hd].index=-1;hn=hh[hi].angle;hd=hi;hc=hf}}else{hn=hh[hi].angle;hd=hi;hc=hf}}ha.push(hj);for(hi=0,hf=0;hi<2;++hf){if(hh[hf].index!==-1){ha.push(hh[hf].index);hi++}}hg=ha.length;for(;hf0}gg.geom.polygon=function(e){e.area=function(){var g1=0,g3=e.length,g2=e[g3-1][1]*e[0][0]-e[g3-1][0]*e[0][1];while(++g1=0){hn=hh.ep.r;hm=hh.ep.l}else{hn=hh.ep.l;hm=hh.ep.r}if(hh.a===1){hk=hn?hn.y:-g6;hg=hh.c-hh.b*hk;hi=hm?hm.y:g6;hf=hh.c-hh.b*hi}else{hg=hn?hn.x:-g6;hk=hh.c-hh.a*hg;hf=hm?hm.x:g6;hi=hh.c-hh.a*hf}var hl=[hg,hk],hj=[hf,hi];hd[hh.region.l.index].push(hl,hj);hd[hh.region.r.index].push(hl,hj)});hd=hd.map(function(hi,hh){var hg=he[hh][0],hk=he[hh][1],hj=hi.map(function(hl){return Math.atan2(hl[0]-hg,hl[1]-hk)}),hf=gg.range(hi.length).sort(function(hm,hl){return hj[hm]-hj[hl]});return hf.filter(function(hm,hl){return !hl||hj[hm]-hj[hf[hl-1]]>fu}).map(function(hl){return hi[hl]})});hd.forEach(function(hp,hj){var hi=hp.length;if(!hi){return hp.push([-g6,-g6],[-g6,g6],[g6,g6],[g6,-g6])}if(hi>2){return}var hu=he[hj],hs=hp[0],hr=hp[1],hh=hu[0],hq=hu[1],hg=hs[0],ho=hs[1],hf=hr[0],hm=hr[1],hv=Math.abs(hf-hg),ht=hm-ho;if(Math.abs(ht)0){hl*=-1}hp.push([-g6,hl],[g6,hl])}}}});if(g3){for(hb=0;hbe.y?1:hl.xe.x?1:0}),bottomSite:null};var g2={list:[],leftEnd:null,rightEnd:null,init:function(){g2.leftEnd=g2.createHalfEdge(null,"l");g2.rightEnd=g2.createHalfEdge(null,"l");g2.leftEnd.r=g2.rightEnd;g2.rightEnd.l=g2.leftEnd;g2.list.unshift(g2.leftEnd,g2.rightEnd)},createHalfEdge:function(hl,e){return{edge:hl,side:e,vertex:null,l:null,r:null}},insert:function(hl,e){e.l=hl;e.r=hl.r;hl.r.l=e;hl.r=e},leftBound:function(hl){var e=g2.leftEnd;do{e=e.r}while(e!=g2.rightEnd&&hi.rightOf(e,hl));e=e.l;return e},del:function(e){e.l.r=e.r;e.r.l=e.l;e.edge=null},right:function(e){return e.r},left:function(e){return e.l},leftRegion:function(e){return e.edge==null?hj.bottomSite:e.edge.region[e.side]},rightRegion:function(e){return e.edge==null?hj.bottomSite:e.edge.region[bG[e.side]]}};var hi={bisect:function(ho,hm){var hn={region:{l:ho,r:hm},ep:{l:null,r:null}};var hl=hm.x-ho.x,e=hm.y-ho.y,hq=hl>0?hl:-hl,hp=e>0?e:-e;hn.c=ho.x*hl+ho.y*e+(hl*hl+e*e)*0.5;if(hq>hp){hn.a=1;hn.b=e/hl;hn.c/=hl}else{hn.b=1;hn.a=hl/e;hn.c/=e}return hn},intersect:function(ho,hn){var ht=ho.edge,hs=hn.edge;if(!ht||!hs||ht.region.r==hs.region.r){return null}var hr=ht.a*hs.b-ht.b*hs.a;if(Math.abs(hr)<1e-10){return null}var hm=(ht.c*hs.b-hs.c*ht.b)/hr,hw=(hs.c*ht.a-ht.c*hs.a)/hr,hv=ht.region.r,hq=hs.region.r,hl,hp;if(hv.y=hp.region.r.x;if(hu&&hl.side==="l"||!hu&&hl.side==="r"){return null}return{x:hm,y:hw}},rightOf:function(hq,hl){var hu=hq.edge,ho=hu.region.r,hx=hl.x>ho.x;if(hx&&hq.side==="l"){return 1}if(!hx&&hq.side==="r"){return 0}if(hu.a===1){var hw=hl.y-ho.y,hm=hl.x-ho.x,hr=0,hv=0;if(!hx&&hu.b<0||hx&&hu.b>=0){hv=hr=hw>=hu.b*hm}else{hv=hl.x+hl.y*hu.b>hu.c;if(hu.b<0){hv=!hv}if(!hv){hr=1}}if(!hr){var hy=ho.x-hu.region.l.x;hv=hu.b*(hm*hm-hw*hw)hs*hs+hp*hp}return hq.side==="l"?hv:!hv},endPoint:function(hm,hl,e){hm.ep[hl]=e;if(!hm.ep[bG[hl]]){return}g3(hm)},distance:function(hn,hm){var hl=hn.x-hm.x,e=hn.y-hm.y;return Math.sqrt(hl*hl+e*e)}};var g1={list:[],insert:function(hp,hl,hq){hp.vertex=hl;hp.ystar=hl.y+hq;for(var hm=0,ho=g1.list,e=ho.length;hmhn.ystar||hp.ystar==hn.ystar&&hl.x>hn.vertex.x){continue}else{break}}ho.splice(hm,0,hp)},del:function(hn){for(var hm=0,hl=g1.list,e=hl.length;hmha.y){hh=g9;g9=ha;ha=hh;he="r"}hf=hi.bisect(g9,ha);g5=g2.createHalfEdge(hf,he);g2.insert(g4,g5);hi.endPoint(hf,bG[he],g7);hc=hi.intersect(g4,g5);if(hc){g1.del(g4);g1.insert(g4,hc,hi.distance(hc,g9))}hc=hi.intersect(g5,hg);if(hc){g1.insert(g5,hc,hi.distance(hc,g9))}}else{break}}}for(hb=g2.right(g2.leftEnd);hb!=g2.rightEnd;hb=g2.right(hb)){g3(hb.edge)}}gg.geom.quadtree=function(g7,g1,g5,e,g4){var g6=fS,g3=fR,g8;if(g8=arguments.length){g6=fb;g3=fa;if(g8===3){g4=g5;e=g1;g5=g1=0}return g2(g7)}function g2(hr){var ho,hd=O(g6),hb=O(g3),hf,hl,hm,hj,g9,hi,he,hq;if(g1!=null){g9=g1,hi=g5,he=e,hq=g4}else{he=hq=-(g9=hi=Infinity);hf=[],hl=[];hj=hr.length;if(g8){for(hm=0;hmhe){he=ho.x}if(ho.y>hq){hq=ho.y}hf.push(ho.x);hl.push(ho.y)}}else{for(hm=0;hmhe){he=hn}if(hc>hq){hq=hc}hf.push(hn);hl.push(hc)}}}var hh=he-g9,hg=hq-hi;if(hh>hg){hq=hi+hh}else{he=g9+hg}function ha(hu,hy,hC,hz,ht,hB,hs,hA){if(isNaN(hC)||isNaN(hz)){return}if(hu.leaf){var hx=hu.x,hw=hu.y;if(hx!=null){if(Math.abs(hx-hC)+Math.abs(hw-hz)<0.01){hp(hu,hy,hC,hz,ht,hB,hs,hA)}else{var hv=hu.point;hu.x=hu.y=hu.point=null;hp(hu,hv,hx,hw,ht,hB,hs,hA);hp(hu,hy,hC,hz,ht,hB,hs,hA)}}else{hu.x=hC,hu.y=hz,hu.point=hy}}else{hp(hu,hy,hC,hz,ht,hB,hs,hA)}}function hp(hv,hx,hD,hz,hu,hC,ht,hA){var hB=(hu+ht)*0.5,hy=(hC+hA)*0.5,hE=hD>=hB,hs=hz>=hy,hw=(hs<<1)+hE;hv.leaf=false;hv=hv.nodes[hw]||(hv.nodes[hw]=eQ());if(hE){hu=hB}else{ht=hB}if(hs){hC=hy}else{hA=hy}ha(hv,hx,hD,hz,hu,hC,ht,hA)}var hk=eQ();hk.add=function(hs){ha(hk,hs,+hd(hs,++hm),+hb(hs,hm),g9,hi,he,hq)};hk.visit=function(hs){cm(hs,hk,g9,hi,he,hq)};hm=-1;if(g1==null){while(++hm180){e+=360}else{if(e-g2>180){g2+=360}}g1.push({i:he.push(he.pop()+"rotate(",null,")")-2,x:bj(g2,e)})}else{if(e){he.push(he.pop()+"rotate("+e+")")}}if(g7!=g6){g1.push({i:he.push(he.pop()+"skewX(",null,")")-2,x:bj(g7,g6)})}else{if(g6){he.push(he.pop()+"skewX("+g6+")")}}if(hb[0]!=ha[0]||hb[1]!=ha[1]){g5=he.push(he.pop()+"scale(",null,",",null,")");g1.push({i:g5-4,x:bj(hb[0],ha[0])},{i:g5-2,x:bj(hb[1],ha[1])})}else{if(ha[0]!=1||ha[1]!=1){he.push(he.pop()+"scale("+ha+")")}}g5=g1.length;return function(hg){var hf=-1,hh;while(++hf=0&&!(g3=gg.interpolators[g2](g1,e))){}return g3}function eC(e){return e=="transform"?dt:eL}gg.interpolators=[bo,function(g1,e){return Array.isArray(e)&&dP(g1,e)},function(g1,e){return(typeof g1==="string"||typeof e==="string")&&bB(g1+"",e+"")},function(g1,e){return(typeof e==="string"?ad.has(e)||/^(#|rgb\(|hsl\()/.test(e):e instanceof D)&&aD(g1,e)},function(g1,e){return !isNaN(g1=+g1)&&!isNaN(e=+e)&&bj(g1,e)}];gg.interpolateArray=dP;function dP(g4,g2){var g1=[],g7=[],g3=g4.length,e=g2.length,g6=Math.min(g4.length,g2.length),g5;for(g5=0;g5=0?g1.substring(0,g3):g1,e=g3>=0?g1.substring(g3+1):"in";g2=W.get(g2)||gd;e=k.get(e)||fs;return eI(e(g2.apply(null,Array.prototype.slice.call(arguments,1))))};function eI(e){return function(g1){return g1<=0?0:g1>=1?1:e(g1)}}function B(e){return function(g1){return 1-e(1-g1)}}function aP(e){return function(g1){return 0.5*(g1<0.5?e(2*g1):2-e(2-2*g1))}}function bz(e){return e*e}function G(e){return e*e*e}function am(g1){if(g1<=0){return 0}if(g1>=1){return 1}var g2=g1*g1,e=g2*g1;return 4*(g1<0.5?e:3*(g1-g2)+e-0.75)}function aI(g1){return function(e){return Math.pow(e,g1)}}function fk(e){return 1-Math.cos(e*fj/2)}function eB(e){return Math.pow(2,10*(e-1))}function dn(e){return 1-Math.sqrt(1-e*e)}function U(e,g2){var g1;if(arguments.length<2){g2=0.45}if(arguments.length){g1=g2/(2*fj)*Math.asin(1/e)}else{e=1,g1=g2/4}return function(g3){return 1+e*Math.pow(2,10*-g3)*Math.sin((g3-g1)*2*fj/g2)}}function fX(e){if(!e){e=1.70158}return function(g1){return g1*g1*((e+1)*g1-e)}}function bU(e){return e<1/2.75?7.5625*e*e:e<2/2.75?7.5625*(e-=1.5/2.75)*e+0.75:e<2.5/2.75?7.5625*(e-=2.25/2.75)*e+0.9375:7.5625*(e-=2.625/2.75)*e+0.984375}gg.interpolateHcl=fQ;function fQ(g2,e){g2=gg.hcl(g2);e=gg.hcl(e);var g1=g2.h,g4=g2.c,g6=g2.l,g3=e.h-g1,g5=e.c-g4,g7=e.l-g6;if(g3>180){g3-=360}else{if(g3<-180){g3+=360}}return function(g8){return bh(g1+g3*g8,g4+g5*g8,g6+g7*g8)+""}}gg.interpolateHsl=aQ;function aQ(g2,e){g2=gg.hsl(g2);e=gg.hsl(e);var g7=g2.h,g6=g2.s,g3=g2.l,g5=e.h-g7,g4=e.s-g6,g1=e.l-g3;if(g5>180){g5-=360}else{if(g5<-180){g5+=360}}return function(g8){return aZ(g7+g5*g8,g6+g4*g8,g3+g1*g8)+""}}gg.interpolateLab=gp;function gp(g1,e){g1=gg.lab(g1);e=gg.lab(e);var g4=g1.l,g3=g1.a,g2=g1.b,g7=e.l-g4,g6=e.a-g3,g5=e.b-g2;return function(g8){return aq(g4+g7*g8,g3+g6*g8,g2+g5*g8)+""}}gg.interpolateRound=eG;function eG(g1,e){e-=g1;return function(g2){return Math.round(g1+e*g2)}}function gn(g1,e){e=e-(g1=+g1)?1/(e-g1):0;return function(g2){return(g2-g1)*e}}function ao(g1,e){e=e-(g1=+g1)?1/(e-g1):0;return function(g2){return Math.max(0,Math.min(1,(g2-g1)*e))}}gg.layout={};gg.layout.bundle=function(){return function(e){var g2=[],g1=-1,g3=e.length;while(++g10){g4=hi}else{g4=0}}else{if(hi>0){he.start({type:"start",alpha:g4=hi});gg.timer(g1.tick)}}return g1};g1.start=function(){var hm,hl,hj=hc.length,hk=g2.length,hp=ha[0],ho=ha[1],hr,hi;for(hm=0;hmg7){g7=e}g6.push(e)}for(g3=0;g3g1){g2=g3;g1=e}}return g2}function cQ(e){return e.reduce(eM,0)}function eM(e,g1){return e+g1[1]}gg.layout.histogram=function(){var g4=true,g1=Number,e=a2,g2=go;function g3(g8,g9){var he=[],hc=g8.map(g1,this),ha=e.call(this,hc,g9),hd=g2.call(this,ha,hc,g9),hf,g9=-1,g5=hc.length,g6=hd.length-1,g7=g4?1:1/g5,hb;while(++g90){g9=-1;while(++g9=ha[0]&&hb<=ha[1]){hf=he[gg.bisect(hd,hb,1,g6)-1];hf.y+=g7;hf.push(g8[g9])}}}return he}g3.value=function(g5){if(!arguments.length){return g1}g1=g5;return g3};g3.range=function(g5){if(!arguments.length){return e}e=O(g5);return g3};g3.bins=function(g5){if(!arguments.length){return g2}g2=typeof g5==="number"?function(g6){return i(g6,g5)}:O(g5);return g3};g3.frequency=function(g5){if(!arguments.length){return g4}g4=!!g5;return g3};return g3};function go(g1,e){return i(g1,Math.ceil(Math.log(e.length)/Math.LN2+1))}function i(g3,g5){var g2=-1,g1=+g3[0],e=(g3[1]-g1)/g5,g4=[];while(++g2<=g5){g4[g2]=e*g2+g1}return g4}function a2(e){return[gg.min(e),gg.max(e)]}gg.layout.tree=function(){var g1=gg.layout.hierarchy().sort(null).value(null),g3=cT,g2=[1,1];function e(hb,g9){var g5=g1.call(this,hb,g9),hd=g5[0];function ha(hl,hh){var hj=hl.children,ho=hl._tree;if(hj&&(hk=hj.length)){var hk,hq=hj[0],hp,hn=hq,hi,hm=-1;while(++hm0){gR(e5(hn,hl,ho),hl,hi);hq+=hi;hp+=hi}hs+=hn._tree.mod;hq+=hk._tree.mod;hr+=hm._tree.mod;hp+=hj._tree.mod}if(hn&&!ap(hj)){hj._tree.thread=hn;hj._tree.mod+=hs-hp}if(hk&&!gO(hm)){hm._tree.thread=hk;hm._tree.mod+=hq-hr;ho=hl}}return ho}gv(hd,function(hi,hh){hi._tree={ancestor:hi,prelim:0,mod:0,change:0,shift:0,number:hh?hh._tree.number+1:0}});ha(hd);hc(hd,-hd._tree.prelim);var g8=dj(hd,bk),hg=dj(hd,cB),hf=dj(hd,C),g7=g8.x-g3(g8,hg)/2,g6=hg.x+g3(hg,g8)/2,he=hf.depth||1;gv(hd,function(hh){hh.x=(hh.x-g7)/(g6-g7)*g2[0];hh.y=hh.depth/he*g2[1];delete hh._tree});return g5}e.separation=function(g4){if(!arguments.length){return g3}g3=g4;return e};e.size=function(g4){if(!arguments.length){return g2}g2=g4;return e};return fz(e,g1)};function cT(g1,e){return g1.parent==e.parent?1:2}function gO(g1){var e=g1.children;return e&&e.length?e[0]:g1._tree.thread}function ap(g1){var e=g1.children,g2;return e&&(g2=e.length)?e[g2-1]:g1._tree.thread}function dj(g2,g3){var g1=g2.children;if(g1&&(g4=g1.length)){var g5,g4,e=-1;while(++e0){g2=g5}}}return g2}function cB(g1,e){return g1.x-e.x}function bk(g1,e){return e.x-g1.x}function C(g1,e){return g1.depth-e.depth}function gv(g1,g2){function e(g7,g6){var g5=g7.children;if(g5&&(g8=g5.length)){var g9,g4=null,g3=-1,g8;while(++g3=0){g4=g2[g1]._tree;g4.prelim+=e;g4.mod+=e;e+=g4.shift+(g5+=g4.change)}}function gR(g1,g2,e){g1=g1._tree;g2=g2._tree;var g3=e/(g2.number-g1.number);g1.change+=g3;g2.change-=g3;g2.shift+=e;g2.prelim+=e;g2.mod+=e}function e5(e,g2,g1){return e._tree.ancestor.parent==g2.parent?e._tree.ancestor:g1}gg.layout.pack=function(){var e=gg.layout.hierarchy().sort(dF),g3=0,g2=[1,1];function g1(hb,g8){var g7=e.call(this,hb,g8),g5=g7[0];g5.x=0;g5.y=0;gv(g5,function(hc){hc.r=Math.sqrt(hc.value)});gv(g5,Q);var g4=g2[0],g9=g2[1],g6=Math.max(2*g5.r/g4,2*g5.r/g9);if(g3>0){var ha=g3*g6/2;gv(g5,function(hc){hc.r+=ha});gv(g5,Q);gv(g5,function(hc){hc.r-=ha});g6=Math.max(2*g5.r/g4,2*g5.r/g9)}ew(g5,g4/2,g9/2,1/g6);return g7}g1.size=function(g4){if(!arguments.length){return g2}g2=g4;return g1};g1.padding=function(g4){if(!arguments.length){return g3}g3=+g4;return g1};return fz(g1,e)};function dF(g1,e){return g1.value-e.value}function bs(g1,e){var g2=g1._pack_next;g1._pack_next=e;e._pack_prev=g1;e._pack_next=g2;g2._pack_prev=e}function aX(g1,e){g1._pack_next=e;e._pack_prev=g1}function dx(g2,e){var g3=e.x-g2.x,g1=e.y-g2.y,g4=g2.r+e.r;return g4*g4-g3*g3-g1*g1>0.001}function Q(hb){if(!(ha=hb.children)||!(g9=ha.length)){return}var ha,hf=Infinity,hj=-Infinity,e=Infinity,g4=-Infinity,hi,hh,hg,he,hd,hc,g9;function g2(hk){hf=Math.min(hk.x-hk.r,hf);hj=Math.max(hk.x+hk.r,hj);e=Math.min(hk.y-hk.r,e);g4=Math.max(hk.y+hk.r,g4)}ha.forEach(aG);hi=ha[0];hi.x=-hi.r;hi.y=0;g2(hi);if(g9>1){hh=ha[1];hh.x=hh.r;hh.y=0;g2(hh);if(g9>2){hg=ha[2];aW(hi,hh,hg);g2(hg);bs(hi,hg);hi._pack_prev=hg;bs(hg,hh);hh=hi._pack_next;for(he=3;he0){ho.push(hf=hl[hh-1]);ho.area+=hf.area;if(g5!=="squarify"||(hi=g2(ho,hn))<=hk){hl.pop();hk=hi}else{ho.area-=ho.pop().area;g4(ho,hn,hm,false);hn=Math.min(hm.dx,hm.dy);ho.length=ho.area=0;hk=Infinity}}if(ho.length){g4(ho,hn,hm,true);ho.length=ho.area=0}hg.forEach(g3)}}function hd(hi){var hf=hi.children;if(hf&&hf.length){var hh=e(hi),hg=hf.slice(),hk,hj=[];g1(hg,hh.dx*hh.dy/hi.value);hj.area=0;while(hk=hg.pop()){hj.push(hk);hj.area+=hk.area;if(hk.z!=null){g4(hj,hk.z?hh.dx:hh.dy,hh,!hg.length);hj.length=hj.area=0}}hf.forEach(hd)}}function g2(hk,hg){var hi=hk.area,hj,hm=0,hf=Infinity,hh=-1,hl=hk.length;while(++hhhm){hm=hj}}hi*=hi;hg*=hg;return hi?Math.max(hg*hm*g7/hi,hi/(hg*hf*g7)):Infinity}function g4(ho,hn,hj,hm){var hh=-1,hg=ho.length,hk=hj.x,hi=hj.y,hl=hn?hc(ho.area/hn):0,hf;if(hn==hj.dx){if(hm||hl>hj.dy){hl=hj.dy}while(++hhhj.dx){hl=hj.dx}while(++hh1);return g1+e*g3*Math.sqrt(-2*Math.log(g4)/g4)}},logNormal:function(){var e=gg.random.normal.apply(gg,arguments);return function(){return Math.exp(e())}},irwinHall:function(e){return function(){for(var g2=0,g1=0;g12?I:au,g9=g7?ao:gn;g2=g8(g5,g3,g9,g4);g1=g8(g3,g5,g9,eL);return g6}function g6(g8){return g2(g8)}g6.invert=function(g8){return g1(g8)};g6.domain=function(g8){if(!arguments.length){return g5}g5=g8.map(Number);return e()};g6.range=function(g8){if(!arguments.length){return g3}g3=g8;return e()};g6.rangeRound=function(g8){return g6.range(g8).interpolate(eG)};g6.clamp=function(g8){if(!arguments.length){return g7}g7=g8;return e()};g6.interpolate=function(g8){if(!arguments.length){return g4}g4=g8;return e()};g6.ticks=function(g8){return d4(g5,g8)};g6.tickFormat=function(g8,g9){return eg(g5,g8,g9)};g6.nice=function(){fq(g5,aC);return e()};g6.copy=function(){return dv(g5,g3,g4,g7)};return e()}function fh(g1,e){return gg.rebind(g1,e,"range","rangeRound","interpolate","clamp")}function aC(e){e=Math.pow(10,Math.round(Math.log(e)/Math.LN10)-1);return e&&{floor:function(g1){return Math.floor(g1/e)*e},ceil:function(g1){return Math.ceil(g1/e)*e}}}function r(g5,e){var g2=dz(g5),g1=g2[1]-g2[0],g4=Math.pow(10,Math.floor(Math.log(g1/e)/Math.LN10)),g3=e/g1*g4;if(g3<=0.15){g4*=10}else{if(g3<=0.35){g4*=5}else{if(g3<=0.75){g4*=2}}}g2[0]=Math.ceil(g2[0]/g4)*g4;g2[1]=Math.floor(g2[1]/g4)*g4+g4*0.5;g2[2]=g4;return g2}function d4(g1,e){return gg.range.apply(gg,r(g1,e))}function eg(g2,e,g3){var g1=-Math.floor(Math.log(r(g2,e)[2])/Math.LN10+0.01);return gg.format(g3?g3.replace(b9,function(hd,hc,hb,ha,g9,g8,g7,g6,g5,g4){return[hc,hb,ha,g9,g8,g7,g6,g5||"."+(g1-(g4==="%")*2),g4].join("")}):",."+g1+"f")}gg.scale.log=function(){return fI(gg.scale.linear().domain([0,Math.LN10]),10,fi,e9)};function fI(e,g3,g1,g2){function g4(g5){return e(g1(g5))}g4.invert=function(g5){return g2(e.invert(g5))};g4.domain=function(g5){if(!arguments.length){return e.domain().map(g2)}if(g5[0]<0){g1=fl,g2=fd}else{g1=fi,g2=e9}e.domain(g5.map(g1));return g4};g4.base=function(g5){if(!arguments.length){return g3}g3=+g5;return g4};g4.nice=function(){e.domain(fq(e.domain(),ch(g3)));return g4};g4.ticks=function(){var hd=dz(e.domain()),ha=[];if(hd.every(isFinite)){var g9=Math.log(g3),g8=Math.floor(hd[0]/g9),g7=Math.ceil(hd[1]/g9),hc=g2(hd[0]),hb=g2(hd[1]),g5=g3%1?2:g3;if(g1===fl){ha.push(-Math.pow(g3,-g8));for(;g8++0;g6--){ha.push(-Math.pow(g3,-g8)*g6)}}}else{for(;g8hb;g7--){}ha=ha.slice(g8,g7)}return ha};g4.tickFormat=function(ha,g9){if(arguments.length<2){g9=cP}if(!arguments.length){return g9}var g5=Math.log(g3),g6=Math.max(0.1,ha/g4.ticks().length),g7=g1===fl?(g8=-1e-12,Math.floor):(g8=1e-12,Math.ceil),g8;return function(hb){return hb/g2(g5*g7(g1(hb)/g5+g8))<=g6?g9(hb):""}};g4.copy=function(){return fI(e.copy(),g3,g1,g2)};return fh(g4,e)}var cP=gg.format(".0e");function fi(e){return Math.log(e<0?0:e)}function e9(e){return Math.exp(e)}function fl(e){return -Math.log(e>0?0:-e)}function fd(e){return -Math.exp(-e)}function ch(g1){g1=Math.log(g1);var e={floor:function(g2){return Math.floor(g2/g1)*g1},ceil:function(g2){return Math.ceil(g2/g1)*g1}};return function(){return e}}gg.scale.pow=function(){return fy(gg.scale.linear(),1)};function fy(e,g2){var g1=e1(g2),g3=e1(1/g2);function g4(g5){return e(g1(g5))}g4.invert=function(g5){return g3(e.invert(g5))};g4.domain=function(g5){if(!arguments.length){return e.domain().map(g3)}e.domain(g5.map(g1));return g4};g4.ticks=function(g5){return d4(g4.domain(),g5)};g4.tickFormat=function(g5,g6){return eg(g4.domain(),g5,g6)};g4.nice=function(){return g4.domain(fq(g4.domain(),aC))};g4.exponent=function(g5){if(!arguments.length){return g2}var g6=g4.domain();g1=e1(g2=g5);g3=e1(1/g2);return g4.domain(g6)};g4.copy=function(){return fy(e.copy(),g2)};return fh(g4,e)}function e1(g1){return function(e){return e<0?-Math.pow(-e,g1):Math.pow(e,g1)}}gg.scale.sqrt=function(){return gg.scale.pow().exponent(0.5)};gg.scale.ordinal=function(){return b2([],{t:"range",a:[[]]})};function b2(g5,g2){var g4,g1,g3;function g6(g7){return g1[((g4.get(g7)||g4.set(g7,g5.push(g7)))-1)%g1.length]}function e(g8,g7){return gg.range(g5.length).map(function(g9){return g8+g7*g9})}g6.domain=function(g7){if(!arguments.length){return g5}g5=[];g4=new aw();var g9=-1,ha=g7.length,g8;while(++g9=gF?g8?"M0,"+g7+"A"+g7+","+g7+" 0 1,1 0,"+-g7+"A"+g7+","+g7+" 0 1,1 0,"+g7+"M0,"+g8+"A"+g8+","+g8+" 0 1,0 0,"+-g8+"A"+g8+","+g8+" 0 1,0 0,"+g8+"Z":"M0,"+g7+"A"+g7+","+g7+" 0 1,1 0,"+-g7+"A"+g7+","+g7+" 0 1,1 0,"+g7+"Z":g8?"M"+g7*ha+","+g7*hd+"A"+g7+","+g7+" 0 "+hb+",1 "+g7*g9+","+g7*hc+"L"+g8*g9+","+g8*hc+"A"+g8+","+g8+" 0 "+hb+",0 "+g8*ha+","+g8*hd+"Z":"M"+g7*ha+","+g7*hd+"A"+g7+","+g7+" 0 "+hb+",1 "+g7*g9+","+g7*hc+"L0,0Z"}g1.innerRadius=function(g5){if(!arguments.length){return g4}g4=O(g5);return g1};g1.outerRadius=function(g5){if(!arguments.length){return g3}g3=O(g5);return g1};g1.startAngle=function(g5){if(!arguments.length){return g2}g2=O(g5);return g1};g1.endAngle=function(g5){if(!arguments.length){return e}e=O(g5);return g1};g1.centroid=function(){var g6=(g4.apply(this,arguments)+g3.apply(this,arguments))/2,g5=(g2.apply(this,arguments)+e.apply(this,arguments))/2+gx;return[Math.cos(g5)*g6,Math.sin(g5)*g6]};return g1};var gx=-fj/2,gF=2*fj-0.000001;function bf(e){return e.innerRadius}function cs(e){return e.outerRadius}function fn(e){return e.startAngle}function af(e){return e.endAngle}gg.svg.line.radial=function(){var e=eh(L);e.radius=e.x,delete e.x;e.angle=e.y,delete e.y;return e};function L(g3){var e,g2=-1,g5=g3.length,g4,g1;while(++g2fj)+",1 "+hc}function g3(hb,hd,ha,hc){return"Q 0,0 "+hc}g5.radius=function(ha){if(!arguments.length){return g6}g6=O(ha);return g5};g5.source=function(ha){if(!arguments.length){return e}e=O(ha);return g5};g5.target=function(ha){if(!arguments.length){return g7}g7=O(ha);return g5};g5.startAngle=function(ha){if(!arguments.length){return g8}g8=O(ha);return g5};g5.endAngle=function(ha){if(!arguments.length){return g4}g4=O(ha);return g5};return g5};function E(e){return e.radius}gg.svg.diagonal=function(){var g2=gy,g3=gJ,e=n;function g1(g8,g5){var g9=g2.call(this,g8,g5),g6=g3.call(this,g8,g5),g4=(g9.y+g6.y)/2,g7=[g9,{x:g9.x,y:g4},{x:g6.x,y:g4},g6];g7=g7.map(e);return"M"+g7[0]+"C"+g7[1]+" "+g7[2]+" "+g7[3]}g1.source=function(g4){if(!arguments.length){return g2}g2=O(g4);return g1};g1.target=function(g4){if(!arguments.length){return g3}g3=O(g4);return g1};g1.projection=function(g4){if(!arguments.length){return e}e=g4;return g1};return g1};function n(e){return[e.x,e.y]}gg.svg.diagonal.radial=function(){var g2=gg.svg.diagonal(),e=n,g1=g2.projection;g2.projection=function(g3){return arguments.length?g1(o(e=g3)):e};return g2};function o(e){return function(){var g3=e.apply(this,arguments),g2=g3[0],g1=g3[1]+gx;return[g2*Math.cos(g1),g2*Math.sin(g1)]}}gg.svg.symbol=function(){var g1=aJ,e=ea;function g2(g4,g3){return(gH.get(g1.call(this,g4,g3))||bQ)(e.call(this,g4,g3))}g2.type=function(g3){if(!arguments.length){return g1}g1=O(g3);return g2};g2.size=function(g3){if(!arguments.length){return e}e=O(g3);return g2};return g2};function ea(){return 64}function aJ(){return"circle"}function bQ(e){var g1=Math.sqrt(e/fj);return"M0,"+g1+"A"+g1+","+g1+" 0 1,1 0,"+-g1+"A"+g1+","+g1+" 0 1,1 0,"+g1+"Z"}var gH=gg.map({circle:bQ,cross:function(e){var g1=Math.sqrt(e/5)/2;return"M"+-3*g1+","+-g1+"H"+-g1+"V"+-3*g1+"H"+g1+"V"+-g1+"H"+3*g1+"V"+g1+"H"+g1+"V"+3*g1+"H"+-g1+"V"+g1+"H"+-3*g1+"Z"},diamond:function(e){var g1=Math.sqrt(e/(2*bW)),g2=g1*bW;return"M0,"+-g1+"L"+g2+",0 0,"+g1+" "+-g2+",0Z"},square:function(e){var g1=Math.sqrt(e)/2;return"M"+-g1+","+-g1+"L"+g1+","+-g1+" "+g1+","+g1+" "+-g1+","+g1+"Z"},"triangle-down":function(e){var g2=Math.sqrt(e/bI),g1=g2*bI/2;return"M0,"+g1+"L"+g2+","+-g1+" "+-g2+","+-g1+"Z"},"triangle-up":function(e){var g2=Math.sqrt(e/bI),g1=g2*bI/2;return"M0,"+-g1+"L"+g2+","+g1+" "+-g2+","+g1+"Z"}});gg.svg.symbolTypes=gH.keys();var bI=Math.sqrt(3),bW=Math.tan(30*F);function fO(e,g1){dY(e,dA);e.id=g1;return e}var dA=[],f6=0,ai,dl={ease:am,delay:0,duration:250};dA.call=df.call;dA.empty=df.empty;dA.node=df.node;gg.transition=function(e){return arguments.length?ai?e.transition():e:fP.transition()};gg.transition.prototype=dA;dA.select=function(g6){var e=this.id,g3=[],ha,g1,g4;if(typeof g6!=="function"){g6=w(g6)}for(var g7=-1,g5=this.length;++g7g6){return he()}g1.active=g6;g7.start.call(g3,hd,g2);g5.tween.forEach(function(hi,hj){if(hj=hj.call(g3,hd,g2)){hf.push(hj)}});if(!hc(hh)){gg.timer(hc,0,g4)}return 1}function hc(hh){if(g1.active!==g6){return he()}var hi=(hh-hb)/ha,hj=g9(hi),hk=hf.length;while(hk>0){hf[--hk].call(g3,hj)}if(hi>=1){he();g7.end.call(g3,hd,g2);return 1}}function he(){if(--g1.count){delete g1[g6]}else{delete g3.__transition__}return 1}},0,g4);return g5}}gg.svg.axis=function(){var g3=gg.scale.linear(),g6=gN,g4=6,e=6,g1=6,ha=3,g9=[10],g5=null,g8,g7=0;function g2(hb){hb.each(function(){var hx=gg.select(this);var hz=g5==null?g3.ticks?g3.ticks.apply(g3,g9):g3.domain():g5,hq=g8==null?g3.tickFormat?g3.tickFormat.apply(g3,g9):String:g8;var hg=cu(g3,hz,g7),hf=hx.selectAll(".tick.minor").data(hg,String),hs=hf.enter().insert("line",".tick").attr("class","tick minor").style("opacity",0.000001),hr=gg.transition(hf.exit()).style("opacity",0.000001).remove(),hd=gg.transition(hf).style("opacity",1);var hv=hx.selectAll(".tick.major").data(hz,String),hn=hv.enter().insert("g","path").attr("class","tick major").style("opacity",0.000001),hp=gg.transition(hv.exit()).style("opacity",0.000001).remove(),hi=gg.transition(hv).style("opacity",1),hc;var hm=aM(g3),ho=hx.selectAll(".domain").data([0]),hA=(ho.enter().append("path").attr("class","domain"),gg.transition(ho));var ht=g3.copy(),hu=this.__chart__||ht;this.__chart__=ht;hn.append("line");hn.append("text");var he=hn.select("line"),hy=hi.select("line"),hl=hv.select("text").text(hq),hh=hn.select("text"),hw=hi.select("text");switch(g6){case"bottom":hc=ev;hs.attr("y2",e);hd.attr("x2",0).attr("y2",e);he.attr("y2",g4);hh.attr("y",Math.max(g4,0)+ha);hy.attr("x2",0).attr("y2",g4);hw.attr("x",0).attr("y",Math.max(g4,0)+ha);hl.attr("dy",".71em").style("text-anchor","middle");hA.attr("d","M"+hm[0]+","+g1+"V0H"+hm[1]+"V"+g1);break;case"top":hc=ev;hs.attr("y2",-e);hd.attr("x2",0).attr("y2",-e);he.attr("y2",-g4);hh.attr("y",-(Math.max(g4,0)+ha));hy.attr("x2",0).attr("y2",-g4);hw.attr("x",0).attr("y",-(Math.max(g4,0)+ha));hl.attr("dy","0em").style("text-anchor","middle");hA.attr("d","M"+hm[0]+","+-g1+"V0H"+hm[1]+"V"+-g1);break;case"left":hc=eu;hs.attr("x2",-e);hd.attr("x2",-e).attr("y2",0);he.attr("x2",-g4);hh.attr("x",-(Math.max(g4,0)+ha));hy.attr("x2",-g4).attr("y2",0);hw.attr("x",-(Math.max(g4,0)+ha)).attr("y",0);hl.attr("dy",".32em").style("text-anchor","end");hA.attr("d","M"+-g1+","+hm[0]+"H0V"+hm[1]+"H"+-g1);break;case"right":hc=eu;hs.attr("x2",e);hd.attr("x2",e).attr("y2",0);he.attr("x2",g4);hh.attr("x",Math.max(g4,0)+ha);hy.attr("x2",g4).attr("y2",0);hw.attr("x",Math.max(g4,0)+ha).attr("y",0);hl.attr("dy",".32em").style("text-anchor","start");hA.attr("d","M"+g1+","+hm[0]+"H0V"+hm[1]+"H"+g1);break}if(g3.ticks){hn.call(hc,hu);hi.call(hc,ht);hp.call(hc,ht);hs.call(hc,hu);hd.call(hc,ht);hr.call(hc,ht)}else{var hk=ht.rangeBand()/2,hj=function(hB){return ht(hB)+hk};hn.call(hc,hj);hi.call(hc,hj)}})}g2.scale=function(hb){if(!arguments.length){return g3}g3=hb;return g2};g2.orient=function(hb){if(!arguments.length){return g6}g6=hb in d5?hb+"":gN;return g2};g2.ticks=function(){if(!arguments.length){return g9}g9=arguments;return g2};g2.tickValues=function(hb){if(!arguments.length){return g5}g5=hb;return g2};g2.tickFormat=function(hb){if(!arguments.length){return g8}g8=hb;return g2};g2.tickSize=function(hb,hd){if(!arguments.length){return g4}var hc=arguments.length-1;g4=+hb;e=hc>1?+hd:g4;g1=hc>0?+arguments[hc]:g4;return g2};g2.tickPadding=function(hb){if(!arguments.length){return ha}ha=+hb;return g2};g2.tickSubdivide=function(hb){if(!arguments.length){return g7}g7=+hb;return g2};return g2};var gN="bottom",d5={top:1,right:1,bottom:1,left:1};function ev(g1,e){g1.attr("transform",function(g2){return"translate("+e(g2)+",0)"})}function eu(e,g1){e.attr("transform",function(g2){return"translate(0,"+g1(g2)+")"})}function cu(g2,g6,g1){g7=[];if(g1&&g6.length>1){var g9=dz(g2.domain()),g7,g4=-1,e=g6.length,g5=(g6[1]-g6[0])/++g1,g3,g8;while(++g40;){if((g8=+g6[g4]-g3*g5)>=g9[0]){g7.push(g8)}}}for(--g4,g3=0;++g3rect,.s>rect").attr("width",ha[1][0]-ha[0][0])}function g2(hb){hb.select(".extent").attr("y",ha[0][1]);hb.selectAll(".extent,.e>rect,.w>rect").attr("height",ha[1][1]-ha[0][1])}function g8(){var hu=this,hc=gg.select(gg.event.target),hi=e.of(hu,arguments),hp=gg.select(hu),hr=hc.datum(),hn=!/^(n|s)$/.test(hr)&&g6,hl=!/^(e|w)$/.test(hr)&&g5,hm=hc.classed("extent"),hs,ht=hj(),hd;var hg=gg.select(eH).on("mousemove.brush",hh).on("mouseup.brush",hb).on("touchmove.brush",hh).on("touchend.brush",hb).on("keydown.brush",hk).on("keyup.brush",hf);if(hm){ht[0]=ha[0][0]-ht[0];ht[1]=ha[0][1]-ht[1]}else{if(hr){var hq=+/w$/.test(hr),ho=+/^n/.test(hr);hd=[ha[1-hq][0]-ht[0],ha[1-ho][1]-ht[1]];ht[0]=ha[hq][0];ht[1]=ha[ho][1]}else{if(gg.event.altKey){hs=ht.slice()}}}hp.style("pointer-events","none").selectAll(".resize").style("display",null);gg.select("body").style("cursor",hc.style("cursor"));hi({type:"brushstart"});hh();gA();function hj(){var hv=gg.event.changedTouches;return hv?gg.touches(hu,hv)[0]:gg.mouse(hu)}function hk(){if(gg.event.keyCode==32){if(!hm){hs=null;ht[0]-=ha[1][0];ht[1]-=ha[1][1];hm=2}gA()}}function hf(){if(gg.event.keyCode==32&&hm==2){ht[0]+=ha[1][0];ht[1]+=ha[1][1];hm=0;gA()}}function hh(){var hv=hj(),hw=false;if(hd){hv[0]+=hd[0];hv[1]+=hd[1]}if(!hm){if(gg.event.altKey){if(!hs){hs=[(ha[0][0]+ha[1][0])/2,(ha[0][1]+ha[1][1])/2]}ht[0]=ha[+(hv[0]1?Date.UTC.apply(this,arguments):arguments[0])}f0.prototype={getDate:function(){return this._.getUTCDate()},getDay:function(){return this._.getUTCDay()},getFullYear:function(){return this._.getUTCFullYear()},getHours:function(){return this._.getUTCHours()},getMilliseconds:function(){return this._.getUTCMilliseconds()},getMinutes:function(){return this._.getUTCMinutes()},getMonth:function(){return this._.getUTCMonth()},getSeconds:function(){return this._.getUTCSeconds()},getTime:function(){return this._.getTime()},getTimezoneOffset:function(){return 0},valueOf:function(){return this._.valueOf()},setDate:function(){e8.setUTCDate.apply(this._,arguments)},setDay:function(){e8.setUTCDay.apply(this._,arguments)},setFullYear:function(){e8.setUTCFullYear.apply(this._,arguments)},setHours:function(){e8.setUTCHours.apply(this._,arguments)},setMilliseconds:function(){e8.setUTCMilliseconds.apply(this._,arguments)},setMinutes:function(){e8.setUTCMinutes.apply(this._,arguments)},setMonth:function(){e8.setUTCMonth.apply(this._,arguments)},setSeconds:function(){e8.setUTCSeconds.apply(this._,arguments)},setTime:function(){e8.setTime.apply(this._,arguments)}};var e8=Date.prototype;var N="%a %b %e %X %Y",dV="%m/%d/%Y",cI="%H:%M:%S";var bV=["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],ct=["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],bZ=["January","February","March","April","May","June","July","August","September","October","November","December"],ay=["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"];function cA(g5,e,g1){function g8(g9){var hb=g5(g9),ha=g3(hb,1);return g9-hb1){while(hc=g2){return -1}g7=g8.charCodeAt(g5++);if(g7===37){e=gI[g8.charAt(g5++)];if(!e||(g4=e(g3,g6,g4))<0){return -1}}else{if(g7!=g6.charCodeAt(g4++)){return -1}}}return g4}function ck(e){return new RegExp("^(?:"+e.map(gg.requote).join("|")+")","i")}function fT(g2){var g1=new aw(),e=-1,g3=g2.length;while(++e=12?"PM":"AM"},S:function(g1,e){return cS(g1.getSeconds(),e,2)},U:function(g1,e){return cS(gg.time.sundayOfYear(g1),e,2)},w:function(e){return e.getDay()},W:function(g1,e){return cS(gg.time.mondayOfYear(g1),e,2)},x:gg.time.format(dV),X:gg.time.format(cI),y:function(g1,e){return cS(g1.getFullYear()%100,e,2)},Y:function(g1,e){return cS(g1.getFullYear()%10000,e,4)},Z:e3,"%":function(){return"%"}};var gI={a:fZ,A:ce,b:aO,B:d1,c:dB,d:bC,e:bC,H:aE,I:aE,L:fU,m:fK,M:cw,p:gr,S:de,x:gX,X:fH,y:K,Y:be};function fZ(g1,e,g2){eR.lastIndex=0;var g3=eR.exec(e.substring(g2));return g3?g2+=g3[0].length:-1}function ce(g1,e,g2){gt.lastIndex=0;var g3=gt.exec(e.substring(g2));return g3?g2+=g3[0].length:-1}function aO(g1,e,g2){d0.lastIndex=0;var g3=d0.exec(e.substring(g2));return g3?(g1.m=d8.get(g3[0].toLowerCase()),g2+=g3[0].length):-1}function d1(g1,e,g2){f4.lastIndex=0;var g3=f4.exec(e.substring(g2));return g3?(g1.m=eE.get(g3[0].toLowerCase()),g2+=g3[0].length):-1}function dB(g1,e,g2){return dk(g1,fD.c.toString(),e,g2)}function gX(g1,e,g2){return dk(g1,fD.x.toString(),e,g2)}function fH(g1,e,g2){return dk(g1,fD.X.toString(),e,g2)}function be(g1,e,g2){fe.lastIndex=0;var g3=fe.exec(e.substring(g2,g2+4));return g3?(g1.y=+g3[0],g2+=g3[0].length):-1}function K(g1,e,g2){fe.lastIndex=0;var g3=fe.exec(e.substring(g2,g2+2));return g3?(g1.y=cD(+g3[0]),g2+=g3[0].length):-1}function cD(e){return e+(e>68?1900:2000)}function fK(g1,e,g2){fe.lastIndex=0;var g3=fe.exec(e.substring(g2,g2+2));return g3?(g1.m=g3[0]-1,g2+=g3[0].length):-1}function bC(g1,e,g2){fe.lastIndex=0;var g3=fe.exec(e.substring(g2,g2+2));return g3?(g1.d=+g3[0],g2+=g3[0].length):-1}function aE(g1,e,g2){fe.lastIndex=0;var g3=fe.exec(e.substring(g2,g2+2));return g3?(g1.H=+g3[0],g2+=g3[0].length):-1}function cw(g1,e,g2){fe.lastIndex=0;var g3=fe.exec(e.substring(g2,g2+2));return g3?(g1.M=+g3[0],g2+=g3[0].length):-1}function de(g1,e,g2){fe.lastIndex=0;var g3=fe.exec(e.substring(g2,g2+2));return g3?(g1.S=+g3[0],g2+=g3[0].length):-1}function fU(g1,e,g2){fe.lastIndex=0;var g3=fe.exec(e.substring(g2,g2+3));return g3?(g1.L=+g3[0],g2+=g3[0].length):-1}var fe=/^\s*\d+/;function gr(g1,e,g2){var g3=f.get(e.substring(g2,g2+=2).toLowerCase());return g3==null?-1:(g1.p=g3,g2)}var f=gg.map({am:0,pm:1});function e3(g4){var g3=g4.getTimezoneOffset(),g2=g3>0?"-":"+",g1=~~(Math.abs(g3)/60),e=Math.abs(g3)%60;return g2+cS(g1,"0",2)+cS(e,"0",2)}gg.time.format.utc=function(g1){var e=gg.time.format(g1);function g2(g3){try{eP=f0;var g4=new eP();g4._=g3;return e(g4)}finally{eP=Date}}g2.parse=function(g4){try{eP=f0;var g3=e.parse(g4);return g3&&g3._}finally{eP=Date}};g2.toString=e.toString;return g2};var c7=gg.time.format.utc("%Y-%m-%dT%H:%M:%S.%LZ");gg.time.format.iso=Date.prototype.toISOString&&+new Date("2000-01-01T00:00:00.000Z")?cR:c7;function cR(e){return e.toISOString()}cR.parse=function(g1){var e=new Date(g1);return isNaN(e)?null:e};cR.toString=c7.toString;gg.time.second=cA(function(e){return new eP(Math.floor(e/1000)*1000)},function(e,g1){e.setTime(e.getTime()+Math.floor(g1)*1000)},function(e){return e.getSeconds()});gg.time.seconds=gg.time.second.range;gg.time.seconds.utc=gg.time.second.utc.range;gg.time.minute=cA(function(e){return new eP(Math.floor(e/60000)*60000)},function(e,g1){e.setTime(e.getTime()+Math.floor(g1)*60000)},function(e){return e.getMinutes()});gg.time.minutes=gg.time.minute.range;gg.time.minutes.utc=gg.time.minute.utc.range;gg.time.hour=cA(function(e){var g1=e.getTimezoneOffset()/60;return new eP((Math.floor(e/3600000-g1)+g1)*3600000)},function(e,g1){e.setTime(e.getTime()+Math.floor(g1)*3600000)},function(e){return e.getHours()});gg.time.hours=gg.time.hour.range;gg.time.hours.utc=gg.time.hour.utc.range;gg.time.month=cA(function(e){e=gg.time.day(e);e.setDate(1);return e},function(e,g1){e.setMonth(e.getMonth()+g1)},function(e){return e.getMonth()});gg.time.months=gg.time.month.range;gg.time.months.utc=gg.time.month.utc.range;function a5(e,g1,g2){function g3(g4){return e(g4)}g3.invert=function(g4){return e7(e.invert(g4))};g3.domain=function(g4){if(!arguments.length){return e.domain().map(e7)}e.domain(g4);return g3};g3.nice=function(g4){return g3.domain(fq(g3.domain(),function(){return g4}))};g3.ticks=function(g4,g5){var g8=e0(g3.domain());if(typeof g4!=="function"){var g7=g8[1]-g8[0],g9=g7/g4,g6=gg.bisect(ei,g9);if(g6==ei.length){return g1.year(g8,g4)}if(!g6){return e.ticks(g4).map(e7)}if(Math.log(g9/ei[g6-1])0){for(var e=0,b=f.length;e0){for(var e=0,b=f.length;e2){m.push("'"+this.terminals_[y]+"'")}}var A="";if(this.lexer.showPosition){A="Parse error on line "+(q+1)+":\n"+this.lexer.showPosition()+"\nExpecting "+m.join(", ")+", got '"+this.terminals_[D]+"'"}else{A="Parse error on line "+(q+1)+": Unexpected "+(D==1?"end of input":"'"+(this.terminals_[D]||D)+"'")}this.parseError(A,{text:this.lexer.match,token:this.terminals_[D]||D,line:this.lexer.yylineno,loc:i,expected:m})}}if(C[0] instanceof Array&&C.length>1){throw new Error("Parse Error: multiple actions possible at state: "+k+", token: "+D)}switch(C[0]){case 1:l.push(D);G.push(this.lexer.yytext);s.push(this.lexer.yylloc);l.push(C[1]);D=null;if(!z){E=this.lexer.yyleng;h=this.lexer.yytext;q=this.lexer.yylineno;i=this.lexer.yylloc;if(j>0){j--}}else{D=z;z=null}break;case 2:F=this.productions_[C[1]][1];B.$=G[G.length-F];B._$={first_line:s[s.length-(F||1)].first_line,last_line:s[s.length-1].last_line,first_column:s[s.length-(F||1)].first_column,last_column:s[s.length-1].last_column};t=this.performAction.call(B,h,E,q,this.yy,C[1],G,s);if(typeof t!=="undefined"){return t}if(F){l=l.slice(0,-1*F*2);G=G.slice(0,-1*F);s=s.slice(0,-1*F)}l.push(this.productions_[C[1]][0]);G.push(B.$);s.push(B._$);g=H[l[l.length-2]][l[l.length-1]];l.push(g);break;case 3:return true}}return true}};var a=(function(){var j=({EOF:1,parseError:function l(o,n){if(this.yy.parseError){this.yy.parseError(o,n)}else{throw new Error(o)}},setInput:function(n){this._input=n;this._more=this._less=this.done=false;this.yylineno=this.yyleng=0;this.yytext=this.matched=this.match="";this.conditionStack=["INITIAL"];this.yylloc={first_line:1,first_column:0,last_line:1,last_column:0};return this},input:function(){var o=this._input[0];this.yytext+=o;this.yyleng++;this.match+=o;this.matched+=o;var n=o.match(/\n/);if(n){this.yylineno++}this._input=this._input.slice(1);return o},unput:function(n){this._input=n+this._input;return this},more:function(){this._more=true;return this},pastInput:function(){var n=this.matched.substr(0,this.matched.length-this.match.length);return(n.length>20?"...":"")+n.substr(-20).replace(/\n/g,"")},upcomingInput:function(){var n=this.match;if(n.length<20){n+=this._input.substr(0,20-n.length)}return(n.substr(0,20)+(n.length>20?"...":"")).replace(/\n/g,"")},showPosition:function(){var n=this.pastInput();var o=new Array(n.length+1).join("-");return n+this.upcomingInput()+"\n"+o+"^"},next:function(){if(this.done){return this.EOF}if(!this._input){this.done=true}var r,p,o,n;if(!this._more){this.yytext="";this.match=""}var s=this._currentRules();for(var q=0;q/,/^\{\{#/,/^\{\{\//,/^\{\{\^/,/^\{\{\s*else\b/,/^\{\{\{/,/^\{\{&/,/^\{\{![\s\S]*?\}\}/,/^\{\{/,/^=/,/^\.(?=[} ])/,/^\.\./,/^[\/.]/,/^\s+/,/^\}\}\}/,/^\}\}/,/^"(\\["]|[^"])*"/,/^true(?=[}\s])/,/^false(?=[}\s])/,/^[0-9]+(?=[}\s])/,/^[a-zA-Z0-9_$-]+(?=[=}\s\/.])/,/^\[[^\]]*\]/,/^./,/^$/];j.conditions={mu:{rules:[3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26],inclusive:false},emu:{rules:[2],inclusive:false},INITIAL:{rules:[0,1,26],inclusive:true}};return j})();f.lexer=a;return f})();if(typeof require!=="undefined"&&typeof exports!=="undefined"){exports.parser=handlebars;exports.parse=function(){return handlebars.parse.apply(handlebars,arguments)};exports.main=function commonjsMain(a){if(!a[1]){throw new Error("Usage: "+a[0]+" FILE")}if(typeof process!=="undefined"){var c=require("fs").readFileSync(require("path").join(process.cwd(),a[1]),"utf8")}else{var b=require("file").path(require("file").cwd());var c=b.join(a[1]).read({charset:"utf-8"})}return exports.parser.parse(c)};if(typeof module!=="undefined"&&require.main===module){exports.main(typeof process!=="undefined"?process.argv.slice(1):require("system").args)}}Handlebars.Parser=handlebars;Handlebars.parse=function(a){Handlebars.Parser.yy=Handlebars.AST;return Handlebars.Parser.parse(a)};Handlebars.print=function(a){return new Handlebars.PrintVisitor().accept(a)};Handlebars.logger={DEBUG:0,INFO:1,WARN:2,ERROR:3,level:3,log:function(b,a){}};Handlebars.log=function(b,a){Handlebars.logger.log(b,a)};(function(){Handlebars.AST={};Handlebars.AST.ProgramNode=function(c,b){this.type="program";this.statements=c;if(b){this.inverse=new Handlebars.AST.ProgramNode(b)}};Handlebars.AST.MustacheNode=function(d,c,b){this.type="mustache";this.id=d[0];this.params=d.slice(1);this.hash=c;this.escaped=!b};Handlebars.AST.PartialNode=function(c,b){this.type="partial";this.id=c;this.context=b};var a=function(b,c){if(b.original!==c.original){throw new Handlebars.Exception(b.original+" doesn't match "+c.original)}};Handlebars.AST.BlockNode=function(c,b,d){a(c.id,d);this.type="block";this.mustache=c;this.program=b};Handlebars.AST.InverseNode=function(c,b,d){a(c.id,d);this.type="inverse";this.mustache=c;this.program=b};Handlebars.AST.ContentNode=function(b){this.type="content";this.string=b};Handlebars.AST.HashNode=function(b){this.type="hash";this.pairs=b};Handlebars.AST.IdNode=function(f){this.type="ID";this.original=f.join(".");var d=[],g=0;for(var e=0,b=f.length;e":">",'"':""","'":"'","`":"`"};var d=/&(?!\w+;)|[<>"'`]/g;var b=/[&<>"'`]/;var a=function(e){return c[e]||"&"};Handlebars.Utils={escapeExpression:function(e){if(e instanceof Handlebars.SafeString){return e.toString()}else{if(e==null||e===false){return""}}if(!b.test(e)){return e}return e.replace(d,a)},isEmpty:function(e){if(typeof e==="undefined"){return true}else{if(e===null){return true}else{if(e===false){return true}else{if(Object.prototype.toString.call(e)==="[object Array]"&&e.length===0){return true}else{return false}}}}}}})();Handlebars.Compiler=function(){};Handlebars.JavaScriptCompiler=function(){};(function(f,e){f.OPCODE_MAP={appendContent:1,getContext:2,lookupWithHelpers:3,lookup:4,append:5,invokeMustache:6,appendEscaped:7,pushString:8,truthyOrFallback:9,functionOrFallback:10,invokeProgram:11,invokePartial:12,push:13,assignToHash:15,pushStringParam:16};f.MULTI_PARAM_OPCODES={appendContent:1,getContext:1,lookupWithHelpers:2,lookup:1,invokeMustache:3,pushString:1,truthyOrFallback:1,functionOrFallback:1,invokeProgram:3,invokePartial:1,push:1,assignToHash:1,pushStringParam:1};f.DISASSEMBLE_MAP={};for(var h in f.OPCODE_MAP){var g=f.OPCODE_MAP[h];f.DISASSEMBLE_MAP[g]=h}f.multiParamSize=function(i){return f.MULTI_PARAM_OPCODES[f.DISASSEMBLE_MAP[i]]};f.prototype={compiler:f,disassemble:function(){var t=this.opcodes,r,n;var q=[],v,m,w;for(var s=0,o=t.length;s0){this.source[1]=this.source[1]+", "+q.join(", ")}if(!this.isChild){var k=[];for(var o in this.context.aliases){this.source[1]=this.source[1]+", "+o+"="+this.context.aliases[o]}}if(this.source[1]){this.source[1]="var "+this.source[1].substring(2)+";"}if(!this.isChild){this.source[1]+="\n"+this.context.programs.join("\n")+"\n"}if(!this.environment.isSimple){this.source.push("return buffer;")}var r=this.isChild?["depth0","data"]:["Handlebars","depth0","helpers","partials","data"];for(var n=0,j=this.environment.depths.list.length;nthis.stackVars.length){this.stackVars.push("stack"+this.stackSlot)}return"stack"+this.stackSlot},popStack:function(){return"stack"+this.stackSlot--},topStack:function(){return"stack"+this.stackSlot},quotedString:function(i){return'"'+i.replace(/\\/g,"\\\\").replace(/"/g,'\\"').replace(/\n/g,"\\n").replace(/\r/g,"\\r")+'"'}};var a=("break else new var case finally return void catch for switch while continue function this with default if throw delete in try do instanceof typeof abstract enum int short boolean export interface static byte extends long super char final native synchronized class float package throws const goto private transient debugger implements protected volatile double import public let yield").split(" ");var d=e.RESERVED_WORDS={};for(var c=0,b=a.length;c0)},toString:function(){var a=this.get("id")||"";if(this.get("name")){a+=':"'+this.get("name")+'"'}return"HistoryDatasetAssociation("+a+")"}});HistoryDatasetAssociation.STATES={UPLOAD:"upload",QUEUED:"queued",RUNNING:"running",SETTING_METADATA:"setting_metadata",NEW:"new",OK:"ok",EMPTY:"empty",FAILED_METADATA:"failed_metadata",NOT_VIEWABLE:"noPermission",DISCARDED:"discarded",ERROR:"error"};var HDACollection=Backbone.Collection.extend(LoggableMixin).extend({model:HistoryDatasetAssociation,initialize:function(){},ids:function(){return this.map(function(a){return a.id})},getVisible:function(a,b){return this.filter(function(c){return c.isVisible(a,b)})},getStateLists:function(){var a={};_.each(_.values(HistoryDatasetAssociation.STATES),function(b){a[b]=[]});this.each(function(b){a[b.get("state")].push(b.get("id"))});return a},running:function(){var a=[];this.each(function(b){if(!b.inReadyState()){a.push(b.get("id"))}});return a},update:function(a){this.log(this+"update:",a);if(!(a&&a.length)){return}var b=this;_.each(a,function(e,c){var d=b.get(e);d.fetch()})},toString:function(){return("HDACollection("+this.ids().join(",")+")")}});var History=BaseModel.extend(LoggableMixin).extend({defaults:{id:"",name:"",state:"",show_deleted:false,show_hidden:false,diskSize:0,deleted:false,tags:[],annotation:null,message:null,quotaMsg:false},url:function(){return"api/histories/"+this.get("id")},initialize:function(a,b){this.log(this+".initialize:",a,b);this.hdas=new HDACollection();if(b&&b.length){this.hdas.reset(b);this.checkForUpdates()}},loadFromApi:function(a,c){var b=this;b.attributes.id=a;jQuery.when(jQuery.ajax("api/users/current"),b.fetch()).then(function(e,d){b.attributes.user=e[0];b.log(b)}).then(function(){jQuery.ajax(b.url()+"/contents?"+jQuery.param({ids:b.itemIdsFromStateIds().join(",")})).success(function(d){b.hdas.reset(d);b.checkForUpdates();c()})})},hdaIdsFromStateIds:function(){return _.reduce(_.values(this.get("state_ids")),function(b,a){return b.concat(a)})},checkForUpdates:function(a){if(this.hdas.running().length){this.stateUpdater()}return this},stateUpdater:function(){var c=this,a=this.get("state"),b=this.get("state_ids");jQuery.ajax("api/histories/"+this.get("id")).success(function(d){c.set(d);c.log("current history state:",c.get("state"),"(was)",a,"new size:",c.get("nice_size"));var e=[];_.each(_.keys(d.state_ids),function(g){var f=_.difference(d.state_ids[g],b[g]);e=e.concat(f)});if(e.length){c.hdas.update(e)}if((c.get("state")===HistoryDatasetAssociation.STATES.RUNNING)||(c.get("state")===HistoryDatasetAssociation.STATES.QUEUED)){setTimeout(function(){c.stateUpdater()},4000)}}).error(function(f,d,e){if(console&&console.warn){console.warn("Error getting history updates from the server:",f,d,e)}alert("Error getting history updates from the server.\n"+e)})},toString:function(){var a=(this.get("name"))?(","+this.get("name")):("");return"History("+this.get("id")+a+")"}});var HDAView=BaseView.extend(LoggableMixin).extend({tagName:"div",className:"historyItemContainer",initialize:function(a){this.log(this+".initialize:",a);if(!a.urlTemplates){throw ("HDAView needs urlTemplates on initialize")}this.urls=this.renderUrls(a.urlTemplates,this.model.toJSON());this.expanded=a.expanded||false;this.model.bind("change",this.render,this)},renderUrls:function(d,a){var b=this,c={};_.each(d,function(e,f){if(_.isObject(e)){c[f]=b.renderUrls(e,a)}else{if(f==="meta_download"){c[f]=b.renderMetaDownloadUrls(e,a)}else{c[f]=_.template(e,a)}}});return c},renderMetaDownloadUrls:function(b,a){return _.map(a.meta_files,function(c){return{url:_.template(b,{id:a.id,file_type:c.file_type}),file_type:c.file_type}})},render:function(){var b=this,e=this.model.get("id"),c=this.model.get("state"),a=$("
    ").attr("id","historyItem-"+e),d=(this.$el.children().size()===0);this._clearReferences();this.$el.attr("id","historyItemContainer-"+e);a.addClass("historyItemWrapper").addClass("historyItem").addClass("historyItem-"+c);a.append(this._render_warnings());a.append(this._render_titleBar());this.body=$(this._render_body());a.append(this.body);make_popup_menus(a);a.find(".tooltip").tooltip({placement:"bottom"});this.$el.fadeOut("fast",function(){b.$el.children().remove();b.$el.append(a).fadeIn("fast",function(){b.log(b+" rendered:",b.$el);var f="rendered";if(d){f+=":initial"}else{if(b.model.inReadyState()){f+=":ready"}}b.trigger(f)})});return this},_clearReferences:function(){this.displayButton=null;this.editButton=null;this.deleteButton=null;this.errButton=null;this.showParamsButton=null;this.rerunButton=null;this.visualizationsButton=null;this.tagButton=null;this.annotateButton=null},_render_warnings:function(){return $(jQuery.trim(HDAView.templates.messages(_.extend(this.model.toJSON(),{urls:this.urls}))))},_render_titleBar:function(){var a=$('
    ');a.append(this._render_titleButtons());a.append('');a.append(this._render_titleLink());return a},_render_titleButtons:function(){var a=$('
    ');a.append(this._render_displayButton());a.append(this._render_editButton());a.append(this._render_deleteButton());return a},_render_displayButton:function(){if((!this.model.inReadyState())||(this.model.get("state")===HistoryDatasetAssociation.STATES.ERROR)||(this.model.get("state")===HistoryDatasetAssociation.STATES.NOT_VIEWABLE)||(!this.model.get("accessible"))){return null}var a={icon_class:"display"};if(this.model.get("purged")){a.enabled=false;a.title="Cannot display datasets removed from disk"}else{a.title="Display data in browser";a.href=this.urls.display}if(this.model.get("for_editing")){a.target="galaxy_main"}this.displayButton=new IconButtonView({model:new IconButton(a)});return this.displayButton.render().$el},_render_editButton:function(){if((this.model.get("state")===HistoryDatasetAssociation.STATES.UPLOAD)||(this.model.get("state")===HistoryDatasetAssociation.STATES.ERROR)||(this.model.get("state")===HistoryDatasetAssociation.STATES.NOT_VIEWABLE)||(!this.model.get("accessible"))||(!this.model.get("for_editing"))){return null}var c=this.model.get("purged"),a=this.model.get("deleted"),b={title:"Edit attributes",href:this.urls.edit,target:"galaxy_main",icon_class:"edit"};if(a||c){b.enabled=false;if(c){b.title="Cannot edit attributes of datasets removed from disk"}else{if(a){b.title="Undelete dataset to edit attributes"}}}this.editButton=new IconButtonView({model:new IconButton(b)});return this.editButton.render().$el},_render_deleteButton:function(){if((!this.model.get("for_editing"))||(this.model.get("state")===HistoryDatasetAssociation.STATES.NOT_VIEWABLE)||(!this.model.get("accessible"))){return null}var a={title:"Delete",href:this.urls["delete"],id:"historyItemDeleter-"+this.model.get("id"),icon_class:"delete"};if(this.model.get("deleted")||this.model.get("purged")){a={title:"Dataset is already deleted",icon_class:"delete",enabled:false}}this.deleteButton=new IconButtonView({model:new IconButton(a)});return this.deleteButton.render().$el},_render_titleLink:function(){return $(jQuery.trim(HDAView.templates.titleLink(_.extend(this.model.toJSON(),{urls:this.urls}))))},_render_hdaSummary:function(){var a=_.extend(this.model.toJSON(),{urls:this.urls});if(this.model.get("metadata_dbkey")==="?"&&!this.model.isDeletedOrPurged()){_.extend(a,{dbkey_unknown_and_editable:true})}return HDAView.templates.hdaSummary(a)},_render_primaryActionButtons:function(c){var b=$("
    ").attr("id","primary-actions-"+this.model.get("id")),a=this;_.each(c,function(d){b.append(d.call(a))});return b},_render_downloadButton:function(){if(this.model.get("purged")){return null}var a=HDAView.templates.downloadLinks(_.extend(this.model.toJSON(),{urls:this.urls}));return $(a)},_render_errButton:function(){if((this.model.get("state")!==HistoryDatasetAssociation.STATES.ERROR)||(!this.model.get("for_editing"))){return null}this.errButton=new IconButtonView({model:new IconButton({title:"View or report this error",href:this.urls.report_error,target:"galaxy_main",icon_class:"bug"})});return this.errButton.render().$el},_render_showParamsButton:function(){this.showParamsButton=new IconButtonView({model:new IconButton({title:"View details",href:this.urls.show_params,target:"galaxy_main",icon_class:"information"})});return this.showParamsButton.render().$el},_render_rerunButton:function(){if(!this.model.get("for_editing")){return null}this.rerunButton=new IconButtonView({model:new IconButton({title:"Run this job again",href:this.urls.rerun,target:"galaxy_main",icon_class:"arrow-circle"})});return this.rerunButton.render().$el},_render_visualizationsButton:function(){var c=this.model.get("dbkey"),a=this.model.get("visualizations"),f=this.urls.visualization,d={},g={dataset_id:this.model.get("id"),hda_ldda:"hda"};if(!(this.model.hasData())||!(this.model.get("for_editing"))||!(a&&a.length)||!(f)){return null}this.visualizationsButton=new IconButtonView({model:new IconButton({title:"Visualize",href:f,icon_class:"chart_curve"})});var b=this.visualizationsButton.render().$el;b.addClass("visualize-icon");if(c){g.dbkey=c}function e(h){switch(h){case"trackster":return create_trackster_action_fn(f,g,c);case"scatterplot":return create_scatterplot_action_fn(f,g);default:return function(){window.parent.location=f+"/"+h+"?"+$.param(g)}}}if(a.length===1){b.attr("title",a[0]);b.click(e(a[0]))}else{_.each(a,function(i){var h=i.charAt(0).toUpperCase()+i.slice(1);d[h]=e(i)});make_popupmenu(b,d)}return b},_render_secondaryActionButtons:function(b){var c=$("
    "),a=this;c.attr("style","float: right;").attr("id","secondary-actions-"+this.model.get("id"));_.each(b,function(d){c.append(d.call(a))});return c},_render_tagButton:function(){if(!(this.model.hasData())||!(this.model.get("for_editing"))||(!this.urls.tags.get)){return null}this.tagButton=new IconButtonView({model:new IconButton({title:"Edit dataset tags",target:"galaxy_main",href:this.urls.tags.get,icon_class:"tags"})});return this.tagButton.render().$el},_render_annotateButton:function(){if(!(this.model.hasData())||!(this.model.get("for_editing"))||(!this.urls.annotation.get)){return null}this.annotateButton=new IconButtonView({model:new IconButton({title:"Edit dataset annotation",target:"galaxy_main",icon_class:"annotate"})});return this.annotateButton.render().$el},_render_displayApps:function(){if(!this.model.hasData()){return null}var a=$("
    ").addClass("display-apps");if(!_.isEmpty(this.model.get("display_types"))){a.append(HDAView.templates.displayApps({displayApps:this.model.get("display_types")}))}if(!_.isEmpty(this.model.get("display_apps"))){a.append(HDAView.templates.displayApps({displayApps:this.model.get("display_apps")}))}return a},_render_tagArea:function(){if(!this.urls.tags.set){return null}return $(HDAView.templates.tagArea(_.extend(this.model.toJSON(),{urls:this.urls})))},_render_annotationArea:function(){if(!this.urls.annotation.get){return null}return $(HDAView.templates.annotationArea(_.extend(this.model.toJSON(),{urls:this.urls})))},_render_peek:function(){if(!this.model.get("peek")){return null}return $("
    ").append($("
    ").attr("id","peek"+this.model.get("id")).addClass("peek").append(this.model.get("peek")))},_render_body_not_viewable:function(a){a.append($("
    You do not have permission to view dataset.
    "))},_render_body_uploading:function(a){a.append($("
    Dataset is uploading
    "))},_render_body_queued:function(a){a.append($("
    Job is waiting to run.
    "));a.append(this._render_primaryActionButtons([this._render_showParamsButton,this._render_rerunButton]))},_render_body_running:function(a){a.append("
    Job is currently running.
    ");a.append(this._render_primaryActionButtons([this._render_showParamsButton,this._render_rerunButton]))},_render_body_error:function(a){if(!this.model.get("purged")){a.append($("
    "+this.model.get("misc_blurb")+"
    "))}a.append(("An error occurred running this job: "+$.trim(this.model.get("misc_info"))+""));a.append(this._render_primaryActionButtons([this._render_downloadButton,this._render_errButton,this._render_showParamsButton,this._render_rerunButton]))},_render_body_discarded:function(a){a.append("
    The job creating this dataset was cancelled before completion.
    ");a.append(this._render_primaryActionButtons([this._render_showParamsButton,this._render_rerunButton]))},_render_body_setting_metadata:function(a){a.append($("
    Metadata is being auto-detected.
    "))},_render_body_empty:function(a){a.append($("
    No data: "+this.model.get("misc_blurb")+"
    "));a.append(this._render_primaryActionButtons([this._render_showParamsButton,this._render_rerunButton]))},_render_body_failed_metadata:function(a){a.append($(HDAView.templates.failedMetadata(this.model.toJSON())));this._render_body_ok(a)},_render_body_ok:function(a){a.append(this._render_hdaSummary());if(this.model.isDeletedOrPurged()){a.append(this._render_primaryActionButtons([this._render_downloadButton,this._render_showParamsButton,this._render_rerunButton]));return}a.append(this._render_primaryActionButtons([this._render_downloadButton,this._render_errButton,this._render_showParamsButton,this._render_rerunButton,this._render_visualizationsButton]));a.append(this._render_secondaryActionButtons([this._render_tagButton,this._render_annotateButton]));a.append('
    ');a.append(this._render_tagArea());a.append(this._render_annotationArea());a.append(this._render_displayApps());a.append(this._render_peek())},_render_body:function(){var a=$("
    ").attr("id","info-"+this.model.get("id")).addClass("historyItemBody").attr("style","display: block");switch(this.model.get("state")){case HistoryDatasetAssociation.STATES.NOT_VIEWABLE:this._render_body_not_viewable(a);break;case HistoryDatasetAssociation.STATES.UPLOAD:this._render_body_uploading(a);break;case HistoryDatasetAssociation.STATES.QUEUED:this._render_body_queued(a);break;case HistoryDatasetAssociation.STATES.RUNNING:this._render_body_running(a);break;case HistoryDatasetAssociation.STATES.ERROR:this._render_body_error(a);break;case HistoryDatasetAssociation.STATES.DISCARDED:this._render_body_discarded(a);break;case HistoryDatasetAssociation.STATES.SETTING_METADATA:this._render_body_setting_metadata(a);break;case HistoryDatasetAssociation.STATES.EMPTY:this._render_body_empty(a);break;case HistoryDatasetAssociation.STATES.FAILED_METADATA:this._render_body_failed_metadata(a);break;case HistoryDatasetAssociation.STATES.OK:this._render_body_ok(a);break;default:a.append($('
    Error: unknown dataset state "'+state+'".
    '))}a.append('
    ');if(this.expanded){a.show()}else{a.hide()}return a},events:{"click .historyItemTitle":"toggleBodyVisibility","click a.icon-button.tags":"loadAndDisplayTags","click a.icon-button.annotate":"loadAndDisplayAnnotation"},loadAndDisplayTags:function(b){this.log(this+".loadAndDisplayTags",b);var c=this.$el.find(".tag-area"),a=c.find(".tag-elt");if(c.is(":hidden")){if(!jQuery.trim(a.html())){$.ajax({url:this.urls.tags.get,error:function(){alert("Tagging failed")},success:function(d){a.html(d);a.find(".tooltip").tooltip();c.slideDown("fast")}})}else{c.slideDown("fast")}}else{c.slideUp("fast")}return false},loadAndDisplayAnnotation:function(b){this.log(this+".loadAndDisplayAnnotation",b);var d=this.$el.find(".annotation-area"),c=d.find(".annotation-elt"),a=this.urls.annotation.set;if(d.is(":hidden")){if(!jQuery.trim(c.html())){$.ajax({url:this.urls.annotation.get,error:function(){alert("Annotations failed")},success:function(e){if(e===""){e="Describe or add notes to dataset"}c.html(e);d.find(".tooltip").tooltip();async_save_text(c.attr("id"),c.attr("id"),a,"new_annotation",18,true,4);d.slideDown("fast")}})}else{d.slideDown("fast")}}else{d.slideUp("fast")}return false},toggleBodyVisibility:function(b,a){var c=this.$el.find(".historyItemBody");a=(a===undefined)?(!c.is(":visible")):(a);if(a){c.slideDown("fast")}else{c.slideUp("fast")}this.trigger("toggleBodyVisibility",this.model.get("id"),a)},toString:function(){var a=(this.model)?(this.model+""):("(no model)");return"HDAView("+a+")"}});HDAView.templates={warningMsg:Handlebars.templates["template-warningmessagesmall"],messages:Handlebars.templates["template-history-warning-messages"],titleLink:Handlebars.templates["template-history-titleLink"],hdaSummary:Handlebars.templates["template-history-hdaSummary"],downloadLinks:Handlebars.templates["template-history-downloadLinks"],failedMetadata:Handlebars.templates["template-history-failedMetaData"],tagArea:Handlebars.templates["template-history-tagArea"],annotationArea:Handlebars.templates["template-history-annotationArea"],displayApps:Handlebars.templates["template-history-displayApps"]};function create_scatterplot_action_fn(a,b){action=function(){var d=$(window.parent.document).find("iframe#galaxy_main"),c=a+"/scatterplot?"+$.param(b);d.attr("src",c);$("div.popmenu-wrapper").remove();return false};return action}function create_trackster_action_fn(a,c,b){return function(){var d={};if(b){d.dbkey=b}$.ajax({url:a+"/list_tracks?f-"+$.param(d),dataType:"html",error:function(){alert("Could not add this dataset to browser.")},success:function(e){var f=window.parent;f.show_modal("View Data in a New or Saved Visualization","",{Cancel:function(){f.hide_modal()},"View in saved visualization":function(){f.show_modal("Add Data to Saved Visualization",e,{Cancel:function(){f.hide_modal()},"Add to visualization":function(){$(f.document).find("input[name=id]:checked").each(function(){var g=$(this).val();c.id=g;f.location=a+"/trackster?"+$.param(c)})}})},"View in new visualization":function(){f.location=a+"/trackster?"+$.param(c)}})}});return false}}var HistoryView=BaseView.extend(LoggableMixin).extend({el:"body.historyPage",initialize:function(a){this.log(this+".initialize:",a);if(!a.urlTemplates){throw ("HDAView needs urlTemplates on initialize")}if(!a.urlTemplates.history){throw ("HDAView needs urlTemplates.history on initialize")}if(!a.urlTemplates.hda){throw ("HDAView needs urlTemplates.hda on initialize")}this.urlTemplates=a.urlTemplates.history;this.hdaUrlTemplates=a.urlTemplates.hda;this.storage=new PersistantStorage("HistoryView."+this.model.get("id"),{expandedHdas:{}});this.model.bind("change:nice_size",this.updateHistoryDiskSize,this);this.model.hdas.bind("add",this.add,this);this.model.hdas.bind("reset",this.addAll,this);this.model.hdas.bind("all",this.all,this);this.hdaViews={};this.urls={}},add:function(a){},addAll:function(){this.render()},all:function(a){},renderUrls:function(a){var b=this;b.urls={};_.each(this.urlTemplates,function(d,c){b.urls[c]=_.template(d,a)});return b.urls},render:function(){var b=this,d=b.toString()+".set-up",c=$("
    "),a=this.model.toJSON(),e=(this.$el.children().size()===0);a.urls=this.renderUrls(a);c.append(HistoryView.templates.historyPanel(a));c.find(".tooltip").tooltip();if(!this.model.hdas.length||!this.renderItems(c.find("#"+this.model.get("id")+"-datasets"))){c.find("#emptyHistoryMessage").show()}$(b).queue(d,function(f){b.$el.fadeOut("fast",function(){f()})});$(b).queue(d,function(f){b.$el.html("");b.$el.append(c.children());b.$el.fadeIn("fast",function(){f()})});$(b).queue(d,function(f){this.log(b+" rendered:",b.$el);b.setUpBehaviours();if(e){b.trigger("rendered:initial")}else{b.trigger("rendered")}f()});$(b).dequeue(d);return this},renderItems:function(c){this.hdaViews={};var b=this,a=this.model.get("show_deleted"),e=this.model.get("show_hidden"),d=this.model.hdas.getVisible(a,e);_.each(d,function(h){var g=h.get("id"),f=b.storage.get("expandedHdas").get(g);b.hdaViews[g]=new HDAView({model:h,expanded:f,urlTemplates:b.hdaUrlTemplates});b.setUpHdaListeners(b.hdaViews[g]);c.prepend(b.hdaViews[g].render().$el)});return d.length},setUpHdaListeners:function(b){var a=this;b.bind("toggleBodyVisibility",function(d,c){if(c){a.storage.get("expandedHdas").set(d,true)}else{a.storage.get("expandedHdas").deleteKey(d)}});b.bind("rendered:ready",function(){a.trigger("hda:rendered:ready")})},setUpBehaviours:function(){if(!(this.model.get("user")&&this.model.get("user").email)){return}var a=this.$("#history-annotation-area");this.$("#history-annotate").click(function(){if(a.is(":hidden")){a.slideDown("fast")}else{a.slideUp("fast")}return false});async_save_text("history-name-container","history-name",this.urls.rename,"new_name",18);async_save_text("history-annotation-container","history-annotation",this.urls.annotate,"new_annotation",18,true,4)},updateHistoryDiskSize:function(){this.$el.find("#history-size").text(this.model.get("nice_size"))},showQuotaMessage:function(a){var b=this.$el.find("#quota-message-container");if(b.is(":hidden")){b.slideDown("fast")}},hideQuotaMessage:function(a){var b=this.$el.find("#quota-message-container");if(!b.is(":hidden")){b.slideUp("fast")}},events:{"click #history-collapse-all":"hideAllHdaBodies","click #history-tag":"loadAndDisplayTags"},hideAllHdaBodies:function(){_.each(this.hdaViews,function(a){a.toggleBodyVisibility(null,false)});this.storage.set("expandedHdas",{})},loadAndDisplayTags:function(c){this.log(this+".loadAndDisplayTags",c);var d=this.$el.find("#history-tag-area"),b=d.find(".tag-elt");this.log("\t tagArea",d," tagElt",b);if(d.is(":hidden")){if(!jQuery.trim(b.html())){var a=this;$.ajax({url:a.urls.tag,error:function(){alert("Tagging failed")},success:function(e){b.html(e);b.find(".tooltip").tooltip();d.slideDown("fast")}})}else{d.slideDown("fast")}}else{d.slideUp("fast")}return false},toString:function(){var a=this.model.get("name")||"";return"HistoryView("+a+")"}});HistoryView.templates={historyPanel:Handlebars.templates["template-history-historyPanel"]}; \ No newline at end of file diff --git a/static/scripts/packed/templates/compiled/panel_label.js b/static/scripts/packed/templates/compiled/panel_label.js deleted file mode 100644 index 85f9f943b15..00000000000 --- a/static/scripts/packed/templates/compiled/panel_label.js +++ /dev/null @@ -1 +0,0 @@ -(function(){var b=Handlebars.template,a=Handlebars.templates=Handlebars.templates||{};a.panel_label=b(function(e,n,d,l,k){d=d||e.helpers;var i="",c,h,o=this,f="function",m=d.helperMissing,g=void 0,j=this.escapeExpression;i+="";h=d.name;c=h||n.name;if(typeof c===f){c=c.call(n,{hash:{}})}else{if(c===g){c=m.call(n,"name",{hash:{}})}}i+=j(c)+"";return i})})(); \ No newline at end of file diff --git a/static/scripts/packed/templates/compiled/template-visualization-chartSettings.js b/static/scripts/packed/templates/compiled/template-visualization-chartSettings.js deleted file mode 100644 index 059f1fbdbbc..00000000000 --- a/static/scripts/packed/templates/compiled/template-visualization-chartSettings.js +++ /dev/null @@ -1 +0,0 @@ -(function(){var b=Handlebars.template,a=Handlebars.templates=Handlebars.templates||{};a["template-visualization-chartSettings"]=b(function(f,m,e,l,k){e=e||f.helpers;var i="",c,h,g="function",j=this.escapeExpression,n=this;function d(p,o){return' checked="true"'}i+='

    \n Use the following controls to how the chart is displayed.\n The slide controls can be moved by the mouse or, if the \'handle\' is in focus, your keyboard\'s arrow keys.\n Move the focus between controls by using the tab or shift+tab keys on your keyboard.\n Use the \'Draw\' button to render (or re-render) the chart with the current settings.\n

    \n \n
    \n \n
    ';h=e.datapointSize;if(h){c=h.call(m,{hash:{}})}else{c=m.datapointSize;c=typeof c===g?c():c}i+=j(c)+'
    \n
    \n

    \n Size of the graphic representation of each data point\n

    \n
    \n\n
    \n \n \n

    \n Uncheck this to disable the animations used on the graph\n

    \n
    \n\n
    \n \n
    ';h=e.width;if(h){c=h.call(m,{hash:{}})}else{c=m.width;c=typeof c===g?c():c}i+=j(c)+'
    \n
    \n

    \n (not including graph margins and axes)\n

    \n
    \n\n
    \n \n
    ';h=e.height;if(h){c=h.call(m,{hash:{}})}else{c=m.height;c=typeof c===g?c():c}i+=j(c)+'
    \n
    \n

    \n (not including graph margins and axes)\n

    \n
    \n\n
    \n \n \n

    \n
    \n\n
    \n \n \n

    \n
    \n\n ';return i})})(); \ No newline at end of file diff --git a/static/scripts/packed/templates/compiled/template-visualization-statsTable.js b/static/scripts/packed/templates/compiled/template-visualization-statsTable.js deleted file mode 100644 index b668c57209d..00000000000 --- a/static/scripts/packed/templates/compiled/template-visualization-statsTable.js +++ /dev/null @@ -1 +0,0 @@ -(function(){var b=Handlebars.template,a=Handlebars.templates=Handlebars.templates||{};a["template-visualization-statsTable"]=b(function(f,l,e,k,j){e=e||f.helpers;var h="",c,g="function",i=this.escapeExpression,m=this;function d(r,q){var o="",p,n;o+="\n
    ";n=e.name;if(n){p=n.call(r,{hash:{}})}else{p=r.name;p=typeof p===g?p():p}o+=i(p)+"";n=e.xval;if(n){p=n.call(r,{hash:{}})}else{p=r.xval;p=typeof p===g?p():p}o+=i(p)+"";n=e.yval;if(n){p=n.call(r,{hash:{}})}else{p=r.yval;p=typeof p===g?p():p}o+=i(p)+"
    \n \n ';c=l.stats;c=e.each.call(l,c,{hash:{},inverse:m.noop,fn:m.program(1,d,j)});if(c||c===0){h+=c}h+="\n
    XY
    ";return h})})(); \ No newline at end of file From ce6c114c6ada081e4c7479ebfb24435d072347c5 Mon Sep 17 00:00:00 2001 From: Carl Eberhard Date: Tue, 2 Apr 2013 14:27:23 -0400 Subject: [PATCH 062/523] update js libs: underscore 1.3.1 -> 1.4.4, backbone 0.9.2 -> 1.0, backbone-relational 0.5.0 -> 0.8.0, handlebars 1.0.beta.6 -> 1.0.rc1 --- .../libs/backbone/backbone-relational.js | 1456 +++++++------- static/scripts/libs/backbone/backbone.js | 1676 +++++++++-------- static/scripts/libs/handlebars.runtime.js | 44 +- static/scripts/libs/underscore.js | 790 +++++--- .../libs/backbone/backbone-relational.js | 2 +- .../scripts/packed/libs/backbone/backbone.js | 2 +- .../scripts/packed/libs/handlebars.runtime.js | 2 +- static/scripts/packed/libs/underscore.js | 2 +- 8 files changed, 2262 insertions(+), 1712 deletions(-) diff --git a/static/scripts/libs/backbone/backbone-relational.js b/static/scripts/libs/backbone/backbone-relational.js index 62b48eee968..4f2910b845d 100644 --- a/static/scripts/libs/backbone/backbone-relational.js +++ b/static/scripts/libs/backbone/backbone-relational.js @@ -1,14 +1,15 @@ +/* vim: set tabstop=4 softtabstop=4 shiftwidth=4 noexpandtab: */ /** - * Backbone-relational.js 0.5.0 - * (c) 2011 Paul Uithol + * Backbone-relational.js 0.8.0 + * (c) 2011-2013 Paul Uithol and contributors (https://github.com/PaulUithol/Backbone-relational/graphs/contributors) * - * Backbone-relational may be freely distributed under the MIT license. + * Backbone-relational may be freely distributed under the MIT license; see the accompanying LICENSE.txt. * For details and documentation: https://github.com/PaulUithol/Backbone-relational. - * Depends on Backbone: https://github.com/documentcloud/backbone. + * Depends on Backbone (and thus on Underscore as well): https://github.com/documentcloud/backbone. */ ( function( undefined ) { "use strict"; - + /** * CommonJS shim **/ @@ -34,7 +35,7 @@ Backbone.Semaphore = { _permitsAvailable: null, _permitsUsed: 0, - + acquire: function() { if ( this._permitsAvailable && this._permitsUsed >= this._permitsAvailable ) { throw new Error( 'Max permits acquired' ); @@ -43,7 +44,7 @@ this._permitsUsed++; } }, - + release: function() { if ( this._permitsUsed === 0 ) { throw new Error( 'All permits released' ); @@ -52,11 +53,11 @@ this._permitsUsed--; } }, - + isLocked: function() { return this._permitsUsed > 0; }, - + setAvailablePermits: function( amount ) { if ( this._permitsUsed > amount ) { throw new Error( 'Available permits cannot be less than used permits' ); @@ -64,7 +65,7 @@ this._permitsAvailable = amount; } }; - + /** * A BlockingQueue that accumulates items while blocked (via 'block'), * and processes them when unblocked (via 'unblock'). @@ -75,7 +76,7 @@ }; _.extend( Backbone.BlockingQueue.prototype, Backbone.Semaphore, { _queue: null, - + add: function( func ) { if ( this.isBlocked() ) { this._queue.push( func ); @@ -84,34 +85,34 @@ func(); } }, - + process: function() { while ( this._queue && this._queue.length ) { this._queue.shift()(); } }, - + block: function() { this.acquire(); }, - + unblock: function() { this.release(); if ( !this.isBlocked() ) { this.process(); } }, - + isBlocked: function() { return this.isLocked(); } }); /** - * Global event queue. Accumulates external events ('add:', 'remove:' and 'update:') + * Global event queue. Accumulates external events ('add:', 'remove:' and 'change:') * until the top-level object is fully initialized (see 'Backbone.RelationalModel'). */ Backbone.Relational.eventQueue = new Backbone.BlockingQueue(); - + /** * Backbone.Store keeps track of all created (and destruction of) Backbone.RelationalModel. * Handles lookup for relations. @@ -119,10 +120,31 @@ Backbone.Store = function() { this._collections = []; this._reverseRelations = []; + this._orphanRelations = []; this._subModels = []; this._modelScopes = [ exports ]; }; _.extend( Backbone.Store.prototype, Backbone.Events, { + /** + * Create a new `Relation`. + * @param {Backbone.RelationalModel} [model] + * @param {Object} relation + * @param {Object} [options] + */ + initializeRelation: function( model, relation, options ) { + var type = !_.isString( relation.type ) ? relation.type : Backbone[ relation.type ] || this.getObjectByName( relation.type ); + if ( type && type.prototype instanceof Backbone.Relation ) { + new type( model, relation, options ); // Also pushes the new Relation into `model._relations` + } + else { + Backbone.Relational.showWarnings && typeof console !== 'undefined' && console.warn( 'Relation=%o; missing or invalid relation type!', relation ); + } + }, + + /** + * Add a scope for `getObjectByName` to look for model types by name. + * @param {Object} scope + */ addModelScope: function( scope ) { this._modelScopes.push( scope ); }, @@ -149,7 +171,7 @@ */ setupSuperModel: function( modelType ) { _.find( this._subModels, function( subModelDef ) { - return _.find( subModelDef.subModels, function( subModelTypeName, typeValue ) { + return _.find( subModelDef.subModels || [], function( subModelTypeName, typeValue ) { var subModelType = this.getObjectByName( subModelTypeName ); if ( modelType === subModelType ) { @@ -165,7 +187,7 @@ }, this ); }, this ); }, - + /** * Add a reverse relation. Is added to the 'relations' property on model's prototype, and to * existing instances of 'model' in the store as well. @@ -177,74 +199,108 @@ */ addReverseRelation: function( relation ) { var exists = _.any( this._reverseRelations, function( rel ) { - return _.all( relation, function( val, key ) { - return val === rel[ key ]; - }); + return _.all( relation || [], function( val, key ) { + return val === rel[ key ]; }); + }); if ( !exists && relation.model && relation.type ) { this._reverseRelations.push( relation ); - - var addRelation = function( model, relation ) { - if ( !model.prototype.relations ) { - model.prototype.relations = []; - } - model.prototype.relations.push( relation ); - - _.each( model._subModels, function( subModel ) { - addRelation( subModel, relation ); - }, this ); - }; - - addRelation( relation.model, relation ); - + this._addRelation( relation.model, relation ); this.retroFitRelation( relation ); } }, - + + /** + * Deposit a `relation` for which the `relatedModel` can't be resolved at the moment. + * + * @param {Object} relation + */ + addOrphanRelation: function( relation ) { + var exists = _.any( this._orphanRelations, function( rel ) { + return _.all( relation || [], function( val, key ) { + return val === rel[ key ]; + }); + }); + + if ( !exists && relation.model && relation.type ) { + this._orphanRelations.push( relation ); + } + }, + + /** + * Try to initialize any `_orphanRelation`s + */ + processOrphanRelations: function() { + // Make sure to operate on a copy since we're removing while iterating + _.each( this._orphanRelations.slice( 0 ), function( rel ) { + var relatedModel = Backbone.Relational.store.getObjectByName( rel.relatedModel ); + if ( relatedModel ) { + this.initializeRelation( null, rel ); + this._orphanRelations = _.without( this._orphanRelations, rel ); + } + }, this ); + }, + + /** + * + * @param {Backbone.RelationalModel.constructor} type + * @param {Object} relation + * @private + */ + _addRelation: function( type, relation ) { + if ( !type.prototype.relations ) { + type.prototype.relations = []; + } + type.prototype.relations.push( relation ); + + _.each( type._subModels || [], function( subModel ) { + this._addRelation( subModel, relation ); + }, this ); + }, + /** * Add a 'relation' to all existing instances of 'relation.model' in the store * @param {Object} relation */ retroFitRelation: function( relation ) { - var coll = this.getCollection( relation.model ); - coll.each( function( model ) { + var coll = this.getCollection( relation.model, false ); + coll && coll.each( function( model ) { if ( !( model instanceof relation.model ) ) { return; } new relation.type( model, relation ); - }, this); + }, this ); }, - + /** * Find the Store's collection for a certain type of model. - * @param {Backbone.RelationalModel} model + * @param {Backbone.RelationalModel} type + * @param {Boolean} [create=true] Should a collection be created if none is found? * @return {Backbone.Collection} A collection if found (or applicable for 'model'), or null */ - getCollection: function( model ) { - if ( model instanceof Backbone.RelationalModel ) { - model = model.constructor; + getCollection: function( type, create ) { + if ( type instanceof Backbone.RelationalModel ) { + type = type.constructor; } - var rootModel = model; + var rootModel = type; while ( rootModel._superModel ) { rootModel = rootModel._superModel; } - var coll = _.detect( this._collections, function( c ) { - return c.model === rootModel; - }); + var coll = _.findWhere( this._collections, { model: rootModel } ); - if ( !coll ) { - coll = this._createCollection( model ); + if ( !coll && create !== false ) { + coll = this._createCollection( rootModel ); } return coll; }, - + /** - * Find a type on the global object by name. Splits name on dots. + * Find a model type on one of the modelScopes by name. Names are split on dots. * @param {String} name * @return {Object} */ @@ -253,8 +309,8 @@ type = null; _.find( this._modelScopes, function( scope ) { - type = _.reduce( parts, function( memo, val ) { - return memo[ val ]; + type = _.reduce( parts || [], function( memo, val ) { + return memo ? memo[ val ] : undefined; }, scope ); if ( type && type !== scope ) { @@ -264,7 +320,7 @@ return type; }, - + _createCollection: function( type ) { var coll; @@ -288,11 +344,12 @@ * Find the attribute that is to be used as the `id` on a given object * @param type * @param {String|Number|Object|Backbone.RelationalModel} item + * @return {String|Number} */ resolveIdForItem: function( type, item ) { var id = _.isString( item ) || _.isNumber( item ) ? item : null; - if ( id == null ) { + if ( id === null ) { if ( item instanceof Backbone.RelationalModel ) { id = item.id; } @@ -301,6 +358,11 @@ } } + // Make all falsy values `null` (except for 0, which could be an id.. see '/issues/179') + if ( !id && id !== 0 ) { + id = null; + } + return id; }, @@ -325,45 +387,67 @@ return null; }, - + /** - * Add a 'model' to it's appropriate collection. Retain the original contents of 'model.collection'. + * Add a 'model' to its appropriate collection. Retain the original contents of 'model.collection'. * @param {Backbone.RelationalModel} model */ register: function( model ) { - var modelColl = model.collection; var coll = this.getCollection( model ); - coll && coll.add( model ); - model.bind( 'destroy', this.unregister, this ); - model.collection = modelColl; + + if ( coll ) { + if ( coll.get( model ) ) { + if ( Backbone.Relational.showWarnings && typeof console !== 'undefined' ) { + console.warn( 'Duplicate id! Old RelationalModel=%o, new RelationalModel=%o', coll.get( model ), model ); + } + throw new Error( "Cannot instantiate more than one Backbone.RelationalModel with the same id per type!" ); + } + + var modelColl = model.collection; + coll.add( model ); + this.listenTo( model, 'destroy', this.unregister, this ); + model.collection = modelColl; + } }, - + /** - * Explicitly update a model's id in it's store collection + * Explicitly update a model's id in its store collection * @param {Backbone.RelationalModel} model - */ + */ update: function( model ) { var coll = this.getCollection( model ); coll._onModelEvent( 'change:' + model.idAttribute, model, coll ); }, - + /** * Remove a 'model' from the store. * @param {Backbone.RelationalModel} model */ unregister: function( model ) { - model.unbind( 'destroy', this.unregister ); + this.stopListening( model, 'destroy', this.unregister ); var coll = this.getCollection( model ); coll && coll.remove( model ); + }, + + /** + * Reset the `store` to it's original state. The `reverseRelations` are kept though, since attempting to + * re-initialize these on models would lead to a large amount of warnings. + */ + reset: function() { + this.stopListening(); + this._collections = []; + this._subModels = []; + this._modelScopes = [ exports ]; } }); Backbone.Relational.store = new Backbone.Store(); - + /** * The main Relation class, from which 'HasOne' and 'HasMany' inherit. Internally, 'relational:' events * are used to regulate addition and removal of models from relations. * - * @param {Backbone.RelationalModel} instance + * @param {Backbone.RelationalModel} [instance] Model that this relation is created for. If no model is supplied, + * Relation just tries to instantiate it's `reverseRelation` if specified, and bails out after that. * @param {Object} options * @param {string} options.key * @param {Backbone.RelationalModel.constructor} options.relatedModel @@ -372,41 +456,30 @@ * @param {Object} [options.reverseRelation] Specify a bi-directional relation. If provided, Relation will reciprocate * the relation to the 'relatedModel'. Required and optional properties match 'options', except that it also needs * {Backbone.Relation|String} type ('HasOne' or 'HasMany'). + * @param {Object} opts */ - Backbone.Relation = function( instance, options ) { + Backbone.Relation = function( instance, options, opts ) { this.instance = instance; // Make sure 'options' is sane, and fill with defaults from subclasses and this object's prototype options = _.isObject( options ) ? options : {}; this.reverseRelation = _.defaults( options.reverseRelation || {}, this.options.reverseRelation ); + this.options = _.defaults( options, this.options, Backbone.Relation.prototype.options ); + this.reverseRelation.type = !_.isString( this.reverseRelation.type ) ? this.reverseRelation.type : Backbone[ this.reverseRelation.type ] || Backbone.Relational.store.getObjectByName( this.reverseRelation.type ); - this.model = options.model || this.instance.constructor; - this.options = _.defaults( options, this.options, Backbone.Relation.prototype.options ); - + this.key = this.options.key; this.keySource = this.options.keySource || this.key; this.keyDestination = this.options.keyDestination || this.keySource || this.key; - // 'exports' should be the global object where 'relatedModel' can be found on if given as a string. + this.model = this.options.model || this.instance.constructor; this.relatedModel = this.options.relatedModel; if ( _.isString( this.relatedModel ) ) { this.relatedModel = Backbone.Relational.store.getObjectByName( this.relatedModel ); } if ( !this.checkPreconditions() ) { - return false; - } - - if ( instance ) { - this.keyContents = this.instance.get( this.keySource ); - - // Explicitly clear 'keySource', to prevent a leaky abstraction if 'keySource' differs from 'key'. - if ( this.key !== this.keySource ) { - this.instance.unset( this.keySource, { silent: true } ); - } - - // Add this Relation to instance._relations - this.instance._relations.push( this ); + return; } // Add the reverse relation on 'relatedModel' to the store's reverseRelations @@ -415,25 +488,39 @@ isAutoRelation: true, model: this.relatedModel, relatedModel: this.model, - reverseRelation: this.options // current relation is the 'reverseRelation' for it's own reverseRelation + reverseRelation: this.options // current relation is the 'reverseRelation' for its own reverseRelation }, this.reverseRelation // Take further properties from this.reverseRelation (type, key, etc.) ) ); } - _.bindAll( this, '_modelRemovedFromCollection', '_relatedModelAdded', '_relatedModelRemoved' ); - if ( instance ) { - this.initialize(); + var contentKey = this.keySource; + if ( contentKey !== this.key && typeof this.instance.get( this.key ) === 'object' ) { + contentKey = this.key; + } - // When a model in the store is destroyed, check if it is 'this.instance'. - Backbone.Relational.store.getCollection( this.instance ) - .bind( 'relational:remove', this._modelRemovedFromCollection ); + this.setKeyContents( this.instance.get( contentKey ) ); + this.relatedCollection = Backbone.Relational.store.getCollection( this.relatedModel ); + + // Explicitly clear 'keySource', to prevent a leaky abstraction if 'keySource' differs from 'key'. + if ( this.keySource !== this.key ) { + this.instance.unset( this.keySource, { silent: true } ); + } + + // Add this Relation to instance._relations + this.instance._relations[ this.key ] = this; + + this.initialize( opts ); + + if ( this.options.autoFetch ) { + this.instance.fetchRelated( this.key, _.isObject( this.options.autoFetch ) ? this.options.autoFetch : {} ); + } // When 'relatedModel' are created or destroyed, check if it affects this relation. - Backbone.Relational.store.getCollection( this.relatedModel ) - .bind( 'relational:add', this._relatedModelAdded ) - .bind( 'relational:remove', this._relatedModelRemoved ); + this.listenTo( this.instance, 'destroy', this.destroy ) + .listenTo( this.relatedCollection, 'relational:add', this.tryAddRelated ) + .listenTo( this.relatedCollection, 'relational:remove', this.removeRelated ) } }; // Fix inheritance :\ @@ -443,35 +530,19 @@ options: { createModels: true, includeInJSON: true, - isAutoRelation: false + isAutoRelation: false, + autoFetch: false, + parse: false }, - + instance: null, key: null, keyContents: null, relatedModel: null, + relatedCollection: null, reverseRelation: null, related: null, - - _relatedModelAdded: function( model, coll, options ) { - // Allow 'model' to set up it's relations, before calling 'tryAddRelated' - // (which can result in a call to 'addRelated' on a relation of 'model') - var dit = this; - model.queue( function() { - dit.tryAddRelated( model, options ); - }); - }, - - _relatedModelRemoved: function( model, coll, options ) { - this.removeRelated( model, options ); - }, - - _modelRemovedFromCollection: function( model ) { - if ( model === this.instance ) { - this.destroy(); - } - }, - + /** * Check several pre-conditions. * @return {Boolean} True if pre-conditions are satisfied, false if they're not. @@ -484,36 +555,33 @@ warn = Backbone.Relational.showWarnings && typeof console !== 'undefined'; if ( !m || !k || !rm ) { - warn && console.warn( 'Relation=%o; no model, key or relatedModel (%o, %o, %o)', this, m, k, rm ); + warn && console.warn( 'Relation=%o: missing model, key or relatedModel (%o, %o, %o).', this, m, k, rm ); return false; } - // Check if the type in 'relatedModel' inherits from Backbone.RelationalModel + // Check if the type in 'model' inherits from Backbone.RelationalModel if ( !( m.prototype instanceof Backbone.RelationalModel ) ) { - warn && console.warn( 'Relation=%o; model does not inherit from Backbone.RelationalModel (%o)', this, i ); + warn && console.warn( 'Relation=%o: model does not inherit from Backbone.RelationalModel (%o).', this, i ); return false; } // Check if the type in 'relatedModel' inherits from Backbone.RelationalModel if ( !( rm.prototype instanceof Backbone.RelationalModel ) ) { - warn && console.warn( 'Relation=%o; relatedModel does not inherit from Backbone.RelationalModel (%o)', this, rm ); + warn && console.warn( 'Relation=%o: relatedModel does not inherit from Backbone.RelationalModel (%o).', this, rm ); return false; } // Check if this is not a HasMany, and the reverse relation is HasMany as well if ( this instanceof Backbone.HasMany && this.reverseRelation.type === Backbone.HasMany ) { - warn && console.warn( 'Relation=%o; relation is a HasMany, and the reverseRelation is HasMany as well.', this ); + warn && console.warn( 'Relation=%o: relation is a HasMany, and the reverseRelation is HasMany as well.', this ); return false; } + // Check if we're not attempting to create a relationship on a `key` that's already used. + if ( i && _.keys( i._relations ).length ) { + var existing = _.find( i._relations, function( rel ) { + return rel.key === k; + }, this ); - // Check if we're not attempting to create a duplicate relationship - if ( i && i._relations.length ) { - var exists = _.any( i._relations, function( rel ) { - var hasReverseRelation = this.reverseRelation.key && rel.reverseRelation.key; - return rel.relatedModel === rm && rel.key === k && - ( !hasReverseRelation || this.reverseRelation.key === rel.reverseRelation.key ); - }, this ); - - if ( exists ) { - warn && console.warn( 'Relation=%o between instance=%o.%s and relatedModel=%o.%s already exists', - this, i, k, rm, this.reverseRelation.key ); + if ( existing ) { + warn && console.warn( 'Cannot create relation=%o on %o for model=%o: already taken by relation=%o.', + this, k, i, existing ); return false; } } @@ -523,17 +591,16 @@ /** * Set the related model(s) for this relation - * @param {Backbone.Mode|Backbone.Collection} related - * @param {Object} [options] + * @param {Backbone.Model|Backbone.Collection} related */ - setRelated: function( related, options ) { + setRelated: function( related ) { this.related = related; this.instance.acquire(); - this.instance.set( this.key, related, _.defaults( options || {}, { silent: true } ) ); + this.instance.attributes[ this.key ] = related; this.instance.release(); }, - + /** * Determine if a relation (on a different RelationalModel) is the reverse * relation of the current one. @@ -541,13 +608,10 @@ * @return {Boolean} */ _isReverseRelation: function( relation ) { - if ( relation.instance instanceof this.relatedModel && this.reverseRelation.key === relation.key && - this.key === relation.reverseRelation.key ) { - return true; - } - return false; + return relation.instance instanceof this.relatedModel && this.reverseRelation.key === relation.key && + this.key === relation.reverseRelation.key; }, - + /** * Get the reverse relations (pointing back to 'this.key' on 'this.instance') for the currently related model(s). * @param {Backbone.RelationalModel} [model] Get the reverse relations for a specific model. @@ -558,97 +622,87 @@ var reverseRelations = []; // Iterate over 'model', 'this.related.models' (if this.related is a Backbone.Collection), or wrap 'this.related' in an array. var models = !_.isUndefined( model ) ? [ model ] : this.related && ( this.related.models || [ this.related ] ); - _.each( models , function( related ) { - _.each( related.getRelations(), function( relation ) { - if ( this._isReverseRelation( relation ) ) { - reverseRelations.push( relation ); - } - }, this ); - }, this ); + _.each( models || [], function( related ) { + _.each( related.getRelations() || [], function( relation ) { + if ( this._isReverseRelation( relation ) ) { + reverseRelations.push( relation ); + } + }, this ); + }, this ); return reverseRelations; }, - - /** - * Rename options.silent to options.silentChange, so events propagate properly. - * (for example in HasMany, from 'addRelated'->'handleAddition') - * @param {Object} [options] - * @return {Object} - */ - sanitizeOptions: function( options ) { - options = options ? _.clone( options ) : {}; - if ( options.silent ) { - options.silentChange = true; - delete options.silent; - } - return options; - }, /** - * Rename options.silentChange to options.silent, so events are silenced as intended in Backbone's - * original functions. - * @param {Object} [options] - * @return {Object} + * When `this.instance` is destroyed, cleanup our relations. + * Get reverse relation, call removeRelated on each. */ - unsanitizeOptions: function( options ) { - options = options ? _.clone( options ) : {}; - if ( options.silentChange ) { - options.silent = true; - delete options.silentChange; - } - return options; - }, - - // Cleanup. Get reverse relation, call removeRelated on each. destroy: function() { - Backbone.Relational.store.getCollection( this.instance ) - .unbind( 'relational:remove', this._modelRemovedFromCollection ); - - Backbone.Relational.store.getCollection( this.relatedModel ) - .unbind( 'relational:add', this._relatedModelAdded ) - .unbind( 'relational:remove', this._relatedModelRemoved ); + this.stopListening(); + + if ( this instanceof Backbone.HasOne ) { + this.setRelated( null ); + } + else if ( this instanceof Backbone.HasMany ) { + this.setRelated( this._prepareCollection() ); + } _.each( this.getReverseRelations(), function( relation ) { - relation.removeRelated( this.instance ); - }, this ); + relation.removeRelated( this.instance ); + }, this ); } }); - + Backbone.HasOne = Backbone.Relation.extend({ options: { reverseRelation: { type: 'HasMany' } }, - - initialize: function() { - _.bindAll( this, 'onChange' ); - this.instance.bind( 'relational:change:' + this.key, this.onChange ); + initialize: function( opts ) { + this.listenTo( this.instance, 'relational:change:' + this.key, this.onChange ); - var model = this.findRelated( { silent: true } ); - this.setRelated( model ); + var related = this.findRelated( opts ); + this.setRelated( related ); // Notify new 'related' object of the new relation. _.each( this.getReverseRelations(), function( relation ) { - relation.addRelated( this.instance ); - }, this ); + relation.addRelated( this.instance, opts ); + }, this ); }, - - findRelated: function( options ) { - var item = this.keyContents; - var model = null; - - if ( item instanceof this.relatedModel ) { - model = item; - } - else if ( item ) { - model = this.relatedModel.findOrCreate( item, { create: this.options.createModels } ); - } - - return model; - }, - + /** - * If the key is changed, notify old & new reverse relations and initialize the new relation + * Find related Models. + * @param {Object} [options] + * @return {Backbone.Model} + */ + findRelated: function( options ) { + var related = null; + + options = _.defaults( { parse: this.options.parse }, options ); + + if ( this.keyContents instanceof this.relatedModel ) { + related = this.keyContents; + } + else if ( this.keyContents || this.keyContents === 0 ) { // since 0 can be a valid `id` as well + var opts = _.defaults( { create: this.options.createModels }, options ); + related = this.relatedModel.findOrCreate( this.keyContents, opts ); + } + + return related; + }, + + /** + * Normalize and reduce `keyContents` to an `id`, for easier comparison + * @param {String|Number|Backbone.Model} keyContents + */ + setKeyContents: function( keyContents ) { + this.keyContents = keyContents; + this.keyId = Backbone.Relational.store.resolveIdForItem( this.relatedModel, this.keyContents ); + }, + + /** + * Event handler for `change:`. + * If the key is changed, notify old & new reverse relations and initialize the new relation. */ onChange: function( model, attr, options ) { // Don't accept recursive calls to onChange (like onChange->findRelated->findOrCreate->initializeRelations->addRelated->onChange) @@ -656,81 +710,70 @@ return; } this.acquire(); - options = this.sanitizeOptions( options ); + options = options ? _.clone( options ) : {}; - // 'options._related' is set by 'addRelated'/'removeRelated'. If it is set, the change + // 'options.__related' is set by 'addRelated'/'removeRelated'. If it is set, the change // is the result of a call from a relation. If it's not, the change is the result of // a 'set' call on this.instance. - var changed = _.isUndefined( options._related ); - var oldRelated = changed ? this.related : options._related; + var changed = _.isUndefined( options.__related ), + oldRelated = changed ? this.related : options.__related; - if ( changed ) { - this.keyContents = attr; - - // Set new 'related' - if ( attr instanceof this.relatedModel ) { - this.related = attr; - } - else if ( attr ) { - var related = this.findRelated( options ); - this.setRelated( related ); - } - else { - this.setRelated( null ); - } + if ( changed ) { + this.setKeyContents( attr ); + var related = this.findRelated( options ); + this.setRelated( related ); } // Notify old 'related' object of the terminated relation if ( oldRelated && this.related !== oldRelated ) { _.each( this.getReverseRelations( oldRelated ), function( relation ) { - relation.removeRelated( this.instance, options ); - }, this ); + relation.removeRelated( this.instance, null, options ); + }, this ); } - + // Notify new 'related' object of the new relation. Note we do re-apply even if this.related is oldRelated; // that can be necessary for bi-directional relations if 'this.instance' was created after 'this.related'. // In that case, 'this.instance' will already know 'this.related', but the reverse might not exist yet. _.each( this.getReverseRelations(), function( relation ) { - relation.addRelated( this.instance, options ); - }, this); + relation.addRelated( this.instance, options ); + }, this ); - // Fire the 'update:' event if 'related' was updated - if ( !options.silentChange && this.related !== oldRelated ) { + // Fire the 'change:' event if 'related' was updated + if ( !options.silent && this.related !== oldRelated ) { var dit = this; + this.changed = true; Backbone.Relational.eventQueue.add( function() { - dit.instance.trigger( 'update:' + dit.key, dit.instance, dit.related, options ); + dit.instance.trigger( 'change:' + dit.key, dit.instance, dit.related, options, true ); + dit.changed = false; }); } this.release(); }, - + /** * If a new 'this.relatedModel' appears in the 'store', try to match it to the last set 'keyContents' */ - tryAddRelated: function( model, options ) { - if ( this.related ) { - return; - } - options = this.sanitizeOptions( options ); - - var item = this.keyContents; - if ( item ) { - var id = Backbone.Relational.store.resolveIdForItem( this.relatedModel, item ); - if ( model.id === id ) { - this.addRelated( model, options ); - } + tryAddRelated: function( model, coll, options ) { + if ( ( this.keyId || this.keyId === 0 ) && model.id === this.keyId ) { // since 0 can be a valid `id` as well + this.addRelated( model, options ); + this.keyId = null; } }, - + addRelated: function( model, options ) { - if ( model !== this.related ) { - var oldRelated = this.related || null; - this.setRelated( model ); - this.onChange( this.instance, model, { _related: oldRelated } ); - } + // Allow 'model' to set up its relations before proceeding. + // (which can result in a call to 'addRelated' from a relation of 'model') + var dit = this; + model.queue( function() { + if ( model !== dit.related ) { + var oldRelated = dit.related || null; + dit.setRelated( model ); + dit.onChange( dit.instance, model, _.defaults( { __related: oldRelated }, options ) ); + } + }); }, - - removeRelated: function( model, options ) { + + removeRelated: function( model, coll, options ) { if ( !this.related ) { return; } @@ -738,24 +781,23 @@ if ( model === this.related ) { var oldRelated = this.related || null; this.setRelated( null ); - this.onChange( this.instance, model, { _related: oldRelated } ); + this.onChange( this.instance, model, _.defaults( { __related: oldRelated }, options ) ); } } }); - + Backbone.HasMany = Backbone.Relation.extend({ collectionType: null, - + options: { reverseRelation: { type: 'HasOne' }, collectionType: Backbone.Collection, collectionKey: true, collectionOptions: {} }, - - initialize: function() { - _.bindAll( this, 'onChange', 'handleAddition', 'handleRemoval', 'handleReset' ); - this.instance.bind( 'relational:change:' + this.key, this.onChange ); + + initialize: function( opts ) { + this.listenTo( this.instance, 'relational:change:' + this.key, this.onChange ); // Handle a custom 'collectionType' this.collectionType = this.options.collectionType; @@ -763,41 +805,29 @@ this.collectionType = Backbone.Relational.store.getObjectByName( this.collectionType ); } if ( !this.collectionType.prototype instanceof Backbone.Collection ){ - throw new Error( 'collectionType must inherit from Backbone.Collection' ); + throw new Error( '`collectionType` must inherit from Backbone.Collection' ); } - // Handle cases where a model/relation is created with a collection passed straight into 'attributes' - if ( this.keyContents instanceof Backbone.Collection ) { - this.setRelated( this._prepareCollection( this.keyContents ) ); - } - else { - this.setRelated( this._prepareCollection() ); - } - - this.findRelated( { silent: true } ); - }, - - _getCollectionOptions: function() { - return _.isFunction( this.options.collectionOptions ) ? - this.options.collectionOptions( this.instance ) : - this.options.collectionOptions; + var related = this.findRelated( opts ); + this.setRelated( related ); }, /** * Bind events and setup collectionKeys for a collection that is to be used as the backing store for a HasMany. * If no 'collection' is supplied, a new collection will be created of the specified 'collectionType' option. * @param {Backbone.Collection} [collection] + * @return {Backbone.Collection} */ _prepareCollection: function( collection ) { if ( this.related ) { - this.related - .unbind( 'relational:add', this.handleAddition ) - .unbind( 'relational:remove', this.handleRemoval ) - .unbind( 'relational:reset', this.handleReset ) + this.stopListening( this.related ); } if ( !collection || !( collection instanceof Backbone.Collection ) ) { - collection = new this.collectionType( [], this._getCollectionOptions() ); + var options = _.isFunction( this.options.collectionOptions ) ? + this.options.collectionOptions( this.instance ) : this.options.collectionOptions; + + collection = new this.collectionType( null, options ); } collection.model = this.relatedModel; @@ -814,191 +844,184 @@ collection[ key ] = this.instance; } } - - collection - .bind( 'relational:add', this.handleAddition ) - .bind( 'relational:remove', this.handleRemoval ) - .bind( 'relational:reset', this.handleReset ); + + this.listenTo( collection, 'relational:add', this.handleAddition ) + .listenTo( collection, 'relational:remove', this.handleRemoval ) + .listenTo( collection, 'relational:reset', this.handleReset ); return collection; }, - - findRelated: function( options ) { - if ( this.keyContents ) { - var models = []; - if ( this.keyContents instanceof Backbone.Collection ) { - models = this.keyContents.models; - } - else { - // Handle cases the an API/user supplies just an Object/id instead of an Array - this.keyContents = _.isArray( this.keyContents ) ? this.keyContents : [ this.keyContents ]; - - // Try to find instances of the appropriate 'relatedModel' in the store - _.each( this.keyContents, function( item ) { - var model = null; - if ( item instanceof this.relatedModel ) { - model = item; - } - else { - model = this.relatedModel.findOrCreate( item, { create: this.options.createModels } ); - } - - if ( model && !this.related.getByCid( model ) && !this.related.get( model ) ) { - models.push( model ); - } - }, this ); - } - - // Add all found 'models' in on go, so 'add' will only be called once (and thus 'sort', etc.) - if ( models.length ) { - options = this.unsanitizeOptions( options ); - this.related.add( models, options ); - } - } - }, - /** - * If the key is changed, notify old & new reverse relations and initialize the new relation + * Find related Models. + * @param {Object} [options] + * @return {Backbone.Collection} */ - onChange: function( model, attr, options ) { - options = this.sanitizeOptions( options ); - this.keyContents = attr; - - // Notify old 'related' object of the terminated relation - _.each( this.getReverseRelations(), function( relation ) { - relation.removeRelated( this.instance, options ); - }, this ); - - // Replace 'this.related' by 'attr' if it is a Backbone.Collection - if ( attr instanceof Backbone.Collection ) { - this._prepareCollection( attr ); - this.related = attr; + findRelated: function( options ) { + var related = null; + + options = _.defaults( { parse: this.options.parse }, options ); + + // Replace 'this.related' by 'this.keyContents' if it is a Backbone.Collection + if ( this.keyContents instanceof Backbone.Collection ) { + this._prepareCollection( this.keyContents ); + related = this.keyContents; } - // Otherwise, 'attr' should be an array of related object ids. - // Re-use the current 'this.related' if it is a Backbone.Collection, and remove any current entries. - // Otherwise, create a new collection. + // Otherwise, 'this.keyContents' should be an array of related object ids. + // Re-use the current 'this.related' if it is a Backbone.Collection; otherwise, create a new collection. else { - var coll; + var toAdd = []; + + _.each( this.keyContents, function( attributes ) { + if ( attributes instanceof this.relatedModel ) { + var model = attributes; + } + else { + // If `merge` is true, update models here, instead of during update. + model = this.relatedModel.findOrCreate( attributes, _.extend( { merge: true }, options, { create: this.options.createModels } ) ); + } + + model && toAdd.push( model ); + }, this ); if ( this.related instanceof Backbone.Collection ) { - coll = this.related; - coll.remove( coll.models ); + related = this.related; } else { - coll = this._prepareCollection(); + related = this._prepareCollection(); } - this.setRelated( coll ); - this.findRelated( options ); + related.update( toAdd, _.defaults( { merge: false, parse: false }, options ) ); } - - // Notify new 'related' object of the new relation - _.each( this.getReverseRelations(), function( relation ) { - relation.addRelated( this.instance, options ); + + return related; + }, + + /** + * Normalize and reduce `keyContents` to a list of `ids`, for easier comparison + * @param {String|Number|String[]|Number[]|Backbone.Collection} keyContents + */ + setKeyContents: function( keyContents ) { + this.keyContents = keyContents instanceof Backbone.Collection ? keyContents : null; + this.keyIds = []; + + if ( !this.keyContents && ( keyContents || keyContents === 0 ) ) { // since 0 can be a valid `id` as well + // Handle cases the an API/user supplies just an Object/id instead of an Array + this.keyContents = _.isArray( keyContents ) ? keyContents : [ keyContents ]; + + _.each( this.keyContents, function( item ) { + var itemId = Backbone.Relational.store.resolveIdForItem( this.relatedModel, item ); + if ( itemId || itemId === 0 ) { + this.keyIds.push( itemId ); + } }, this ); - - var dit = this; - Backbone.Relational.eventQueue.add( function() { - !options.silentChange && dit.instance.trigger( 'update:' + dit.key, dit.instance, dit.related, options ); - }); - }, - - tryAddRelated: function( model, options ) { - options = this.sanitizeOptions( options ); - if ( !this.related.getByCid( model ) && !this.related.get( model ) ) { - // Check if this new model was specified in 'this.keyContents' - var item = _.any( this.keyContents, function( item ) { - var id = Backbone.Relational.store.resolveIdForItem( this.relatedModel, item ); - return id && id === model.id; - }, this ); - - if ( item ) { - this.related.add( model, options ); - } } }, - + + /** + * Event handler for `change:`. + * If the contents of the key are changed, notify old & new reverse relations and initialize the new relation. + */ + onChange: function( model, attr, options ) { + options = options ? _.clone( options ) : {}; + this.setKeyContents( attr ); + this.changed = false; + + var related = this.findRelated( options ); + this.setRelated( related ); + + if ( !options.silent ) { + var dit = this; + Backbone.Relational.eventQueue.add( function() { + // The `changed` flag can be set in `handleAddition` or `handleRemoval` + if ( dit.changed ) { + dit.instance.trigger( 'change:' + dit.key, dit.instance, dit.related, options, true ); + dit.changed = false; + } + }); + } + }, + /** * When a model is added to a 'HasMany', trigger 'add' on 'this.instance' and notify reverse relations. * (should be 'HasOne', must set 'this.instance' as their related). - */ + */ handleAddition: function( model, coll, options ) { //console.debug('handleAddition called; args=%o', arguments); - // Make sure the model is in fact a valid model before continuing. - // (it can be invalid as a result of failing validation in Backbone.Collection._prepareModel) - if ( !( model instanceof Backbone.Model ) ) { - return; - } - - options = this.sanitizeOptions( options ); + options = options ? _.clone( options ) : {}; + this.changed = true; _.each( this.getReverseRelations( model ), function( relation ) { - relation.addRelated( this.instance, options ); - }, this ); + relation.addRelated( this.instance, options ); + }, this ); - // Only trigger 'add' once the newly added model is initialized (so, has it's relations set up) + // Only trigger 'add' once the newly added model is initialized (so, has its relations set up) var dit = this; - Backbone.Relational.eventQueue.add( function() { - !options.silentChange && dit.instance.trigger( 'add:' + dit.key, model, dit.related, options ); + !options.silent && Backbone.Relational.eventQueue.add( function() { + dit.instance.trigger( 'add:' + dit.key, model, dit.related, options ); }); }, - + /** * When a model is removed from a 'HasMany', trigger 'remove' on 'this.instance' and notify reverse relations. * (should be 'HasOne', which should be nullified) */ handleRemoval: function( model, coll, options ) { //console.debug('handleRemoval called; args=%o', arguments); - if ( !( model instanceof Backbone.Model ) ) { - return; - } - - options = this.sanitizeOptions( options ); + options = options ? _.clone( options ) : {}; + this.changed = true; _.each( this.getReverseRelations( model ), function( relation ) { - relation.removeRelated( this.instance, options ); - }, this ); + relation.removeRelated( this.instance, null, options ); + }, this ); var dit = this; - Backbone.Relational.eventQueue.add( function() { - !options.silentChange && dit.instance.trigger( 'remove:' + dit.key, model, dit.related, options ); + !options.silent && Backbone.Relational.eventQueue.add( function() { + dit.instance.trigger( 'remove:' + dit.key, model, dit.related, options ); }); }, handleReset: function( coll, options ) { - options = this.sanitizeOptions( options ); - var dit = this; - Backbone.Relational.eventQueue.add( function() { - !options.silentChange && dit.instance.trigger( 'reset:' + dit.key, dit.related, options ); + options = options ? _.clone( options ) : {}; + !options.silent && Backbone.Relational.eventQueue.add( function() { + dit.instance.trigger( 'reset:' + dit.key, dit.related, options ); }); }, - + + tryAddRelated: function( model, coll, options ) { + var item = _.contains( this.keyIds, model.id ); + + if ( item ) { + this.addRelated( model, options ); + this.keyIds = _.without( this.keyIds, model.id ); + } + }, + addRelated: function( model, options ) { + // Allow 'model' to set up its relations before proceeding. + // (which can result in a call to 'addRelated' from a relation of 'model') var dit = this; - options = this.unsanitizeOptions( options ); - model.queue( function() { // Queued to avoid errors for adding 'model' to the 'this.related' set twice - if ( dit.related && !dit.related.getByCid( model ) && !dit.related.get( model ) ) { + model.queue( function() { + if ( dit.related && !dit.related.get( model ) ) { dit.related.add( model, options ); } }); }, - - removeRelated: function( model, options ) { - options = this.unsanitizeOptions( options ); - if ( this.related.getByCid( model ) || this.related.get( model ) ) { + + removeRelated: function( model, coll, options ) { + if ( this.related.get( model ) ) { this.related.remove( model, options ); } } }); - + /** * A type of Backbone.Model that also maintains relations to other models and collections. * New events when compared to the original: * - 'add:' (model, related collection, options) * - 'remove:' (model, related collection, options) - * - 'update:' (model, related model or collection, options) + * - 'change:' (model, related model or collection, options) */ Backbone.RelationalModel = Backbone.Model.extend({ relations: null, // Relation descriptions on the prototype @@ -1006,56 +1029,98 @@ _isInitialized: false, _deferProcessing: false, _queue: null, - + subModelTypeAttribute: 'type', subModelTypes: null, - + constructor: function( attributes, options ) { // Nasty hack, for cases like 'model.get( ).add( item )'. - // Defer 'processQueue', so that when 'Relation.createModels' is used we: - // a) Survive 'Backbone.Collection.add'; this takes care we won't error on "can't add model to a set twice" - // (by creating a model from properties, having the model add itself to the collection via one of - // it's relations, then trying to add it to the collection). - // b) Trigger 'HasMany' collection events only after the model is really fully set up. - // Example that triggers both a and b: "p.get('jobs').add( { company: c, person: p } )". - var dit = this; + // Defer 'processQueue', so that when 'Relation.createModels' is used we trigger 'HasMany' + // collection events only after the model is really fully set up. + // Example: "p.get('jobs').add( { company: c, person: p } )". if ( options && options.collection ) { + var dit = this, + collection = this.collection = options.collection; + + // Prevent this option from cascading down to related models; they shouldn't go into this `if` clause. + delete options.collection; + this._deferProcessing = true; - + var processQueue = function( model ) { if ( model === dit ) { dit._deferProcessing = false; dit.processQueue(); - options.collection.unbind( 'relational:add', processQueue ); + collection.off( 'relational:add', processQueue ); } }; - options.collection.bind( 'relational:add', processQueue ); - - // So we do process the queue eventually, regardless of whether this model really gets added to 'options.collection'. + collection.on( 'relational:add', processQueue ); + + // So we do process the queue eventually, regardless of whether this model actually gets added to 'options.collection'. _.defer( function() { processQueue( dit ); }); } + + Backbone.Relational.store.processOrphanRelations(); this._queue = new Backbone.BlockingQueue(); this._queue.block(); Backbone.Relational.eventQueue.block(); - - Backbone.Model.apply( this, arguments ); - - // Try to run the global queue holding external events - Backbone.Relational.eventQueue.unblock(); + + try { + Backbone.Model.apply( this, arguments ); + } + finally { + // Try to run the global queue holding external events + Backbone.Relational.eventQueue.unblock(); + } }, - + /** * Override 'trigger' to queue 'change' and 'change:*' events */ trigger: function( eventName ) { - if ( eventName.length > 5 && 'change' === eventName.substr( 0, 6 ) ) { - var dit = this, args = arguments; + if ( eventName.length > 5 && eventName.indexOf( 'change' ) === 0 ) { + var dit = this, + args = arguments; + Backbone.Relational.eventQueue.add( function() { - Backbone.Model.prototype.trigger.apply( dit, args ); - }); + if ( !dit._isInitialized ) { + return; + } + + // Determine if the `change` event is still valid, now that all relations are populated + var changed = true; + if ( eventName === 'change' ) { + changed = dit.hasChanged(); + } + else { + var attr = eventName.slice( 7 ), + rel = dit.getRelation( attr ); + + if ( rel ) { + // If `attr` is a relation, `change:attr` get triggered from `Relation.onChange`. + // These take precedence over `change:attr` events triggered by `Model.set`. + // The relation set a fourth attribute to `true`. If this attribute is present, + // continue triggering this event; otherwise, it's from `Model.set` and should be stopped. + changed = ( args[ 4 ] === true ); + + // If this event was triggered by a relation, set the right value in `this.changed` + // (a Collection or Model instead of raw data). + if ( changed ) { + dit.changed[ attr ] = args[ 2 ]; + } + // Otherwise, this event is from `Model.set`. If the relation doesn't report a change, + // remove attr from `dit.changed` so `hasChanged` doesn't take it into account. + else if ( !rel.changed ) { + delete dit.changed[ attr ]; + } + } + } + + changed && Backbone.Model.prototype.trigger.apply( dit, args ); + }); } else { Backbone.Model.prototype.trigger.apply( this, arguments ); @@ -1063,24 +1128,18 @@ return this; }, - + /** * Initialize Relations present in this.relations; determine the type (HasOne/HasMany), then creates a new instance. * Invoked in the first call so 'set' (which is made from the Backbone.Model constructor). */ - initializeRelations: function() { + initializeRelations: function( options ) { this.acquire(); // Setting up relations often also involve calls to 'set', and we only want to enter this function once - this._relations = []; + this._relations = {}; - _.each( this.relations, function( rel ) { - var type = !_.isString( rel.type ) ? rel.type : Backbone[ rel.type ] || Backbone.Relational.store.getObjectByName( rel.type ); - if ( type && type.prototype instanceof Backbone.Relation ) { - new type( this, rel ); // Also pushes the new Relation into _relations - } - else { - Backbone.Relational.showWarnings && typeof console !== 'undefined' && console.warn( 'Relation=%o; missing or invalid type!', rel ); - } - }, this ); + _.each( this.relations || [], function( rel ) { + Backbone.Relational.store.initializeRelation( this, rel, options ); + }, this ); this._isInitialized = true; this.release(); @@ -1102,14 +1161,14 @@ }, this ); } }, - + /** * Either add to the queue (if we're not initialized yet), or execute right away. */ queue: function( func ) { this._queue.add( func ); }, - + /** * Process _queue */ @@ -1118,62 +1177,58 @@ this._queue.unblock(); } }, - + /** * Get a specific relation. * @param key {string} The relation key to look for. * @return {Backbone.Relation} An instance of 'Backbone.Relation', if a relation was found for 'key', or null. */ getRelation: function( key ) { - return _.detect( this._relations, function( rel ) { - if ( rel.key === key ) { - return true; - } - }, this ); + return this._relations[ key ]; }, - + /** * Get all of the created relations. * @return {Backbone.Relation[]} */ getRelations: function() { - return this._relations; + return _.values( this._relations ); }, - + /** * Retrieve related objects. * @param key {string} The relation key to fetch models for. - * @param options {Object} Options for 'Backbone.Model.fetch' and 'Backbone.sync'. - * @param update {boolean} Whether to force a fetch from the server (updating existing models). + * @param [options] {Object} Options for 'Backbone.Model.fetch' and 'Backbone.sync'. + * @param [refresh=false] {boolean} Fetch existing models from the server as well (in order to update them). * @return {jQuery.when[]} An array of request objects */ - fetchRelated: function( key, options, update ) { - options || ( options = {} ); + fetchRelated: function( key, options, refresh ) { + // Set default `options` for fetch + options = _.extend( { update: true, remove: false }, options ); + var setUrl, requests = [], rel = this.getRelation( key ), - keyContents = rel && rel.keyContents, - toFetch = keyContents && _.select( _.isArray( keyContents ) ? keyContents : [ keyContents ], function( item ) { - var id = Backbone.Relational.store.resolveIdForItem( rel.relatedModel, item ); - return id && ( update || !Backbone.Relational.store.find( rel.relatedModel, id ) ); + keys = rel && ( rel.keyIds || [ rel.keyId ] ), + toFetch = keys && _.select( keys || [], function( id ) { + return ( id || id === 0 ) && ( refresh || !Backbone.Relational.store.find( rel.relatedModel, id ) ); }, this ); if ( toFetch && toFetch.length ) { - // Create a model for each entry in 'keyContents' that is to be fetched - var models = _.map( toFetch, function( item ) { - var model; + // Find (or create) a model for each one that is to be fetched + var created = [], + models = _.map( toFetch, function( id ) { + var model = Backbone.Relational.store.find( rel.relatedModel, id ); + + if ( !model ) { + var attrs = {}; + attrs[ rel.relatedModel.prototype.idAttribute ] = id; + model = rel.relatedModel.findOrCreate( attrs, options ); + created.push( model ); + } - if ( _.isObject( item ) ) { - model = rel.relatedModel.build( item ); - } - else { - var attrs = {}; - attrs[ rel.relatedModel.prototype.idAttribute ] = item; - model = rel.relatedModel.build( attrs ); - } - - return model; - }, this ); + return model; + }, this ); // Try if the 'collection' can provide a url to fetch a set of models in one request. if ( rel.related instanceof Backbone.Collection && _.isFunction( rel.related.url ) ) { @@ -1188,15 +1243,14 @@ { error: function() { var args = arguments; - _.each( models, function( model ) { - model.trigger( 'destroy', model, model.collection, options ); - options.error && options.error.apply( model, args ); - }); + _.each( created, function( model ) { + model.trigger( 'destroy', model, model.collection, options ); + options.error && options.error.apply( model, args ); + }); }, url: setUrl }, - options, - { add: true } + options ); requests = [ rel.related.fetch( opts ) ]; @@ -1206,8 +1260,10 @@ var opts = _.defaults( { error: function() { - model.trigger( 'destroy', model, model.collection, options ); - options.error && options.error.apply( model, arguments ); + if ( _.contains( created, model ) ) { + model.trigger( 'destroy', model, model.collection, options ); + options.error && options.error.apply( model, arguments ); + } } }, options @@ -1219,7 +1275,32 @@ return requests; }, - + + get: function( attr ) { + var originalResult = Backbone.Model.prototype.get.call( this, attr ); + + // Use `originalResult` get if dotNotation not enabled or not required because no dot is in `attr` + if ( !this.dotNotation || attr.indexOf( '.' ) === -1 ) { + return originalResult; + } + + // Go through all splits and return the final result + var splits = attr.split( '.' ); + var result = _.reduce(splits, function( model, split ) { + if ( !( model instanceof Backbone.Model ) ) { + throw new Error( 'Attribute must be an instanceof Backbone.Model. Is: ' + model + ', currentSplit: ' + split ); + } + + return Backbone.Model.prototype.get.call( model, split ); + }, this ); + + if ( originalResult !== undefined && result !== undefined ) { + throw new Error( "Ambiguous result for '" + attr + "'. direct result: " + originalResult + ", dotNotation: " + result ); + } + + return originalResult || result; + }, + set: function( key, value, options ) { Backbone.Relational.eventQueue.block(); @@ -1237,28 +1318,31 @@ var result = Backbone.Model.prototype.set.apply( this, arguments ); // Ideal place to set up relations :) - if ( !this._isInitialized && !this.isLocked() ) { - this.constructor.initializeModelHierarchy(); + try { + if ( !this._isInitialized && !this.isLocked() ) { + this.constructor.initializeModelHierarchy(); - Backbone.Relational.store.register( this ); + Backbone.Relational.store.register( this ); - this.initializeRelations(); + this.initializeRelations( options ); + } + // Update the 'idAttribute' in Backbone.store if; we don't want it to miss an 'id' update due to {silent:true} + else if ( attributes && this.idAttribute in attributes ) { + Backbone.Relational.store.update( this ); + } + + if ( attributes ) { + this.updateRelations( options ); + } } - // Update the 'idAttribute' in Backbone.store if; we don't want it to miss an 'id' update due to {silent:true} - else if ( attributes && this.idAttribute in attributes ) { - Backbone.Relational.store.update( this ); + finally { + // Try to run the global queue holding external events + Backbone.Relational.eventQueue.unblock(); } - if ( attributes ) { - this.updateRelations( options ); - } - - // Try to run the global queue holding external events - Backbone.Relational.eventQueue.unblock(); - return result; }, - + unset: function( attribute, options ) { Backbone.Relational.eventQueue.block(); @@ -1270,7 +1354,7 @@ return result; }, - + clear: function( options ) { Backbone.Relational.eventQueue.block(); @@ -1282,17 +1366,6 @@ return result; }, - - /** - * Override 'change', so the change will only execute after 'set' has finised (relations are updated), - * and 'previousAttributes' will be available when the event is fired. - */ - change: function( options ) { - var dit = this, args = arguments; - Backbone.Relational.eventQueue.add( function() { - Backbone.Model.prototype.change.apply( dit, args ); - }); - }, clone: function() { var attributes = _.clone( this.attributes ); @@ -1301,87 +1374,92 @@ } _.each( this.getRelations(), function( rel ) { - delete attributes[ rel.key ]; - }); + delete attributes[ rel.key ]; + }); return new this.constructor( attributes ); }, - + /** * Convert relations to JSON, omits them when required */ - toJSON: function() { + toJSON: function( options ) { // If this Model has already been fully serialized in this branch once, return to avoid loops if ( this.isLocked() ) { return this.id; } this.acquire(); - var json = Backbone.Model.prototype.toJSON.call( this ); + var json = Backbone.Model.prototype.toJSON.call( this, options ); if ( this.constructor._superModel && !( this.constructor._subModelTypeAttribute in json ) ) { json[ this.constructor._subModelTypeAttribute ] = this.constructor._subModelTypeValue; } _.each( this._relations, function( rel ) { - var value = json[ rel.key ]; + var value = json[ rel.key ]; - if ( rel.options.includeInJSON === true) { - if ( value && _.isFunction( value.toJSON ) ) { - json[ rel.keyDestination ] = value.toJSON(); - } - else { - json[ rel.keyDestination ] = null; - } - } - else if ( _.isString( rel.options.includeInJSON ) ) { - if ( value instanceof Backbone.Collection ) { - json[ rel.keyDestination ] = value.pluck( rel.options.includeInJSON ); - } - else if ( value instanceof Backbone.Model ) { - json[ rel.keyDestination ] = value.get( rel.options.includeInJSON ); - } - else { - json[ rel.keyDestination ] = null; - } - } - else if ( _.isArray( rel.options.includeInJSON ) ) { - if ( value instanceof Backbone.Collection ) { - var valueSub = []; - value.each( function( model ) { - var curJson = {}; - _.each( rel.options.includeInJSON, function( key ) { - curJson[ key ] = model.get( key ); - }); - valueSub.push( curJson ); - }); - json[ rel.keyDestination ] = valueSub; - } - else if ( value instanceof Backbone.Model ) { - var valueSub = {}; - _.each( rel.options.includeInJSON, function( key ) { - valueSub[ key ] = value.get( key ); - }); - json[ rel.keyDestination ] = valueSub; - } - else { - json[ rel.keyDestination ] = null; - } + if ( rel.options.includeInJSON === true) { + if ( value && _.isFunction( value.toJSON ) ) { + json[ rel.keyDestination ] = value.toJSON( options ); } else { - delete json[ rel.key ]; + json[ rel.keyDestination ] = null; } + } + else if ( _.isString( rel.options.includeInJSON ) ) { + if ( value instanceof Backbone.Collection ) { + json[ rel.keyDestination ] = value.pluck( rel.options.includeInJSON ); + } + else if ( value instanceof Backbone.Model ) { + json[ rel.keyDestination ] = value.get( rel.options.includeInJSON ); + } + else { + json[ rel.keyDestination ] = null; + } + } + else if ( _.isArray( rel.options.includeInJSON ) ) { + if ( value instanceof Backbone.Collection ) { + var valueSub = []; + value.each( function( model ) { + var curJson = {}; + _.each( rel.options.includeInJSON, function( key ) { + curJson[ key ] = model.get( key ); + }); + valueSub.push( curJson ); + }); + json[ rel.keyDestination ] = valueSub; + } + else if ( value instanceof Backbone.Model ) { + var valueSub = {}; + _.each( rel.options.includeInJSON, function( key ) { + valueSub[ key ] = value.get( key ); + }); + json[ rel.keyDestination ] = valueSub; + } + else { + json[ rel.keyDestination ] = null; + } + } + else { + delete json[ rel.key ]; + } - if ( rel.keyDestination !== rel.key ) { - delete json[ rel.key ]; - } - }); + if ( rel.keyDestination !== rel.key ) { + delete json[ rel.key ]; + } + }); this.release(); return json; } }, { + /** + * + * @param superModel + * @returns {Backbone.RelationalModel.constructor} + */ setup: function( superModel ) { // We don't want to share a relations array with a parent, as this will cause problems with // reverse relations. @@ -1400,30 +1478,37 @@ } // Initialize all reverseRelations that belong to this new model. - _.each( this.prototype.relations, function( rel ) { - if ( !rel.model ) { - rel.model = this; + _.each( this.prototype.relations || [], function( rel ) { + if ( !rel.model ) { + rel.model = this; + } + + if ( rel.reverseRelation && rel.model === this ) { + var preInitialize = true; + if ( _.isString( rel.relatedModel ) ) { + /** + * The related model might not be defined for two reasons + * 1. it is related to itself + * 2. it never gets defined, e.g. a typo + * 3. the model hasn't been defined yet, but will be later + * In neither of these cases do we need to pre-initialize reverse relations. + * However, for 3. (which is, to us, indistinguishable from 2.), we do need to attempt + * setting up this relation again later, in case the related model is defined later. + */ + var relatedModel = Backbone.Relational.store.getObjectByName( rel.relatedModel ); + preInitialize = relatedModel && ( relatedModel.prototype instanceof Backbone.RelationalModel ); } - if ( rel.reverseRelation && rel.model === this ) { - var preInitialize = true; - if ( _.isString( rel.relatedModel ) ) { - /** - * The related model might not be defined for two reasons - * 1. it never gets defined, e.g. a typo - * 2. it is related to itself - * In neither of these cases do we need to pre-initialize reverse relations. - */ - var relatedModel = Backbone.Relational.store.getObjectByName( rel.relatedModel ); - preInitialize = relatedModel && ( relatedModel.prototype instanceof Backbone.RelationalModel ); - } - - var type = !_.isString( rel.type ) ? rel.type : Backbone[ rel.type ] || Backbone.Relational.store.getObjectByName( rel.type ); - if ( preInitialize && type && type.prototype instanceof Backbone.Relation ) { - new type( null, rel ); - } + if ( preInitialize ) { + Backbone.Relational.store.initializeRelation( null, rel ); } - }, this ); + else if ( _.isString( rel.relatedModel ) ) { + Backbone.Relational.store.addOrphanRelation( rel ); + } + } + }, this ); + + return this; }, /** @@ -1451,6 +1536,9 @@ return new model( attributes, options ); }, + /** + * + */ initializeModelHierarchy: function() { // If we're here for the first time, try to determine if this modelType has a 'superModel'. if ( _.isUndefined( this._superModel ) || _.isNull( this._superModel ) ) { @@ -1463,7 +1551,7 @@ if ( this._superModel ) { // if ( this._superModel.prototype.relations ) { - var supermodelRelationsExist = _.any( this.prototype.relations, function( rel ) { + var supermodelRelationsExist = _.any( this.prototype.relations || [], function( rel ) { return rel.model && rel.model !== this; }, this ); @@ -1479,7 +1567,7 @@ // If we came here through 'build' for a model that has 'subModelTypes', and not all of them have been resolved yet, try to resolve each. if ( this.prototype.subModelTypes && _.keys( this.prototype.subModelTypes ).length !== _.keys( this._subModels ).length ) { - _.each( this.prototype.subModelTypes, function( subModelTypeName ) { + _.each( this.prototype.subModelTypes || [], function( subModelTypeName ) { var subModelType = Backbone.Relational.store.getObjectByName( subModelTypeName ); subModelType && subModelType.initializeModelHierarchy(); }); @@ -1489,24 +1577,30 @@ /** * Find an instance of `this` type in 'Backbone.Relational.store'. * - If `attributes` is a string or a number, `findOrCreate` will just query the `store` and return a model if found. - * - If `attributes` is an object, the model will be updated with `attributes` if found. + * - If `attributes` is an object and is found in the store, the model will be updated with `attributes` unless `options.update` is `false`. * Otherwise, a new model is created with `attributes` (unless `options.create` is explicitly set to `false`). * @param {Object|String|Number} attributes Either a model's id, or the attributes used to create or update a model. * @param {Object} [options] * @param {Boolean} [options.create=true] + * @param {Boolean} [options.merge=true] + * @param {Boolean} [options.parse=false] * @return {Backbone.RelationalModel} */ findOrCreate: function( attributes, options ) { - // Try to find an instance of 'this' model type in the store - var model = Backbone.Relational.store.find( this, attributes ); + options || ( options = {} ); + var parsedAttributes = ( _.isObject( attributes ) && options.parse && this.prototype.parse ) ? + this.prototype.parse( attributes ) : attributes; - // If we found an instance, update it with the data in 'item'; if not, create an instance - // (unless 'options.create' is false). + // Try to find an instance of 'this' model type in the store + var model = Backbone.Relational.store.find( this, parsedAttributes ); + + // If we found an instance, update it with the data in 'item' (unless 'options.merge' is false). + // If not, create an instance (unless 'options.create' is false). if ( _.isObject( attributes ) ) { - if ( model ) { - model.set( attributes, options ); + if ( model && options.merge !== false ) { + model.set( parsedAttributes, options ); } - else if ( !options || ( options && options.create !== false ) ) { + else if ( !model && options.create !== false ) { model = this.build( attributes, options ); } } @@ -1515,103 +1609,123 @@ } }); _.extend( Backbone.RelationalModel.prototype, Backbone.Semaphore ); - + /** * Override Backbone.Collection._prepareModel, so objects will be built using the correct type * if the collection.model has subModels. + * Attempts to find a model for `attrs` in Backbone.store through `findOrCreate` + * (which sets the new properties on it if found), or instantiates a new model. */ Backbone.Collection.prototype.__prepareModel = Backbone.Collection.prototype._prepareModel; - Backbone.Collection.prototype._prepareModel = function ( model, options ) { - options || (options = {}); - if ( !( model instanceof Backbone.Model ) ) { - var attrs = model; + Backbone.Collection.prototype._prepareModel = function ( attrs, options ) { + var model; + + if ( attrs instanceof Backbone.Model ) { + if ( !attrs.collection ) { + attrs.collection = this; + } + model = attrs; + } + else { + options || (options = {}); options.collection = this; - if ( typeof this.model.build !== 'undefined' ) { - model = this.model.build( attrs, options ); + if ( typeof this.model.findOrCreate !== 'undefined' ) { + model = this.model.findOrCreate( attrs, options ); } else { model = new this.model( attrs, options ); } - if ( !model._validate( model.attributes, options ) ) { + if ( model && model.isNew() && !model._validate( attrs, options ) ) { + this.trigger( 'invalid', this, attrs, options ); model = false; } } - else if ( !model.collection ) { - model.collection = this; - } return model; - } - + }; + + /** - * Override Backbone.Collection.add, so objects fetched from the server multiple times will - * update the existing Model. Also, trigger 'relational:add'. + * Override Backbone.Collection.add, so we'll create objects from attributes where required, + * and update the existing models. Also, trigger 'relational:add'. */ var add = Backbone.Collection.prototype.__add = Backbone.Collection.prototype.add; Backbone.Collection.prototype.add = function( models, options ) { - options || (options = {}); - if ( !_.isArray( models ) ) { - models = [ models ]; + // Short-circuit if this Collection doesn't hold RelationalModels + if ( !( this.model.prototype instanceof Backbone.RelationalModel ) ) { + return add.apply( this, arguments ); } - var modelsToAdd = []; + models = _.isArray( models ) ? models.slice() : [ models ]; + // Set default options to the same values as `add` uses, so `findOrCreate` will also respect those. + options = _.extend( { merge: false }, options ); + + var newModels = [], + toAdd = []; //console.debug( 'calling add on coll=%o; model=%o, options=%o', this, models, options ); _.each( models, function( model ) { - if ( !( model instanceof Backbone.Model ) ) { - // Try to find 'model' in Backbone.store. If it already exists, set the new properties on it. - var existingModel = Backbone.Relational.store.find( this.model, model[ this.model.prototype.idAttribute ] ); - if ( existingModel ) { - existingModel.set( existingModel.parse ? existingModel.parse( model ) : model, options ); - model = existingModel; - } - else { - model = Backbone.Collection.prototype._prepareModel.call( this, model, options ); - } - } + if ( !( model instanceof Backbone.Model ) ) { + model = Backbone.Collection.prototype._prepareModel.call( this, model, options ); + } - if ( model instanceof Backbone.Model && !this.get( model ) && !this.getByCid( model ) ) { - modelsToAdd.push( model ); - } - }, this ); + if ( model ) { + toAdd.push( model ); + if ( !( this.get( model ) || this.get( model.cid ) ) ) { + newModels.push( model ); + } + // If we arrive in `add` while performing a `set` (after a create, so the model gains an `id`), + // we may get here before `_onModelEvent` has had the chance to update `_byId`. + else if ( model.id != null ) { + this._byId[ model.id ] = model; + } + } + }, this ); // Add 'models' in a single batch, so the original add will only be called once (and thus 'sort', etc). - if ( modelsToAdd.length ) { - add.call( this, modelsToAdd, options ); + add.call( this, toAdd, options ); - _.each( modelsToAdd, function( model ) { - this.trigger( 'relational:add', model, this, options ); - }, this ); - } + _.each( newModels, function( model ) { + // Fire a `relational:add` event for any model in `newModels` that has actually been added to the collection. + if ( this.get( model ) || this.get( model.cid ) ) { + this.trigger( 'relational:add', model, this, options ); + } + }, this ); return this; }; - + /** * Override 'Backbone.Collection.remove' to trigger 'relational:remove'. */ var remove = Backbone.Collection.prototype.__remove = Backbone.Collection.prototype.remove; Backbone.Collection.prototype.remove = function( models, options ) { - options || (options = {}); - if ( !_.isArray( models ) ) { - models = [ models ]; - } - else { - models = models.slice( 0 ); + // Short-circuit if this Collection doesn't hold RelationalModels + if ( !( this.model.prototype instanceof Backbone.RelationalModel ) ) { + return remove.apply( this, arguments ); } + models = _.isArray( models ) ? models.slice() : [ models ]; + options || ( options = {} ); + + var toRemove = []; + //console.debug('calling remove on coll=%o; models=%o, options=%o', this, models, options ); _.each( models, function( model ) { - model = this.getByCid( model ) || this.get( model ); + model = this.get( model ) || this.get( model.cid ); + model && toRemove.push( model ); + }, this ); - if ( model instanceof Backbone.Model ) { - remove.call( this, model, options ); - this.trigger('relational:remove', model, this, options); - } + if ( toRemove.length ) { + remove.call( this, toRemove, options ); + + _.each( toRemove, function( model ) { + this.trigger('relational:remove', model, this, options); }, this ); + } return this; }; @@ -1622,7 +1736,10 @@ var reset = Backbone.Collection.prototype.__reset = Backbone.Collection.prototype.reset; Backbone.Collection.prototype.reset = function( models, options ) { reset.call( this, models, options ); - this.trigger( 'relational:reset', this, options ); + + if ( this.model.prototype instanceof Backbone.RelationalModel ) { + this.trigger( 'relational:reset', this, options ); + } return this; }; @@ -1633,22 +1750,39 @@ var sort = Backbone.Collection.prototype.__sort = Backbone.Collection.prototype.sort; Backbone.Collection.prototype.sort = function( options ) { sort.call( this, options ); - this.trigger( 'relational:reset', this, options ); + + if ( this.model.prototype instanceof Backbone.RelationalModel ) { + this.trigger( 'relational:reset', this, options ); + } return this; }; - + /** * Override 'Backbone.Collection.trigger' so 'add', 'remove' and 'reset' events are queued until relations * are ready. */ var trigger = Backbone.Collection.prototype.__trigger = Backbone.Collection.prototype.trigger; Backbone.Collection.prototype.trigger = function( eventName ) { + // Short-circuit if this Collection doesn't hold RelationalModels + if ( !( this.model.prototype instanceof Backbone.RelationalModel ) ) { + return trigger.apply( this, arguments ); + } + if ( eventName === 'add' || eventName === 'remove' || eventName === 'reset' ) { - var dit = this, args = arguments; + var dit = this, + args = arguments; + + if ( _.isObject( args[ 3 ] ) ) { + args = _.toArray( args ); + // the fourth argument is the option object. + // we need to clone it, as it could be modified while we wait on the eventQueue to be unblocked + args[ 3 ] = _.clone( args[ 3 ] ); + } + Backbone.Relational.eventQueue.add( function() { - trigger.apply( dit, args ); - }); + trigger.apply( dit, args ); + }); } else { trigger.apply( this, arguments ); diff --git a/static/scripts/libs/backbone/backbone.js b/static/scripts/libs/backbone/backbone.js index 3373c952bfa..3512d42fb43 100644 --- a/static/scripts/libs/backbone/backbone.js +++ b/static/scripts/libs/backbone/backbone.js @@ -1,6 +1,6 @@ -// Backbone.js 0.9.2 +// Backbone.js 1.0.0 -// (c) 2010-2012 Jeremy Ashkenas, DocumentCloud Inc. +// (c) 2010-2013 Jeremy Ashkenas, DocumentCloud Inc. // Backbone may be freely distributed under the MIT license. // For all details and documentation: // http://backbonejs.org @@ -10,7 +10,7 @@ // Initial Setup // ------------- - // Save a reference to the global object (`window` in the browser, `global` + // Save a reference to the global object (`window` in the browser, `exports` // on the server). var root = this; @@ -18,12 +18,14 @@ // restored later on, if `noConflict` is used. var previousBackbone = root.Backbone; - // Create a local reference to slice/splice. - var slice = Array.prototype.slice; - var splice = Array.prototype.splice; + // Create local references to array methods we'll want to use later. + var array = []; + var push = array.push; + var slice = array.slice; + var splice = array.splice; // The top-level namespace. All public Backbone classes and modules will - // be attached to this. Exported for both CommonJS and the browser. + // be attached to this. Exported for both the browser and the server. var Backbone; if (typeof exports !== 'undefined') { Backbone = exports; @@ -32,23 +34,15 @@ } // Current version of the library. Keep in sync with `package.json`. - Backbone.VERSION = '0.9.2'; + Backbone.VERSION = '1.0.0'; // Require Underscore, if we're on the server, and it's not already present. var _ = root._; if (!_ && (typeof require !== 'undefined')) _ = require('underscore'); - // For Backbone's purposes, jQuery, Zepto, or Ender owns the `$` variable. - var $ = root.jQuery || root.Zepto || root.ender; - - // Set the JavaScript library that will be used for DOM manipulation and - // Ajax calls (a.k.a. the `$` variable). By default Backbone will use: jQuery, - // Zepto, or Ender; but the `setDomLibrary()` method lets you inject an - // alternate JavaScript library (or a mock library for testing your views - // outside of a browser). - Backbone.setDomLibrary = function(lib) { - $ = lib; - }; + // For Backbone's purposes, jQuery, Zepto, Ender, or My Library (kidding) owns + // the `$` variable. + Backbone.$ = root.jQuery || root.Zepto || root.ender || root.$; // Runs Backbone.js in *noConflict* mode, returning the `Backbone` variable // to its previous owner. Returns a reference to this Backbone object. @@ -69,14 +63,12 @@ Backbone.emulateJSON = false; // Backbone.Events - // ----------------- - - // Regular expression used to split event strings - var eventSplitter = /\s+/; + // --------------- // A module that can be mixed in to *any object* in order to provide it with - // custom events. You may bind with `on` or remove with `off` callback functions - // to an event; trigger`-ing an event fires all callbacks in succession. + // custom events. You may bind with `on` or remove with `off` callback + // functions to an event; `trigger`-ing an event fires all callbacks in + // succession. // // var object = {}; // _.extend(object, Backbone.Events); @@ -85,58 +77,56 @@ // var Events = Backbone.Events = { - // Bind one or more space separated events, `events`, to a `callback` - // function. Passing `"all"` will bind the callback to all events fired. - on: function(events, callback, context) { - - var calls, event, node, tail, list; - if (!callback) return this; - events = events.split(eventSplitter); - calls = this._callbacks || (this._callbacks = {}); - - // Create an immutable callback list, allowing traversal during - // modification. The tail is an empty object that will always be used - // as the next node. - while (event = events.shift()) { - list = calls[event]; - node = list ? list.tail : {}; - node.next = tail = {}; - node.context = context; - node.callback = callback; - calls[event] = {tail: tail, next: list ? list.next : node}; - } - + // Bind an event to a `callback` function. Passing `"all"` will bind + // the callback to all events fired. + on: function(name, callback, context) { + if (!eventsApi(this, 'on', name, [callback, context]) || !callback) return this; + this._events || (this._events = {}); + var events = this._events[name] || (this._events[name] = []); + events.push({callback: callback, context: context, ctx: context || this}); return this; }, - // Remove one or many callbacks. If `context` is null, removes all callbacks - // with that function. If `callback` is null, removes all callbacks for the - // event. If `events` is null, removes all bound callbacks for all events. - off: function(events, callback, context) { - var event, calls, node, tail, cb, ctx; + // Bind an event to only be triggered a single time. After the first time + // the callback is invoked, it will be removed. + once: function(name, callback, context) { + if (!eventsApi(this, 'once', name, [callback, context]) || !callback) return this; + var self = this; + var once = _.once(function() { + self.off(name, once); + callback.apply(this, arguments); + }); + once._callback = callback; + return this.on(name, once, context); + }, - // No events, or removing *all* events. - if (!(calls = this._callbacks)) return; - if (!(events || callback || context)) { - delete this._callbacks; + // Remove one or many callbacks. If `context` is null, removes all + // callbacks with that function. If `callback` is null, removes all + // callbacks for the event. If `name` is null, removes all bound + // callbacks for all events. + off: function(name, callback, context) { + var retain, ev, events, names, i, l, j, k; + if (!this._events || !eventsApi(this, 'off', name, [callback, context])) return this; + if (!name && !callback && !context) { + this._events = {}; return this; } - // Loop through the listed events and contexts, splicing them out of the - // linked list of callbacks if appropriate. - events = events ? events.split(eventSplitter) : _.keys(calls); - while (event = events.shift()) { - node = calls[event]; - delete calls[event]; - if (!node || !(callback || context)) continue; - // Create a new list, omitting the indicated callbacks. - tail = node.tail; - while ((node = node.next) !== tail) { - cb = node.callback; - ctx = node.context; - if ((callback && cb !== callback) || (context && ctx !== context)) { - this.on(event, cb, ctx); + names = name ? [name] : _.keys(this._events); + for (i = 0, l = names.length; i < l; i++) { + name = names[i]; + if (events = this._events[name]) { + this._events[name] = retain = []; + if (callback || context) { + for (j = 0, k = events.length; j < k; j++) { + ev = events[j]; + if ((callback && callback !== ev.callback && callback !== ev.callback._callback) || + (context && context !== ev.context)) { + retain.push(ev); + } + } } + if (!retain.length) delete this._events[name]; } } @@ -147,81 +137,138 @@ // passed the same arguments as `trigger` is, apart from the event name // (unless you're listening on `"all"`, which will cause your callback to // receive the true name of the event as the first argument). - trigger: function(events) { - var event, node, calls, tail, args, all, rest; - if (!(calls = this._callbacks)) return this; - all = calls.all; - events = events.split(eventSplitter); - rest = slice.call(arguments, 1); + trigger: function(name) { + if (!this._events) return this; + var args = slice.call(arguments, 1); + if (!eventsApi(this, 'trigger', name, args)) return this; + var events = this._events[name]; + var allEvents = this._events.all; + if (events) triggerEvents(events, args); + if (allEvents) triggerEvents(allEvents, arguments); + return this; + }, - // For each event, walk through the linked list of callbacks twice, - // first to trigger the event, then to trigger any `"all"` callbacks. - while (event = events.shift()) { - if (node = calls[event]) { - tail = node.tail; - while ((node = node.next) !== tail) { - node.callback.apply(node.context || this, rest); - } - } - if (node = all) { - tail = node.tail; - args = [event].concat(rest); - while ((node = node.next) !== tail) { - node.callback.apply(node.context || this, args); - } - } + // Tell this object to stop listening to either specific events ... or + // to every object it's currently listening to. + stopListening: function(obj, name, callback) { + var listeners = this._listeners; + if (!listeners) return this; + var deleteListener = !name && !callback; + if (typeof name === 'object') callback = this; + if (obj) (listeners = {})[obj._listenerId] = obj; + for (var id in listeners) { + listeners[id].off(name, callback, this); + if (deleteListener) delete this._listeners[id]; } - return this; } }; + // Regular expression used to split event strings. + var eventSplitter = /\s+/; + + // Implement fancy features of the Events API such as multiple event + // names `"change blur"` and jQuery-style event maps `{change: action}` + // in terms of the existing API. + var eventsApi = function(obj, action, name, rest) { + if (!name) return true; + + // Handle event maps. + if (typeof name === 'object') { + for (var key in name) { + obj[action].apply(obj, [key, name[key]].concat(rest)); + } + return false; + } + + // Handle space separated event names. + if (eventSplitter.test(name)) { + var names = name.split(eventSplitter); + for (var i = 0, l = names.length; i < l; i++) { + obj[action].apply(obj, [names[i]].concat(rest)); + } + return false; + } + + return true; + }; + + // A difficult-to-believe, but optimized internal dispatch function for + // triggering events. Tries to keep the usual cases speedy (most internal + // Backbone events have 3 arguments). + var triggerEvents = function(events, args) { + var ev, i = -1, l = events.length, a1 = args[0], a2 = args[1], a3 = args[2]; + switch (args.length) { + case 0: while (++i < l) (ev = events[i]).callback.call(ev.ctx); return; + case 1: while (++i < l) (ev = events[i]).callback.call(ev.ctx, a1); return; + case 2: while (++i < l) (ev = events[i]).callback.call(ev.ctx, a1, a2); return; + case 3: while (++i < l) (ev = events[i]).callback.call(ev.ctx, a1, a2, a3); return; + default: while (++i < l) (ev = events[i]).callback.apply(ev.ctx, args); + } + }; + + var listenMethods = {listenTo: 'on', listenToOnce: 'once'}; + + // Inversion-of-control versions of `on` and `once`. Tell *this* object to + // listen to an event in another object ... keeping track of what it's + // listening to. + _.each(listenMethods, function(implementation, method) { + Events[method] = function(obj, name, callback) { + var listeners = this._listeners || (this._listeners = {}); + var id = obj._listenerId || (obj._listenerId = _.uniqueId('l')); + listeners[id] = obj; + if (typeof name === 'object') callback = this; + obj[implementation](name, callback, this); + return this; + }; + }); + // Aliases for backwards compatibility. Events.bind = Events.on; Events.unbind = Events.off; + // Allow the `Backbone` object to serve as a global event bus, for folks who + // want global "pubsub" in a convenient place. + _.extend(Backbone, Events); + // Backbone.Model // -------------- - // Create a new model, with defined attributes. A client id (`cid`) + // Backbone **Models** are the basic data object in the framework -- + // frequently representing a row in a table in a database on your server. + // A discrete chunk of data and a bunch of useful, related methods for + // performing computations and transformations on that data. + + // Create a new model with the specified attributes. A client id (`cid`) // is automatically generated and assigned for you. var Model = Backbone.Model = function(attributes, options) { var defaults; - attributes || (attributes = {}); - if (options && options.parse) attributes = this.parse(attributes); - if (defaults = getValue(this, 'defaults')) { - attributes = _.extend({}, defaults, attributes); - } - if (options && options.collection) this.collection = options.collection; - this.attributes = {}; - this._escapedAttributes = {}; + var attrs = attributes || {}; + options || (options = {}); this.cid = _.uniqueId('c'); + this.attributes = {}; + _.extend(this, _.pick(options, modelOptions)); + if (options.parse) attrs = this.parse(attrs, options) || {}; + if (defaults = _.result(this, 'defaults')) { + attrs = _.defaults({}, attrs, defaults); + } + this.set(attrs, options); this.changed = {}; - this._silent = {}; - this._pending = {}; - this.set(attributes, {silent: true}); - // Reset change tracking. - this.changed = {}; - this._silent = {}; - this._pending = {}; - this._previousAttributes = _.clone(this.attributes); this.initialize.apply(this, arguments); }; + // A list of options to be attached directly to the model, if provided. + var modelOptions = ['url', 'urlRoot', 'collection']; + // Attach all inheritable methods to the Model prototype. _.extend(Model.prototype, Events, { // A hash of attributes whose current and previous value differ. changed: null, - // A hash of attributes that have silently changed since the last time - // `change` was called. Will become pending attributes on the next call. - _silent: null, - - // A hash of attributes that have changed since the last `'change'` event - // began. - _pending: null, + // The value returned during the last failed validation. + validationError: null, // The default name for the JSON `id` attribute is `"id"`. MongoDB and // CouchDB users may want to set this to `"_id"`. @@ -236,6 +283,12 @@ return _.clone(this.attributes); }, + // Proxy `Backbone.sync` by default -- but override this if you need + // custom syncing semantics for *this* particular model. + sync: function() { + return Backbone.sync.apply(this, arguments); + }, + // Get the value of an attribute. get: function(attr) { return this.attributes[attr]; @@ -243,10 +296,7 @@ // Get the HTML-escaped value of an attribute. escape: function(attr) { - var html; - if (html = this._escapedAttributes[attr]) return html; - var val = this.get(attr); - return this._escapedAttributes[attr] = _.escape(val == null ? '' : '' + val); + return _.escape(this.get(attr)); }, // Returns `true` if the attribute contains a value that is not null @@ -255,146 +305,195 @@ return this.get(attr) != null; }, - // Set a hash of model attributes on the object, firing `"change"` unless - // you choose to silence it. - set: function(key, value, options) { - var attrs, attr, val; + // Set a hash of model attributes on the object, firing `"change"`. This is + // the core primitive operation of a model, updating the data and notifying + // anyone who needs to know about the change in state. The heart of the beast. + set: function(key, val, options) { + var attr, attrs, unset, changes, silent, changing, prev, current; + if (key == null) return this; // Handle both `"key", value` and `{key: value}` -style arguments. - if (_.isObject(key) || key == null) { + if (typeof key === 'object') { attrs = key; - options = value; + options = val; } else { - attrs = {}; - attrs[key] = value; + (attrs = {})[key] = val; } - // Extract attributes and options. options || (options = {}); - if (!attrs) return this; - if (attrs instanceof Model) attrs = attrs.attributes; - if (options.unset) for (attr in attrs) attrs[attr] = void 0; // Run validation. if (!this._validate(attrs, options)) return false; + // Extract attributes and options. + unset = options.unset; + silent = options.silent; + changes = []; + changing = this._changing; + this._changing = true; + + if (!changing) { + this._previousAttributes = _.clone(this.attributes); + this.changed = {}; + } + current = this.attributes, prev = this._previousAttributes; + // Check for changes of `id`. if (this.idAttribute in attrs) this.id = attrs[this.idAttribute]; - var changes = options.changes = {}; - var now = this.attributes; - var escaped = this._escapedAttributes; - var prev = this._previousAttributes || {}; - - // For each `set` attribute... + // For each `set` attribute, update or delete the current value. for (attr in attrs) { val = attrs[attr]; - - // If the new and current value differ, record the change. - if (!_.isEqual(now[attr], val) || (options.unset && _.has(now, attr))) { - delete escaped[attr]; - (options.silent ? this._silent : changes)[attr] = true; - } - - // Update or delete the current value. - options.unset ? delete now[attr] : now[attr] = val; - - // If the new and previous value differ, record the change. If not, - // then remove changes for this attribute. - if (!_.isEqual(prev[attr], val) || (_.has(now, attr) != _.has(prev, attr))) { + if (!_.isEqual(current[attr], val)) changes.push(attr); + if (!_.isEqual(prev[attr], val)) { this.changed[attr] = val; - if (!options.silent) this._pending[attr] = true; } else { delete this.changed[attr]; - delete this._pending[attr]; + } + unset ? delete current[attr] : current[attr] = val; + } + + // Trigger all relevant attribute changes. + if (!silent) { + if (changes.length) this._pending = true; + for (var i = 0, l = changes.length; i < l; i++) { + this.trigger('change:' + changes[i], this, current[changes[i]], options); } } - // Fire the `"change"` events. - if (!options.silent) this.change(options); + // You might be wondering why there's a `while` loop here. Changes can + // be recursively nested within `"change"` events. + if (changing) return this; + if (!silent) { + while (this._pending) { + this._pending = false; + this.trigger('change', this, options); + } + } + this._pending = false; + this._changing = false; return this; }, - // Remove an attribute from the model, firing `"change"` unless you choose - // to silence it. `unset` is a noop if the attribute doesn't exist. + // Remove an attribute from the model, firing `"change"`. `unset` is a noop + // if the attribute doesn't exist. unset: function(attr, options) { - (options || (options = {})).unset = true; - return this.set(attr, null, options); + return this.set(attr, void 0, _.extend({}, options, {unset: true})); }, - // Clear all attributes on the model, firing `"change"` unless you choose - // to silence it. + // Clear all attributes on the model, firing `"change"`. clear: function(options) { - (options || (options = {})).unset = true; - return this.set(_.clone(this.attributes), options); + var attrs = {}; + for (var key in this.attributes) attrs[key] = void 0; + return this.set(attrs, _.extend({}, options, {unset: true})); + }, + + // Determine if the model has changed since the last `"change"` event. + // If you specify an attribute name, determine if that attribute has changed. + hasChanged: function(attr) { + if (attr == null) return !_.isEmpty(this.changed); + return _.has(this.changed, attr); + }, + + // Return an object containing all the attributes that have changed, or + // false if there are no changed attributes. Useful for determining what + // parts of a view need to be updated and/or what attributes need to be + // persisted to the server. Unset attributes will be set to undefined. + // You can also pass an attributes object to diff against the model, + // determining if there *would be* a change. + changedAttributes: function(diff) { + if (!diff) return this.hasChanged() ? _.clone(this.changed) : false; + var val, changed = false; + var old = this._changing ? this._previousAttributes : this.attributes; + for (var attr in diff) { + if (_.isEqual(old[attr], (val = diff[attr]))) continue; + (changed || (changed = {}))[attr] = val; + } + return changed; + }, + + // Get the previous value of an attribute, recorded at the time the last + // `"change"` event was fired. + previous: function(attr) { + if (attr == null || !this._previousAttributes) return null; + return this._previousAttributes[attr]; + }, + + // Get all of the attributes of the model at the time of the previous + // `"change"` event. + previousAttributes: function() { + return _.clone(this._previousAttributes); }, // Fetch the model from the server. If the server's representation of the - // model differs from its current attributes, they will be overriden, + // model differs from its current attributes, they will be overridden, // triggering a `"change"` event. fetch: function(options) { options = options ? _.clone(options) : {}; + if (options.parse === void 0) options.parse = true; var model = this; var success = options.success; - options.success = function(resp, status, xhr) { - if (!model.set(model.parse(resp, xhr), options)) return false; - if (success) success(model, resp); + options.success = function(resp) { + if (!model.set(model.parse(resp, options), options)) return false; + if (success) success(model, resp, options); + model.trigger('sync', model, resp, options); }; - options.error = Backbone.wrapError(options.error, model, options); - return (this.sync || Backbone.sync).call(this, 'read', this, options); + wrapError(this, options); + return this.sync('read', this, options); }, // Set a hash of model attributes, and sync the model to the server. // If the server returns an attributes hash that differs, the model's // state will be `set` again. - save: function(key, value, options) { - var attrs, current; + save: function(key, val, options) { + var attrs, method, xhr, attributes = this.attributes; - // Handle both `("key", value)` and `({key: value})` -style calls. - if (_.isObject(key) || key == null) { + // Handle both `"key", value` and `{key: value}` -style arguments. + if (key == null || typeof key === 'object') { attrs = key; - options = value; + options = val; } else { - attrs = {}; - attrs[key] = value; - } - options = options ? _.clone(options) : {}; - - // If we're "wait"-ing to set changed attributes, validate early. - if (options.wait) { - if (!this._validate(attrs, options)) return false; - current = _.clone(this.attributes); + (attrs = {})[key] = val; } - // Regular saves `set` attributes before persisting to the server. - var silentOptions = _.extend({}, options, {silent: true}); - if (attrs && !this.set(attrs, options.wait ? silentOptions : options)) { - return false; + // If we're not waiting and attributes exist, save acts as `set(attr).save(null, opts)`. + if (attrs && (!options || !options.wait) && !this.set(attrs, options)) return false; + + options = _.extend({validate: true}, options); + + // Do not persist invalid models. + if (!this._validate(attrs, options)) return false; + + // Set temporary attributes if `{wait: true}`. + if (attrs && options.wait) { + this.attributes = _.extend({}, attributes, attrs); } // After a successful server-side save, the client is (optionally) // updated with the server-side state. + if (options.parse === void 0) options.parse = true; var model = this; var success = options.success; - options.success = function(resp, status, xhr) { - var serverAttrs = model.parse(resp, xhr); - if (options.wait) { - delete options.wait; - serverAttrs = _.extend(attrs || {}, serverAttrs); - } - if (!model.set(serverAttrs, options)) return false; - if (success) { - success(model, resp); - } else { - model.trigger('sync', model, resp, options); + options.success = function(resp) { + // Ensure attributes are restored during synchronous saves. + model.attributes = attributes; + var serverAttrs = model.parse(resp, options); + if (options.wait) serverAttrs = _.extend(attrs || {}, serverAttrs); + if (_.isObject(serverAttrs) && !model.set(serverAttrs, options)) { + return false; } + if (success) success(model, resp, options); + model.trigger('sync', model, resp, options); }; + wrapError(this, options); + + method = this.isNew() ? 'create' : (options.patch ? 'patch' : 'update'); + if (method === 'patch') options.attrs = attrs; + xhr = this.sync(method, this, options); + + // Restore attributes. + if (attrs && options.wait) this.attributes = attributes; - // Finish configuring and sending the Ajax request. - options.error = Backbone.wrapError(options.error, model, options); - var method = this.isNew() ? 'create' : 'update'; - var xhr = (this.sync || Backbone.sync).call(this, method, this, options); - if (options.wait) this.set(current, silentOptions); return xhr; }, @@ -406,27 +505,24 @@ var model = this; var success = options.success; - var triggerDestroy = function() { + var destroy = function() { model.trigger('destroy', model, model.collection, options); }; - if (this.isNew()) { - triggerDestroy(); - return false; - } - options.success = function(resp) { - if (options.wait) triggerDestroy(); - if (success) { - success(model, resp); - } else { - model.trigger('sync', model, resp, options); - } + if (options.wait || model.isNew()) destroy(); + if (success) success(model, resp, options); + if (!model.isNew()) model.trigger('sync', model, resp, options); }; - options.error = Backbone.wrapError(options.error, model, options); - var xhr = (this.sync || Backbone.sync).call(this, 'delete', this, options); - if (!options.wait) triggerDestroy(); + if (this.isNew()) { + options.success(); + return false; + } + wrapError(this, options); + + var xhr = this.sync('delete', this, options); + if (!options.wait) destroy(); return xhr; }, @@ -434,14 +530,14 @@ // using Backbone's restful methods, override this to change the endpoint // that will be called. url: function() { - var base = getValue(this, 'urlRoot') || getValue(this.collection, 'url') || urlError(); + var base = _.result(this, 'urlRoot') || _.result(this.collection, 'url') || urlError(); if (this.isNew()) return base; - return base + (base.charAt(base.length - 1) == '/' ? '' : '/') + encodeURIComponent(this.id); + return base + (base.charAt(base.length - 1) === '/' ? '' : '/') + encodeURIComponent(this.id); }, // **parse** converts a response into the hash of attributes to be `set` on // the model. The default implementation is just to pass the response along. - parse: function(resp, xhr) { + parse: function(resp, options) { return resp; }, @@ -455,116 +551,63 @@ return this.id == null; }, - // Call this method to manually fire a `"change"` event for this model and - // a `"change:attribute"` event for each changed attribute. - // Calling this will cause all objects observing the model to update. - change: function(options) { - options || (options = {}); - var changing = this._changing; - this._changing = true; - - // Silent changes become pending changes. - for (var attr in this._silent) this._pending[attr] = true; - - // Silent changes are triggered. - var changes = _.extend({}, options.changes, this._silent); - this._silent = {}; - for (var attr in changes) { - this.trigger('change:' + attr, this, this.get(attr), options); - } - if (changing) return this; - - // Continue firing `"change"` events while there are pending changes. - while (!_.isEmpty(this._pending)) { - this._pending = {}; - this.trigger('change', this, options); - // Pending and silent changes still remain. - for (var attr in this.changed) { - if (this._pending[attr] || this._silent[attr]) continue; - delete this.changed[attr]; - } - this._previousAttributes = _.clone(this.attributes); - } - - this._changing = false; - return this; - }, - - // Determine if the model has changed since the last `"change"` event. - // If you specify an attribute name, determine if that attribute has changed. - hasChanged: function(attr) { - if (!arguments.length) return !_.isEmpty(this.changed); - return _.has(this.changed, attr); - }, - - // Return an object containing all the attributes that have changed, or - // false if there are no changed attributes. Useful for determining what - // parts of a view need to be updated and/or what attributes need to be - // persisted to the server. Unset attributes will be set to undefined. - // You can also pass an attributes object to diff against the model, - // determining if there *would be* a change. - changedAttributes: function(diff) { - if (!diff) return this.hasChanged() ? _.clone(this.changed) : false; - var val, changed = false, old = this._previousAttributes; - for (var attr in diff) { - if (_.isEqual(old[attr], (val = diff[attr]))) continue; - (changed || (changed = {}))[attr] = val; - } - return changed; - }, - - // Get the previous value of an attribute, recorded at the time the last - // `"change"` event was fired. - previous: function(attr) { - if (!arguments.length || !this._previousAttributes) return null; - return this._previousAttributes[attr]; - }, - - // Get all of the attributes of the model at the time of the previous - // `"change"` event. - previousAttributes: function() { - return _.clone(this._previousAttributes); - }, - - // Check if the model is currently in a valid state. It's only possible to - // get into an *invalid* state if you're using silent changes. - isValid: function() { - return !this.validate(this.attributes); + // Check if the model is currently in a valid state. + isValid: function(options) { + return this._validate({}, _.extend(options || {}, { validate: true })); }, // Run validation against the next complete set of model attributes, - // returning `true` if all is well. If a specific `error` callback has - // been passed, call that instead of firing the general `"error"` event. + // returning `true` if all is well. Otherwise, fire an `"invalid"` event. _validate: function(attrs, options) { - if (options.silent || !this.validate) return true; + if (!options.validate || !this.validate) return true; attrs = _.extend({}, this.attributes, attrs); - var error = this.validate(attrs, options); + var error = this.validationError = this.validate(attrs, options) || null; if (!error) return true; - if (options && options.error) { - options.error(this, error, options); - } else { - this.trigger('error', this, error, options); - } + this.trigger('invalid', this, error, _.extend(options || {}, {validationError: error})); return false; } }); + // Underscore methods that we want to implement on the Model. + var modelMethods = ['keys', 'values', 'pairs', 'invert', 'pick', 'omit']; + + // Mix in each Underscore method as a proxy to `Model#attributes`. + _.each(modelMethods, function(method) { + Model.prototype[method] = function() { + var args = slice.call(arguments); + args.unshift(this.attributes); + return _[method].apply(_, args); + }; + }); + // Backbone.Collection // ------------------- - // Provides a standard collection class for our sets of models, ordered - // or unordered. If a `comparator` is specified, the Collection will maintain + // If models tend to represent a single row of data, a Backbone Collection is + // more analagous to a table full of data ... or a small slice or page of that + // table, or a collection of rows that belong together for a particular reason + // -- all of the messages in this particular folder, all of the documents + // belonging to this particular author, and so on. Collections maintain + // indexes of their models, both in order, and for lookup by `id`. + + // Create a new **Collection**, perhaps to contain a specific type of `model`. + // If a `comparator` is specified, the Collection will maintain // its models in sort order, as they're added and removed. var Collection = Backbone.Collection = function(models, options) { options || (options = {}); + if (options.url) this.url = options.url; if (options.model) this.model = options.model; - if (options.comparator) this.comparator = options.comparator; + if (options.comparator !== void 0) this.comparator = options.comparator; this._reset(); this.initialize.apply(this, arguments); - if (models) this.reset(models, {silent: true, parse: options.parse}); + if (models) this.reset(models, _.extend({silent: true}, options)); }; + // Default options for `Collection#set`. + var setOptions = {add: true, remove: true, merge: true}; + var addOptions = {add: true, merge: false, remove: false}; + // Define the Collection's inheritable methods. _.extend(Collection.prototype, Events, { @@ -582,68 +625,26 @@ return this.map(function(model){ return model.toJSON(options); }); }, - // Add a model, or list of models to the set. Pass **silent** to avoid - // firing the `add` event for every new model. - add: function(models, options) { - var i, index, length, model, cid, id, cids = {}, ids = {}, dups = []; - options || (options = {}); - models = _.isArray(models) ? models.slice() : [models]; - - // Begin by turning bare objects into model references, and preventing - // invalid models or duplicate models from being added. - for (i = 0, length = models.length; i < length; i++) { - if (!(model = models[i] = this._prepareModel(models[i], options))) { - throw new Error("Can't add an invalid model to a collection"); - } - cid = model.cid; - id = model.id; - if (cids[cid] || this._byCid[cid] || ((id != null) && (ids[id] || this._byId[id]))) { - dups.push(i); - continue; - } - cids[cid] = ids[id] = model; - } - - // Remove duplicates. - i = dups.length; - while (i--) { - models.splice(dups[i], 1); - } - - // Listen to added models' events, and index models for lookup by - // `id` and by `cid`. - for (i = 0, length = models.length; i < length; i++) { - (model = models[i]).on('all', this._onModelEvent, this); - this._byCid[model.cid] = model; - if (model.id != null) this._byId[model.id] = model; - } - - // Insert models into the collection, re-sorting if needed, and triggering - // `add` events unless silenced. - this.length += length; - index = options.at != null ? options.at : this.models.length; - splice.apply(this.models, [index, 0].concat(models)); - if (this.comparator) this.sort({silent: true}); - if (options.silent) return this; - for (i = 0, length = this.models.length; i < length; i++) { - if (!cids[(model = this.models[i]).cid]) continue; - options.index = i; - model.trigger('add', model, this, options); - } - return this; + // Proxy `Backbone.sync` by default. + sync: function() { + return Backbone.sync.apply(this, arguments); }, - // Remove a model, or a list of models from the set. Pass silent to avoid - // firing the `remove` event for every model removed. + // Add a model, or list of models to the set. + add: function(models, options) { + return this.set(models, _.defaults(options || {}, addOptions)); + }, + + // Remove a model, or a list of models from the set. remove: function(models, options) { - var i, l, index, model; - options || (options = {}); models = _.isArray(models) ? models.slice() : [models]; + options || (options = {}); + var i, l, index, model; for (i = 0, l = models.length; i < l; i++) { - model = this.getByCid(models[i]) || this.get(models[i]); + model = this.get(models[i]); if (!model) continue; delete this._byId[model.id]; - delete this._byCid[model.cid]; + delete this._byId[model.cid]; index = this.indexOf(model); this.models.splice(index, 1); this.length--; @@ -656,10 +657,100 @@ return this; }, + // Update a collection by `set`-ing a new list of models, adding new ones, + // removing models that are no longer present, and merging models that + // already exist in the collection, as necessary. Similar to **Model#set**, + // the core operation for updating the data contained by the collection. + set: function(models, options) { + options = _.defaults(options || {}, setOptions); + if (options.parse) models = this.parse(models, options); + if (!_.isArray(models)) models = models ? [models] : []; + var i, l, model, attrs, existing, sort; + var at = options.at; + var sortable = this.comparator && (at == null) && options.sort !== false; + var sortAttr = _.isString(this.comparator) ? this.comparator : null; + var toAdd = [], toRemove = [], modelMap = {}; + + // Turn bare objects into model references, and prevent invalid models + // from being added. + for (i = 0, l = models.length; i < l; i++) { + if (!(model = this._prepareModel(models[i], options))) continue; + + // If a duplicate is found, prevent it from being added and + // optionally merge it into the existing model. + if (existing = this.get(model)) { + if (options.remove) modelMap[existing.cid] = true; + if (options.merge) { + existing.set(model.attributes, options); + if (sortable && !sort && existing.hasChanged(sortAttr)) sort = true; + } + + // This is a new model, push it to the `toAdd` list. + } else if (options.add) { + toAdd.push(model); + + // Listen to added models' events, and index models for lookup by + // `id` and by `cid`. + model.on('all', this._onModelEvent, this); + this._byId[model.cid] = model; + if (model.id != null) this._byId[model.id] = model; + } + } + + // Remove nonexistent models if appropriate. + if (options.remove) { + for (i = 0, l = this.length; i < l; ++i) { + if (!modelMap[(model = this.models[i]).cid]) toRemove.push(model); + } + if (toRemove.length) this.remove(toRemove, options); + } + + // See if sorting is needed, update `length` and splice in new models. + if (toAdd.length) { + if (sortable) sort = true; + this.length += toAdd.length; + if (at != null) { + splice.apply(this.models, [at, 0].concat(toAdd)); + } else { + push.apply(this.models, toAdd); + } + } + + // Silently sort the collection if appropriate. + if (sort) this.sort({silent: true}); + + if (options.silent) return this; + + // Trigger `add` events. + for (i = 0, l = toAdd.length; i < l; i++) { + (model = toAdd[i]).trigger('add', model, this, options); + } + + // Trigger `sort` if the collection was sorted. + if (sort) this.trigger('sort', this, options); + return this; + }, + + // When you have more items than you want to add or remove individually, + // you can reset the entire set with a new list of models, without firing + // any granular `add` or `remove` events. Fires `reset` when finished. + // Useful for bulk operations and optimizations. + reset: function(models, options) { + options || (options = {}); + for (var i = 0, l = this.models.length; i < l; i++) { + this._removeReference(this.models[i]); + } + options.previousModels = this.models; + this._reset(); + this.add(models, _.extend({silent: true}, options)); + if (!options.silent) this.trigger('reset', this, options); + return this; + }, + // Add a model to the end of the collection. push: function(model, options) { model = this._prepareModel(model, options); - this.add(model, options); + this.add(model, _.extend({at: this.length}, options)); return model; }, @@ -684,15 +775,15 @@ return model; }, - // Get a model from the set by id. - get: function(id) { - if (id == null) return void 0; - return this._byId[id.id != null ? id.id : id]; + // Slice out a sub-array of models from the collection. + slice: function(begin, end) { + return this.models.slice(begin, end); }, - // Get a model from the set by client id. - getByCid: function(cid) { - return cid && this._byCid[cid.cid || cid]; + // Get a model from the set by id. + get: function(obj) { + if (obj == null) return void 0; + return this._byId[obj.id != null ? obj.id : obj.cid || obj]; }, // Get the model at the given index. @@ -700,10 +791,11 @@ return this.models[index]; }, - // Return models with matching attributes. Useful for simple cases of `filter`. - where: function(attrs) { - if (_.isEmpty(attrs)) return []; - return this.filter(function(model) { + // Return models with matching attributes. Useful for simple cases of + // `filter`. + where: function(attrs, first) { + if (_.isEmpty(attrs)) return first ? void 0 : []; + return this[first ? 'find' : 'filter'](function(model) { for (var key in attrs) { if (attrs[key] !== model.get(key)) return false; } @@ -711,75 +803,75 @@ }); }, + // Return the first model with matching attributes. Useful for simple cases + // of `find`. + findWhere: function(attrs) { + return this.where(attrs, true); + }, + // Force the collection to re-sort itself. You don't need to call this under // normal circumstances, as the set will maintain sort order as each item // is added. sort: function(options) { - options || (options = {}); if (!this.comparator) throw new Error('Cannot sort a set without a comparator'); - var boundComparator = _.bind(this.comparator, this); - if (this.comparator.length == 1) { - this.models = this.sortBy(boundComparator); + options || (options = {}); + + // Run sort based on type of `comparator`. + if (_.isString(this.comparator) || this.comparator.length === 1) { + this.models = this.sortBy(this.comparator, this); } else { - this.models.sort(boundComparator); + this.models.sort(_.bind(this.comparator, this)); } - if (!options.silent) this.trigger('reset', this, options); + + if (!options.silent) this.trigger('sort', this, options); return this; }, + // Figure out the smallest index at which a model should be inserted so as + // to maintain order. + sortedIndex: function(model, value, context) { + value || (value = this.comparator); + var iterator = _.isFunction(value) ? value : function(model) { + return model.get(value); + }; + return _.sortedIndex(this.models, model, iterator, context); + }, + // Pluck an attribute from each model in the collection. pluck: function(attr) { - return _.map(this.models, function(model){ return model.get(attr); }); - }, - - // When you have more items than you want to add or remove individually, - // you can reset the entire set with a new list of models, without firing - // any `add` or `remove` events. Fires `reset` when finished. - reset: function(models, options) { - models || (models = []); - options || (options = {}); - for (var i = 0, l = this.models.length; i < l; i++) { - this._removeReference(this.models[i]); - } - this._reset(); - this.add(models, _.extend({silent: true}, options)); - if (!options.silent) this.trigger('reset', this, options); - return this; + return _.invoke(this.models, 'get', attr); }, // Fetch the default set of models for this collection, resetting the - // collection when they arrive. If `add: true` is passed, appends the - // models to the collection instead of resetting. + // collection when they arrive. If `reset: true` is passed, the response + // data will be passed through the `reset` method instead of `set`. fetch: function(options) { options = options ? _.clone(options) : {}; - if (options.parse === undefined) options.parse = true; - var collection = this; + if (options.parse === void 0) options.parse = true; var success = options.success; - options.success = function(resp, status, xhr) { - collection[options.add ? 'add' : 'reset'](collection.parse(resp, xhr), options); - if (success) success(collection, resp); + var collection = this; + options.success = function(resp) { + var method = options.reset ? 'reset' : 'set'; + collection[method](resp, options); + if (success) success(collection, resp, options); + collection.trigger('sync', collection, resp, options); }; - options.error = Backbone.wrapError(options.error, collection, options); - return (this.sync || Backbone.sync).call(this, 'read', this, options); + wrapError(this, options); + return this.sync('read', this, options); }, // Create a new instance of a model in this collection. Add the model to the // collection immediately, unless `wait: true` is passed, in which case we // wait for the server to agree. create: function(model, options) { - var coll = this; options = options ? _.clone(options) : {}; - model = this._prepareModel(model, options); - if (!model) return false; - if (!options.wait) coll.add(model, options); + if (!(model = this._prepareModel(model, options))) return false; + if (!options.wait) this.add(model, options); + var collection = this; var success = options.success; - options.success = function(nextModel, resp, xhr) { - if (options.wait) coll.add(nextModel, options); - if (success) { - success(nextModel, resp); - } else { - nextModel.trigger('sync', model, resp, options); - } + options.success = function(resp) { + if (options.wait) collection.add(model, options); + if (success) success(model, resp, options); }; model.save(null, options); return model; @@ -787,44 +879,43 @@ // **parse** converts a response into a list of models to be added to the // collection. The default implementation is just to pass it through. - parse: function(resp, xhr) { + parse: function(resp, options) { return resp; }, - // Proxy to _'s chain. Can't be proxied the same way the rest of the - // underscore methods are proxied because it relies on the underscore - // constructor. - chain: function () { - return _(this.models).chain(); + // Create a new collection with an identical list of models as this one. + clone: function() { + return new this.constructor(this.models); }, - // Reset all internal state. Called when the collection is reset. - _reset: function(options) { + // Private method to reset all internal state. Called when the collection + // is first initialized or reset. + _reset: function() { this.length = 0; this.models = []; this._byId = {}; - this._byCid = {}; }, - // Prepare a model or hash of attributes to be added to this collection. - _prepareModel: function(model, options) { + // Prepare a hash of attributes (or other model) to be added to this + // collection. + _prepareModel: function(attrs, options) { + if (attrs instanceof Model) { + if (!attrs.collection) attrs.collection = this; + return attrs; + } options || (options = {}); - if (!(model instanceof Model)) { - var attrs = model; - options.collection = this; - model = new this.model(attrs, options); - if (!model._validate(model.attributes, options)) model = false; - } else if (!model.collection) { - model.collection = this; + options.collection = this; + var model = new this.model(attrs, options); + if (!model._validate(attrs, options)) { + this.trigger('invalid', this, attrs, options); + return false; } return model; }, - // Internal method to remove a model's ties to a collection. + // Internal method to sever a model's ties to a collection. _removeReference: function(model) { - if (this == model.collection) { - delete model.collection; - } + if (this === model.collection) delete model.collection; model.off('all', this._onModelEvent, this); }, @@ -833,13 +924,11 @@ // events simply proxy through. "add" and "remove" events that originate // in other collections are ignored. _onModelEvent: function(event, model, collection, options) { - if ((event == 'add' || event == 'remove') && collection != this) return; - if (event == 'destroy') { - this.remove(model, options); - } + if ((event === 'add' || event === 'remove') && collection !== this) return; + if (event === 'destroy') this.remove(model, options); if (model && event === 'change:' + model.idAttribute) { delete this._byId[model.previous(model.idAttribute)]; - this._byId[model.id] = model; + if (model.id != null) this._byId[model.id] = model; } this.trigger.apply(this, arguments); } @@ -847,21 +936,274 @@ }); // Underscore methods that we want to implement on the Collection. - var methods = ['forEach', 'each', 'map', 'reduce', 'reduceRight', 'find', - 'detect', 'filter', 'select', 'reject', 'every', 'all', 'some', 'any', - 'include', 'contains', 'invoke', 'max', 'min', 'sortBy', 'sortedIndex', - 'toArray', 'size', 'first', 'initial', 'rest', 'last', 'without', 'indexOf', - 'shuffle', 'lastIndexOf', 'isEmpty', 'groupBy']; + // 90% of the core usefulness of Backbone Collections is actually implemented + // right here: + var methods = ['forEach', 'each', 'map', 'collect', 'reduce', 'foldl', + 'inject', 'reduceRight', 'foldr', 'find', 'detect', 'filter', 'select', + 'reject', 'every', 'all', 'some', 'any', 'include', 'contains', 'invoke', + 'max', 'min', 'toArray', 'size', 'first', 'head', 'take', 'initial', 'rest', + 'tail', 'drop', 'last', 'without', 'indexOf', 'shuffle', 'lastIndexOf', + 'isEmpty', 'chain']; // Mix in each Underscore method as a proxy to `Collection#models`. _.each(methods, function(method) { Collection.prototype[method] = function() { - return _[method].apply(_, [this.models].concat(_.toArray(arguments))); + var args = slice.call(arguments); + args.unshift(this.models); + return _[method].apply(_, args); }; }); + // Underscore methods that take a property name as an argument. + var attributeMethods = ['groupBy', 'countBy', 'sortBy']; + + // Use attributes instead of properties. + _.each(attributeMethods, function(method) { + Collection.prototype[method] = function(value, context) { + var iterator = _.isFunction(value) ? value : function(model) { + return model.get(value); + }; + return _[method](this.models, iterator, context); + }; + }); + + // Backbone.View + // ------------- + + // Backbone Views are almost more convention than they are actual code. A View + // is simply a JavaScript object that represents a logical chunk of UI in the + // DOM. This might be a single item, an entire list, a sidebar or panel, or + // even the surrounding frame which wraps your whole app. Defining a chunk of + // UI as a **View** allows you to define your DOM events declaratively, without + // having to worry about render order ... and makes it easy for the view to + // react to specific changes in the state of your models. + + // Creating a Backbone.View creates its initial element outside of the DOM, + // if an existing element is not provided... + var View = Backbone.View = function(options) { + this.cid = _.uniqueId('view'); + this._configure(options || {}); + this._ensureElement(); + this.initialize.apply(this, arguments); + this.delegateEvents(); + }; + + // Cached regex to split keys for `delegate`. + var delegateEventSplitter = /^(\S+)\s*(.*)$/; + + // List of view options to be merged as properties. + var viewOptions = ['model', 'collection', 'el', 'id', 'attributes', 'className', 'tagName', 'events']; + + // Set up all inheritable **Backbone.View** properties and methods. + _.extend(View.prototype, Events, { + + // The default `tagName` of a View's element is `"div"`. + tagName: 'div', + + // jQuery delegate for element lookup, scoped to DOM elements within the + // current view. This should be prefered to global lookups where possible. + $: function(selector) { + return this.$el.find(selector); + }, + + // Initialize is an empty function by default. Override it with your own + // initialization logic. + initialize: function(){}, + + // **render** is the core function that your view should override, in order + // to populate its element (`this.el`), with the appropriate HTML. The + // convention is for **render** to always return `this`. + render: function() { + return this; + }, + + // Remove this view by taking the element out of the DOM, and removing any + // applicable Backbone.Events listeners. + remove: function() { + this.$el.remove(); + this.stopListening(); + return this; + }, + + // Change the view's element (`this.el` property), including event + // re-delegation. + setElement: function(element, delegate) { + if (this.$el) this.undelegateEvents(); + this.$el = element instanceof Backbone.$ ? element : Backbone.$(element); + this.el = this.$el[0]; + if (delegate !== false) this.delegateEvents(); + return this; + }, + + // Set callbacks, where `this.events` is a hash of + // + // *{"event selector": "callback"}* + // + // { + // 'mousedown .title': 'edit', + // 'click .button': 'save' + // 'click .open': function(e) { ... } + // } + // + // pairs. Callbacks will be bound to the view, with `this` set properly. + // Uses event delegation for efficiency. + // Omitting the selector binds the event to `this.el`. + // This only works for delegate-able events: not `focus`, `blur`, and + // not `change`, `submit`, and `reset` in Internet Explorer. + delegateEvents: function(events) { + if (!(events || (events = _.result(this, 'events')))) return this; + this.undelegateEvents(); + for (var key in events) { + var method = events[key]; + if (!_.isFunction(method)) method = this[events[key]]; + if (!method) continue; + + var match = key.match(delegateEventSplitter); + var eventName = match[1], selector = match[2]; + method = _.bind(method, this); + eventName += '.delegateEvents' + this.cid; + if (selector === '') { + this.$el.on(eventName, method); + } else { + this.$el.on(eventName, selector, method); + } + } + return this; + }, + + // Clears all callbacks previously bound to the view with `delegateEvents`. + // You usually don't need to use this, but may wish to if you have multiple + // Backbone views attached to the same DOM element. + undelegateEvents: function() { + this.$el.off('.delegateEvents' + this.cid); + return this; + }, + + // Performs the initial configuration of a View with a set of options. + // Keys with special meaning *(e.g. model, collection, id, className)* are + // attached directly to the view. See `viewOptions` for an exhaustive + // list. + _configure: function(options) { + if (this.options) options = _.extend({}, _.result(this, 'options'), options); + _.extend(this, _.pick(options, viewOptions)); + this.options = options; + }, + + // Ensure that the View has a DOM element to render into. + // If `this.el` is a string, pass it through `$()`, take the first + // matching element, and re-assign it to `el`. Otherwise, create + // an element from the `id`, `className` and `tagName` properties. + _ensureElement: function() { + if (!this.el) { + var attrs = _.extend({}, _.result(this, 'attributes')); + if (this.id) attrs.id = _.result(this, 'id'); + if (this.className) attrs['class'] = _.result(this, 'className'); + var $el = Backbone.$('<' + _.result(this, 'tagName') + '>').attr(attrs); + this.setElement($el, false); + } else { + this.setElement(_.result(this, 'el'), false); + } + } + + }); + + // Backbone.sync + // ------------- + + // Override this function to change the manner in which Backbone persists + // models to the server. You will be passed the type of request, and the + // model in question. By default, makes a RESTful Ajax request + // to the model's `url()`. Some possible customizations could be: + // + // * Use `setTimeout` to batch rapid-fire updates into a single request. + // * Send up the models as XML instead of JSON. + // * Persist models via WebSockets instead of Ajax. + // + // Turn on `Backbone.emulateHTTP` in order to send `PUT` and `DELETE` requests + // as `POST`, with a `_method` parameter containing the true HTTP method, + // as well as all requests with the body as `application/x-www-form-urlencoded` + // instead of `application/json` with the model in a param named `model`. + // Useful when interfacing with server-side languages like **PHP** that make + // it difficult to read the body of `PUT` requests. + Backbone.sync = function(method, model, options) { + var type = methodMap[method]; + + // Default options, unless specified. + _.defaults(options || (options = {}), { + emulateHTTP: Backbone.emulateHTTP, + emulateJSON: Backbone.emulateJSON + }); + + // Default JSON-request options. + var params = {type: type, dataType: 'json'}; + + // Ensure that we have a URL. + if (!options.url) { + params.url = _.result(model, 'url') || urlError(); + } + + // Ensure that we have the appropriate request data. + if (options.data == null && model && (method === 'create' || method === 'update' || method === 'patch')) { + params.contentType = 'application/json'; + params.data = JSON.stringify(options.attrs || model.toJSON(options)); + } + + // For older servers, emulate JSON by encoding the request into an HTML-form. + if (options.emulateJSON) { + params.contentType = 'application/x-www-form-urlencoded'; + params.data = params.data ? {model: params.data} : {}; + } + + // For older servers, emulate HTTP by mimicking the HTTP method with `_method` + // And an `X-HTTP-Method-Override` header. + if (options.emulateHTTP && (type === 'PUT' || type === 'DELETE' || type === 'PATCH')) { + params.type = 'POST'; + if (options.emulateJSON) params.data._method = type; + var beforeSend = options.beforeSend; + options.beforeSend = function(xhr) { + xhr.setRequestHeader('X-HTTP-Method-Override', type); + if (beforeSend) return beforeSend.apply(this, arguments); + }; + } + + // Don't process data on a non-GET request. + if (params.type !== 'GET' && !options.emulateJSON) { + params.processData = false; + } + + // If we're sending a `PATCH` request, and we're in an old Internet Explorer + // that still has ActiveX enabled by default, override jQuery to use that + // for XHR instead. Remove this line when jQuery supports `PATCH` on IE8. + if (params.type === 'PATCH' && window.ActiveXObject && + !(window.external && window.external.msActiveXFilteringEnabled)) { + params.xhr = function() { + return new ActiveXObject("Microsoft.XMLHTTP"); + }; + } + + // Make the request, allowing the user to override any Ajax options. + var xhr = options.xhr = Backbone.ajax(_.extend(params, options)); + model.trigger('request', model, xhr, options); + return xhr; + }; + + // Map from CRUD to HTTP for our default `Backbone.sync` implementation. + var methodMap = { + 'create': 'POST', + 'update': 'PUT', + 'patch': 'PATCH', + 'delete': 'DELETE', + 'read': 'GET' + }; + + // Set the default implementation of `Backbone.ajax` to proxy through to `$`. + // Override this if you'd like to use a different library. + Backbone.ajax = function() { + return Backbone.$.ajax.apply(Backbone.$, arguments); + }; + // Backbone.Router - // ------------------- + // --------------- // Routers map faux-URLs to actions, and fire events when routes are // matched. Creating a new one sets its `routes` hash, if not set statically. @@ -874,9 +1216,10 @@ // Cached regular expressions for matching named param parts and splatted // parts of route strings. - var namedParam = /:\w+/g; + var optionalParam = /\((.*?)\)/g; + var namedParam = /(\(\?)?:\w+/g; var splatParam = /\*\w+/g; - var escapeRegExp = /[-[\]{}()+?.,\\^$|#\s]/g; + var escapeRegExp = /[\-{}\[\]+?.,\\\^$|#\s]/g; // Set up all inheritable **Backbone.Router** properties and methods. _.extend(Router.prototype, Events, { @@ -892,21 +1235,27 @@ // }); // route: function(route, name, callback) { - Backbone.history || (Backbone.history = new History); if (!_.isRegExp(route)) route = this._routeToRegExp(route); + if (_.isFunction(name)) { + callback = name; + name = ''; + } if (!callback) callback = this[name]; - Backbone.history.route(route, _.bind(function(fragment) { - var args = this._extractParameters(route, fragment); - callback && callback.apply(this, args); - this.trigger.apply(this, ['route:' + name].concat(args)); - Backbone.history.trigger('route', this, name, args); - }, this)); + var router = this; + Backbone.history.route(route, function(fragment) { + var args = router._extractParameters(route, fragment); + callback && callback.apply(router, args); + router.trigger.apply(router, ['route:' + name].concat(args)); + router.trigger('route', name, args); + Backbone.history.trigger('route', router, name, args); + }); return this; }, // Simple proxy to `Backbone.history` to save a fragment into the history. navigate: function(fragment, options) { Backbone.history.navigate(fragment, options); + return this; }, // Bind all defined routes to `Backbone.history`. We have to reverse the @@ -914,12 +1263,10 @@ // routes can be defined at the bottom of the route map. _bindRoutes: function() { if (!this.routes) return; - var routes = []; - for (var route in this.routes) { - routes.unshift([route, this.routes[route]]); - } - for (var i = 0, l = routes.length; i < l; i++) { - this.route(routes[i][0], routes[i][1], this[routes[i][1]]); + this.routes = _.result(this, 'routes'); + var route, routes = _.keys(this.routes); + while ((route = routes.pop()) != null) { + this.route(route, this.routes[route]); } }, @@ -927,15 +1274,22 @@ // against the current location hash. _routeToRegExp: function(route) { route = route.replace(escapeRegExp, '\\$&') - .replace(namedParam, '([^\/]+)') + .replace(optionalParam, '(?:$1)?') + .replace(namedParam, function(match, optional){ + return optional ? match : '([^\/]+)'; + }) .replace(splatParam, '(.*?)'); return new RegExp('^' + route + '$'); }, // Given a route, and a URL fragment that it matches, return the array of - // extracted parameters. + // extracted decoded parameters. Empty or unmatched parameters will be + // treated as `null` to normalize cross-browser behavior. _extractParameters: function(route, fragment) { - return route.exec(fragment).slice(1); + var params = route.exec(fragment).slice(1); + return _.map(params, function(param) { + return param ? decodeURIComponent(param) : null; + }); } }); @@ -943,19 +1297,34 @@ // Backbone.History // ---------------- - // Handles cross-browser history management, based on URL fragments. If the - // browser does not support `onhashchange`, falls back to polling. + // Handles cross-browser history management, based on either + // [pushState](http://diveintohtml5.info/history.html) and real URLs, or + // [onhashchange](https://developer.mozilla.org/en-US/docs/DOM/window.onhashchange) + // and URL fragments. If the browser supports neither (old IE, natch), + // falls back to polling. var History = Backbone.History = function() { this.handlers = []; _.bindAll(this, 'checkUrl'); + + // Ensure that `History` can be used outside of the browser. + if (typeof window !== 'undefined') { + this.location = window.location; + this.history = window.history; + } }; - // Cached regex for cleaning leading hashes and slashes . - var routeStripper = /^[#\/]/; + // Cached regex for stripping a leading hash/slash and trailing space. + var routeStripper = /^[#\/]|\s+$/g; + + // Cached regex for stripping leading and trailing slashes. + var rootStripper = /^\/+|\/+$/g; // Cached regex for detecting MSIE. var isExplorer = /msie [\w.]+/; + // Cached regex for removing a trailing slash. + var trailingSlash = /\/$/; + // Has the history handling already been started? History.started = false; @@ -968,9 +1337,8 @@ // Gets the true hash value. Cannot use location.hash directly due to bug // in Firefox where location.hash will always be decoded. - getHash: function(windowOverride) { - var loc = windowOverride ? windowOverride.location : window.location; - var match = loc.href.match(/#(.*)$/); + getHash: function(window) { + var match = (window || this).location.href.match(/#(.*)$/); return match ? match[1] : ''; }, @@ -978,15 +1346,14 @@ // the hash, or the override. getFragment: function(fragment, forcePushState) { if (fragment == null) { - if (this._hasPushState || forcePushState) { - fragment = window.location.pathname; - var search = window.location.search; - if (search) fragment += search; + if (this._hasPushState || !this._wantsHashChange || forcePushState) { + fragment = this.location.pathname; + var root = this.root.replace(trailingSlash, ''); + if (!fragment.indexOf(root)) fragment = fragment.substr(root.length); } else { fragment = this.getHash(); } } - if (!fragment.indexOf(this.options.root)) fragment = fragment.substr(this.options.root.length); return fragment.replace(routeStripper, ''); }, @@ -999,24 +1366,28 @@ // Figure out the initial configuration. Do we need an iframe? // Is pushState desired ... is it available? this.options = _.extend({}, {root: '/'}, this.options, options); + this.root = this.options.root; this._wantsHashChange = this.options.hashChange !== false; this._wantsPushState = !!this.options.pushState; - this._hasPushState = !!(this.options.pushState && window.history && window.history.pushState); + this._hasPushState = !!(this.options.pushState && this.history && this.history.pushState); var fragment = this.getFragment(); var docMode = document.documentMode; var oldIE = (isExplorer.exec(navigator.userAgent.toLowerCase()) && (!docMode || docMode <= 7)); - if (oldIE) { - this.iframe = $(' From 0cdd555222fb35f88baa8ce52b3f46449b750f61 Mon Sep 17 00:00:00 2001 From: Dave Bouvier Date: Fri, 26 Apr 2013 11:16:16 -0400 Subject: [PATCH 242/523] Update rss feed per changes in tool test results dict. --- lib/galaxy/webapps/tool_shed/controllers/repository.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/galaxy/webapps/tool_shed/controllers/repository.py b/lib/galaxy/webapps/tool_shed/controllers/repository.py index 1a97630510c..f4e098fe5af 100644 --- a/lib/galaxy/webapps/tool_shed/controllers/repository.py +++ b/lib/galaxy/webapps/tool_shed/controllers/repository.py @@ -1122,10 +1122,11 @@ class RepositoryController( BaseUIController, common_util.ItemRatings ): # Generate a citable URL for this repository with owner and changeset revision. repository_citable_url = suc.url_join( tool_shed_url, 'view', user.username, repository.name, metadata_row.changeset_revision ) title = 'Functional test results for changeset revision %s of %s' % ( metadata_row.changeset_revision, repository.name ) - tests_passed = len( metadata_row.tool_test_results.get( 'tests_passed', [] ) ) - tests_failed = len( metadata_row.tool_test_results.get( 'test_errors', [] ) ) - invalid_tests = len( metadata_row.tool_test_results.get( 'invalid_tests', [] ) ) - description = '%d tests passed, %d tests failed, %d tests determined to be invalid.' % ( tests_passed, tests_failed, invalid_tests ) + passed_tests = len( metadata_row.tool_test_results.get( 'passed_tests', [] ) ) + failed_tests = len( metadata_row.tool_test_results.get( 'failed_tests', [] ) ) + missing_test_components = len( metadata_row.tool_test_results.get( 'missing_test_components', [] ) ) + description = '%d tests passed, %d tests failed, %d tests missing test components.' % \ + ( passed_tests, failed_tests, missing_test_components ) # The guid attribute in an RSS feed's list of items allows a feed reader to choose not to show an item as updated # if the guid is unchanged. For functional test results, the citable URL is sufficiently unique to enable # that behavior. From 0787d31002a1b6c4c8f4f5b785f4541275ca2b4c Mon Sep 17 00:00:00 2001 From: Dave Bouvier Date: Fri, 26 Apr 2013 11:22:27 -0400 Subject: [PATCH 243/523] Remove stderr from passed test display. --- lib/galaxy/webapps/tool_shed/util/container_util.py | 4 +--- templates/webapps/tool_shed/repository/common.mako | 1 - 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/lib/galaxy/webapps/tool_shed/util/container_util.py b/lib/galaxy/webapps/tool_shed/util/container_util.py index 9ca5c4561d7..d66e2b77950 100644 --- a/lib/galaxy/webapps/tool_shed/util/container_util.py +++ b/lib/galaxy/webapps/tool_shed/util/container_util.py @@ -160,9 +160,8 @@ class MissingTestComponent( object ): class PassedTest( object ): """Passed tool tests object""" - def __init__( self, id=None, stderr=None, test_id=None, tool_id=None, tool_version=None ): + def __init__( self, id=None, test_id=None, tool_id=None, tool_version=None ): self.id = id - self.stderr = stderr self.test_id = test_id self.tool_id = tool_id self.tool_version = tool_version @@ -1027,7 +1026,6 @@ def build_tool_test_results_folder( trans, folder_id, tool_test_results_dict, la for passed_tests_dict in passed_tests_dicts: passed_test_id += 1 passed_test = PassedTest( id=passed_test_id, - stderr=passed_tests_dict.get( 'stderr', '' ), test_id=passed_tests_dict.get( 'test_id' '' ), tool_id=passed_tests_dict.get( 'tool_id', '' ), tool_version=passed_tests_dict.get( 'tool_version', '' ) ) diff --git a/templates/webapps/tool_shed/repository/common.mako b/templates/webapps/tool_shed/repository/common.mako index 8f5e7df9a41..c829239e948 100644 --- a/templates/webapps/tool_shed/repository/common.mako +++ b/templates/webapps/tool_shed/repository/common.mako @@ -618,7 +618,6 @@
    Tool id: ${passed_test.tool_id | h}
    Tool version: ${passed_test.tool_id | h}
    Test: ${passed_test.test_id | h}
    Stderr:
    ${passed_test.stderr | h}
    From f4efadb694bb37c51d755862f7ac6553a44c5ec0 Mon Sep 17 00:00:00 2001 From: Dave Bouvier Date: Fri, 26 Apr 2013 15:01:21 -0400 Subject: [PATCH 244/523] More compatibility with main tool shed. --- .../functional_tests.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/test/install_and_test_tool_shed_repositories/functional_tests.py b/test/install_and_test_tool_shed_repositories/functional_tests.py index 9b49a7135ae..d12c0491954 100644 --- a/test/install_and_test_tool_shed_repositories/functional_tests.py +++ b/test/install_and_test_tool_shed_repositories/functional_tests.py @@ -269,9 +269,11 @@ def get_tool_test_results_from_api( tool_shed_url, metadata_revision_id ): api_path = [ 'api', 'repository_revisions', metadata_revision_id ] api_url = get_api_url( base=tool_shed_url, parts=api_path ) repository_metadata = json_from_url( api_url ) - if repository_metadata[ 'tool_test_results' ] is None: - return {} - return repository_metadata[ 'tool_test_results' ] + tool_test_results = repository_metadata.get( 'tool_test_results', {} ) + # Compatibility code, remove before next release. + if not tool_test_results: + tool_test_results = repository_metadata.get( 'tool_test_errors', {} ) + return tool_test_results def json_from_url( url ): url_handle = urllib.urlopen( url ) From 50b554e87b1cbd7e00be1a790e2270095f4e3194 Mon Sep 17 00:00:00 2001 From: Carl Eberhard Date: Fri, 26 Apr 2013 15:17:08 -0400 Subject: [PATCH 245/523] Browser tests: allow for use of test admin user, test configuration API, begin tests of user API --- lib/galaxy/model/__init__.py | 45 ++++++- .../webapps/galaxy/api/configuration.py | 29 ++--- test/casperjs/api-configuration-tests.js | 121 +++++++++++++++++ test/casperjs/api-user-tests.js | 105 +++++++++++++++ test/casperjs/modules/api.js | 123 +++++++++++++++++- test/casperjs/modules/user.js | 61 ++++++++- test/casperjs/spaceghost.js | 68 ++++++++-- 7 files changed, 515 insertions(+), 37 deletions(-) create mode 100644 test/casperjs/api-configuration-tests.js create mode 100644 test/casperjs/api-user-tests.js diff --git a/lib/galaxy/model/__init__.py b/lib/galaxy/model/__init__.py index c7d58f448be..c4f76558565 100644 --- a/lib/galaxy/model/__init__.py +++ b/lib/galaxy/model/__init__.py @@ -48,9 +48,17 @@ def set_datatypes_registry( d_registry ): global datatypes_registry datatypes_registry = d_registry + class User( object, APIItem ): + """ + Data for a Galaxy user or admin and relations to their + histories, credentials, and roles. + """ + # attributes that will be accessed and returned when calling get_api_value( view='collection' ) api_collection_visible_keys = ( 'id', 'email' ) + # attributes that will be accessed and returned when calling get_api_value( view='element' ) api_element_visible_keys = ( 'id', 'email', 'username', 'total_disk_usage', 'nice_total_disk_usage' ) + def __init__( self, email=None, password=None ): self.email = email self.password = password @@ -61,45 +69,78 @@ class User( object, APIItem ): # Relationships self.histories = [] self.credentials = [] + #? self.roles = [] def set_password_cleartext( self, cleartext ): - """Set 'self.password' to the digest of 'cleartext'.""" + """ + Set user password to the digest of `cleartext`. + """ self.password = new_secure_hash( text_type=cleartext ) + def check_password( self, cleartext ): - """Check if 'cleartext' matches 'self.password' when hashed.""" + """ + Check if `cleartext` matches user password when hashed. + """ return self.password == new_secure_hash( text_type=cleartext ) + def all_roles( self ): + """ + Return a unique list of Roles associated with this user or any of their groups. + """ roles = [ ura.role for ura in self.roles ] for group in [ uga.group for uga in self.groups ]: for role in [ gra.role for gra in group.roles ]: if role not in roles: roles.append( role ) return roles + def get_disk_usage( self, nice_size=False ): + """ + Return byte count of disk space used by user or a human-readable + string if `nice_size` is `True`. + """ rval = 0 if self.disk_usage is not None: rval = self.disk_usage if nice_size: rval = galaxy.datatypes.data.nice_size( rval ) return rval + def set_disk_usage( self, bytes ): + """ + Manually set the disk space used by a user to `bytes`. + """ self.disk_usage = bytes + total_disk_usage = property( get_disk_usage, set_disk_usage ) + @property def nice_total_disk_usage( self ): + """ + Return byte count of disk space used in a human-readable string. + """ return self.get_disk_usage( nice_size=True ) + def calculate_disk_usage( self ): + """ + Return byte count total of disk space used by all non-purged, non-library + HDAs in non-purged histories. + """ + # maintain a list so that we don't double count dataset_ids = [] total = 0 # this can be a huge number and can run out of memory, so we avoid the mappers db_session = object_session( self ) for history in db_session.query( History ).enable_eagerloads( False ).filter_by( user_id=self.id, purged=False ).yield_per( 1000 ): for hda in db_session.query( HistoryDatasetAssociation ).enable_eagerloads( False ).filter_by( history_id=history.id, purged=False ).yield_per( 1000 ): + #TODO: def hda.counts_toward_disk_usage(): + # return ( not self.dataset.purged and not self.dataset.library_associations ) if not hda.dataset.id in dataset_ids and not hda.dataset.purged and not hda.dataset.library_associations: dataset_ids.append( hda.dataset.id ) total += hda.dataset.get_total_size() return total + class Job( object ): """ A job represents a request to run a tool given input datasets, tool diff --git a/lib/galaxy/webapps/galaxy/api/configuration.py b/lib/galaxy/webapps/galaxy/api/configuration.py index b85537cabe3..25c9067f7ea 100644 --- a/lib/galaxy/webapps/galaxy/api/configuration.py +++ b/lib/galaxy/webapps/galaxy/api/configuration.py @@ -2,30 +2,29 @@ API operations allowing clients to determine Galaxy instance's capabilities. """ -import logging from galaxy import web from galaxy.web.base.controller import BaseAPIController +import logging log = logging.getLogger( __name__ ) - class ConfigurationController( BaseAPIController ): EXPOSED_USER_OPTIONS = [ - 'enable_unique_workflow_defaults', - 'ftp_upload_site', - 'ftp_upload_dir', - 'wiki_url', - 'support_url', - 'logo_url', - 'terms_url', - 'allow_user_dataset_purge', + 'enable_unique_workflow_defaults', + 'ftp_upload_site', + 'ftp_upload_dir', + 'wiki_url', + 'support_url', + 'logo_url', + 'terms_url', + 'allow_user_dataset_purge', ] EXPOSED_ADMIN_OPTIONS = [ - 'library_import_dir', - 'user_library_import_dir', - 'allow_library_path_paste', - 'allow_user_creation', - 'allow_user_deletion', + 'library_import_dir', + 'user_library_import_dir', + 'allow_library_path_paste', + 'allow_user_creation', + 'allow_user_deletion', ] @web.expose_api diff --git a/test/casperjs/api-configuration-tests.js b/test/casperjs/api-configuration-tests.js new file mode 100644 index 00000000000..628ca5dbd2c --- /dev/null +++ b/test/casperjs/api-configuration-tests.js @@ -0,0 +1,121 @@ +/* Utility to load a specific page and output html, page text, or a screenshot + * Optionally wait for some time, text, or dom selector + */ +try { + //...if there's a better way - please let me know, universe + var scriptDir = require( 'system' ).args[3] + // remove the script filename + .replace( /[\w|\.|\-|_]*$/, '' ) + // if given rel. path, prepend the curr dir + .replace( /^(?!\/)/, './' ), + spaceghost = require( scriptDir + 'spaceghost' ).create({ + // script options here (can be overridden by CLI) + //verbose: true, + //logLevel: debug, + scriptDir: scriptDir + }); + +} catch( error ){ + console.debug( error ); + phantom.exit( 1 ); +} +spaceghost.start(); + +// =================================================================== SET UP +var utils = require( 'utils' ); + +var email = spaceghost.user.getRandomEmail(), + password = '123456'; +if( spaceghost.fixtureData.testUser ){ + email = spaceghost.fixtureData.testUser.email; + password = spaceghost.fixtureData.testUser.password; +} +spaceghost.user.loginOrRegisterUser( email, password ); + +function hasKeys( object, keysArray ){ + if( !utils.isObject( object ) ){ return false; } + for( var i=0; i= 1, 'Has at least one user' ); + + //TODO: index( deleted ) + + // ------------------------------------------------------------------------------------------- SHOW + this.test.comment( 'show should get detailed data about the user with the given id' ); + var userShow = this.api.users.show( userIndex[0].id ); + this.debug( this.jsonStr( userShow ) ); + + //TODO: show( current ) + //TODO: show( deleted ) + + // ------------------------------------------------------------------------------------------- CREATE + + // ------------------------------------------------------------------------------------------- MISC +}); + +// =================================================================== +spaceghost.run( function(){ +}); diff --git a/test/casperjs/modules/api.js b/test/casperjs/modules/api.js index 4ce61a0b816..0ea1c84877a 100644 --- a/test/casperjs/modules/api.js +++ b/test/casperjs/modules/api.js @@ -18,10 +18,12 @@ var API = function API( spaceghost, apikey ){ this.encodedIdExpectedLength = 16; this.jQueryLocation = '../../static/scripts/libs/jquery/jquery.js'; + this.configuration = new ConfigurationAPI( this ); this.histories = new HistoriesAPI( this ); this.hdas = new HDAAPI( this ); this.tools = new ToolsAPI( this ); this.workflows = new WorkflowsAPI( this ); + this.users = new UsersAPI( this ); }; exports.API = API; @@ -121,6 +123,28 @@ API.prototype.ensureJQuery = function ensureJQuery(){ }; +// =================================================================== CONFIGURATION +var ConfigurationAPI = function ConfigurationAPI( api ){ + this.api = api; +}; +ConfigurationAPI.prototype.toString = function toString(){ + return this.api + '.ConfigurationAPI'; +}; + +// ------------------------------------------------------------------- +ConfigurationAPI.prototype.urlTpls = { + index : 'api/configuration' +}; + +ConfigurationAPI.prototype.index = function index( deleted ){ + this.api.spaceghost.info( 'configuration.index' ); + + return this.api._ajax( this.urlTpls.index, { + data : {} + }); +}; + + // =================================================================== HISTORIES var HistoriesAPI = function HistoriesAPI( api ){ this.api = api; @@ -140,7 +164,7 @@ HistoriesAPI.prototype.urlTpls = { }; HistoriesAPI.prototype.index = function index( deleted ){ - this.api.spaceghost.info( 'history.index: ' + (( deleted )?( 'w deleted' ):( '(wo deleted)' )) ); + this.api.spaceghost.info( 'histories.index: ' + (( deleted )?( 'w deleted' ):( '(wo deleted)' )) ); deleted = deleted || false; return this.api._ajax( this.urlTpls.index, { @@ -149,7 +173,7 @@ HistoriesAPI.prototype.index = function index( deleted ){ }; HistoriesAPI.prototype.show = function show( id, deleted ){ - this.api.spaceghost.info( 'history.show: ' + [ id, (( deleted )?( 'w deleted' ):( '' )) ] ); + this.api.spaceghost.info( 'histories.show: ' + [ id, (( deleted )?( 'w deleted' ):( '' )) ] ); id = ( id === 'most_recently_used' )?( id ):( this.api.ensureId( id ) ); deleted = deleted || false; @@ -159,7 +183,7 @@ HistoriesAPI.prototype.show = function show( id, deleted ){ }; HistoriesAPI.prototype.create = function create( payload ){ - this.api.spaceghost.info( 'history.create: ' + this.api.spaceghost.jsonStr( payload ) ); + this.api.spaceghost.info( 'histories.create: ' + this.api.spaceghost.jsonStr( payload ) ); // py.payload <-> ajax.data payload = this.api.ensureObject( payload ); @@ -170,7 +194,7 @@ HistoriesAPI.prototype.create = function create( payload ){ }; HistoriesAPI.prototype.delete_ = function delete_( id, purge ){ - this.api.spaceghost.info( 'history.delete: ' + [ id, (( purge )?( '(purge!)' ):( '' )) ] ); + this.api.spaceghost.info( 'histories.delete: ' + [ id, (( purge )?( '(purge!)' ):( '' )) ] ); // py.payload <-> ajax.data var payload = ( purge )?({ purge: true }):({}); @@ -182,7 +206,7 @@ HistoriesAPI.prototype.delete_ = function delete_( id, purge ){ HistoriesAPI.prototype.undelete = function undelete( id ){ //throw ( 'unimplemented' ); - this.api.spaceghost.info( 'history.undelete: ' + id ); + this.api.spaceghost.info( 'histories.undelete: ' + id ); return this.api._ajax( utils.format( this.urlTpls.undelete, this.api.ensureId( id ) ), { type : 'POST' @@ -190,7 +214,7 @@ HistoriesAPI.prototype.undelete = function undelete( id ){ }; HistoriesAPI.prototype.update = function create( id, payload ){ - this.api.spaceghost.info( 'history.update: ' + id + ',' + this.api.spaceghost.jsonStr( payload ) ); + this.api.spaceghost.info( 'histories.update: ' + id + ',' + this.api.spaceghost.jsonStr( payload ) ); // py.payload <-> ajax.data id = this.api.ensureId( id ); @@ -269,6 +293,7 @@ HDAAPI.prototype.update = function create( historyId, id, payload ){ }); }; + // =================================================================== TOOLS var ToolsAPI = function HDAAPI( api ){ this.api = api; @@ -319,6 +344,7 @@ ToolsAPI.prototype.show = function show( id ){ // }); //}; + // =================================================================== WORKFLOWS var WorkflowsAPI = function WorkflowsAPI( api ){ this.api = api; @@ -394,3 +420,88 @@ WorkflowsAPI.prototype.upload = function upload( filepath ){ }); }; + +// =================================================================== USERS +var UsersAPI = function UsersAPI( api ){ + this.api = api; +}; +UsersAPI.prototype.toString = function toString(){ + return this.api + '.UsersAPI'; +}; + +// ------------------------------------------------------------------- +//NOTE: lots of admin only functionality in this section +UsersAPI.prototype.urlTpls = { + index : 'api/users', + show : 'api/users/%s', + create : 'api/users', + delete_ : 'api/users/%s', + undelete: 'api/users/deleted/%s/undelete', + update : 'api/users/%s' +}; + +UsersAPI.prototype.index = function index( deleted ){ + this.api.spaceghost.info( 'users.index: ' + (( deleted )?( 'w deleted' ):( '(wo deleted)' )) ); + + deleted = deleted || false; + return this.api._ajax( this.urlTpls.index, { + data : { deleted: deleted } + }); +}; + +UsersAPI.prototype.show = function show( id, deleted ){ + this.api.spaceghost.info( 'users.show: ' + [ id, (( deleted )?( 'w deleted' ):( '' )) ] ); + + id = ( id === 'current' )?( id ):( this.api.ensureId( id ) ); + deleted = deleted || false; + return this.api._ajax( utils.format( this.urlTpls.show, id ), { + data : { deleted: deleted } + }); +}; + +UsersAPI.prototype.create = function create( payload ){ + this.api.spaceghost.info( 'users.create: ' + this.api.spaceghost.jsonStr( payload ) ); + + // py.payload <-> ajax.data + payload = this.api.ensureObject( payload ); + return this.api._ajax( utils.format( this.urlTpls.create ), { + type : 'POST', + data : payload + }); +}; + +UsersAPI.prototype.delete_ = function delete_( id, purge ){ + this.api.spaceghost.info( 'users.delete: ' + [ id, (( purge )?( '(purge!)' ):( '' )) ] ); + + // py.payload <-> ajax.data + var payload = ( purge )?({ purge: true }):({}); + return this.api._ajax( utils.format( this.urlTpls.delete_, this.api.ensureId( id ) ), { + type : 'DELETE', + data : payload + }); +}; + +UsersAPI.prototype.undelete = function undelete( id ){ + //throw ( 'unimplemented' ); + this.api.spaceghost.info( 'users.undelete: ' + id ); + + return this.api._ajax( utils.format( this.urlTpls.undelete, this.api.ensureId( id ) ), { + type : 'POST' + }); +}; + +UsersAPI.prototype.update = function create( id, payload ){ + this.api.spaceghost.info( 'users.update: ' + id + ',' + this.api.spaceghost.jsonStr( payload ) ); + + // py.payload <-> ajax.data + id = this.api.ensureId( id ); + payload = this.api.ensureObject( payload ); + url = utils.format( this.urlTpls.update, id ); + + return this.api._ajax( url, { + type : 'PUT', + data : payload + }); +}; + + diff --git a/test/casperjs/modules/user.js b/test/casperjs/modules/user.js index 5617ff5c261..9bed8d14988 100644 --- a/test/casperjs/modules/user.js +++ b/test/casperjs/modules/user.js @@ -134,7 +134,7 @@ User.prototype.login = function login( email, password ){ this._submitLogin( email, password ); spaceghost.withMainPanel( function mainAfterLogin(){ - if( spaceghost.getCurrentUrl().search( spaceghost.data.selectors.loginPage.url_regex ) != -1 ){ + if( spaceghost.getCurrentUrl().search( spaceghost.data.selectors.loginPage.url_regex ) !== -1 ){ var messageInfo = spaceghost.getElementInfo( spaceghost.data.selectors.messages.all ); if( messageInfo && messageInfo.attributes[ 'class' ] === 'errormessage' ){ this.warning( 'Login failed: ' + messageInfo.text ); @@ -171,6 +171,7 @@ User.prototype.loggedInAs = function loggedInAs(){ User.prototype.logout = function logout(){ var spaceghost = this.spaceghost; spaceghost.thenOpen( spaceghost.baseUrl, function(){ + this.info( 'user logging out' ); //TODO: handle already logged out spaceghost.clickLabel( spaceghost.data.labels.masthead.menus.user ); spaceghost.clickLabel( spaceghost.data.labels.masthead.userMenu.logout ); @@ -196,6 +197,63 @@ User.prototype.loginOrRegisterUser = function loginOrRegisterUser( email, passwo return spaceghost; }; +// ------------------------------------------------------------------- Admin +/** Gets the admin user data from spaceghost if set and checks the universe_wsgi.ini file for the email. + * @returns {Object|null} the admin data object (email, pasword, username) + * or null if no admin is set in both the universe_wsgi.ini and spaceghost. + */ +User.prototype.getAdminData = function getAdminData(){ + //TODO: this might be better inside sg + // check for the setting in sg and the universe_wsgi.ini file + var adminData = this.spaceghost.options.adminUser, + iniAdminEmails = this.spaceghost.getUniverseSetting( 'admin_users' ); + iniAdminEmails = ( iniAdminEmails )?( iniAdminEmails.split( ',' ) ):( null ); + + //TODO: seems like we only need the wsgi setting - that's the only thing we can't change + if( adminData ){ + if( iniAdminEmails.indexOf( adminData.email ) !== -1 ){ return adminData; } + + // if not set in options, but there are entries in the ini and a default admin pass: + // return the first email with the default pass + // Hopefully this is no less secure than the user/pwd in twilltestcase + } else if( iniAdminEmails.length && this.spaceghost.options.adminPassword ){ + return { email: iniAdminEmails[0], password: this.spaceghost.options.adminPassword }; + } + + return null; +}; + +/** Logs in the admin user (if available) as the current user. + * Note: logs out any other current users. + * @throws {GalaxyError} err if specified user is not admin or no admin found + * @returns {SpaceGhost} the spaceghost instance (for chaining) + */ +User.prototype.loginAdmin = function loginAdmin(){ + this.spaceghost.then( function(){ + var adminData = this.user.getAdminData(); + if( !adminData ){ + throw new this.GalaxyError( 'No admin users found' ); + } + this.info( 'logging in administrator' ); + return this.user.loginOrRegisterUser( adminData.email, adminData.password ); + }); +}; + +/** Is the currently logged in user an admin? + * @returns {Boolean} true if the currently logged in user is admin, false if not. + */ +User.prototype.userIsAdmin = function userIsAdmin(){ + // simple test of whether the Admin tab is displayed in the masthead + return this.spaceghost.jumpToTop( function(){ + if( this.visible( this.data.selectors.masthead.adminLink ) ){ + return true; + } + return false; + }); +}; + + +// ------------------------------------------------------------------- Utility /** Gets a psuedo-random (unique?) email based on the time stamp. * Helpful for testing registration. * @param {String} username email user (defaults to 'test') @@ -207,3 +265,4 @@ User.prototype.getRandomEmail = function getRandomEmail( username, domain ){ domain = domain || 'test.test'; return username + Date.now() + '@' + domain; }; + diff --git a/test/casperjs/spaceghost.js b/test/casperjs/spaceghost.js index 57b8be58547..9c41bcdb9b3 100644 --- a/test/casperjs/spaceghost.js +++ b/test/casperjs/spaceghost.js @@ -47,7 +47,17 @@ function SpaceGhost(){ } utils.inherits( SpaceGhost, Casper ); -//console.debug( 'CasperError:' + CasperError ); +/** String representation + * @returns {String} + */ +SpaceGhost.prototype.toString = function(){ + var currentUrl = ''; + try { + currentUrl = this.getCurrentUrl(); + } catch( err ){} + return 'SpaceGhost(' + currentUrl + ')'; +}; + // ------------------------------------------------------------------- included libs //??: can we require underscore, etc. from the ../../static/scripts/lib? @@ -157,9 +167,14 @@ SpaceGhost.prototype._processCLIArguments = function _processCLIArguments(){ //htmlOnError : { defaultsTo: false, flag: 'error-html', help: 'output page html on a page error' } //htmlOnFail : { defaultsTo: false, flag: 'fail-html', help: 'output page html on a test failure' }, //screenOnFail : { defaultsTo: false, flag: 'fail-screen', help: 'capture a screenshot on a test failure' } - logNamespace : { defaultsTo: false, flag: 'log-namespace', help: 'filter log messages to this namespace' } + logNamespace : { defaultsTo: false, flag: 'log-namespace', help: 'filter log messages to this namespace' }, + + adminUser : { defaultsTo: null, flag: 'admin', help: 'JSON string with email and password of admin' } }; + // no switches/hardcoded options: + this.options.adminPassword = 'testuser'; + // --url parameter required (the url of the server to test with) if( !this.cli.has( 'url' ) ){ this.die( 'Test server URL is required - ' + @@ -237,6 +252,13 @@ SpaceGhost.prototype._processCLIArguments = function _processCLIArguments(){ this._setLogNamespaceFilter( this.options.logNamespace ); } + /** email and password JSON string for admin user */ + this.options.adminUser = CLI_OPTIONS.adminUser.defaultsTo; + if( this.cli.has( CLI_OPTIONS.adminUser.flag ) ){ + this.options.adminUser = JSON.parse( this.cli.get( CLI_OPTIONS.adminUser.flag ) ); + this.warning( 'Using admin user from CLI: ' + this.jsonStr( this.options.adminUser ) ); + } + }; /** Suppress the normal output from the casper object (echo, errors) @@ -1002,6 +1024,7 @@ SpaceGhost.prototype.out = function( msg, namespace ){ } }; +// ------------------------------------------------------------------- debugging /** JSON formatter */ SpaceGhost.prototype.jsonStr = function( obj ){ @@ -1036,17 +1059,7 @@ SpaceGhost.prototype.lastError = function(){ return this.errors[( this.errors.length - 1 )]; }; -/** String representation - * @returns {String} - */ -SpaceGhost.prototype.toString = function(){ - var currentUrl = ''; - try { - currentUrl = this.getCurrentUrl(); - } catch( err ){} - return 'SpaceGhost(' + currentUrl + ')'; -}; - +// ------------------------------------------------------------------- file system /** Load and parse a JSON file into an object. * @param {String} filepath filepath relative to the current scriptDir * @returns {Object} the object parsed @@ -1078,6 +1091,34 @@ SpaceGhost.prototype.writeHTMLFile = function writeHTMLFile( filepath, selector, return fs.write( filepath, this.getHTML( selector, outer ), 'w' ); }; +/** Read and search a file for the given regex. + * @param {String} filepath filepath relative to the current scriptDir + * @param {Regex} searchFor regex to search for + * @returns {Object} search results + */ +SpaceGhost.prototype.searchFile = function searchFile( filepath, regex ){ + //precondition: filepath is relative to script dir + filepath = this.options.scriptDir + filepath; + var read = fs.read( filepath ); + return read.match( regex ); +}; + +/** Read a configuration setting from the universe_wsgi.ini file. + * @param {String} iniKey the setting key to find + * @returns {String} value from file for iniKey (or null if not found or commented out) + */ +SpaceGhost.prototype.getUniverseSetting = function getUniverseSetting( iniKey ){ + var iniFilepath = '../../universe_wsgi.ini', + regex = new RegExp( '^([#]*)\\\s*' + iniKey + '\\\s*=\\\s*(.*)$', 'm' ), + match = this.searchFile( iniFilepath, regex ); + this.debug( 'regex: ' + regex ); + // if nothing found or found and first group (the ini comment char) is not empty + if( match === null || match[1] || !match[2] ){ + return null; + } + return match[2]; +}; + // =================================================================== TEST DATA /** General use selectors, labels, and text. Kept here to allow a centralized location. @@ -1105,6 +1146,7 @@ SpaceGhost.prototype.data = { }, masthead : { + adminLink : '#masthead a[href="/admin"]', userMenu : { userEmail : 'a #user-email', userEmail_xpath : '//a[contains(text(),"Logged in as")]/span["id=#user-email"]' From ce8f2e9ea67b4e2571aa545f26353817629ef06c Mon Sep 17 00:00:00 2001 From: Carl Eberhard Date: Fri, 26 Apr 2013 15:26:33 -0400 Subject: [PATCH 246/523] Functional tests: update test_history_functions 060 to use the new json variable name in history.mako --- test/functional/test_history_functions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/functional/test_history_functions.py b/test/functional/test_history_functions.py index 329d0b98221..5ed1c30ca55 100644 --- a/test/functional/test_history_functions.py +++ b/test/functional/test_history_functions.py @@ -656,7 +656,7 @@ class TestHistory( TwillTestCase ): if hda[ 'id' ] == self.security.encode_id( hda_2_bed.id ): return ( not hda[ 'accessible' ] ) return False - self.check_history_json( r'\bhdas\s*=\s*(.*);', hda_2_bed_is_inaccessible ) + self.check_history_json( r'\bhdaJson\s*=\s*(.*);', hda_2_bed_is_inaccessible ) # Admin users can view all datasets ( using the history/view feature ), so make sure 2.bed is accessible to the admin self.logout() From 2815cf185bddf6303a8f741728efbf4e245563c5 Mon Sep 17 00:00:00 2001 From: Carl Eberhard Date: Fri, 26 Apr 2013 15:36:33 -0400 Subject: [PATCH 247/523] Configuration API: include and standardize error handling, complete documentation --- .../webapps/galaxy/api/configuration.py | 31 +++++++++++++++---- 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/lib/galaxy/webapps/galaxy/api/configuration.py b/lib/galaxy/webapps/galaxy/api/configuration.py index 25c9067f7ea..b98f0d60764 100644 --- a/lib/galaxy/webapps/galaxy/api/configuration.py +++ b/lib/galaxy/webapps/galaxy/api/configuration.py @@ -1,5 +1,6 @@ """ -API operations allowing clients to determine Galaxy instance's capabilities. +API operations allowing clients to determine Galaxy instance's capabilities +and configuration settings. """ from galaxy import web @@ -9,6 +10,7 @@ import logging log = logging.getLogger( __name__ ) class ConfigurationController( BaseAPIController ): + # config attributes viewable by non-admin users EXPOSED_USER_OPTIONS = [ 'enable_unique_workflow_defaults', 'ftp_upload_site', @@ -19,6 +21,7 @@ class ConfigurationController( BaseAPIController ): 'terms_url', 'allow_user_dataset_purge', ] + # config attributes viewable by admin users EXPOSED_ADMIN_OPTIONS = [ 'library_import_dir', 'user_library_import_dir', @@ -30,12 +33,28 @@ class ConfigurationController( BaseAPIController ): @web.expose_api def index( self, trans, **kwd ): """ + GET /api/configuration + Return an object containing exposable configuration settings. + + Note: a more complete list is returned if the user is an admin. """ - config = trans.app.config - options = self._get_options( config, self.EXPOSED_USER_OPTIONS ) - if trans.user_is_admin(): - options.update( self._get_options( config, self.EXPOSED_ADMIN_OPTIONS ) ) - return options + try: + config = trans.app.config + options = self._get_options( config, self.EXPOSED_USER_OPTIONS ) + if trans.user_is_admin(): + options.update( self._get_options( config, self.EXPOSED_ADMIN_OPTIONS ) ) + return options + + except Exception, exception: + log.error( 'could not get configuration: %s', str( exception ), exc_info=True ) + trans.response.status = 500 + return { 'error': str( exception ) } def _get_options( self, config, keys ): + """ + Build and return a subset of the config dictionary restricted to the + list `keys`. + + The attribute value will default to None if not available. + """ return dict( [ (key, getattr( config, key, None ) ) for key in keys ] ) From 663b3905368d3401c82414a0c801c51faae75871 Mon Sep 17 00:00:00 2001 From: Dave Bouvier Date: Sun, 28 Apr 2013 11:03:41 -0400 Subject: [PATCH 248/523] Fix failing functional test for dataset features. --- test/base/twilltestcase.py | 7 +++++++ test/functional/test_dataset_features.py | 10 ++++++++-- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/test/base/twilltestcase.py b/test/base/twilltestcase.py index e532975fc30..fd6d3371615 100644 --- a/test/base/twilltestcase.py +++ b/test/base/twilltestcase.py @@ -255,6 +255,13 @@ class TwillTestCase( unittest.TestCase ): def get_latest_history( self ): return self.json_from_url( '/api/histories' )[ 0 ] + + def find_hda_by_dataset_name( self, name, history=None ): + if history is None: + history = self.get_history_from_api() + for hda in history: + if hda[ 'name' ] == name: + return hda def check_history_for_errors( self ): """Raises an exception if there are errors in a history""" diff --git a/test/functional/test_dataset_features.py b/test/functional/test_dataset_features.py index 5c4cc0a5747..6fd2777a016 100644 --- a/test/functional/test_dataset_features.py +++ b/test/functional/test_dataset_features.py @@ -1,7 +1,9 @@ from base.twilltestcase import TwillTestCase import base.test_db_util as test_db_util + class TestDatasetFeatures( TwillTestCase ): + def test_0000_initiate_users( self ): """Ensuring all required user accounts exist""" self.logout() @@ -16,14 +18,18 @@ class TestDatasetFeatures( TwillTestCase ): self.new_history() latest_history = test_db_util.get_latest_history_for_user( admin_user ) assert latest_history is not None, "Problem retrieving latest_history from database" + def test_0005_initiate_data( self ): '''Ensure that data exists for this test suite.''' self.upload_file( '1.bed' ) + def test_0010_view_dataset_params( self ): '''Test viewing a dataset's parameters.''' - hda = test_db_util.get_latest_hda() - self.visit_url( '/datasets/%s/show_params' % self.security.encode_id( hda.hid ) ) + hda = self.find_hda_by_dataset_name( '1.bed' ) + assert hda is not None, 'Could not retrieve latest hda from history API.' + self.visit_url( '/datasets/%s/show_params' % hda[ 'id'] ) self.check_for_strings( strings_displayed=[ '1.bed', 'uploaded' ] ) + def test_0015_report_dataset_error( self ): '''Load and submit the report error form. This should show an error message, as the functional test instance should not be configured for email.''' hda = test_db_util.get_latest_hda() From 253f2d0fe8af72e0a7e6267333718cffc1a3d2ed Mon Sep 17 00:00:00 2001 From: Dannon Baker Date: Mon, 29 Apr 2013 13:09:15 -0400 Subject: [PATCH 249/523] Additional migration changes necessary to start w/ a clean sqlite database. --- .../migrate/versions/0012_user_address.py | 8 +- .../versions/0019_request_library_folder.py | 2 +- .../model/migrate/versions/0025_user_info.py | 21 +++-- .../migrate/versions/0027_request_events.py | 31 +++--- .../0031_community_and_workflow_tags.py | 94 +++++++++++++------ .../0032_stored_workflow_slug_column.py | 24 +++-- ...lished_cols_for_histories_and_workflows.py | 45 ++++----- .../migrate/versions/0037_samples_library.py | 5 +- .../versions/0047_job_table_user_id_column.py | 21 +++-- .../migrate/versions/0057_request_notify.py | 11 ++- .../versions/0058_history_import_export.py | 2 +- 11 files changed, 161 insertions(+), 103 deletions(-) diff --git a/lib/galaxy/model/migrate/versions/0012_user_address.py b/lib/galaxy/model/migrate/versions/0012_user_address.py index 330b8aae9a0..da1dab09c7e 100644 --- a/lib/galaxy/model/migrate/versions/0012_user_address.py +++ b/lib/galaxy/model/migrate/versions/0012_user_address.py @@ -24,7 +24,6 @@ handler.setFormatter( formatter ) log.addHandler( handler ) metadata = MetaData() -#db_session = scoped_session( sessionmaker( bind=migrate_engine, autoflush=False, autocommit=True ) ) def display_migration_details(): print "========================================" @@ -75,15 +74,18 @@ def upgrade(migrate_engine): assert col is RequestType_table.c.deleted except Exception, e: log.debug( "Adding column 'deleted' to request_type table failed: %s" % ( str( e ) ) ) + # Delete the submitted column + # This fails for sqlite, so skip the drop -- no conflicts in the future try: Request_table = Table( "request", metadata, autoload=True ) except NoSuchTableError: Request_table = None log.debug( "Failed loading table request" ) if Request_table is not None: - #DBTODO Re-try - Request_table.c.submitted.drop(alter_metadata=True) + if migrate_engine.name != 'sqlite': + #DBTODO drop from table doesn't work in sqlite w/ sqlalchemy-migrate .6+ + Request_table.c.submitted.drop() col = Column( "state", TrimmedString( 255 ), index=True ) col.create( Request_table, index_name='ix_request_state') assert col is Request_table.c.state diff --git a/lib/galaxy/model/migrate/versions/0019_request_library_folder.py b/lib/galaxy/model/migrate/versions/0019_request_library_folder.py index 5bebf4697d7..9f15abe2c90 100644 --- a/lib/galaxy/model/migrate/versions/0019_request_library_folder.py +++ b/lib/galaxy/model/migrate/versions/0019_request_library_folder.py @@ -50,7 +50,7 @@ def upgrade(migrate_engine): LibraryFolder_table = None log.debug( "Failed loading table library_folder" ) # Add 1 foreign key constraint to the library_folder table - if Request_table is not None and LibraryFolder_table is not None: + if migrate_engine.name != 'sqlite' and Request_table is not None and LibraryFolder_table is not None: try: cons = ForeignKeyConstraint( [Request_table.c.folder_id], [LibraryFolder_table.c.id], diff --git a/lib/galaxy/model/migrate/versions/0025_user_info.py b/lib/galaxy/model/migrate/versions/0025_user_info.py index 285f7a7de87..d8d141b0e95 100644 --- a/lib/galaxy/model/migrate/versions/0025_user_info.py +++ b/lib/galaxy/model/migrate/versions/0025_user_info.py @@ -48,16 +48,17 @@ def upgrade(migrate_engine): except NoSuchTableError: FormValues_table = None log.debug( "Failed loading table form_values" ) - # Add 1 foreign key constraint to the form_values table - if User_table is not None and FormValues_table is not None: - try: - cons = ForeignKeyConstraint( [User_table.c.form_values_id], - [FormValues_table.c.id], - name='user_form_values_id_fk' ) - # Create the constraint - cons.create() - except Exception, e: - log.debug( "Adding foreign key constraint 'user_form_values_id_fk' to table 'galaxy_user' failed: %s" % ( str( e ) ) ) + if migrate_engine.name != 'sqlite': + # Add 1 foreign key constraint to the form_values table + if User_table is not None and FormValues_table is not None: + try: + cons = ForeignKeyConstraint( [User_table.c.form_values_id], + [FormValues_table.c.id], + name='user_form_values_id_fk' ) + # Create the constraint + cons.create() + except Exception, e: + log.debug( "Adding foreign key constraint 'user_form_values_id_fk' to table 'galaxy_user' failed: %s" % ( str( e ) ) ) def downgrade(migrate_engine): metadata.bind = migrate_engine pass diff --git a/lib/galaxy/model/migrate/versions/0027_request_events.py b/lib/galaxy/model/migrate/versions/0027_request_events.py index ba40834060a..65a7f5c7b33 100644 --- a/lib/galaxy/model/migrate/versions/0027_request_events.py +++ b/lib/galaxy/model/migrate/versions/0027_request_events.py @@ -2,6 +2,7 @@ This migration script adds the request_event table and removes the state field in the request table """ +# Need our custom types, but don't import anything else from model from sqlalchemy import * from sqlalchemy.orm import * from sqlalchemy.exc import * @@ -9,9 +10,13 @@ from migrate import * from migrate.changeset import * import datetime +import sys +import logging + now = datetime.datetime.utcnow -import sys, logging +from galaxy.model.custom_types import TrimmedString + log = logging.getLogger( __name__ ) log.setLevel(logging.DEBUG) handler = logging.StreamHandler( sys.stdout ) @@ -20,8 +25,6 @@ formatter = logging.Formatter( format ) handler.setFormatter( formatter ) log.addHandler( handler ) -# Need our custom types, but don't import anything else from model -from galaxy.model.custom_types import * metadata = MetaData() @@ -65,6 +68,7 @@ def upgrade(migrate_engine): except Exception, e: log.debug( "Creating request_event table failed: %s" % str( e ) ) # move the current state of all existing requests to the request_event table + cmd = \ "INSERT INTO request_event " + \ "SELECT %s AS id," + \ @@ -77,17 +81,18 @@ def upgrade(migrate_engine): cmd = cmd % ( nextval('request_event'), localtimestamp(), localtimestamp(), 'Imported from request table') migrate_engine.execute( cmd ) - # Delete the state column - try: - Request_table = Table( "request", metadata, autoload=True ) - except NoSuchTableError: - Request_table = None - log.debug( "Failed loading table request" ) - if Request_table is not None: + if migrate_engine.name != 'sqlite': + # Delete the state column try: - Request_table.c.state.drop() - except Exception, e: - log.debug( "Deleting column 'state' to request table failed: %s" % ( str( e ) ) ) + Request_table = Table( "request", metadata, autoload=True ) + except NoSuchTableError: + Request_table = None + log.debug( "Failed loading table request" ) + if Request_table is not None: + try: + Request_table.c.state.drop() + except Exception, e: + log.debug( "Deleting column 'state' to request table failed: %s" % ( str( e ) ) ) def downgrade(migrate_engine): metadata.bind = migrate_engine diff --git a/lib/galaxy/model/migrate/versions/0031_community_and_workflow_tags.py b/lib/galaxy/model/migrate/versions/0031_community_and_workflow_tags.py index 0a826805781..c982b844c87 100644 --- a/lib/galaxy/model/migrate/versions/0031_community_and_workflow_tags.py +++ b/lib/galaxy/model/migrate/versions/0031_community_and_workflow_tags.py @@ -41,15 +41,24 @@ def upgrade(migrate_engine): # Create user_id column in history_tag_association table. HistoryTagAssociation_table = Table( "history_tag_association", metadata, autoload=True ) - c = Column( "user_id", Integer, ForeignKey( "galaxy_user.id" ), index=True ) - try: - c.create( HistoryTagAssociation_table, index_name='ix_history_tag_association_user_id') - assert c is HistoryTagAssociation_table.c.user_id - except Exception, e: - # SQLite does not support 'ALTER TABLE ADD FOREIGN KEY', so catch exception if it arises. - print str(e) - log.debug( "Adding user_id column to history_tag_association table failed: %s" % str( e ) ) - + if migrate_engine.name != 'sqlite': + c = Column( "user_id", Integer, ForeignKey( "galaxy_user.id" ), index=True ) + try: + c.create( HistoryTagAssociation_table, index_name='ix_history_tag_association_user_id') + assert c is HistoryTagAssociation_table.c.user_id + except Exception, e: + # SQLite does not support 'ALTER TABLE ADD FOREIGN KEY', so catch exception if it arises. + print str(e) + log.debug( "Adding user_id column to history_tag_association table failed: %s" % str( e ) ) + else: + c = Column( "user_id", Integer) + try: + c.create( HistoryTagAssociation_table) + assert c is HistoryTagAssociation_table.c.user_id + except Exception, e: + # SQLite does not support 'ALTER TABLE ADD FOREIGN KEY', so catch exception if it arises. + print str(e) + log.debug( "Adding user_id column to history_tag_association table failed: %s" % str( e ) ) # Populate column so that user_id is the id of the user who owns the history (and, up to now, was the only person able to tag the history). if c is HistoryTagAssociation_table.c.user_id: @@ -57,34 +66,57 @@ def upgrade(migrate_engine): "UPDATE history_tag_association SET user_id=( SELECT user_id FROM history WHERE history_tag_association.history_id = history.id )" ) - # Create user_id column in history_dataset_association_tag_association table. - HistoryDatasetAssociationTagAssociation_table = Table( "history_dataset_association_tag_association", metadata, autoload=True ) - c = Column( "user_id", Integer, ForeignKey( "galaxy_user.id" ), index=True ) - try: - c.create( HistoryDatasetAssociationTagAssociation_table, index_name='ix_history_dataset_association_tag_association_user_id') - assert c is HistoryDatasetAssociationTagAssociation_table.c.user_id - except Exception, e: - # SQLite does not support 'ALTER TABLE ADD FOREIGN KEY', so catch exception if it arises. - print str(e) - log.debug( "Adding user_id column to history_dataset_association_tag_association table failed: %s" % str( e ) ) - + if migrate_engine.name != 'sqlite': + # Create user_id column in history_dataset_association_tag_association table. + HistoryDatasetAssociationTagAssociation_table = Table( "history_dataset_association_tag_association", metadata, autoload=True ) + c = Column( "user_id", Integer, ForeignKey( "galaxy_user.id" ), index=True ) + try: + c.create( HistoryDatasetAssociationTagAssociation_table, index_name='ix_history_dataset_association_tag_association_user_id') + assert c is HistoryDatasetAssociationTagAssociation_table.c.user_id + except Exception, e: + # SQLite does not support 'ALTER TABLE ADD FOREIGN KEY', so catch exception if it arises. + print str(e) + log.debug( "Adding user_id column to history_dataset_association_tag_association table failed: %s" % str( e ) ) + else: + #In sqlite, we can no longer quietly fail to add foreign key. + # Create user_id column in history_dataset_association_tag_association table. + HistoryDatasetAssociationTagAssociation_table = Table( "history_dataset_association_tag_association", metadata, autoload=True ) + c = Column( "user_id", Integer) + try: + c.create( HistoryDatasetAssociationTagAssociation_table) + assert c is HistoryDatasetAssociationTagAssociation_table.c.user_id + except Exception, e: + # SQLite does not support 'ALTER TABLE ADD FOREIGN KEY', so catch exception if it arises. + print str(e) + log.debug( "Adding user_id column to history_dataset_association_tag_association table failed: %s" % str( e ) ) # Populate column so that user_id is the id of the user who owns the history_dataset_association (and, up to now, was the only person able to tag the page). if c is HistoryDatasetAssociationTagAssociation_table.c.user_id: migrate_engine.execute( "UPDATE history_dataset_association_tag_association SET user_id=( SELECT history.user_id FROM history, history_dataset_association WHERE history_dataset_association.history_id = history.id AND history_dataset_association.id = history_dataset_association_tag_association.history_dataset_association_id)" ) - - # Create user_id column in page_tag_association table. - PageTagAssociation_table = Table( "page_tag_association", metadata, autoload=True ) - c = Column( "user_id", Integer, ForeignKey( "galaxy_user.id" ), index=True ) - try: - c.create( PageTagAssociation_table, index_name='ix_page_tag_association_user_id') - assert c is PageTagAssociation_table.c.user_id - except Exception, e: - # SQLite does not support 'ALTER TABLE ADD FOREIGN KEY', so catch exception if it arises. - print str(e) - log.debug( "Adding user_id column to page_tag_association table failed: %s" % str( e ) ) + if migrate_engine.name != 'sqlite': + # Create user_id column in page_tag_association table. + PageTagAssociation_table = Table( "page_tag_association", metadata, autoload=True ) + c = Column( "user_id", Integer, ForeignKey( "galaxy_user.id" ), index=True ) + try: + c.create( PageTagAssociation_table, index_name='ix_page_tag_association_user_id') + assert c is PageTagAssociation_table.c.user_id + except Exception, e: + # SQLite does not support 'ALTER TABLE ADD FOREIGN KEY', so catch exception if it arises. + print str(e) + log.debug( "Adding user_id column to page_tag_association table failed: %s" % str( e ) ) + else: + # Create user_id column in page_tag_association table. + PageTagAssociation_table = Table( "page_tag_association", metadata, autoload=True ) + c = Column( "user_id", Integer ) + try: + c.create( PageTagAssociation_table ) + assert c is PageTagAssociation_table.c.user_id + except Exception, e: + # SQLite does not support 'ALTER TABLE ADD FOREIGN KEY', so catch exception if it arises. + print str(e) + log.debug( "Adding user_id column to page_tag_association table failed: %s" % str( e ) ) # Populate column so that user_id is the id of the user who owns the page (and, up to now, was the only person able to tag the page). if c is PageTagAssociation_table.c.user_id: diff --git a/lib/galaxy/model/migrate/versions/0032_stored_workflow_slug_column.py b/lib/galaxy/model/migrate/versions/0032_stored_workflow_slug_column.py index bfc03714aee..40ee762858b 100644 --- a/lib/galaxy/model/migrate/versions/0032_stored_workflow_slug_column.py +++ b/lib/galaxy/model/migrate/versions/0032_stored_workflow_slug_column.py @@ -19,18 +19,24 @@ def upgrade(migrate_engine): StoredWorkflow_table = Table( "stored_workflow", metadata, autoload=True ) - # Create slug column. - c = Column( "slug", TEXT, index=True ) - c.create( StoredWorkflow_table, index_name='ix_stored_workflow_slug' ) + if migrate_engine.name != 'sqlite': + # Create slug column. + c = Column( "slug", TEXT, index=True ) + c.create( StoredWorkflow_table, index_name='ix_stored_workflow_slug' ) + else: + # Create slug column. + c = Column( "slug", TEXT ) + c.create( StoredWorkflow_table ) assert c is StoredWorkflow_table.c.slug # Create slug index. - try: - i = Index( "ix_stored_workflow_slug", StoredWorkflow_table.c.slug ) - i.create() - except: - # Mysql doesn't have a named index, but alter should work - StoredWorkflow_table.c.slug.alter( unique=False ) + if migrate_engine.name != 'sqlite': + try: + i = Index( "ix_stored_workflow_slug", StoredWorkflow_table.c.slug ) + i.create() + except: + # Mysql doesn't have a named index, but alter should work + StoredWorkflow_table.c.slug.alter( unique=False ) def downgrade(migrate_engine): metadata.bind = migrate_engine diff --git a/lib/galaxy/model/migrate/versions/0033_published_cols_for_histories_and_workflows.py b/lib/galaxy/model/migrate/versions/0033_published_cols_for_histories_and_workflows.py index d961f8e2799..a6caf68958b 100644 --- a/lib/galaxy/model/migrate/versions/0033_published_cols_for_histories_and_workflows.py +++ b/lib/galaxy/model/migrate/versions/0033_published_cols_for_histories_and_workflows.py @@ -30,13 +30,14 @@ def upgrade(migrate_engine): log.debug( "Adding published column to history table failed: %s" % str( e ) ) - # Create index for published column in history table. - try: - i = Index( "ix_history_published", History_table.c.published ) - i.create() - except: - # Mysql doesn't have a named index, but alter should work - History_table.c.published.alter( unique=False ) + if migrate_engine.name != 'sqlite': + # Create index for published column in history table. + try: + i = Index( "ix_history_published", History_table.c.published ) + i.create() + except: + # Mysql doesn't have a named index, but alter should work + History_table.c.published.alter( unique=False ) # Create published column in stored workflows table. StoredWorkflow_table = Table( "stored_workflow", metadata, autoload=True ) @@ -48,13 +49,14 @@ def upgrade(migrate_engine): print "Adding published column to stored_workflow table failed: %s" % str( e ) log.debug( "Adding published column to stored_workflow table failed: %s" % str( e ) ) - # Create index for published column in stored workflows table. - try: - i = Index( "ix_stored_workflow_published", StoredWorkflow_table.c.published ) - i.create() - except: - # Mysql doesn't have a named index, but alter should work - StoredWorkflow_table.c.published.alter( unique=False ) + if migrate_engine.name != 'sqlite': + # Create index for published column in stored workflows table. + try: + i = Index( "ix_stored_workflow_published", StoredWorkflow_table.c.published ) + i.create() + except: + # Mysql doesn't have a named index, but alter should work + StoredWorkflow_table.c.published.alter( unique=False ) # Create importable column in page table. Page_table = Table( "page", metadata, autoload=True ) @@ -66,13 +68,14 @@ def upgrade(migrate_engine): print "Adding importable column to page table failed: %s" % str( e ) log.debug( "Adding importable column to page table failed: %s" % str( e ) ) - # Create index for importable column in page table. - try: - i = Index( "ix_page_importable", Page_table.c.importable ) - i.create() - except: - # Mysql doesn't have a named index, but alter should work - Page_table.c.importable.alter( unique=False ) + if migrate_engine.name != 'sqlite': + # Create index for importable column in page table. + try: + i = Index( "ix_page_importable", Page_table.c.importable ) + i.create() + except: + # Mysql doesn't have a named index, but alter should work + Page_table.c.importable.alter( unique=False ) def downgrade(migrate_engine): metadata.bind = migrate_engine diff --git a/lib/galaxy/model/migrate/versions/0037_samples_library.py b/lib/galaxy/model/migrate/versions/0037_samples_library.py index cc80f8281c2..eedf669de09 100644 --- a/lib/galaxy/model/migrate/versions/0037_samples_library.py +++ b/lib/galaxy/model/migrate/versions/0037_samples_library.py @@ -23,7 +23,6 @@ handler.setFormatter( formatter ) log.addHandler( handler ) metadata = MetaData() -#db_session = scoped_session( sessionmaker( bind=migrate_engine, autoflush=False, autocommit=True ) ) def upgrade(migrate_engine): metadata.bind = migrate_engine @@ -83,7 +82,7 @@ def upgrade(migrate_engine): "user_id," + \ "deleted " + \ "FROM request;" - db_session.execute( cmd ) + migrate_engine.execute( cmd ) # delete the 'request' table try: Request_table.drop() @@ -91,7 +90,7 @@ def upgrade(migrate_engine): log.debug( "Dropping request table failed: %s" % str( e ) ) # rename table request_temp to request cmd = "ALTER TABLE request_temp RENAME TO request" - db_session.execute( cmd ) + migrate_engine.execute( cmd ) else: # Delete the library_id column in 'request' table try: diff --git a/lib/galaxy/model/migrate/versions/0047_job_table_user_id_column.py b/lib/galaxy/model/migrate/versions/0047_job_table_user_id_column.py index c3aab78eb2a..35fb6a8c018 100644 --- a/lib/galaxy/model/migrate/versions/0047_job_table_user_id_column.py +++ b/lib/galaxy/model/migrate/versions/0047_job_table_user_id_column.py @@ -33,12 +33,21 @@ def upgrade(migrate_engine): Job_table = None log.debug( "Failed loading table job" ) if Job_table is not None: - try: - col = Column( "user_id", Integer, ForeignKey( "galaxy_user.id" ), index=True, nullable=True ) - col.create( Job_table, index_name='ix_job_user_id' ) - assert col is Job_table.c.user_id - except Exception, e: - log.debug( "Adding column 'user_id' to job table failed: %s" % ( str( e ) ) ) + + if migrate_engine.name != 'sqlite': + try: + col = Column( "user_id", Integer, ForeignKey( "galaxy_user.id" ), index=True, nullable=True ) + col.create( Job_table, index_name='ix_job_user_id' ) + assert col is Job_table.c.user_id + except Exception, e: + log.debug( "Adding column 'user_id' to job table failed: %s" % ( str( e ) ) ) + else: + try: + col = Column( "user_id", Integer, nullable=True) + col.create( Job_table ) + assert col is Job_table.c.user_id + except Exception, e: + log.debug( "Adding column 'user_id' to job table failed: %s" % ( str( e ) ) ) try: cmd = "SELECT job.id AS galaxy_job_id, " \ + "galaxy_session.user_id AS galaxy_user_id " \ diff --git a/lib/galaxy/model/migrate/versions/0057_request_notify.py b/lib/galaxy/model/migrate/versions/0057_request_notify.py index 96c454c95d4..9edf9dee593 100644 --- a/lib/galaxy/model/migrate/versions/0057_request_notify.py +++ b/lib/galaxy/model/migrate/versions/0057_request_notify.py @@ -54,11 +54,12 @@ def upgrade(migrate_engine): for r in result: rr = from_json_string(str(r[1])) - # remove the 'notify' column - try: - Request_table.c.notify.drop() - except Exception, e: - log.debug( "Deleting column 'notify' from the 'request' table failed: %s" % ( str( e ) ) ) + # remove the 'notify' column for non-sqlite databases. + if migrate_engine.name != 'sqlite': + try: + Request_table.c.notify.drop() + except Exception, e: + log.debug( "Deleting column 'notify' from the 'request' table failed: %s" % ( str( e ) ) ) diff --git a/lib/galaxy/model/migrate/versions/0058_history_import_export.py b/lib/galaxy/model/migrate/versions/0058_history_import_export.py index 417a2f4dbef..96b615f41e0 100644 --- a/lib/galaxy/model/migrate/versions/0058_history_import_export.py +++ b/lib/galaxy/model/migrate/versions/0058_history_import_export.py @@ -27,8 +27,8 @@ JobExportHistoryArchive_table = Table( "job_export_history_archive", metadata, ) def upgrade(migrate_engine): - metadata.bind = migrate_engine print __doc__ + metadata.bind = migrate_engine metadata.reflect() # Create job_export_history_archive table. From 0a9c52a468367b6d5745a663c2e5e1f8a65eb78a Mon Sep 17 00:00:00 2001 From: Dannon Baker Date: Mon, 29 Apr 2013 16:38:02 -0400 Subject: [PATCH 250/523] Drop cloud tables in order to avoid hacky cascade drop. --- .../versions/0050_drop_cloud_tables.py | 23 ++++--------------- 1 file changed, 5 insertions(+), 18 deletions(-) diff --git a/lib/galaxy/model/migrate/versions/0050_drop_cloud_tables.py b/lib/galaxy/model/migrate/versions/0050_drop_cloud_tables.py index 6182fef9167..dd357af5395 100644 --- a/lib/galaxy/model/migrate/versions/0050_drop_cloud_tables.py +++ b/lib/galaxy/model/migrate/versions/0050_drop_cloud_tables.py @@ -132,27 +132,14 @@ def upgrade(migrate_engine): display_migration_details() # Load existing tables metadata.reflect() - if migrate_engine.name == 'postgres': - # http://blog.pythonisito.com/2008/01/cascading-drop-table-with-sqlalchemy.html - from sqlalchemy.databases import postgres - class PGCascadeSchemaDropper(postgres.PGSchemaDropper): - def visit_table(self, table): - for column in table.columns: - if column.default is not None: - self.traverse_single(column.default) - self.append("\nDROP TABLE " + - self.preparer.format_table(table) + - " CASCADE") - self.execute() - postgres.dialect.schemadropper = PGCascadeSchemaDropper try: - CloudProvider_table.drop() - CloudUserCredentials_table.drop() + CloudSnapshot_table.drop() + CloudStore_table.drop() + CloudInstance_table.drop() CloudImage_table.drop() UCI_table.drop() - CloudInstance_table.drop() - CloudStore_table.drop() - CloudSnapshot_table.drop() + CloudUserCredentials_table.drop() + CloudProvider_table.drop() except Exception, e: log.debug( "Dropping cloud tables failed: %s" % str( e ) ) From 0e9d409119260cce37fc3ada6ce5c473fdc0100f Mon Sep 17 00:00:00 2001 From: Greg Von Kuster Date: Mon, 29 Apr 2013 17:09:44 -0400 Subject: [PATCH 251/523] Add more granular lists of writable repositories for the current user: repositories with missing tool test components, repositories with failing tool tests and repositories with no failing tool tests. --- lib/galaxy/webapps/tool_shed/buildapp.py | 24 +- .../tool_shed/controllers/repository.py | 175 +++++++++++-- .../grids/admin_toolshed_grids.py | 2 +- lib/tool_shed/grids/repository_grids.py | 232 ++++++++++++++---- .../grids/repository_review_grids.py | 15 +- lib/tool_shed/util/shed_util_common.py | 23 ++ templates/webapps/tool_shed/index.mako | 17 +- .../repository/manage_repository.mako | 2 +- 8 files changed, 399 insertions(+), 91 deletions(-) diff --git a/lib/galaxy/webapps/tool_shed/buildapp.py b/lib/galaxy/webapps/tool_shed/buildapp.py index 4d6b348cde5..f3f12e15e9a 100644 --- a/lib/galaxy/webapps/tool_shed/buildapp.py +++ b/lib/galaxy/webapps/tool_shed/buildapp.py @@ -77,19 +77,19 @@ def app_factory( global_conf, **kwargs ): # Add the web API. # A good resource for RESTful services - http://routes.readthedocs.org/en/latest/restful.html webapp.add_api_controllers( 'galaxy.webapps.tool_shed.api', app ) webapp.mapper.resource( 'repository', - 'repositories', - controller='repositories', - collection={ 'get_repository_revision_install_info' : 'GET', - 'get_ordered_installable_revisions' : 'GET' }, - name_prefix='repository_', - path_prefix='/api', - parent_resources=dict( member_name='repository', collection_name='repositories' ) ) + 'repositories', + controller='repositories', + collection={ 'get_repository_revision_install_info' : 'GET', + 'get_ordered_installable_revisions' : 'GET' }, + name_prefix='repository_', + path_prefix='/api', + parent_resources=dict( member_name='repository', collection_name='repositories' ) ) webapp.mapper.resource( 'repository_revision', - 'repository_revisions', - controller='repository_revisions', - name_prefix='repository_revision_', - path_prefix='/api', - parent_resources=dict( member_name='repository_revision', collection_name='repository_revisions' ) ) + 'repository_revisions', + controller='repository_revisions', + name_prefix='repository_revision_', + path_prefix='/api', + parent_resources=dict( member_name='repository_revision', collection_name='repository_revisions' ) ) webapp.finalize_config() # Wrap the webapp in some useful middleware if kwargs.get( 'middleware', True ): diff --git a/lib/galaxy/webapps/tool_shed/controllers/repository.py b/lib/galaxy/webapps/tool_shed/controllers/repository.py index f4e098fe5af..bb526421b43 100644 --- a/lib/galaxy/webapps/tool_shed/controllers/repository.py +++ b/lib/galaxy/webapps/tool_shed/controllers/repository.py @@ -59,6 +59,9 @@ class RepositoryController( BaseUIController, common_util.ItemRatings ): install_matched_repository_grid = repository_grids.InstallMatchedRepositoryGrid() matched_repository_grid = repository_grids.MatchedRepositoryGrid() my_writable_repositories_grid = repository_grids.MyWritableRepositoriesGrid() + my_writable_repositories_missing_tool_test_components_grid = repository_grids.MyWritableRepositoriesMissingToolTestComponentsGrid() + my_writable_repositories_with_failing_tool_tests_grid = repository_grids.MyWritableRepositoriesWithFailingToolTestsGrid() + my_writable_repositories_with_no_failing_tool_tests_grid = repository_grids.MyWritableRepositoriesWithNoFailingToolTestsGrid() repositories_by_user_grid = repository_grids.RepositoriesByUserGrid() repositories_i_own_grid = repository_grids.RepositoriesIOwnGrid() repositories_in_category_grid = repository_grids.RepositoriesInCategoryGrid() @@ -158,6 +161,128 @@ class RepositoryController( BaseUIController, common_util.ItemRatings ): message=message, status=status ) + @web.expose + def browse_my_writable_repositories( self, trans, **kwd ): + if 'operation' in kwd: + operation = kwd[ 'operation' ].lower() + if operation == "view_or_manage_repository": + return trans.response.send_redirect( web.url_for( controller='repository', + action='view_or_manage_repository', + **kwd ) ) + elif operation == "repositories_by_user": + return trans.response.send_redirect( web.url_for( controller='repository', + action='browse_repositories_by_user', + **kwd ) ) + elif operation in [ 'mark as deprecated', 'mark as not deprecated' ]: + kwd[ 'mark_deprecated' ] = operation == 'mark as deprecated' + return trans.response.send_redirect( web.url_for( controller='repository', + action='deprecate', + **kwd ) ) + selected_changeset_revision, repository = self.__get_repository_from_refresh_on_change( trans, **kwd ) + if repository: + return trans.response.send_redirect( web.url_for( controller='repository', + action='browse_repositories', + operation='view_or_manage_repository', + id=trans.security.encode_id( repository.id ), + changeset_revision=selected_changeset_revision ) ) + return self.my_writable_repositories_grid( trans, **kwd ) + + @web.expose + def browse_my_writable_repositories_with_failing_tool_tests( self, trans, **kwd ): + if 'operation' in kwd: + operation = kwd[ 'operation' ].lower() + if operation == "view_or_manage_repository": + return trans.response.send_redirect( web.url_for( controller='repository', + action='view_or_manage_repository', + **kwd ) ) + elif operation == "repositories_by_user": + return trans.response.send_redirect( web.url_for( controller='repository', + action='browse_repositories_by_user', + **kwd ) ) + elif operation in [ 'mark as deprecated', 'mark as not deprecated' ]: + kwd[ 'mark_deprecated' ] = operation == 'mark as deprecated' + return trans.response.send_redirect( web.url_for( controller='repository', + action='deprecate', + **kwd ) ) + if 'message' not in kwd: + message = 'This list contains repositories that match the following criteria:
    ' + message += '
      ' + message += '
    • you are authorized to update them
    • ' + message += '
    • the latest installable revision contains at least 1 tool
    • ' + message += '
    • the latest installable revision is not missing any tool test components
    • ' + message += '
    • the latest installable revision has at least 1 tool test that fails
    • ' + message += '
    ' + kwd[ 'message' ] = message + kwd[ 'status' ] = 'warning' + return self.my_writable_repositories_with_failing_tool_tests_grid( trans, **kwd ) + + @web.expose + def browse_my_writable_repositories_with_no_failing_tool_tests( self, trans, **kwd ): + if 'operation' in kwd: + operation = kwd[ 'operation' ].lower() + if operation == "view_or_manage_repository": + return trans.response.send_redirect( web.url_for( controller='repository', + action='view_or_manage_repository', + **kwd ) ) + elif operation == "repositories_by_user": + return trans.response.send_redirect( web.url_for( controller='repository', + action='browse_repositories_by_user', + **kwd ) ) + elif operation in [ 'mark as deprecated', 'mark as not deprecated' ]: + kwd[ 'mark_deprecated' ] = operation == 'mark as deprecated' + return trans.response.send_redirect( web.url_for( controller='repository', + action='deprecate', + **kwd ) ) + if 'message' not in kwd: + message = 'This list contains repositories that match the following criteria:
    ' + message += '
      ' + message += '
    • you are authorized to update them
    • ' + message += '
    • the latest installable revision contains at least 1 tool
    • ' + message += '
    • the latest installable revision is not missing any tool test components
    • ' + message += '
    • the latest installable revision has no tool tests that fail
    • ' + message += '
    ' + kwd[ 'message' ] = message + kwd[ 'status' ] = 'warning' + return self.my_writable_repositories_with_no_failing_tool_tests_grid( trans, **kwd ) + + @web.expose + def browse_my_writable_repositories_missing_tool_test_components( self, trans, **kwd ): + if 'operation' in kwd: + operation = kwd[ 'operation' ].lower() + if operation == "view_or_manage_repository": + return trans.response.send_redirect( web.url_for( controller='repository', + action='view_or_manage_repository', + **kwd ) ) + elif operation == "repositories_by_user": + return trans.response.send_redirect( web.url_for( controller='repository', + action='browse_repositories_by_user', + **kwd ) ) + elif operation in [ 'mark as deprecated', 'mark as not deprecated' ]: + kwd[ 'mark_deprecated' ] = operation == 'mark as deprecated' + return trans.response.send_redirect( web.url_for( controller='repository', + action='deprecate', + **kwd ) ) + if 'message' not in kwd: + message = 'This list contains repositories that match the following criteria:
    ' + message += '
      ' + message += '
    • you are authorized to update them
    • ' + message += '
    • the latest installable revision contains at least 1 tool with no defined tests OR:
    • ' + message += '
    • the latest installable revision contains at least 1 tool with a test that requires a missing test data file
    • ' + message += '
    ' + kwd[ 'message' ] = message + kwd[ 'status' ] = 'warning' + return self.my_writable_repositories_missing_tool_test_components_grid( trans, **kwd ) + + @web.expose + def browse_deprecated_repositories_i_own( self, trans, **kwd ): + if 'operation' in kwd: + operation = kwd[ 'operation' ].lower() + if operation == "view_or_manage_repository": + return trans.response.send_redirect( web.url_for( controller='repository', + action='view_or_manage_repository', + **kwd ) ) + return self.deprecated_repositories_i_own_grid( trans, **kwd ) + @web.expose def browse_repositories( self, trans, **kwd ): # We add params to the keyword dict in this method in order to rename the param with an "f-" prefix, simulating filtering by clicking a search @@ -176,32 +301,9 @@ class RepositoryController( BaseUIController, common_util.ItemRatings ): return trans.response.send_redirect( web.url_for( controller='repository', action='browse_repositories_by_user', **kwd ) ) - elif operation == "repositories_i_own": - # Eliminate the current filters if any exist. - for k, v in kwd.items(): - if k.startswith( 'f-' ): - del kwd[ k ] - return self.repositories_i_own_grid( trans, **kwd ) - elif operation == "deprecated_repositories_i_own": - # Eliminate the current filters if any exist. - for k, v in kwd.items(): - if k.startswith( 'f-' ): - del kwd[ k ] - return self.deprecated_repositories_i_own_grid( trans, **kwd ) - elif operation in [ 'mark as deprecated', 'mark as not deprecated' ]: - # Eliminate the current filters if any exist. - for k, v in kwd.items(): - if k.startswith( 'f-' ): - del kwd[ k ] - kwd[ 'mark_deprecated' ] = operation == 'mark as deprecated' - return trans.response.send_redirect( web.url_for( controller='repository', - action='deprecate', - **kwd ) ) elif operation == "reviewed_repositories_i_own": return trans.response.send_redirect( web.url_for( controller='repository_review', action='reviewed_repositories_i_own' ) ) - elif operation == "my_writable_repositories": - return self.my_writable_repositories_grid( trans, **kwd ) elif operation == "repositories_by_category": category_id = kwd.get( 'id', None ) message = kwd.get( 'message', '' ) @@ -264,6 +366,32 @@ class RepositoryController( BaseUIController, common_util.ItemRatings ): self.repositories_by_user_grid.title = "Repositories owned by %s" % user.username return self.repositories_by_user_grid( trans, **kwd ) + @web.expose + def browse_repositories_i_own( self, trans, **kwd ): + if 'operation' in kwd: + operation = kwd[ 'operation' ].lower() + if operation == "view_or_manage_repository": + return trans.response.send_redirect( web.url_for( controller='repository', + action='view_or_manage_repository', + **kwd ) ) + elif operation == "repositories_by_user": + return trans.response.send_redirect( web.url_for( controller='repository', + action='browse_repositories_by_user', + **kwd ) ) + elif operation in [ 'mark as deprecated', 'mark as not deprecated' ]: + kwd[ 'mark_deprecated' ] = operation == 'mark as deprecated' + return trans.response.send_redirect( web.url_for( controller='repository', + action='deprecate', + **kwd ) ) + selected_changeset_revision, repository = self.__get_repository_from_refresh_on_change( trans, **kwd ) + if repository: + return trans.response.send_redirect( web.url_for( controller='repository', + action='browse_repositories', + operation='view_or_manage_repository', + id=trans.security.encode_id( repository.id ), + changeset_revision=selected_changeset_revision ) ) + return self.repositories_i_own_grid( trans, **kwd ) + @web.expose def browse_repositories_in_category( self, trans, **kwd ): if 'operation' in kwd: @@ -1194,6 +1322,7 @@ class RepositoryController( BaseUIController, common_util.ItemRatings ): # so we loop through all of the received values to see which value is not the repository tip. If we find it, we know the # refresh_on_change occurred and we have the necessary repository id and change set revision to pass on. repository_id = None + v = None for k, v in kwd.items(): changeset_revision_str = 'changeset_revision_' if k.startswith( changeset_revision_str ): diff --git a/lib/tool_shed/galaxy_install/grids/admin_toolshed_grids.py b/lib/tool_shed/galaxy_install/grids/admin_toolshed_grids.py index aab3c0f5f5a..dcd38b0cdf0 100644 --- a/lib/tool_shed/galaxy_install/grids/admin_toolshed_grids.py +++ b/lib/tool_shed/galaxy_install/grids/admin_toolshed_grids.py @@ -146,7 +146,7 @@ class InstalledRepositoryGrid( grids.Grid ): default_filter = dict( deleted="False" ) num_rows_per_page = 50 preserve_state = False - use_paging = True + use_paging = False def build_initial_query( self, trans, **kwd ): return trans.sa_session.query( self.model_class ) diff --git a/lib/tool_shed/grids/repository_grids.py b/lib/tool_shed/grids/repository_grids.py index 7f6865423f1..d179ef15fb2 100644 --- a/lib/tool_shed/grids/repository_grids.py +++ b/lib/tool_shed/grids/repository_grids.py @@ -133,6 +133,19 @@ class RepositoryGrid( grids.Grid ): return '' + class LatestInstallableRevisionColumn( grids.GridColumn ): + + def __init__( self, col_name ): + grids.GridColumn.__init__( self, col_name ) + + def get_value( self, trans, grid, repository ): + """Display the latest installable revision label (may not be the repository tip).""" + select_field = grids_util.build_changeset_revision_select_field( trans, repository, downloadable=False ) + if select_field.options: + return select_field.options[ 0 ][ 0 ] + return '' + + class TipRevisionColumn( grids.GridColumn ): def __init__( self, col_name ): @@ -258,15 +271,12 @@ class RepositoryGrid( grids.Grid ): key="free-text-search", visible=False, filterable="standard" ) ) - operations = [ grids.GridOperation( "Receive email alerts", - allow_multiple=False, - condition=( lambda item: not item.deleted ), - async_compatible=False ) ] + operations = [] standard_filters = [] default_filter = dict( deleted="False" ) num_rows_per_page = 50 preserve_state = False - use_paging = True + use_paging = False def build_initial_query( self, trans, **kwd ): return trans.sa_session.query( model.Repository ) \ @@ -306,10 +316,7 @@ class RepositoriesInCategoryGrid( RepositoryGrid ): key="free-text-search", visible=False, filterable="standard" ) ) - operations = [ grids.GridOperation( "Receive email alerts", - allow_multiple=False, - condition=( lambda item: not item.deleted ), - async_compatible=False ) ] + operations = [] use_paging = False def build_initial_query( self, trans, **kwd ): @@ -376,10 +383,6 @@ class RepositoriesIOwnGrid( RepositoryGrid ): attach_popup=True ), RepositoryGrid.MetadataRevisionColumn( "Metadata Revisions" ), RepositoryGrid.ToolsFunctionallyCorrectColumn( "Tools Verified" ), - RepositoryGrid.CategoryColumn( "Category", - model_class=model.Category, - key="Category.name", - attach_popup=False ), RepositoryGrid.DeprecatedColumn( "Deprecated" ) ] columns.append( grids.MulticolFilterColumn( "Search repository name", @@ -387,15 +390,7 @@ class RepositoriesIOwnGrid( RepositoryGrid ): key="free-text-search", visible=False, filterable="standard" ) ) - operations = [ grids.GridOperation( "Mark as deprecated", - allow_multiple=False, - condition=( lambda item: not item.deleted and not item.deprecated ), - async_compatible=False, - confirm="Are you sure that you want to deprecate this repository?" ), - grids.GridOperation( "Mark as not deprecated", - allow_multiple=False, - condition=( lambda item: not item.deleted and item.deprecated ), - async_compatible=False ) ] + operations = [] use_paging = False def build_initial_query( self, trans, **kwd ): @@ -459,10 +454,7 @@ class EmailAlertsRepositoryGrid( RepositoryGrid ): visible=False, filterable="advanced" ) ] - operations = [ grids.GridOperation( "Receive email alerts", - allow_multiple=True, - condition=( lambda item: not item.deleted ), - async_compatible=False ) ] + operations = [] global_actions = [ grids.GridAction( "User preferences", dict( controller='user', action='index', cntrller='repository' ) ) ] @@ -470,6 +462,7 @@ class EmailAlertsRepositoryGrid( RepositoryGrid ): class MyWritableRepositoriesGrid( RepositoryGrid ): # This grid filters out repositories that have been marked as either deprecated or deleted. + title = 'Repositories that I can change' columns = [ RepositoryGrid.NameColumn( "Name", key="name", @@ -481,27 +474,14 @@ class MyWritableRepositoriesGrid( RepositoryGrid ): model_class=model.User, link=( lambda item: dict( operation="repositories_by_user", id=item.id ) ), attach_popup=False, - key="User.username" ), - RepositoryGrid.EmailAlertsColumn( "Alert", attach_popup=False ), - # Columns that are valid for filtering but are not visible. - RepositoryGrid.EmailColumn( "Email", - model_class=model.User, - key="email", - visible=False ), - RepositoryGrid.RepositoryCategoryColumn( "Category", - model_class=model.Category, - key="Category.name", - visible=False ) + key="User.username" ) ] columns.append( grids.MulticolFilterColumn( "Search repository name", - cols_to_filter=[ columns[0] ], + cols_to_filter=[ columns[ 0 ] ], key="free-text-search", visible=False, filterable="standard" ) ) - operations = [ grids.GridOperation( "Receive email alerts", - allow_multiple=False, - condition=( lambda item: not item.deleted ), - async_compatible=False ) ] + operations = [] use_paging = False def build_initial_query( self, trans, **kwd ): @@ -519,9 +499,171 @@ class MyWritableRepositoriesGrid( RepositoryGrid ): if clause_list: return trans.sa_session.query( model.Repository ) \ .filter( or_( *clause_list ) ) \ - .join( model.User.table ) \ - .outerjoin( model.RepositoryCategoryAssociation.table ) \ - .outerjoin( model.Category.table ) + .join( model.User.table ) + # Return an empty query. + return trans.sa_session.query( model.Repository ) \ + .filter( model.Repository.table.c.id < 0 ) + + +class MyWritableRepositoriesMissingToolTestComponentsGrid( MyWritableRepositoriesGrid ): + title = "Repositories that I can change with missing tool test components" + columns = [ + RepositoriesIOwnGrid.NameColumn( "Name", + key="name", + link=( lambda item: dict( operation="view_or_manage_repository", id=item.id ) ), + attach_popup=True ), + RepositoryGrid.LatestInstallableRevisionColumn( "Latest Installable Revision" ), + RepositoryGrid.UserColumn( "Owner", + model_class=model.User, + link=( lambda item: dict( operation="repositories_by_user", id=item.id ) ), + attach_popup=False, + key="User.username" ) + ] + columns.append( grids.MulticolFilterColumn( "Search repository name", + cols_to_filter=[ columns[0] ], + key="free-text-search", + visible=False, + filterable="standard" ) ) + operations = [] + use_paging = False + + def build_initial_query( self, trans, **kwd ): + # First get all repositories that the current user is authorized to update. + username = trans.user.username + user_clause_list = [] + for repository in trans.sa_session.query( model.Repository ) \ + .filter( and_( model.Repository.table.c.deprecated == False, + model.Repository.table.c.deleted == False ) ): + allow_push = repository.allow_push( trans.app ) + if allow_push: + allow_push_usernames = allow_push.split( ',' ) + if username in allow_push_usernames: + user_clause_list.append( model.Repository.table.c.id == repository.id ) + if user_clause_list: + # We have the list of repositories that the current user is authorized to update, so filter further by latest installable revisions that contain + # tools with missing tool test components. + revision_clause_list = [] + for repository in trans.sa_session.query( model.Repository ) \ + .filter( or_( *user_clause_list ) ): + changeset_revision = suc.filter_by_latest_downloadable_changeset_revision_that_has_missing_tool_test_components( trans, repository ) + if changeset_revision not in [ None, suc.INITIAL_CHANGELOG_HASH ]: + revision_clause_list.append( model.RepositoryMetadata.table.c.changeset_revision == changeset_revision ) + if revision_clause_list: + return trans.sa_session.query( model.Repository ) \ + .join( model.User.table ) \ + .filter( or_( *user_clause_list ) ) \ + .join( model.RepositoryMetadata ) \ + .filter( or_( *revision_clause_list ) ) + # Return an empty query. + return trans.sa_session.query( model.Repository ) \ + .filter( model.Repository.table.c.id < 0 ) + + +class MyWritableRepositoriesWithFailingToolTestsGrid( MyWritableRepositoriesMissingToolTestComponentsGrid ): + title = "Repositories that I can change with failing tool tests" + columns = [ + RepositoriesIOwnGrid.NameColumn( "Name", + key="name", + link=( lambda item: dict( operation="view_or_manage_repository", id=item.id ) ), + attach_popup=True ), + RepositoryGrid.LatestInstallableRevisionColumn( "Latest Installable Revision" ), + RepositoryGrid.UserColumn( "Owner", + model_class=model.User, + link=( lambda item: dict( operation="repositories_by_user", id=item.id ) ), + attach_popup=False, + key="User.username" ) + ] + columns.append( grids.MulticolFilterColumn( "Search repository name", + cols_to_filter=[ columns[0] ], + key="free-text-search", + visible=False, + filterable="standard" ) ) + operations = [] + use_paging = False + + def build_initial_query( self, trans, **kwd ): + # First get all repositories that the current user is authorized to update. + username = trans.user.username + user_clause_list = [] + for repository in trans.sa_session.query( model.Repository ) \ + .filter( and_( model.Repository.table.c.deprecated == False, + model.Repository.table.c.deleted == False ) ): + allow_push = repository.allow_push( trans.app ) + if allow_push: + allow_push_usernames = allow_push.split( ',' ) + if username in allow_push_usernames: + user_clause_list.append( model.Repository.table.c.id == repository.id ) + if user_clause_list: + # We have the list of repositories that the current user is authorized to update, so filter further by latest installable revisions that contain + # tools with missing tool test components. + revision_clause_list = [] + for repository in trans.sa_session.query( model.Repository ) \ + .filter( or_( *user_clause_list ) ): + changeset_revision = suc.filter_by_latest_downloadable_changeset_revision_that_has_missing_tool_test_components( trans, repository ) + if changeset_revision not in [ None, suc.INITIAL_CHANGELOG_HASH ]: + revision_clause_list.append( model.RepositoryMetadata.table.c.changeset_revision == changeset_revision ) + if revision_clause_list: + return trans.sa_session.query( model.Repository ) \ + .join( model.User.table ) \ + .filter( or_( *user_clause_list ) ) \ + .join( model.RepositoryMetadata ) \ + .filter( or_( *revision_clause_list ) ) \ + .filter( model.RepositoryMetadata.table.c.tools_functionally_correct == False ) + # Return an empty query. + return trans.sa_session.query( model.Repository ) \ + .filter( model.Repository.table.c.id < 0 ) + + +class MyWritableRepositoriesWithNoFailingToolTestsGrid( MyWritableRepositoriesMissingToolTestComponentsGrid ): + title = "Repositories that I can change with failing tool tests" + columns = [ + RepositoriesIOwnGrid.NameColumn( "Name", + key="name", + link=( lambda item: dict( operation="view_or_manage_repository", id=item.id ) ), + attach_popup=True ), + RepositoryGrid.LatestInstallableRevisionColumn( "Latest Installable Revision" ), + RepositoryGrid.UserColumn( "Owner", + model_class=model.User, + link=( lambda item: dict( operation="repositories_by_user", id=item.id ) ), + attach_popup=False, + key="User.username" ) + ] + columns.append( grids.MulticolFilterColumn( "Search repository name", + cols_to_filter=[ columns[0] ], + key="free-text-search", + visible=False, + filterable="standard" ) ) + operations = [] + use_paging = False + + def build_initial_query( self, trans, **kwd ): + # First get all repositories that the current user is authorized to update. + username = trans.user.username + user_clause_list = [] + for repository in trans.sa_session.query( model.Repository ) \ + .filter( and_( model.Repository.table.c.deprecated == False, + model.Repository.table.c.deleted == False ) ): + allow_push = repository.allow_push( trans.app ) + if allow_push: + allow_push_usernames = allow_push.split( ',' ) + if username in allow_push_usernames: + user_clause_list.append( model.Repository.table.c.id == repository.id ) + if user_clause_list: + # We have the list of repositories that the current user is authorized to update, so filter further by latest installable revisions that contain + # tools with missing tool test components. + revision_clause_list = [] + for repository in trans.sa_session.query( model.Repository ) \ + .filter( or_( *user_clause_list ) ): + changeset_revision = suc.filter_by_latest_downloadable_changeset_revision_that_has_missing_tool_test_components( trans, repository ) + if changeset_revision not in [ None, suc.INITIAL_CHANGELOG_HASH ]: + revision_clause_list.append( model.RepositoryMetadata.table.c.changeset_revision == changeset_revision ) + if revision_clause_list: + return trans.sa_session.query( model.Repository ) \ + .join( model.User.table ) \ + .filter( or_( *user_clause_list ) ) \ + .join( model.RepositoryMetadata ) \ + .filter( or_( *revision_clause_list ) ) \ + .filter( model.RepositoryMetadata.table.c.tools_functionally_correct == True ) # Return an empty query. return trans.sa_session.query( model.Repository ) \ .filter( model.Repository.table.c.id < 0 ) diff --git a/lib/tool_shed/grids/repository_review_grids.py b/lib/tool_shed/grids/repository_review_grids.py index 52567b61ac6..7870eb5c324 100644 --- a/lib/tool_shed/grids/repository_review_grids.py +++ b/lib/tool_shed/grids/repository_review_grids.py @@ -1,14 +1,19 @@ -import os, logging +import logging +import os from galaxy.webapps.tool_shed import model from galaxy.web.framework.helpers import grids -from galaxy.model.orm import and_, or_ +from galaxy.model.orm import and_ +from galaxy.model.orm import or_ from tool_shed.grids.repository_grids import RepositoryGrid import tool_shed.util.shed_util_common as suc from tool_shed.util import metadata_util from galaxy import eggs eggs.require('mercurial') -from mercurial import hg, ui, patch, commands +from mercurial import commands +from mercurial import hg +from mercurial import patch +from mercurial import ui log = logging.getLogger( __name__ ) @@ -49,7 +54,7 @@ class ComponentGrid( grids.Grid ): standard_filters = [] num_rows_per_page = 50 preserve_state = False - use_paging = True + use_paging = False class RepositoriesWithReviewsGrid( RepositoryGrid ): # This grid filters out repositories that have been marked as either deprecated or deleted. @@ -351,7 +356,7 @@ class RepositoryReviewsByUserGrid( grids.Grid ): standard_filters = [] num_rows_per_page = 50 preserve_state = False - use_paging = True + use_paging = False def build_initial_query( self, trans, **kwd ): user_id = trans.security.decode_id( kwd[ 'id' ] ) diff --git a/lib/tool_shed/util/shed_util_common.py b/lib/tool_shed/util/shed_util_common.py index e7848353fcc..29590b61668 100644 --- a/lib/tool_shed/util/shed_util_common.py +++ b/lib/tool_shed/util/shed_util_common.py @@ -255,6 +255,29 @@ def create_or_update_tool_shed_repository( app, name, description, installed_cha sa_session.flush() return tool_shed_repository +def filter_by_latest_downloadable_changeset_revision_that_has_missing_tool_test_components( trans, repository ): + """ + Inspect the latest installable changeset revision for the received repository to see if it includes tool that are either missing functional tests + or functional test data. If the changset revision includes tools, but is missing tool test components, return the changeset revision hash. Otherwise + return the INITIAL_CHANGELOG_HASH. + """ + print_debug = repository.name == 'convert_chars' + encoded_repository_id = trans.security.encode_id( repository.id ) + repo = hg.repository( get_configured_ui(), repository.repo_path( trans.app ) ) + tip_ctx = str( repo.changectx( repo.changelog.tip() ) ) + repository_metadata = None + try: + repository_metadata = get_repository_metadata_by_changeset_revision( trans, encoded_repository_id, tip_ctx ) + except: + latest_installable_revision = get_previous_downloadable_changeset_revision( repository, repo, tip_ctx ) + if latest_installable_revision != INITIAL_CHANGELOG_HASH: + repository_metadata = get_repository_metadata_by_changeset_revision( trans, encoded_repository_id, latest_installable_revision ) + else: + repository_metadata = None + if repository_metadata and repository_metadata.downloadable and repository_metadata.missing_test_components: + return repository_metadata.changeset_revision + return INITIAL_CHANGELOG_HASH + def generate_clone_url_for_installed_repository( app, repository ): """Generate the URL for cloning a repository that has been installed into a Galaxy instance.""" tool_shed_url = get_url_from_tool_shed( app, repository.tool_shed ) diff --git a/templates/webapps/tool_shed/index.mako b/templates/webapps/tool_shed/index.mako index 1900c947198..97bf15a7589 100644 --- a/templates/webapps/tool_shed/index.mako +++ b/templates/webapps/tool_shed/index.mako @@ -94,10 +94,10 @@ %if trans.user:
    - My Repositories and Tools + Repositories I Can Change
    %if has_reviewed_repositories:
    @@ -106,11 +106,20 @@ %endif %if has_deprecated_repositories: %endif + + +
    My invalid tools diff --git a/templates/webapps/tool_shed/repository/manage_repository.mako b/templates/webapps/tool_shed/repository/manage_repository.mako index b2ea406c9c4..e43ae00fb51 100644 --- a/templates/webapps/tool_shed/repository/manage_repository.mako +++ b/templates/webapps/tool_shed/repository/manage_repository.mako @@ -100,7 +100,7 @@ Reset all repository metadata %endif %if can_deprecate: - Mark repository as deprecated + Mark repository as deprecated %endif %if can_undeprecate: Mark repository as not deprecated From 0aa8e6149052349f6a5029cd75b47545f4753cbf Mon Sep 17 00:00:00 2001 From: Jeremy Goecks Date: Tue, 30 Apr 2013 09:12:18 -0400 Subject: [PATCH 252/523] Trackster: refactoring to use dataset attributes rather than .dataset_id and .hda_ldda --- static/scripts/viz/trackster/tracks.js | 40 ++++++++++++-------------- 1 file changed, 18 insertions(+), 22 deletions(-) diff --git a/static/scripts/viz/trackster/tracks.js b/static/scripts/viz/trackster/tracks.js index a514f84a174..0882699d369 100644 --- a/static/scripts/viz/trackster/tracks.js +++ b/static/scripts/viz/trackster/tracks.js @@ -1534,7 +1534,7 @@ extend( TracksterView.prototype, DrawableCollection.prototype, { if (this.overview_drawable) { // If drawable to be set as overview is already in overview, do nothing. // Otherwise, remove overview. - if (this.overview_drawable.dataset_id === drawable.dataset_id) { + if (this.overview_drawable.dataset.id === drawable.dataset.id) { return; } this.overview_viewport.find(".track").remove(); @@ -1712,7 +1712,7 @@ extend(Tool.prototype, { tool.run( // URL params. { - target_dataset_id: this.track.dataset_id, + target_dataset_id: this.track.dataset.id, action: 'rerun', tool_id: tool.id }, @@ -1741,7 +1741,7 @@ extend(Tool.prototype, { }), url_params = { - target_dataset_id: this.track.dataset_id, + target_dataset_id: this.track.dataset.id, action: 'rerun', tool_id: this.id, regions: [ @@ -2249,8 +2249,6 @@ var Track = function(view, container, obj_dict) { // Attribute init. // this.dataset = new data.Dataset(obj_dict.dataset); - this.dataset_id = this.dataset.get('id'); - this.hda_ldda = this.dataset.get('hda_ldda'); this.dataset_check_type = 'converted_datasets_state'; this.data_url_extra_params = {}; this.data_query_wait = ('data_query_wait' in obj_dict ? obj_dict.data_query_wait : DEFAULT_DATA_QUERY_WAIT); @@ -2395,8 +2393,8 @@ extend(Track.prototype, Drawable.prototype, { window.location.href = galaxy_paths.get('sweepster_url') + "?" + $.param({ - dataset_id: track.dataset_id, - hda_ldda: track.hda_ldda, + dataset_id: track.dataset.id, + hda_ldda: track.dataset.get('hda_ldda'), regions: JSON.stringify(new Backbone.Collection(regions).toJSON()) }); }, @@ -2420,7 +2418,7 @@ extend(Track.prototype, Drawable.prototype, { ], can_draw: function() { - if ( this.dataset_id && Drawable.prototype.can_draw.call(this) ) { + if ( this.dataset.id && Drawable.prototype.can_draw.call(this) ) { return true; } return false; @@ -2444,8 +2442,6 @@ extend(Track.prototype, Drawable.prototype, { set_dataset: function(dataset) { this.dataset = dataset; this.data_manager.set('dataset', dataset); - this.dataset_id = dataset.get('id'); - this.hda_ldda = dataset.get('hda_ldda'); }, /** @@ -2601,7 +2597,7 @@ extend(Track.prototype, Drawable.prototype, { // Tracks with no dataset id are handled differently. // FIXME: is this really necessary? // - if (!track.dataset_id) { + if (!track.dataset.id) { return; } @@ -2609,7 +2605,7 @@ extend(Track.prototype, Drawable.prototype, { // about track status. var init_deferred = $.Deferred(), params = { - hda_ldda: track.hda_ldda, + hda_ldda: track.dataset.get('hda_ldda'), data_type: this.dataset_check_type, chrom: track.view.chrom, retry: retry @@ -2771,8 +2767,8 @@ extend(TiledTrack.prototype, Drawable.prototype, Track.prototype, { return { "track_type": this.get_type(), "name": this.name, - "hda_ldda": this.hda_ldda, - "dataset_id": this.dataset_id, + "hda_ldda": this.dataset.get('hda_ldda'), + "dataset_id": this.dataset.id, "prefs": this.prefs, "mode": this.mode, "filters": this.filters_manager.to_dict(), @@ -3200,7 +3196,7 @@ extend(TiledTrack.prototype, Drawable.prototype, Track.prototype, { // Reset data URL when dataset indexing has completed/when not pending. var ss_deferred = new util.ServerStateDeferred({ url: self.dataset_state_url, - url_params: {dataset_id : self.dataset_id, hda_ldda: self.hda_ldda}, + url_params: {dataset_id : self.dataset.id, hda_ldda: self.dataset.get('hda_ldda')}, interval: self.data_query_wait, // Set up deferred to check dataset state until it is not pending. success_fn: function(result) { return result !== "pending"; } @@ -3651,7 +3647,7 @@ extend(LineTrack.prototype, Drawable.prototype, TiledTrack.prototype, { */ set_min_value: function(new_val) { this.prefs.min_value = new_val; - $('#linetrack_' + this.dataset_id + '_minval').text(this.prefs.min_value); + $('#linetrack_' + this.dataset.id + '_minval').text(this.prefs.min_value); this.tile_cache.clear(); this.request_draw(); }, @@ -3661,7 +3657,7 @@ extend(LineTrack.prototype, Drawable.prototype, TiledTrack.prototype, { */ set_max_value: function(new_val) { this.prefs.max_value = new_val; - $('#linetrack_' + this.dataset_id + '_maxval').text(this.prefs.max_value); + $('#linetrack_' + this.dataset.id + '_maxval').text(this.prefs.max_value); this.tile_cache.clear(); this.request_draw(); }, @@ -3671,7 +3667,7 @@ extend(LineTrack.prototype, Drawable.prototype, TiledTrack.prototype, { track.vertical_range = undefined; return $.getJSON( track.dataset.url(), { data_type: 'data', stats: true, chrom: track.view.chrom, low: 0, - high: track.view.max_high, hda_ldda: track.hda_ldda }, function(result) { + high: track.view.max_high, hda_ldda: track.dataset.get('hda_ldda') }, function(result) { track.container_div.addClass( "line-track" ); var data = result.data; if ( isNaN(parseFloat(track.prefs.min_value)) || isNaN(parseFloat(track.prefs.max_value)) ) { @@ -3689,8 +3685,8 @@ extend(LineTrack.prototype, Drawable.prototype, TiledTrack.prototype, { // FIXME: we should probably only save this when the user explicately sets it // since we lose the ability to compute it on the fly (when changing // chromosomes for example). - $('#track_' + track.dataset_id + '_minval').val(track.prefs.min_value); - $('#track_' + track.dataset_id + '_maxval').val(track.prefs.max_value); + $('#track_' + track.dataset.id + '_minval').val(track.prefs.min_value); + $('#track_' + track.dataset.id + '_maxval').val(track.prefs.max_value); } track.vertical_range = track.prefs.max_value - track.prefs.min_value; track.total_frequency = data.total_frequency; @@ -3710,7 +3706,7 @@ extend(LineTrack.prototype, Drawable.prototype, TiledTrack.prototype, { } }, help_text: "Set min value" - }).addClass('yaxislabel bottom').attr("id", 'linetrack_' + track.dataset_id + '_minval') + }).addClass('yaxislabel bottom').attr("id", 'linetrack_' + track.dataset.id + '_minval') .prependTo(track.container_div), max_label = $("
    ").text(round(track.prefs.max_value, 3)).make_text_editable({ num_cols: 6, @@ -3722,7 +3718,7 @@ extend(LineTrack.prototype, Drawable.prototype, TiledTrack.prototype, { } }, help_text: "Set max value" - }).addClass('yaxislabel top').attr("id", 'linetrack_' + track.dataset_id + '_maxval') + }).addClass('yaxislabel top').attr("id", 'linetrack_' + track.dataset.id + '_maxval') .prependTo(track.container_div); }); }, From d9866b0351b855856079397d143661f6082440c2 Mon Sep 17 00:00:00 2001 From: Dannon Baker Date: Tue, 30 Apr 2013 09:32:19 -0400 Subject: [PATCH 253/523] History API: Import sanitize_html directly instead of using util reference --- lib/galaxy/webapps/galaxy/api/histories.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/galaxy/webapps/galaxy/api/histories.py b/lib/galaxy/webapps/galaxy/api/histories.py index e6428ef6e26..b8fffe2ad51 100644 --- a/lib/galaxy/webapps/galaxy/api/histories.py +++ b/lib/galaxy/webapps/galaxy/api/histories.py @@ -4,6 +4,7 @@ API operations on a history. from galaxy import web from galaxy.util import string_as_bool, restore_text +from galaxy.util.sanitize_html import sanitize_html from galaxy.web.base.controller import BaseAPIController, UsesHistoryMixin from galaxy.web import url_for from galaxy.model.orm import desc @@ -207,7 +208,7 @@ class HistoriesController( BaseAPIController, UsesHistoryMixin ): if key == 'name': if not ( isinstance( val, str ) or isinstance( val, unicode ) ): raise ValueError( 'name must be a string or unicode: %s' %( str( type( val ) ) ) ) - validated_payload[ 'name' ] = util.sanitize_html.sanitize_html( val, 'utf-8' ) + validated_payload[ 'name' ] = sanitize_html( val, 'utf-8' ) #TODO:?? if sanitized != val: log.warn( 'script kiddie' ) elif key == 'deleted': if not isinstance( val, bool ): @@ -220,11 +221,11 @@ class HistoriesController( BaseAPIController, UsesHistoryMixin ): elif key == 'genome_build': if not ( isinstance( val, str ) or isinstance( val, unicode ) ): raise ValueError( 'genome_build must be a string: %s' %( str( type( val ) ) ) ) - validated_payload[ 'genome_build' ] = util.sanitize_html.sanitize_html( val, 'utf-8' ) + validated_payload[ 'genome_build' ] = sanitize_html( val, 'utf-8' ) elif key == 'annotation': if not ( isinstance( val, str ) or isinstance( val, unicode ) ): raise ValueError( 'annotation must be a string or unicode: %s' %( str( type( val ) ) ) ) - validated_payload[ 'annotation' ] = util.sanitize_html.sanitize_html( val, 'utf-8' ) + validated_payload[ 'annotation' ] = sanitize_html( val, 'utf-8' ) elif key not in valid_but_uneditable_keys: raise AttributeError( 'unknown key: %s' %( str( key ) ) ) return validated_payload From 2d7fec6361e0a41d8e41ca1c7d1891ad6e4436bd Mon Sep 17 00:00:00 2001 From: Carl Eberhard Date: Tue, 30 Apr 2013 11:05:18 -0400 Subject: [PATCH 254/523] Browser tests: first draft at running a workflow from the API, remove screen-caps from tools, fix POST method in API interface --- test/casperjs/api-workflow-tests.js | 238 +++++++++++++++++++++++++--- test/casperjs/modules/api.js | 49 ++---- test/casperjs/modules/tools.js | 4 - 3 files changed, 230 insertions(+), 61 deletions(-) diff --git a/test/casperjs/api-workflow-tests.js b/test/casperjs/api-workflow-tests.js index 814042d12f1..f0225835782 100644 --- a/test/casperjs/api-workflow-tests.js +++ b/test/casperjs/api-workflow-tests.js @@ -43,59 +43,247 @@ function hasKeys( object, keysArray ){ return true; } -function compareObjs( obj1, where ){ - for( var key in where ){ - if( where.hasOwnProperty( key ) ){ - if( !obj1.hasOwnProperty( key ) ){ return false; } - if( obj1[ key ] !== where[ key ] ){ return false; } - } +function countKeys( object ){ + if( !utils.isObject( object ) ){ return 0; } + var count = 0; + for( var key in object ){ + if( object.hasOwnProperty( key ) ){ count += 1; } } - return true; -} - -function findObject( objectArray, where, start ){ - start = start || 0; - for( var i=start; i= 1, "index returned at least one job" ); + + this.test.comment( 'index should have returned an object matching the workflow uploaded' ); + var firstWorkflow = workflowIndex[0]; + this.test.assert( hasKeys( firstWorkflow, workflowSummaryKeys ), "index has the proper keys" ); + this.test.assert( this.api.isEncodedId( firstWorkflow.id ), + "id is of the proper form: " + firstWorkflow.id ); + this.test.assert( firstWorkflow.model_class === workflowModelClass, + "has the proper model_class: " + firstWorkflow.model_class ); + this.test.assert( firstWorkflow.name === workflowToUpload.name + ' ' + '(imported from API)', + "has the proper, modified name: " + firstWorkflow.name ); + this.test.assert( !firstWorkflow.published, + "workflow is un-published: " + firstWorkflow.published ); + this.test.assert( utils.isArray( firstWorkflow.tags ) && firstWorkflow.tags.length === 0, + "tag array is empty: " + this.jsonStr( firstWorkflow.tags ) ); + this.test.assert( firstWorkflow.url === '/' + utils.format( this.api.workflows.urlTpls.show, firstWorkflow.id ), + "url matches the show url: " + firstWorkflow.url ); - // need a way to import/create a workflow here for testing - if( workflowIndex.length <= 0 ){ - log.warn( 'No workflows available' ); - return; - } - this.test.assert( workflowIndex.length >= 1, 'Has at least one workflow' ); // ------------------------------------------------------------------------------------------- SHOW this.test.comment( 'show should get detailed data about the workflow with the given id' ); - var workflowShow = this.api.workflows.show( workflowIndex[0].id ); + var workflowShow = this.api.workflows.show( firstWorkflow.id ); this.debug( this.jsonStr( workflowShow ) ); + this.test.assert( utils.isObject( workflowShow ), "show returned an object" ); + this.test.assert( hasKeys( workflowShow, workflowDetailKeys ), "show has the proper keys" ); + this.test.assert( this.api.isEncodedId( workflowShow.id ), + "id is of the proper form: " + workflowShow.id ); + this.test.assert( workflowShow.model_class === workflowModelClass, + "has the proper model_class: " + workflowShow.model_class ); + this.test.assert( workflowShow.name === workflowToUpload.name + ' ' + '(imported from API)', + "has the proper, modified name: " + workflowShow.name ); + this.test.assert( !workflowShow.published, + "workflow is un-published: " + workflowShow.published ); + this.test.assert( utils.isArray( workflowShow.tags ) && workflowShow.tags.length === 0, + "tag array is empty: " + this.jsonStr( workflowShow.tags ) ); + this.test.assert( workflowShow.url === '/' + utils.format( this.api.workflows.urlTpls.show, workflowShow.id ), + "url matches the show url: " + workflowShow.url ); + + this.test.comment( 'inputs from show should be an object (and, in this case, empty)' ); + var inputs = workflowShow.inputs; + //this.debug( 'inputs:\n' + this.jsonStr( inputs ) ); + this.test.assert( utils.isObject( workflowShow.inputs ), "inputs is an object" ); + //for( var inputKey in inputs ){ + // if( inputs.hasOwnProperty( inputKey ) ){ + // } + //} + this.test.assert( countKeys( workflowShow.inputs ) === 0, "inputs is empty" ); + + this.test.comment( 'steps from show should be an object containing each tool defined as a step' ); + var steps = workflowShow.steps; + //this.debug( 'steps:\n' + this.jsonStr( steps ) ); + this.test.assert( utils.isObject( workflowShow.steps ), "steps is an object" ); + //! ids for steps (and the keys used) are un-encoded (and in strings) + for( var stepKey in steps ){ + if( steps.hasOwnProperty( stepKey ) ){ + // any way to match this up with the workflowToUpload? + + this.test.assert( utils.isString( stepKey ), "step key is a string: " + stepKey ); + var step = steps[ stepKey ]; + this.debug( 'step:\n' + this.jsonStr( step ) ); + this.test.assert( hasKeys( step, stepKeys ), "step has the proper keys" ); + + this.test.assert( utils.isNumber( step.id ), + "step id is a number: " + step.id ); + try { + this.test.assert( parseInt( stepKey, 10 ) === step.id, + "step id matches step key: " + step.id ); + } catch( err ){ + this.test.fail( 'couldnt parse stepKey: ' + stepKey + ',' + err ); + } + + this.test.assert( utils.isObject( step.input_steps ), "input_steps is an object" ); + if( countKeys( step.input_steps ) !== 0 ){ + this.test.assert( hasKeys( step.input_steps, [ 'input' ] ), "input_steps has the proper keys" ); + } + + this.test.assert( step.type === 'tool', + "step type is a tool: " + step.type ); + + // check for tools in this wf with the api + this.test.assert( utils.isString( step.tool_id ), + "step tool_id is a string: " + step.tool_id ); + var tool_used = this.api.tools.show( step.tool_id ); + //this.debug( this.jsonStr( tool_used ) ) + this.test.assert( countKeys( step.input_steps ) !== 0, "found tool in api.tools for: " + step.tool_id ); + + // trace the path through input_steps, source_steps + } + } - // ------------------------------------------------------------------------------------------- CREATE // ------------------------------------------------------------------------------------------- MISC }); +// now run the uploaded workflow +spaceghost.tools.uploadFile( simpleBedFilepath, function( uploadInfo ){ + uploadedFile = uploadInfo; +}); +spaceghost.then( function(){ + var currentHistory = this.api.histories.index()[0], + firstWorkflow = this.api.workflows.show( this.api.workflows.index()[0].id ); + + //this.debug( this.jsonStr( uploadedFile ) ); + var uploadedFileId = uploadedFile.hdaElement.attributes.id.split( '-' )[1]; + this.debug( this.jsonStr( uploadedFileId ) ); + this.debug( this.jsonStr( this.api.hdas.show( currentHistory.id, uploadedFileId ) ) ); + + //this.debug( this.jsonStr( firstWorkflow ) ); + // find the input step by looking for a step where input_steps == {} + var input_step = null; + for( var stepKey in firstWorkflow.steps ){ + if( firstWorkflow.steps.hasOwnProperty( stepKey ) ){ + var step = firstWorkflow.steps[ stepKey ]; + if( countKeys( step.input_steps ) === 0 ){ + input_step = stepKey; + this.debug( 'input step: ' + this.jsonStr( step ) ) + break; + } + } + } + + // ------------------------------------------------------------------------------------------- CREATE + this.test.comment( 'create should allow running an existing workflow' ); + // needs workflow_id, history, ds_map + var executionData = { + workflow_id : firstWorkflow.id, + history : 'hist_id=' + currentHistory.id, + ds_map : {} + }; + executionData.ds_map[ input_step ] = { + src: 'hda', + id: uploadedFileId + }; + var returned = this.api.workflows.create( executionData ); + this.debug( this.jsonStr( returned ) ); + this.test.assert( utils.isObject( returned ), "create returned an object" ); + this.test.assert( hasKeys( returned, workflowCreateKeys ), "create returned the proper keys" ); + this.test.assert( this.api.isEncodedId( returned.history ), + "history id is proper: " + returned.history ); + this.test.assert( utils.isArray( returned.outputs ), + "create.outputs is an array: length " + returned.outputs.length ); + this.test.assert( returned.outputs.length >= 1, "there is at least one output" ); + for( var i=0; i ajax.data -// payload = this.api.ensureObject( payload ); -// return this.api._ajax( utils.format( this.urlTpls.create ), { -// type : 'POST', -// data : payload -// }); -//}; -// -//WorkflowsAPI.prototype.update = function create( id, payload ){ -// this.api.spaceghost.info( 'workflows.update: ' + [ id, this.api.spaceghost.jsonStr( payload ) ] ); -// -// // py.payload <-> ajax.data -// historyId = this.api.ensureId( historyId ); -// id = this.api.ensureId( id ); -// payload = this.api.ensureObject( payload ); -// url = utils.format( this.urlTpls.update, id ); -// -// return this.api._ajax( url, { -// type : 'PUT', -// data : payload -// }); -//}; +WorkflowsAPI.prototype.create = function create( payload ){ + this.api.spaceghost.info( 'workflows.create: ' + [ this.api.spaceghost.jsonStr( payload ) ] ); -WorkflowsAPI.prototype.upload = function upload( filepath ){ - this.api.spaceghost.info( 'workflows.show: ' + [ id ] ); - var data = {}; + // py.payload <-> ajax.data + payload = this.api.ensureObject( payload ); + return this.api._ajax( utils.format( this.urlTpls.create ), { + type : 'POST', + data : payload + }); +}; - id = ( id === 'most_recently_used' )?( id ):( this.api.ensureId( id ) ); - return this.api._ajax( utils.format( this.urlTpls.show, this.api.ensureId( id ) ), { - data : data +WorkflowsAPI.prototype.upload = function create( workflowJSON ){ + this.api.spaceghost.info( 'workflows.upload: ' + [ this.api.spaceghost.jsonStr( workflowJSON ) ] ); + + return this.api._ajax( utils.format( this.urlTpls.upload ), { + type : 'POST', + data : { 'workflow': this.api.ensureObject( workflowJSON ) } }); }; diff --git a/test/casperjs/modules/tools.js b/test/casperjs/modules/tools.js index 19d3b1327ac..e1f94fa3675 100644 --- a/test/casperjs/modules/tools.js +++ b/test/casperjs/modules/tools.js @@ -118,7 +118,6 @@ Tools.prototype._uploadFile = function _uploadFile( filepath ){ }); }, function timeoutWaitingForUploadRefreshes( urlsStillWaitingOn ){ - this.capture( 'upload-error.png' ) throw new this.GalaxyError( 'Upload Error: ' + 'timeout waiting for upload "' + filepath + '" refreshes: ' + urlsStillWaitingOn ); }, @@ -178,7 +177,6 @@ Tools.prototype.uploadFile = function uploadFile( filepath, callback, timeoutAft // capture any other messages on the page var otherInfo = spaceghost.elementInfoOrNull( this.data.selectors.messages.all ), message = ( otherInfo && otherInfo.text )?( otherInfo.text ):( '' ); - this.capture( 'upload-error.png' ) throw new this.GalaxyError( 'Upload Error: no success message uploading "' + filepath + '": ' + message ); } }); @@ -191,7 +189,6 @@ Tools.prototype.uploadFile = function uploadFile( filepath, callback, timeoutAft if( hdaElement === null ){ var hdaContainer = this.historypanel.data.selectors.hdaContainer; this.warning( 'Upload Error: ' + hdaContainer + ':\n' + this.getHTML( hdaContainer ) ); - this.capture( 'upload-error.png' ) throw new this.GalaxyError( 'Upload Error: uploaded file HDA not found: ' + uploadInfo.filename ); } this.debug( 'uploaded HDA element: ' + this.jsonStr( this.quickInfo( hdaElement ) ) ); @@ -206,7 +203,6 @@ Tools.prototype.uploadFile = function uploadFile( filepath, callback, timeoutAft }, function timeoutFn( newHdaInfo ){ this.warning( 'timeout waiting for upload:\n' + this.jsonStr( this.quickInfo( newHdaInfo ) ) ); - this.capture( 'upload-error.png' ) throw new spaceghost.GalaxyError( 'Upload Error: timeout waiting for ok state: ' + '"' + uploadInfo.filepath + '" (waited ' + timeoutAfterMs + ' ms)' ); From 8ee3da555a45830e64018facb1d08541fddcefd3 Mon Sep 17 00:00:00 2001 From: Dave Bouvier Date: Tue, 30 Apr 2013 13:41:26 -0400 Subject: [PATCH 255/523] Update check strings in functional tests to reflect recent changes to the UI. --- .../functional/test_0000_basic_repository_features.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/test/tool_shed/functional/test_0000_basic_repository_features.py b/test/tool_shed/functional/test_0000_basic_repository_features.py index e750edd15eb..4db9adf3dbb 100644 --- a/test/tool_shed/functional/test_0000_basic_repository_features.py +++ b/test/tool_shed/functional/test_0000_basic_repository_features.py @@ -118,14 +118,17 @@ class TestBasicRepositoryFeatures( ShedTwillTestCase ): self.logout() self.login( email=common.test_user_1_email, username=common.test_user_1_name ) self.set_repository_deprecated( repository, - strings_displayed=[ 'has been marked as deprecated', 'Mark as not deprecated' ] ) + strings_displayed=[ 'has been marked as deprecated' ] ) + strings_displayed = [ 'This repository has been marked as deprecated', 'Mark repository as not deprecated' ] self.display_manage_repository_page( repository, - strings_displayed=[ 'This repository has been marked as deprecated' ], + strings_displayed=strings_displayed, strings_not_displayed=[ 'Upload files', 'Reset all repository metadata' ] ) self.browse_repository( repository, strings_not_displayed=[ 'Upload files' ] ) self.set_repository_deprecated( repository, - strings_displayed=[ 'has been marked as not deprecated', 'Mark as deprecated' ], + strings_displayed=[ 'has been marked as not deprecated' ], set_deprecated=False ) + strings_displayed = [ 'Mark repository as deprecated', 'Upload files', 'Reset all repository metadata' ] + self.display_manage_repository_page( repository, strings_displayed=strings_displayed ) def test_0045_display_repository_tip_file( self ): '''Display the contents of filtering.xml in the repository tip revision''' From 9227df439874288161da3f9881319218e0391eac Mon Sep 17 00:00:00 2001 From: Carl Eberhard Date: Tue, 30 Apr 2013 13:56:11 -0400 Subject: [PATCH 256/523] Datasets API: import cleanup, misc. cleanup --- lib/galaxy/webapps/galaxy/api/datasets.py | 25 +++++++++++++---------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/lib/galaxy/webapps/galaxy/api/datasets.py b/lib/galaxy/webapps/galaxy/api/datasets.py index 1d9af60e87e..ea0d6b867f7 100644 --- a/lib/galaxy/webapps/galaxy/api/datasets.py +++ b/lib/galaxy/webapps/galaxy/api/datasets.py @@ -1,15 +1,17 @@ """ API operations on the contents of a dataset. """ -import logging, os, string, shutil, urllib, re, socket -from galaxy import util, datatypes, jobs, web, util +from galaxy import web from galaxy.visualization.data_providers.genome import FeatureLocationIndexDataProvider -from galaxy.web.base.controller import BaseAPIController, UsesVisualizationMixin, UsesHistoryDatasetAssociationMixin, UsesHistoryMixin +from galaxy.web.base.controller import BaseAPIController, UsesVisualizationMixin, UsesHistoryDatasetAssociationMixin +from galaxy.web.base.controller import UsesHistoryMixin from galaxy.web.framework.helpers import is_true +import logging log = logging.getLogger( __name__ ) -class DatasetsController( BaseAPIController, UsesVisualizationMixin, UsesHistoryMixin, UsesHistoryDatasetAssociationMixin ): +class DatasetsController( BaseAPIController, UsesVisualizationMixin, UsesHistoryMixin, + UsesHistoryDatasetAssociationMixin ): @web.expose_api def index( self, trans, **kwd ): @@ -17,7 +19,8 @@ class DatasetsController( BaseAPIController, UsesVisualizationMixin, UsesHistory GET /api/datasets Lists datasets. """ - pass + trans.response.status = 501 + return 'not implemented' @web.expose_api def show( self, trans, id, hda_ldda='hda', data_type=None, **kwd ): @@ -25,7 +28,6 @@ class DatasetsController( BaseAPIController, UsesVisualizationMixin, UsesHistory GET /api/datasets/{encoded_dataset_id} Displays information about and/or content of a dataset. """ - # Get dataset. try: dataset = self.get_hda_or_ldda( trans, hda_ldda=hda_ldda, dataset_id=id ) @@ -98,7 +100,8 @@ class DatasetsController( BaseAPIController, UsesVisualizationMixin, UsesHistory # If there is a chrom, check for data on the chrom. if chrom: data_provider_registry = trans.app.data_provider_registry - data_provider = trans.app.data_provider_registry.get_data_provider( trans, original_dataset=dataset, source='index' ) + data_provider = trans.app.data_provider_registry.get_data_provider( trans, + original_dataset=dataset, source='index' ) if not data_provider.has_data( chrom ): return dataset.conversion_messages.NO_DATA @@ -118,13 +121,11 @@ class DatasetsController( BaseAPIController, UsesVisualizationMixin, UsesHistory return data_provider.get_data( query ) return [] - def _data( self, trans, dataset, chrom, low, high, start_val=0, max_vals=None, **kwargs ): """ Provides a block of data from a dataset. """ - # Parameter check. if not chrom: return dataset.conversion_messages.NO_DATA @@ -151,7 +152,8 @@ class DatasetsController( BaseAPIController, UsesVisualizationMixin, UsesHistory summary = indexer.get_data( chrom, low, high, detail_cutoff=0, draw_cutoff=0, **kwargs ) if summary == "detail": # Use maximum level of detail--2--to get summary data no matter the resolution. - summary = indexer.get_data( chrom, low, high, resolution=kwargs[ 'resolution' ], level=2, detail_cutoff=0, draw_cutoff=0 ) + summary = indexer.get_data( chrom, low, high, resolution=kwargs[ 'resolution' ], + level=2, detail_cutoff=0, draw_cutoff=0 ) return summary if 'index' in data_sources and data_sources['index']['name'] == "summary_tree" and mode == "Auto": @@ -207,7 +209,8 @@ class DatasetsController( BaseAPIController, UsesVisualizationMixin, UsesHistory return data @web.expose_api_raw - def display( self, trans, history_content_id, history_id, preview=False, filename=None, to_ext=None, chunk=None, **kwd ): + def display( self, trans, history_content_id, history_id, + preview=False, filename=None, to_ext=None, chunk=None, **kwd ): """ GET /api/histories/{encoded_history_id}/contents/{encoded_content_id}/display Displays history content (dataset). From aecb8128ee9c7bdf58228498a7d2f2b6035f5183 Mon Sep 17 00:00:00 2001 From: Carl Eberhard Date: Tue, 30 Apr 2013 14:06:12 -0400 Subject: [PATCH 257/523] Datasets API: better error handling on api/datasets/display --- lib/galaxy/webapps/galaxy/api/datasets.py | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/lib/galaxy/webapps/galaxy/api/datasets.py b/lib/galaxy/webapps/galaxy/api/datasets.py index ea0d6b867f7..6d66328c198 100644 --- a/lib/galaxy/webapps/galaxy/api/datasets.py +++ b/lib/galaxy/webapps/galaxy/api/datasets.py @@ -68,8 +68,9 @@ class DatasetsController( BaseAPIController, UsesVisualizationMixin, UsesHistory return rval def _dataset_state( self, trans, dataset, **kwargs ): - """ Returns state of dataset. """ - + """ + Returns state of dataset. + """ msg = self.check_dataset_state( trans, dataset ) if not msg: msg = dataset.conversion_messages.DATA @@ -216,7 +217,7 @@ class DatasetsController( BaseAPIController, UsesVisualizationMixin, UsesHistory Displays history content (dataset). """ # Huge amount of code overlap with lib/galaxy/webapps/galaxy/api/history_content:show here. - hda_dict = {} + rval = '' try: # for anon users: #TODO: check login_required? @@ -233,6 +234,13 @@ class DatasetsController( BaseAPIController, UsesVisualizationMixin, UsesHistory check_ownership=True, check_accessible=True, deleted=False ) hda = self.get_history_dataset_association( trans, history, history_content_id, check_ownership=True, check_accessible=True ) - except: - raise - return hda.datatype.display_data(trans, hda, preview, filename, to_ext, chunk, **kwd) + + rval = hda.datatype.display_data( trans, hda, preview, filename, to_ext, chunk, **kwd ) + + except Exception, exception: + log.error( "Error getting display data for dataset (%s) from history (%s): %s", + history_content_id, history_id, str( exception ), exc_info=True ) + trans.response.status = 500 + rval = ( "Could not get display data for dataset: " + str( exception ) ) + + return rval From 2e88d44345a3741eec50672f645eb28fc5e93937 Mon Sep 17 00:00:00 2001 From: Jeremy Goecks Date: Tue, 30 Apr 2013 14:15:27 -0400 Subject: [PATCH 258/523] Clean up for inheritance chain display. --- templates/show_params.mako | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/templates/show_params.mako b/templates/show_params.mako index 407f87ca522..f314b43cb7a 100644 --- a/templates/show_params.mako +++ b/templates/show_params.mako @@ -153,7 +153,9 @@
    ${hda.name | h}
    % for dep in inherit_chain: -
    ↑
    -
    ${dep[0].name | h}
    ${dep[1]}
    +
    ↑
    +
    + '${dep[0].name | h}' in ${dep[1]}
    +
    % endfor From 2123153a9ba346adf78d6a4852a24ffcc473c21a Mon Sep 17 00:00:00 2001 From: Jeremy Goecks Date: Tue, 30 Apr 2013 14:36:20 -0400 Subject: [PATCH 259/523] Clean up for copying datasets to current history. --- lib/galaxy/webapps/galaxy/controllers/dataset.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/lib/galaxy/webapps/galaxy/controllers/dataset.py b/lib/galaxy/webapps/galaxy/controllers/dataset.py index 6da47071c58..d34ade59ea3 100644 --- a/lib/galaxy/webapps/galaxy/controllers/dataset.py +++ b/lib/galaxy/webapps/galaxy/controllers/dataset.py @@ -546,8 +546,14 @@ class DatasetInterface( BaseUIController, UsesAnnotations, UsesHistoryMixin, Use hda_ids = [ trans.security.encode_id( hda.id ) for hda in hdas ] trans.template_context[ 'seek_hda_ids' ] = hda_ids elif operation == "copy to current history": - # Copy a dataset to the current history. + # + # Copy datasets to the current history. + # + target_histories = [ trans.get_history() ] + + # Reverse HDAs so that they appear in the history in the order they are provided. + hda_ids.reverse() status, message = self._copy_datasets( trans, hda_ids, target_histories ) # Current history changed, refresh history frame. From 969bde734a132958610423e33de5821ef70e475f Mon Sep 17 00:00:00 2001 From: Dannon Baker Date: Tue, 30 Apr 2013 14:53:17 -0400 Subject: [PATCH 260/523] Migration script 0003, prevent sqlite from attempting to add a ForeignKey --- .../versions/0003_security_and_libraries.py | 20 ++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/lib/galaxy/model/migrate/versions/0003_security_and_libraries.py b/lib/galaxy/model/migrate/versions/0003_security_and_libraries.py index b9dfe850e0b..96931dd72c9 100644 --- a/lib/galaxy/model/migrate/versions/0003_security_and_libraries.py +++ b/lib/galaxy/model/migrate/versions/0003_security_and_libraries.py @@ -440,15 +440,17 @@ def upgrade(migrate_engine): except NoSuchTableError: LibraryDatasetDatasetAssociation_table = None log.debug( "Failed loading table library_dataset_dataset_association" ) - if MetadataFile_table is not None and LibraryDatasetDatasetAssociation_table is not None: - try: - cons = ForeignKeyConstraint( [MetadataFile_table.c.lda_id], - [LibraryDatasetDatasetAssociation_table.c.id], - name='metadata_file_lda_id_fkey' ) - # Create the constraint - cons.create() - except Exception, e: - log.debug( "Adding foreign key constraint 'metadata_file_lda_id_fkey' to table 'metadata_file' failed: %s" % ( str( e ) ) ) + if migrate_engine.name != 'sqlite': + #Sqlite can't alter table add foreign key. + if MetadataFile_table is not None and LibraryDatasetDatasetAssociation_table is not None: + try: + cons = ForeignKeyConstraint( [MetadataFile_table.c.lda_id], + [LibraryDatasetDatasetAssociation_table.c.id], + name='metadata_file_lda_id_fkey' ) + # Create the constraint + cons.create() + except Exception, e: + log.debug( "Adding foreign key constraint 'metadata_file_lda_id_fkey' to table 'metadata_file' failed: %s" % ( str( e ) ) ) # Make sure we have at least 1 user cmd = "SELECT * FROM galaxy_user;" users = db_session.execute( cmd ).fetchall() From 46fd8b258abdb182305b3a701ae7ba2f90d04a7c Mon Sep 17 00:00:00 2001 From: Dannon Baker Date: Tue, 30 Apr 2013 14:53:48 -0400 Subject: [PATCH 261/523] Migration script 0020, prevent sqlite from attempting to add a ForeignKey --- .../versions/0020_library_upload_job.py | 20 ++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/lib/galaxy/model/migrate/versions/0020_library_upload_job.py b/lib/galaxy/model/migrate/versions/0020_library_upload_job.py index 1967941d665..edcb5c64af1 100644 --- a/lib/galaxy/model/migrate/versions/0020_library_upload_job.py +++ b/lib/galaxy/model/migrate/versions/0020_library_upload_job.py @@ -66,15 +66,17 @@ def upgrade(migrate_engine): LibraryFolder_table = None log.debug( "Failed loading table library_folder" ) # Add 1 foreign key constraint to the job table - if Job_table is not None and LibraryFolder_table is not None: - try: - cons = ForeignKeyConstraint( [Job_table.c.library_folder_id], - [LibraryFolder_table.c.id], - name='job_library_folder_id_fk' ) - # Create the constraint - cons.create() - except Exception, e: - log.debug( "Adding foreign key constraint 'job_library_folder_id_fk' to table 'library_folder' failed: %s" % ( str( e ) ) ) + if migrate_engine.name != 'sqlite': + #Sqlite can't alter-table-add-foreign-key + if Job_table is not None and LibraryFolder_table is not None: + try: + cons = ForeignKeyConstraint( [Job_table.c.library_folder_id], + [LibraryFolder_table.c.id], + name='job_library_folder_id_fk' ) + # Create the constraint + cons.create() + except Exception, e: + log.debug( "Adding foreign key constraint 'job_library_folder_id_fk' to table 'library_folder' failed: %s" % ( str( e ) ) ) # Create the ix_dataset_state index try: Dataset_table = Table( "dataset", metadata, autoload=True ) From c91d964052fa2e4ecbe8cf30b072aabf8e5a10c1 Mon Sep 17 00:00:00 2001 From: Dannon Baker Date: Tue, 30 Apr 2013 14:55:04 -0400 Subject: [PATCH 262/523] Migration script 0036, 0038, use '0' as boolean for sqlite. --- ...eleted_column_to_library_template_assoc_tables.py | 12 +++++++++--- ...itable_column_to_library_template_assoc_tables.py | 10 ++++++++-- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/lib/galaxy/model/migrate/versions/0036_add_deleted_column_to_library_template_assoc_tables.py b/lib/galaxy/model/migrate/versions/0036_add_deleted_column_to_library_template_assoc_tables.py index 34422accf11..17ff8d4b23f 100644 --- a/lib/galaxy/model/migrate/versions/0036_add_deleted_column_to_library_template_assoc_tables.py +++ b/lib/galaxy/model/migrate/versions/0036_add_deleted_column_to_library_template_assoc_tables.py @@ -12,6 +12,12 @@ log = logging.getLogger( __name__ ) metadata = MetaData() +def get_false_value(migrate_engine): + if migrate_engine.name == 'sqlite': + return '0' + else: + return 'false' + def upgrade(migrate_engine): metadata.bind = migrate_engine print __doc__ @@ -21,7 +27,7 @@ def upgrade(migrate_engine): c = Column( "deleted", Boolean, index=True, default=False ) c.create( LibraryInfoAssociation_table, index_name='ix_library_info_association_deleted') assert c is LibraryInfoAssociation_table.c.deleted - cmd = "UPDATE library_info_association SET deleted = false" + cmd = "UPDATE library_info_association SET deleted = %s" % get_false_value(migrate_engine) try: migrate_engine.execute( cmd ) except Exception, e: @@ -31,7 +37,7 @@ def upgrade(migrate_engine): c = Column( "deleted", Boolean, index=True, default=False ) c.create( LibraryFolderInfoAssociation_table, index_name='ix_library_folder_info_association_deleted') assert c is LibraryFolderInfoAssociation_table.c.deleted - cmd = "UPDATE library_folder_info_association SET deleted = false" + cmd = "UPDATE library_folder_info_association SET deleted = %s" % get_false_value(migrate_engine) try: migrate_engine.execute( cmd ) except Exception, e: @@ -41,7 +47,7 @@ def upgrade(migrate_engine): c = Column( "deleted", Boolean, index=True, default=False ) c.create( LibraryDatasetDatasetInfoAssociation_table, index_name='ix_library_dataset_dataset_info_association_deleted') assert c is LibraryDatasetDatasetInfoAssociation_table.c.deleted - cmd = "UPDATE library_dataset_dataset_info_association SET deleted = false" + cmd = "UPDATE library_dataset_dataset_info_association SET deleted = %s" % get_false_value(migrate_engine) try: migrate_engine.execute( cmd ) except Exception, e: diff --git a/lib/galaxy/model/migrate/versions/0038_add_inheritable_column_to_library_template_assoc_tables.py b/lib/galaxy/model/migrate/versions/0038_add_inheritable_column_to_library_template_assoc_tables.py index 7e2d86b21df..4d93f8459d7 100644 --- a/lib/galaxy/model/migrate/versions/0038_add_inheritable_column_to_library_template_assoc_tables.py +++ b/lib/galaxy/model/migrate/versions/0038_add_inheritable_column_to_library_template_assoc_tables.py @@ -16,6 +16,12 @@ log = logging.getLogger( __name__ ) metadata = MetaData() +def get_false_value(migrate_engine): + if migrate_engine.name == 'sqlite': + return '0' + else: + return 'false' + def upgrade(migrate_engine): metadata.bind = migrate_engine print __doc__ @@ -50,7 +56,7 @@ def upgrade(migrate_engine): c = Column( "inheritable", Boolean, index=True, default=False ) c.create( LibraryInfoAssociation_table, index_name='ix_library_info_association_inheritable') assert c is LibraryInfoAssociation_table.c.inheritable - cmd = "UPDATE library_info_association SET inheritable = false" + cmd = "UPDATE library_info_association SET inheritable = %s" % get_false_value(migrate_engine) try: migrate_engine.execute( cmd ) except Exception, e: @@ -60,7 +66,7 @@ def upgrade(migrate_engine): c = Column( "inheritable", Boolean, index=True, default=False ) c.create( LibraryFolderInfoAssociation_table, index_name='ix_library_folder_info_association_inheritable') assert c is LibraryFolderInfoAssociation_table.c.inheritable - cmd = "UPDATE library_folder_info_association SET inheritable = false" + cmd = "UPDATE library_folder_info_association SET inheritable = %s" % get_false_value(migrate_engine) try: migrate_engine.execute( cmd ) except Exception, e: From 7dac268ef96614031dcf7b6d7e2246ec3b1d6305 Mon Sep 17 00:00:00 2001 From: Dannon Baker Date: Tue, 30 Apr 2013 14:56:43 -0400 Subject: [PATCH 263/523] Sqlalchemy-migrate doesn't properly handle alter table add column w/ nullable=false in sqlite, skip constraint for dialect --- .../versions/0102_add_tool_dependency_status_columns.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/galaxy/model/migrate/versions/0102_add_tool_dependency_status_columns.py b/lib/galaxy/model/migrate/versions/0102_add_tool_dependency_status_columns.py index 743c9b77b36..f089dc3d50d 100644 --- a/lib/galaxy/model/migrate/versions/0102_add_tool_dependency_status_columns.py +++ b/lib/galaxy/model/migrate/versions/0102_add_tool_dependency_status_columns.py @@ -29,7 +29,10 @@ def upgrade(migrate_engine): print __doc__ metadata.reflect() ToolDependency_table = Table( "tool_dependency", metadata, autoload=True ) - col = Column( "status", TrimmedString( 255 ), nullable=False ) + if migrate_engine.name == 'sqlite': + col = Column( "status", TrimmedString( 255 )) + else: + col = Column( "status", TrimmedString( 255 ), nullable=False) try: col.create( ToolDependency_table ) assert col is ToolDependency_table.c.status From af8de814636c6175d0bf35c1e9b9e430c17efbf2 Mon Sep 17 00:00:00 2001 From: Dannon Baker Date: Tue, 30 Apr 2013 14:57:56 -0400 Subject: [PATCH 264/523] Migration scripts 0037, 0068, 0073 -- skip foreign key creation for sqlite --- .../migrate/versions/0037_samples_library.py | 10 +++++++-- ...8_rename_sequencer_to_external_services.py | 21 ++++++++++--------- ...3_add_ldda_to_implicit_conversion_table.py | 6 +++++- 3 files changed, 24 insertions(+), 13 deletions(-) diff --git a/lib/galaxy/model/migrate/versions/0037_samples_library.py b/lib/galaxy/model/migrate/versions/0037_samples_library.py index eedf669de09..a100aa9197a 100644 --- a/lib/galaxy/model/migrate/versions/0037_samples_library.py +++ b/lib/galaxy/model/migrate/versions/0037_samples_library.py @@ -125,7 +125,10 @@ def upgrade(migrate_engine): if Library_table is not None: # Add the library_id column in 'sample' table try: - col = Column( "library_id", Integer, ForeignKey( "library.id" ), index=True ) + if migrate_engine.name != 'sqlite': + col = Column( "library_id", Integer, ForeignKey( "library.id" ), index=True ) + else: + col = Column( "library_id", Integer, index=True ) col.create( Sample_table, index_name='ix_sample_library_id') assert col is Sample_table.c.library_id except Exception, e: @@ -139,7 +142,10 @@ def upgrade(migrate_engine): if LibraryFolder_table is not None: # Add the library_id column in 'sample' table try: - col = Column( "folder_id", Integer, ForeignKey( "library_folder.id" ), index=True ) + if migrate_engine.name != 'sqlite': + col = Column( "folder_id", Integer, ForeignKey( "library_folder.id" ), index=True ) + else: + col = Column( "folder_id", Integer, index=True ) col.create( Sample_table, index_name='ix_sample_library_folder_id') assert col is Sample_table.c.folder_id except Exception, e: diff --git a/lib/galaxy/model/migrate/versions/0068_rename_sequencer_to_external_services.py b/lib/galaxy/model/migrate/versions/0068_rename_sequencer_to_external_services.py index 4fdd63ca26f..282b4ea8854 100644 --- a/lib/galaxy/model/migrate/versions/0068_rename_sequencer_to_external_services.py +++ b/lib/galaxy/model/migrate/versions/0068_rename_sequencer_to_external_services.py @@ -63,15 +63,16 @@ def upgrade(migrate_engine): assert col is SampleDataset_table.c.external_service_id except Exception, e: log.debug( "Creating column 'external_service_id' in the 'sample_dataset' table failed: %s" % ( str( e ) ) ) - # Add the foreign key constraint - try: - cons = ForeignKeyConstraint( [SampleDataset_table.c.external_service_id], - [Sequencer_table.c.id], - name='sample_dataset_external_services_id_fk' ) - # Create the constraint - cons.create() - except Exception, e: - log.debug( "Adding foreign key constraint 'sample_dataset_external_services_id_fk' to table 'sample_dataset' failed: %s" % ( str( e ) ) ) + if migrate_engine.name != 'sqlite': + # Add the foreign key constraint + try: + cons = ForeignKeyConstraint( [SampleDataset_table.c.external_service_id], + [Sequencer_table.c.id], + name='sample_dataset_external_services_id_fk' ) + # Create the constraint + cons.create() + except Exception, e: + log.debug( "Adding foreign key constraint 'sample_dataset_external_services_id_fk' to table 'sample_dataset' failed: %s" % ( str( e ) ) ) # populate the column cmd = "SELECT sample_dataset.id, request_type.sequencer_id " \ + " FROM sample_dataset, sample, request, request_type " \ @@ -102,7 +103,7 @@ def upgrade(migrate_engine): if ExternalServices_table is None: return # if running postgres then rename the primary key sequence too - if migrate_engine.name == 'postgres': + if migrate_engine.name in ['postgres', 'postgresql']: cmd = "ALTER TABLE sequencer_id_seq RENAME TO external_service_id_seq" migrate_engine.execute( cmd ) # rename 'sequencer_type_id' column to 'external_service_type_id' in the table 'external_service' diff --git a/lib/galaxy/model/migrate/versions/0073_add_ldda_to_implicit_conversion_table.py b/lib/galaxy/model/migrate/versions/0073_add_ldda_to_implicit_conversion_table.py index bf6542dc386..4c6ad256321 100644 --- a/lib/galaxy/model/migrate/versions/0073_add_ldda_to_implicit_conversion_table.py +++ b/lib/galaxy/model/migrate/versions/0073_add_ldda_to_implicit_conversion_table.py @@ -19,7 +19,11 @@ def upgrade(migrate_engine): metadata.reflect() try: Implicitly_converted_table = Table( "implicitly_converted_dataset_association", metadata, autoload=True ) - c = Column( "ldda_parent_id", Integer, ForeignKey( "library_dataset_dataset_association.id" ), index=True, nullable=True ) + if migrate_engine.name != 'sqlite': + c = Column( "ldda_parent_id", Integer, ForeignKey( "library_dataset_dataset_association.id" ), index=True, nullable=True ) + else: + #Can't use the ForeignKey in sqlite. + c = Column( "ldda_parent_id", Integer, index=True, nullable=True ) c.create( Implicitly_converted_table, index_name="ix_implicitly_converted_dataset_assoc_ldda_parent_id") assert c is Implicitly_converted_table.c.ldda_parent_id except Exception, e: From 9d59b365c8e4b53b012bf9b38986e8271e74d8da Mon Sep 17 00:00:00 2001 From: Greg Von Kuster Date: Tue, 30 Apr 2013 16:40:06 -0400 Subject: [PATCH 265/523] Fix queries in the current user's lists of repositories that display the latest revision of each of their repositories with 1) missing tool test components 2) failing tool tests and 3) no failing tool tests. Add links for the same filtered lists for all users under the Review repositories with tools section in the tool shed menu. --- .../tool_shed/controllers/repository.py | 169 ++++++++--- lib/tool_shed/grids/repository_grids.py | 280 +++++++++++++----- lib/tool_shed/util/shed_util_common.py | 23 -- templates/webapps/tool_shed/admin/index.mako | 15 +- templates/webapps/tool_shed/index.mako | 8 +- 5 files changed, 357 insertions(+), 138 deletions(-) diff --git a/lib/galaxy/webapps/tool_shed/controllers/repository.py b/lib/galaxy/webapps/tool_shed/controllers/repository.py index bb526421b43..2c036743771 100644 --- a/lib/galaxy/webapps/tool_shed/controllers/repository.py +++ b/lib/galaxy/webapps/tool_shed/controllers/repository.py @@ -65,6 +65,9 @@ class RepositoryController( BaseUIController, common_util.ItemRatings ): repositories_by_user_grid = repository_grids.RepositoriesByUserGrid() repositories_i_own_grid = repository_grids.RepositoriesIOwnGrid() repositories_in_category_grid = repository_grids.RepositoriesInCategoryGrid() + repositories_missing_tool_test_components_grid = repository_grids.RepositoriesMissingToolTestComponentsGrid() + repositories_with_failing_tool_tests_grid = repository_grids.RepositoriesWithFailingToolTestsGrid() + repositories_with_no_failing_tool_tests_grid = repository_grids.RepositoriesWithNoFailingToolTestsGrid() repository_dependencies_grid = repository_grids.RepositoryDependenciesGrid() repository_grid = repository_grids.RepositoryGrid() # The repository_metadata_grid is not currently displayed, but is sub-classed by several grids. @@ -120,6 +123,23 @@ class RepositoryController( BaseUIController, common_util.ItemRatings ): **new_kwd ) ) return self.datatypes_grid( trans, **kwd ) + @web.expose + def browse_deprecated_repositories_i_own( self, trans, **kwd ): + if 'operation' in kwd: + operation = kwd[ 'operation' ].lower() + if operation == "view_or_manage_repository": + return trans.response.send_redirect( web.url_for( controller='repository', + action='view_or_manage_repository', + **kwd ) ) + selected_changeset_revision, repository = self.__get_repository_from_refresh_on_change( trans, **kwd ) + if repository: + return trans.response.send_redirect( web.url_for( controller='repository', + action='browse_repositories', + operation='view_or_manage_repository', + id=trans.security.encode_id( repository.id ), + changeset_revision=selected_changeset_revision ) ) + return self.deprecated_repositories_i_own_grid( trans, **kwd ) + @web.expose def browse_invalid_tools( self, trans, **kwd ): params = util.Params( kwd ) @@ -187,6 +207,34 @@ class RepositoryController( BaseUIController, common_util.ItemRatings ): changeset_revision=selected_changeset_revision ) ) return self.my_writable_repositories_grid( trans, **kwd ) + @web.expose + def browse_my_writable_repositories_missing_tool_test_components( self, trans, **kwd ): + if 'operation' in kwd: + operation = kwd[ 'operation' ].lower() + if operation == "view_or_manage_repository": + return trans.response.send_redirect( web.url_for( controller='repository', + action='view_or_manage_repository', + **kwd ) ) + elif operation == "repositories_by_user": + return trans.response.send_redirect( web.url_for( controller='repository', + action='browse_repositories_by_user', + **kwd ) ) + elif operation in [ 'mark as deprecated', 'mark as not deprecated' ]: + kwd[ 'mark_deprecated' ] = operation == 'mark as deprecated' + return trans.response.send_redirect( web.url_for( controller='repository', + action='deprecate', + **kwd ) ) + if 'message' not in kwd: + message = 'This list contains repositories that match the following criteria:
    ' + message += '
      ' + message += '
    • you are authorized to update them
    • ' + message += '
    • the latest installable revision contains at least 1 tool with no defined tests OR:
    • ' + message += '
    • the latest installable revision contains at least 1 tool with a test that requires a missing test data file
    • ' + message += '
    ' + kwd[ 'message' ] = message + kwd[ 'status' ] = 'warning' + return self.my_writable_repositories_missing_tool_test_components_grid( trans, **kwd ) + @web.expose def browse_my_writable_repositories_with_failing_tool_tests( self, trans, **kwd ): if 'operation' in kwd: @@ -245,44 +293,6 @@ class RepositoryController( BaseUIController, common_util.ItemRatings ): kwd[ 'status' ] = 'warning' return self.my_writable_repositories_with_no_failing_tool_tests_grid( trans, **kwd ) - @web.expose - def browse_my_writable_repositories_missing_tool_test_components( self, trans, **kwd ): - if 'operation' in kwd: - operation = kwd[ 'operation' ].lower() - if operation == "view_or_manage_repository": - return trans.response.send_redirect( web.url_for( controller='repository', - action='view_or_manage_repository', - **kwd ) ) - elif operation == "repositories_by_user": - return trans.response.send_redirect( web.url_for( controller='repository', - action='browse_repositories_by_user', - **kwd ) ) - elif operation in [ 'mark as deprecated', 'mark as not deprecated' ]: - kwd[ 'mark_deprecated' ] = operation == 'mark as deprecated' - return trans.response.send_redirect( web.url_for( controller='repository', - action='deprecate', - **kwd ) ) - if 'message' not in kwd: - message = 'This list contains repositories that match the following criteria:
    ' - message += '
      ' - message += '
    • you are authorized to update them
    • ' - message += '
    • the latest installable revision contains at least 1 tool with no defined tests OR:
    • ' - message += '
    • the latest installable revision contains at least 1 tool with a test that requires a missing test data file
    • ' - message += '
    ' - kwd[ 'message' ] = message - kwd[ 'status' ] = 'warning' - return self.my_writable_repositories_missing_tool_test_components_grid( trans, **kwd ) - - @web.expose - def browse_deprecated_repositories_i_own( self, trans, **kwd ): - if 'operation' in kwd: - operation = kwd[ 'operation' ].lower() - if operation == "view_or_manage_repository": - return trans.response.send_redirect( web.url_for( controller='repository', - action='view_or_manage_repository', - **kwd ) ) - return self.deprecated_repositories_i_own_grid( trans, **kwd ) - @web.expose def browse_repositories( self, trans, **kwd ): # We add params to the keyword dict in this method in order to rename the param with an "f-" prefix, simulating filtering by clicking a search @@ -426,6 +436,89 @@ class RepositoryController( BaseUIController, common_util.ItemRatings ): self.repositories_in_category_grid.title = 'Category %s' % str( category.name ) return self.repositories_in_category_grid( trans, **kwd ) + @web.expose + def browse_repositories_missing_tool_test_components( self, trans, **kwd ): + if 'operation' in kwd: + operation = kwd[ 'operation' ].lower() + if operation == "view_or_manage_repository": + return trans.response.send_redirect( web.url_for( controller='repository', + action='view_or_manage_repository', + **kwd ) ) + elif operation == "repositories_by_user": + return trans.response.send_redirect( web.url_for( controller='repository', + action='browse_repositories_by_user', + **kwd ) ) + elif operation in [ 'mark as deprecated', 'mark as not deprecated' ]: + kwd[ 'mark_deprecated' ] = operation == 'mark as deprecated' + return trans.response.send_redirect( web.url_for( controller='repository', + action='deprecate', + **kwd ) ) + if 'message' not in kwd: + message = 'This list contains repositories that match the following criteria:
    ' + message += '
      ' + message += '
    • the latest installable revision contains at least 1 tool with no defined tests OR:
    • ' + message += '
    • the latest installable revision contains at least 1 tool with a test that requires a missing test data file
    • ' + message += '
    ' + kwd[ 'message' ] = message + kwd[ 'status' ] = 'warning' + return self.repositories_missing_tool_test_components_grid( trans, **kwd ) + + @web.expose + def browse_repositories_with_failing_tool_tests( self, trans, **kwd ): + if 'operation' in kwd: + operation = kwd[ 'operation' ].lower() + if operation == "view_or_manage_repository": + return trans.response.send_redirect( web.url_for( controller='repository', + action='view_or_manage_repository', + **kwd ) ) + elif operation == "repositories_by_user": + return trans.response.send_redirect( web.url_for( controller='repository', + action='browse_repositories_by_user', + **kwd ) ) + elif operation in [ 'mark as deprecated', 'mark as not deprecated' ]: + kwd[ 'mark_deprecated' ] = operation == 'mark as deprecated' + return trans.response.send_redirect( web.url_for( controller='repository', + action='deprecate', + **kwd ) ) + if 'message' not in kwd: + message = 'This list contains repositories that match the following criteria:
    ' + message += '
      ' + message += '
    • the latest installable revision contains at least 1 tool
    • ' + message += '
    • the latest installable revision is not missing any tool test components
    • ' + message += '
    • the latest installable revision has at least 1 tool test that fails
    • ' + message += '
    ' + kwd[ 'message' ] = message + kwd[ 'status' ] = 'warning' + return self.repositories_with_failing_tool_tests_grid( trans, **kwd ) + + @web.expose + def browse_repositories_with_no_failing_tool_tests( self, trans, **kwd ): + if 'operation' in kwd: + operation = kwd[ 'operation' ].lower() + if operation == "view_or_manage_repository": + return trans.response.send_redirect( web.url_for( controller='repository', + action='view_or_manage_repository', + **kwd ) ) + elif operation == "repositories_by_user": + return trans.response.send_redirect( web.url_for( controller='repository', + action='browse_repositories_by_user', + **kwd ) ) + elif operation in [ 'mark as deprecated', 'mark as not deprecated' ]: + kwd[ 'mark_deprecated' ] = operation == 'mark as deprecated' + return trans.response.send_redirect( web.url_for( controller='repository', + action='deprecate', + **kwd ) ) + if 'message' not in kwd: + message = 'This list contains repositories that match the following criteria:
    ' + message += '
      ' + message += '
    • the latest installable revision contains at least 1 tool
    • ' + message += '
    • the latest installable revision is not missing any tool test components
    • ' + message += '
    • the latest installable revision has no tool tests that fail
    • ' + message += '
    ' + kwd[ 'message' ] = message + kwd[ 'status' ] = 'warning' + return self.repositories_with_no_failing_tool_tests_grid( trans, **kwd ) + @web.expose def browse_repository( self, trans, id, **kwd ): params = util.Params( kwd ) diff --git a/lib/tool_shed/grids/repository_grids.py b/lib/tool_shed/grids/repository_grids.py index d179ef15fb2..37a15758dfc 100644 --- a/lib/tool_shed/grids/repository_grids.py +++ b/lib/tool_shed/grids/repository_grids.py @@ -1,17 +1,24 @@ -import os, logging +import logging +import os from galaxy.webapps.tool_shed import model from galaxy.web.framework.helpers import grids -from galaxy.model.orm import and_, or_ +from galaxy.model.orm import and_ +from galaxy.model.orm import or_ from galaxy.util import json import tool_shed.util.shed_util_common as suc import tool_shed.grids.util as grids_util from tool_shed.util import metadata_util from galaxy import eggs + eggs.require('markupsafe') from markupsafe import escape as escape_html + eggs.require('mercurial') -from mercurial import hg, ui, patch, commands +from mercurial import commands +from mercurial import hg +from mercurial import patch +from mercurial import ui log = logging.getLogger( __name__ ) @@ -462,7 +469,7 @@ class EmailAlertsRepositoryGrid( RepositoryGrid ): class MyWritableRepositoriesGrid( RepositoryGrid ): # This grid filters out repositories that have been marked as either deprecated or deleted. - title = 'Repositories that I can change' + title = 'Repositories I can change' columns = [ RepositoryGrid.NameColumn( "Name", key="name", @@ -505,19 +512,19 @@ class MyWritableRepositoriesGrid( RepositoryGrid ): .filter( model.Repository.table.c.id < 0 ) -class MyWritableRepositoriesMissingToolTestComponentsGrid( MyWritableRepositoriesGrid ): - title = "Repositories that I can change with missing tool test components" +class RepositoriesMissingToolTestComponentsGrid( RepositoryGrid ): + title = "Repositories with missing tool test components" columns = [ - RepositoriesIOwnGrid.NameColumn( "Name", - key="name", - link=( lambda item: dict( operation="view_or_manage_repository", id=item.id ) ), - attach_popup=True ), + RepositoryGrid.NameColumn( "Name", + key="name", + link=( lambda item: dict( operation="view_or_manage_repository", id=item.id ) ), + attach_popup=False ), RepositoryGrid.LatestInstallableRevisionColumn( "Latest Installable Revision" ), RepositoryGrid.UserColumn( "Owner", + key="User.username", model_class=model.User, link=( lambda item: dict( operation="repositories_by_user", id=item.id ) ), - attach_popup=False, - key="User.username" ) + attach_popup=False ) ] columns.append( grids.MulticolFilterColumn( "Search repository name", cols_to_filter=[ columns[0] ], @@ -527,6 +534,29 @@ class MyWritableRepositoriesMissingToolTestComponentsGrid( MyWritableRepositorie operations = [] use_paging = False + def build_initial_query( self, trans, **kwd ): + # Filter by latest installable revisions that contain tools with missing tool test components. + revision_clause_list = [] + for repository in trans.sa_session.query( model.Repository ): + changeset_revision = filter_by_latest_downloadable_changeset_revision_that_has_missing_tool_test_components( trans, repository ) + if changeset_revision: + revision_clause_list.append( model.RepositoryMetadata.table.c.changeset_revision == changeset_revision ) + if revision_clause_list: + return trans.sa_session.query( model.Repository ) \ + .join( model.RepositoryMetadata ) \ + .filter( or_( *revision_clause_list ) ) \ + .join( model.User.table ) + # Return an empty query. + return trans.sa_session.query( model.Repository ) \ + .filter( model.Repository.table.c.id < 0 ) + + +class MyWritableRepositoriesMissingToolTestComponentsGrid( RepositoriesMissingToolTestComponentsGrid ): + title = "Repositories I can change with missing tool test components" + columns = [ col for col in RepositoriesMissingToolTestComponentsGrid.columns ] + operations = [] + use_paging = False + def build_initial_query( self, trans, **kwd ): # First get all repositories that the current user is authorized to update. username = trans.user.username @@ -545,8 +575,8 @@ class MyWritableRepositoriesMissingToolTestComponentsGrid( MyWritableRepositorie revision_clause_list = [] for repository in trans.sa_session.query( model.Repository ) \ .filter( or_( *user_clause_list ) ): - changeset_revision = suc.filter_by_latest_downloadable_changeset_revision_that_has_missing_tool_test_components( trans, repository ) - if changeset_revision not in [ None, suc.INITIAL_CHANGELOG_HASH ]: + changeset_revision = filter_by_latest_downloadable_changeset_revision_that_has_missing_tool_test_components( trans, repository ) + if changeset_revision: revision_clause_list.append( model.RepositoryMetadata.table.c.changeset_revision == changeset_revision ) if revision_clause_list: return trans.sa_session.query( model.Repository ) \ @@ -559,19 +589,19 @@ class MyWritableRepositoriesMissingToolTestComponentsGrid( MyWritableRepositorie .filter( model.Repository.table.c.id < 0 ) -class MyWritableRepositoriesWithFailingToolTestsGrid( MyWritableRepositoriesMissingToolTestComponentsGrid ): - title = "Repositories that I can change with failing tool tests" +class RepositoriesWithFailingToolTestsGrid( RepositoryGrid ): + title = "Repositories with failing tool tests" columns = [ - RepositoriesIOwnGrid.NameColumn( "Name", - key="name", - link=( lambda item: dict( operation="view_or_manage_repository", id=item.id ) ), - attach_popup=True ), + RepositoryGrid.NameColumn( "Name", + key="name", + link=( lambda item: dict( operation="view_or_manage_repository", id=item.id ) ), + attach_popup=False ), RepositoryGrid.LatestInstallableRevisionColumn( "Latest Installable Revision" ), RepositoryGrid.UserColumn( "Owner", + key="User.username", model_class=model.User, link=( lambda item: dict( operation="repositories_by_user", id=item.id ) ), - attach_popup=False, - key="User.username" ) + attach_popup=False ) ] columns.append( grids.MulticolFilterColumn( "Search repository name", cols_to_filter=[ columns[0] ], @@ -582,57 +612,25 @@ class MyWritableRepositoriesWithFailingToolTestsGrid( MyWritableRepositoriesMiss use_paging = False def build_initial_query( self, trans, **kwd ): - # First get all repositories that the current user is authorized to update. - username = trans.user.username - user_clause_list = [] - for repository in trans.sa_session.query( model.Repository ) \ - .filter( and_( model.Repository.table.c.deprecated == False, - model.Repository.table.c.deleted == False ) ): - allow_push = repository.allow_push( trans.app ) - if allow_push: - allow_push_usernames = allow_push.split( ',' ) - if username in allow_push_usernames: - user_clause_list.append( model.Repository.table.c.id == repository.id ) - if user_clause_list: - # We have the list of repositories that the current user is authorized to update, so filter further by latest installable revisions that contain - # tools with missing tool test components. - revision_clause_list = [] - for repository in trans.sa_session.query( model.Repository ) \ - .filter( or_( *user_clause_list ) ): - changeset_revision = suc.filter_by_latest_downloadable_changeset_revision_that_has_missing_tool_test_components( trans, repository ) - if changeset_revision not in [ None, suc.INITIAL_CHANGELOG_HASH ]: - revision_clause_list.append( model.RepositoryMetadata.table.c.changeset_revision == changeset_revision ) - if revision_clause_list: - return trans.sa_session.query( model.Repository ) \ - .join( model.User.table ) \ - .filter( or_( *user_clause_list ) ) \ - .join( model.RepositoryMetadata ) \ - .filter( or_( *revision_clause_list ) ) \ - .filter( model.RepositoryMetadata.table.c.tools_functionally_correct == False ) + # Filter by latest installable revisions that contain tools with at least 1 failing tool test. + revision_clause_list = [] + for repository in trans.sa_session.query( model.Repository ): + changeset_revision = filter_by_latest_downloadable_changeset_revision_that_has_failing_tool_tests( trans, repository ) + if changeset_revision: + revision_clause_list.append( model.RepositoryMetadata.table.c.changeset_revision == changeset_revision ) + if revision_clause_list: + return trans.sa_session.query( model.Repository ) \ + .join( model.RepositoryMetadata ) \ + .filter( or_( *revision_clause_list ) ) \ + .join( model.User.table ) # Return an empty query. return trans.sa_session.query( model.Repository ) \ .filter( model.Repository.table.c.id < 0 ) -class MyWritableRepositoriesWithNoFailingToolTestsGrid( MyWritableRepositoriesMissingToolTestComponentsGrid ): - title = "Repositories that I can change with failing tool tests" - columns = [ - RepositoriesIOwnGrid.NameColumn( "Name", - key="name", - link=( lambda item: dict( operation="view_or_manage_repository", id=item.id ) ), - attach_popup=True ), - RepositoryGrid.LatestInstallableRevisionColumn( "Latest Installable Revision" ), - RepositoryGrid.UserColumn( "Owner", - model_class=model.User, - link=( lambda item: dict( operation="repositories_by_user", id=item.id ) ), - attach_popup=False, - key="User.username" ) - ] - columns.append( grids.MulticolFilterColumn( "Search repository name", - cols_to_filter=[ columns[0] ], - key="free-text-search", - visible=False, - filterable="standard" ) ) +class MyWritableRepositoriesWithFailingToolTestsGrid( RepositoriesWithFailingToolTestsGrid ): + title = "Repositories I can change with failing tool tests" + columns = [ col for col in RepositoriesWithFailingToolTestsGrid.columns ] operations = [] use_paging = False @@ -650,20 +648,97 @@ class MyWritableRepositoriesWithNoFailingToolTestsGrid( MyWritableRepositoriesMi user_clause_list.append( model.Repository.table.c.id == repository.id ) if user_clause_list: # We have the list of repositories that the current user is authorized to update, so filter further by latest installable revisions that contain - # tools with missing tool test components. + # tools with at least 1 failing tool test. revision_clause_list = [] for repository in trans.sa_session.query( model.Repository ) \ .filter( or_( *user_clause_list ) ): - changeset_revision = suc.filter_by_latest_downloadable_changeset_revision_that_has_missing_tool_test_components( trans, repository ) - if changeset_revision not in [ None, suc.INITIAL_CHANGELOG_HASH ]: + changeset_revision = filter_by_latest_downloadable_changeset_revision_that_has_failing_tool_tests( trans, repository ) + if changeset_revision: revision_clause_list.append( model.RepositoryMetadata.table.c.changeset_revision == changeset_revision ) if revision_clause_list: return trans.sa_session.query( model.Repository ) \ .join( model.User.table ) \ .filter( or_( *user_clause_list ) ) \ .join( model.RepositoryMetadata ) \ - .filter( or_( *revision_clause_list ) ) \ - .filter( model.RepositoryMetadata.table.c.tools_functionally_correct == True ) + .filter( or_( *revision_clause_list ) ) + # Return an empty query. + return trans.sa_session.query( model.Repository ) \ + .filter( model.Repository.table.c.id < 0 ) + + +class RepositoriesWithNoFailingToolTestsGrid( RepositoryGrid ): + title = "Repositories with no failing tool tests" + columns = [ + RepositoryGrid.NameColumn( "Name", + key="name", + link=( lambda item: dict( operation="view_or_manage_repository", id=item.id ) ), + attach_popup=False ), + RepositoryGrid.LatestInstallableRevisionColumn( "Latest Installable Revision" ), + RepositoryGrid.UserColumn( "Owner", + key="User.username", + model_class=model.User, + link=( lambda item: dict( operation="repositories_by_user", id=item.id ) ), + attach_popup=False ) + ] + columns.append( grids.MulticolFilterColumn( "Search repository name", + cols_to_filter=[ columns[0] ], + key="free-text-search", + visible=False, + filterable="standard" ) ) + operations = [] + use_paging = False + + def build_initial_query( self, trans, **kwd ): + # We have the list of repositories that the current user is authorized to update, so filter further by latest installable revisions that contain + # tools with at least 1 failing tool test. + revision_clause_list = [] + for repository in trans.sa_session.query( model.Repository ): + changeset_revision = filter_by_latest_downloadable_changeset_revision_that_has_no_failing_tool_tests( trans, repository ) + if changeset_revision: + revision_clause_list.append( model.RepositoryMetadata.table.c.changeset_revision == changeset_revision ) + if revision_clause_list: + return trans.sa_session.query( model.Repository ) \ + .join( model.RepositoryMetadata ) \ + .filter( or_( *revision_clause_list ) ) \ + .join( model.User.table ) + # Return an empty query. + return trans.sa_session.query( model.Repository ) \ + .filter( model.Repository.table.c.id < 0 ) + + +class MyWritableRepositoriesWithNoFailingToolTestsGrid( RepositoriesWithNoFailingToolTestsGrid ): + title = "Repositories I can change with no failing tool tests" + columns = [ col for col in RepositoriesWithNoFailingToolTestsGrid.columns ] + operations = [] + use_paging = False + + def build_initial_query( self, trans, **kwd ): + # First get all repositories that the current user is authorized to update. + username = trans.user.username + user_clause_list = [] + for repository in trans.sa_session.query( model.Repository ) \ + .filter( and_( model.Repository.table.c.deprecated == False, + model.Repository.table.c.deleted == False ) ): + allow_push = repository.allow_push( trans.app ) + if allow_push: + allow_push_usernames = allow_push.split( ',' ) + if username in allow_push_usernames: + user_clause_list.append( model.Repository.table.c.id == repository.id ) + if user_clause_list: + # We have the list of repositories that the current user is authorized to update, so filter further by latest installable revisions that contain + # at least 1 tool, no missing tool test components, and no failing tool tests. + revision_clause_list = [] + for repository in trans.sa_session.query( model.Repository ) \ + .filter( or_( *user_clause_list ) ): + changeset_revision = filter_by_latest_downloadable_changeset_revision_that_has_no_failing_tool_tests( trans, repository ) + if changeset_revision: + revision_clause_list.append( model.RepositoryMetadata.table.c.changeset_revision == changeset_revision ) + if revision_clause_list: + return trans.sa_session.query( model.Repository ) \ + .join( model.User.table ) \ + .filter( or_( *user_clause_list ) ) \ + .join( model.RepositoryMetadata ) \ + .filter( or_( *revision_clause_list ) ) # Return an empty query. return trans.sa_session.query( model.Repository ) \ .filter( model.Repository.table.c.id < 0 ) @@ -1259,3 +1334,62 @@ class DatatypesGrid( RepositoryMetadataGrid ): model.Repository.table.c.deleted == False, model.Repository.table.c.deprecated == False ) ) \ .join( model.User.table ) + +# ------ utility methods ------------------- + +def filter_by_latest_downloadable_changeset_revision_that_has_failing_tool_tests( trans, repository ): + """ + Inspect the latest installable changeset revision for the received repository to see if it includes at least 1 tool that has at least 1 failing test. + """ + encoded_repository_id = trans.security.encode_id( repository.id ) + repo = hg.repository( suc.get_configured_ui(), repository.repo_path( trans.app ) ) + tip_ctx = str( repo.changectx( repo.changelog.tip() ) ) + repository_metadata = get_latest_installable_repository_metadata_if_it_includes_tools( trans, repository ) + if repository_metadata and not repository_metadata.tools_functionally_correct: + return repository_metadata.changeset_revision + return None + +def filter_by_latest_downloadable_changeset_revision_that_has_missing_tool_test_components( trans, repository ): + """ + Inspect the latest installable changeset revision for the received repository to see if it includes tools that are either missing functional tests + or functional test data. If the changset revision includes tools, but is missing tool test components, return the changeset revision hash. + """ + encoded_repository_id = trans.security.encode_id( repository.id ) + repo = hg.repository( suc.get_configured_ui(), repository.repo_path( trans.app ) ) + tip_ctx = str( repo.changectx( repo.changelog.tip() ) ) + repository_metadata = get_latest_installable_repository_metadata_if_it_includes_tools( trans, repository ) + if repository_metadata and repository_metadata.missing_test_components: + return repository_metadata.changeset_revision + return None + +def filter_by_latest_downloadable_changeset_revision_that_has_no_failing_tool_tests( trans, repository ): + """ + Inspect the latest installable changeset revision for the received repository to see if it includes tools with no failing tests. + """ + encoded_repository_id = trans.security.encode_id( repository.id ) + repo = hg.repository( suc.get_configured_ui(), repository.repo_path( trans.app ) ) + tip_ctx = str( repo.changectx( repo.changelog.tip() ) ) + repository_metadata = get_latest_installable_repository_metadata_if_it_includes_tools( trans, repository ) + if repository_metadata and not repository_metadata.missing_test_components and repository_metadata.tools_functionally_correct: + return repository_metadata.changeset_revision + return None + +def get_latest_installable_repository_metadata_if_it_includes_tools( trans, repository ): + """Return the latest installable repository_metadata record for the received repository if one exists.""" + encoded_repository_id = trans.security.encode_id( repository.id ) + repo = hg.repository( suc.get_configured_ui(), repository.repo_path( trans.app ) ) + tip_ctx = str( repo.changectx( repo.changelog.tip() ) ) + repository_metadata = None + try: + repository_metadata = suc.get_repository_metadata_by_changeset_revision( trans, encoded_repository_id, tip_ctx ) + if repository_metadata and repository_metadata.includes_tools and repository_metadata.downloadable: + return repository_metadata + return None + except: + latest_installable_revision = suc.get_previous_downloadable_changeset_revision( repository, repo, tip_ctx ) + if latest_installable_revision == suc.INITIAL_CHANGELOG_HASH: + return None + repository_metadata = suc.get_repository_metadata_by_changeset_revision( trans, encoded_repository_id, latest_installable_revision ) + if repository_metadata and repository_metadata.includes_tools and repository_metadata.downloadable: + return repository_metadata + return None diff --git a/lib/tool_shed/util/shed_util_common.py b/lib/tool_shed/util/shed_util_common.py index 29590b61668..e7848353fcc 100644 --- a/lib/tool_shed/util/shed_util_common.py +++ b/lib/tool_shed/util/shed_util_common.py @@ -255,29 +255,6 @@ def create_or_update_tool_shed_repository( app, name, description, installed_cha sa_session.flush() return tool_shed_repository -def filter_by_latest_downloadable_changeset_revision_that_has_missing_tool_test_components( trans, repository ): - """ - Inspect the latest installable changeset revision for the received repository to see if it includes tool that are either missing functional tests - or functional test data. If the changset revision includes tools, but is missing tool test components, return the changeset revision hash. Otherwise - return the INITIAL_CHANGELOG_HASH. - """ - print_debug = repository.name == 'convert_chars' - encoded_repository_id = trans.security.encode_id( repository.id ) - repo = hg.repository( get_configured_ui(), repository.repo_path( trans.app ) ) - tip_ctx = str( repo.changectx( repo.changelog.tip() ) ) - repository_metadata = None - try: - repository_metadata = get_repository_metadata_by_changeset_revision( trans, encoded_repository_id, tip_ctx ) - except: - latest_installable_revision = get_previous_downloadable_changeset_revision( repository, repo, tip_ctx ) - if latest_installable_revision != INITIAL_CHANGELOG_HASH: - repository_metadata = get_repository_metadata_by_changeset_revision( trans, encoded_repository_id, latest_installable_revision ) - else: - repository_metadata = None - if repository_metadata and repository_metadata.downloadable and repository_metadata.missing_test_components: - return repository_metadata.changeset_revision - return INITIAL_CHANGELOG_HASH - def generate_clone_url_for_installed_repository( app, repository ): """Generate the URL for cloning a repository that has been installed into a Galaxy instance.""" tool_shed_url = get_url_from_tool_shed( app, repository.tool_shed ) diff --git a/templates/webapps/tool_shed/admin/index.mako b/templates/webapps/tool_shed/admin/index.mako index 4bfad14c1d4..461e142e218 100644 --- a/templates/webapps/tool_shed/admin/index.mako +++ b/templates/webapps/tool_shed/admin/index.mako @@ -96,9 +96,18 @@ Reviewing Repositories With Tools
    -
    - diff --git a/templates/webapps/tool_shed/index.mako b/templates/webapps/tool_shed/index.mako index 97bf15a7589..f9fead49179 100644 --- a/templates/webapps/tool_shed/index.mako +++ b/templates/webapps/tool_shed/index.mako @@ -164,7 +164,13 @@ From 508b5f3f3619bd8973c241a2f106a6a1b03d7c6c Mon Sep 17 00:00:00 2001 From: Daniel Blankenberg Date: Tue, 30 Apr 2013 17:19:11 -0400 Subject: [PATCH 266/523] Update GenomeSpace serverurl.properties file location. Should resolve network issues experienced with Jumbo Frames and the Broad. --- tools/genomespace/genomespace_exporter.py | 2 +- tools/genomespace/genomespace_file_browser.py | 2 +- tools/genomespace/genomespace_importer.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tools/genomespace/genomespace_exporter.py b/tools/genomespace/genomespace_exporter.py index ef4e0bea1ac..b0165741474 100644 --- a/tools/genomespace/genomespace_exporter.py +++ b/tools/genomespace/genomespace_exporter.py @@ -11,7 +11,7 @@ import simplejson log = logging.getLogger( "tools.genomespace.genomespace_exporter" )#( __name__ ) GENOMESPACE_API_VERSION_STRING = "v1.0" -GENOMESPACE_SERVER_URL_PROPERTIES = "http://www.genomespace.org/sites/genomespacefiles/config/serverurl.properties" +GENOMESPACE_SERVER_URL_PROPERTIES = "https://dm.genomespace.org/config/%s/serverurl.properties" % ( GENOMESPACE_API_VERSION_STRING ) CHUNK_SIZE = 2**20 #1mb diff --git a/tools/genomespace/genomespace_file_browser.py b/tools/genomespace/genomespace_file_browser.py index bfb924d7b72..bc6e35c8f39 100644 --- a/tools/genomespace/genomespace_file_browser.py +++ b/tools/genomespace/genomespace_file_browser.py @@ -9,7 +9,7 @@ pkg_resources.require( "simplejson" ) import simplejson GENOMESPACE_API_VERSION_STRING = "v1.0" -GENOMESPACE_SERVER_URL_PROPERTIES = "http://www.genomespace.org/sites/genomespacefiles/config/serverurl.properties" +GENOMESPACE_SERVER_URL_PROPERTIES = "https://dm.genomespace.org/config/%s/serverurl.properties" % ( GENOMESPACE_API_VERSION_STRING ) CHUNK_SIZE = 2**20 #1mb diff --git a/tools/genomespace/genomespace_importer.py b/tools/genomespace/genomespace_importer.py index a349af27d35..75977fc7363 100644 --- a/tools/genomespace/genomespace_importer.py +++ b/tools/genomespace/genomespace_importer.py @@ -13,7 +13,7 @@ from galaxy.datatypes import sniff from galaxy.datatypes.registry import Registry GENOMESPACE_API_VERSION_STRING = "v1.0" -GENOMESPACE_SERVER_URL_PROPERTIES = "http://www.genomespace.org/sites/genomespacefiles/config/serverurl.properties" +GENOMESPACE_SERVER_URL_PROPERTIES = "https://dm.genomespace.org/config/%s/serverurl.properties" % ( GENOMESPACE_API_VERSION_STRING ) CHUNK_SIZE = 2**20 #1mb From 2f7e0889fe4a60baafe2c27157b22a82eac782fe Mon Sep 17 00:00:00 2001 From: Dannon Baker Date: Tue, 30 Apr 2013 18:56:34 -0400 Subject: [PATCH 267/523] Update migration script 100 to use the new dialect name 'postgresql' -- still compatible with older versions too with 'postgres'. --- .../versions/0100_alter_tool_dependency_table_version_column.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/galaxy/model/migrate/versions/0100_alter_tool_dependency_table_version_column.py b/lib/galaxy/model/migrate/versions/0100_alter_tool_dependency_table_version_column.py index fd2bf328cbb..b2ac949a981 100644 --- a/lib/galaxy/model/migrate/versions/0100_alter_tool_dependency_table_version_column.py +++ b/lib/galaxy/model/migrate/versions/0100_alter_tool_dependency_table_version_column.py @@ -30,7 +30,7 @@ def upgrade(migrate_engine): metadata.reflect() ToolDependency_table = Table( "tool_dependency", metadata, autoload=True ) # Change the tool_dependency table's version column from TrimmedString to Text. - if migrate_engine.name == 'postgres': + if migrate_engine.name in ['postgresql', 'postgres']: cmd = "ALTER TABLE tool_dependency ALTER COLUMN version TYPE Text;" elif migrate_engine.name == 'mysql': cmd = "ALTER TABLE tool_dependency MODIFY COLUMN version Text;" From 039a02fb388cb1dbf52731a2bd6fb3e29a7a41d4 Mon Sep 17 00:00:00 2001 From: Dannon Baker Date: Tue, 30 Apr 2013 19:08:44 -0400 Subject: [PATCH 268/523] Remove assignmapper imports from migration 0080 -- it's gone now. --- lib/galaxy/model/migrate/versions/0080_quota_tables.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/galaxy/model/migrate/versions/0080_quota_tables.py b/lib/galaxy/model/migrate/versions/0080_quota_tables.py index 4e08a83284f..c9097c053ee 100644 --- a/lib/galaxy/model/migrate/versions/0080_quota_tables.py +++ b/lib/galaxy/model/migrate/versions/0080_quota_tables.py @@ -6,8 +6,8 @@ from sqlalchemy import * from sqlalchemy.orm import * from migrate import * from migrate.changeset import * -from galaxy.model.orm.ext.assignmapper import * -from galaxy.model.custom_types import * +# from galaxy.model.orm.ext.assignmapper import * +# from galaxy.model.custom_types import * import datetime now = datetime.datetime.utcnow @@ -16,7 +16,6 @@ import logging log = logging.getLogger( __name__ ) metadata = MetaData() -#db_session = scoped_session( sessionmaker( bind=migrate_engine, autoflush=False, autocommit=True ) ) # Tables to add From 1c77933b858118f62c4df77a2f3bbdd34e8e644f Mon Sep 17 00:00:00 2001 From: Dannon Baker Date: Tue, 30 Apr 2013 19:09:17 -0400 Subject: [PATCH 269/523] Migration 0084 -- don't add ForeignKey for sqlite. --- .../0084_add_ldda_id_to_implicit_conversion_table.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/galaxy/model/migrate/versions/0084_add_ldda_id_to_implicit_conversion_table.py b/lib/galaxy/model/migrate/versions/0084_add_ldda_id_to_implicit_conversion_table.py index c8a5aee11ff..c17616f5cd4 100644 --- a/lib/galaxy/model/migrate/versions/0084_add_ldda_id_to_implicit_conversion_table.py +++ b/lib/galaxy/model/migrate/versions/0084_add_ldda_id_to_implicit_conversion_table.py @@ -19,7 +19,10 @@ def upgrade(migrate_engine): metadata.reflect() try: Implicitly_converted_table = Table( "implicitly_converted_dataset_association", metadata, autoload=True ) - c = Column( "ldda_id", Integer, ForeignKey( "library_dataset_dataset_association.id" ), index=True, nullable=True ) + if migrate_engine.name != 'sqlite': + c = Column( "ldda_id", Integer, ForeignKey( "library_dataset_dataset_association.id" ), index=True, nullable=True ) + else: + c = Column( "ldda_id", Integer, index=True, nullable=True ) c.create( Implicitly_converted_table, index_name="ix_implicitly_converted_ds_assoc_ldda_id") assert c is Implicitly_converted_table.c.ldda_id except Exception, e: From 523cfff1cb5e68caba7f3e4d57091383e2cd6895 Mon Sep 17 00:00:00 2001 From: Dannon Baker Date: Tue, 30 Apr 2013 19:10:56 -0400 Subject: [PATCH 270/523] Fix conflicting util.sanitize_text import in controllers/admin.py --- lib/galaxy/webapps/galaxy/controllers/admin.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/lib/galaxy/webapps/galaxy/controllers/admin.py b/lib/galaxy/webapps/galaxy/controllers/admin.py index e4b8ca6be63..fdeb64c7b07 100644 --- a/lib/galaxy/webapps/galaxy/controllers/admin.py +++ b/lib/galaxy/webapps/galaxy/controllers/admin.py @@ -14,6 +14,7 @@ from tool_shed.util import encoding_util from tool_shed.util import common_util import galaxy.datatypes.registry import logging, imp, subprocess +from galaxy.util import sanitize_text log = logging.getLogger( __name__ ) @@ -474,7 +475,7 @@ class AdminGalaxy( BaseUIController, Admin, AdminActions, UsesQuotaMixin, QuotaP return trans.response.send_redirect( web.url_for( controller='admin', action='quotas', webapp=params.webapp, - message=util.sanitize_text( message ), + message=sanitize_text( message ), status='done' ) ) except MessageException, e: params.message = str( e ) @@ -597,7 +598,7 @@ class AdminGalaxy( BaseUIController, Admin, AdminActions, UsesQuotaMixin, QuotaP return trans.response.send_redirect( web.url_for( controller='admin', action='quotas', webapp=params.webapp, - message=util.sanitize_text( params.message ), + message=sanitize_text( params.message ), status='error' ) ) @web.expose @web.require_admin @@ -608,7 +609,7 @@ class AdminGalaxy( BaseUIController, Admin, AdminActions, UsesQuotaMixin, QuotaP return trans.response.send_redirect( web.url_for( controller='admin', action='quotas', webapp=params.webapp, - message=util.sanitize_text( params.message ), + message=sanitize_text( params.message ), status='error' ) ) @web.expose @web.require_admin @@ -619,7 +620,7 @@ class AdminGalaxy( BaseUIController, Admin, AdminActions, UsesQuotaMixin, QuotaP return trans.response.send_redirect( web.url_for( controller='admin', action='quotas', webapp=params.webapp, - message=util.sanitize_text( params.message ), + message=sanitize_text( params.message ), status='error' ) ) @web.expose @web.require_admin @@ -630,7 +631,7 @@ class AdminGalaxy( BaseUIController, Admin, AdminActions, UsesQuotaMixin, QuotaP return trans.response.send_redirect( web.url_for( controller='admin', action='quotas', webapp=params.webapp, - message=util.sanitize_text( params.message ), + message=sanitize_text( params.message ), status='error' ) ) def _quota_op( self, trans, do_op, op_method, kwd, listify=False ): params = self.get_quota_params( kwd ) @@ -646,7 +647,7 @@ class AdminGalaxy( BaseUIController, Admin, AdminActions, UsesQuotaMixin, QuotaP return None, trans.response.send_redirect( web.url_for( controller='admin', action='quotas', webapp=params.webapp, - message=util.sanitize_text( ', '.join( messages ) ), + message=sanitize_text( ', '.join( messages ) ), status='error' ) ) else: try: @@ -655,7 +656,7 @@ class AdminGalaxy( BaseUIController, Admin, AdminActions, UsesQuotaMixin, QuotaP return None, trans.response.send_redirect( web.url_for( controller='admin', action='quotas', webapp=params.webapp, - message=util.sanitize_text( str( e ) ), + message=sanitize_text( str( e ) ), status='error' ) ) if do_op == True or ( do_op != False and params.get( do_op, False ) ): try: @@ -663,7 +664,7 @@ class AdminGalaxy( BaseUIController, Admin, AdminActions, UsesQuotaMixin, QuotaP return None, trans.response.send_redirect( web.url_for( controller='admin', action='quotas', webapp=params.webapp, - message=util.sanitize_text( message ), + message=sanitize_text( message ), status='done' ) ) except MessageException, e: params.message = e.err_msg From 6c4b38b620908c4d691daf7cab8611fb7edeba91 Mon Sep 17 00:00:00 2001 From: Dannon Baker Date: Tue, 30 Apr 2013 19:11:56 -0400 Subject: [PATCH 271/523] Use migrate_engine.execute() instead of the nonexistent db_session -- migration 0100 --- .../0100_alter_tool_dependency_table_version_column.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/galaxy/model/migrate/versions/0100_alter_tool_dependency_table_version_column.py b/lib/galaxy/model/migrate/versions/0100_alter_tool_dependency_table_version_column.py index b2ac949a981..95554ea2791 100644 --- a/lib/galaxy/model/migrate/versions/0100_alter_tool_dependency_table_version_column.py +++ b/lib/galaxy/model/migrate/versions/0100_alter_tool_dependency_table_version_column.py @@ -22,7 +22,6 @@ handler.setFormatter( formatter ) log.addHandler( handler ) metadata = MetaData() -#db_session = scoped_session( sessionmaker( bind=migrate_engine, autoflush=False, autocommit=True ) ) def upgrade(migrate_engine): metadata.bind = migrate_engine @@ -46,7 +45,7 @@ def upgrade(migrate_engine): cmd = None if cmd: try: - db_session.execute( cmd ) + migrate_engine.execute( cmd ) except Exception, e: log.debug( "Altering tool_dependency.version column from TrimmedString(40) to Text failed: %s" % str( e ) ) def downgrade(migrate_engine): From ed1bfc8ec9a553e6247dd8621cf140323fd6d5d5 Mon Sep 17 00:00:00 2001 From: Dannon Baker Date: Tue, 30 Apr 2013 19:12:59 -0400 Subject: [PATCH 272/523] Remove commented out db_session creations. --- .../model/migrate/versions/0010_hda_display_at_authz_table.py | 1 - lib/galaxy/model/migrate/versions/0011_v0010_mysql_index_fix.py | 1 - lib/galaxy/model/migrate/versions/0017_library_item_indexes.py | 1 - lib/galaxy/model/migrate/versions/0020_library_upload_job.py | 1 - lib/galaxy/model/migrate/versions/0025_user_info.py | 1 - .../versions/0033_published_cols_for_histories_and_workflows.py | 1 - .../model/migrate/versions/0034_page_user_share_association.py | 1 - .../versions/0035_item_annotations_and_workflow_step_tags.py | 1 - lib/galaxy/model/migrate/versions/0040_page_annotations.py | 1 - lib/galaxy/model/migrate/versions/0041_workflow_invocation.py | 1 - lib/galaxy/model/migrate/versions/0046_post_job_actions.py | 1 - lib/galaxy/model/migrate/versions/0053_item_ratings.py | 1 - lib/galaxy/model/migrate/versions/0056_workflow_outputs.py | 1 - lib/galaxy/model/migrate/versions/0058_history_import_export.py | 1 - lib/galaxy/model/migrate/versions/0061_tasks.py | 1 - lib/galaxy/model/migrate/versions/0062_user_openid_table.py | 1 - lib/galaxy/model/migrate/versions/0063_sequencer_table.py | 1 - .../versions/0066_deferred_job_and_transfer_job_tables.py | 1 - .../versions/0070_add_info_column_to_deferred_job_table.py | 1 - .../migrate/versions/0071_add_history_and_workflow_to_sample.py | 1 - .../0072_add_pid_and_socket_columns_to_transfer_job_table.py | 1 - .../versions/0073_add_ldda_to_implicit_conversion_table.py | 1 - .../migrate/versions/0077_create_tool_tag_association_table.py | 1 - .../model/migrate/versions/0079_input_library_to_job_table.py | 1 - .../model/migrate/versions/0081_add_tool_version_to_hda_ldda.py | 1 - .../model/migrate/versions/0083_add_prepare_files_to_task.py | 1 - .../versions/0084_add_ldda_id_to_implicit_conversion_table.py | 1 - lib/galaxy/model/migrate/versions/0085_add_task_info.py | 1 - lib/galaxy/model/migrate/versions/0087_tool_id_guid_map_table.py | 1 - lib/galaxy/model/migrate/versions/0094_add_job_handler_col.py | 1 - lib/galaxy/model/migrate/versions/0095_hda_subsets.py | 1 - lib/galaxy/model/migrate/versions/0097_add_ctx_rev_column.py | 1 - .../migrate/versions/0102_add_tool_dependency_status_columns.py | 1 - lib/galaxy/model/migrate/versions/0106_add_missing_indexes.py | 1 - .../model/migrate/versions/0107_add_exit_code_to_job_and_task.py | 1 - 35 files changed, 35 deletions(-) diff --git a/lib/galaxy/model/migrate/versions/0010_hda_display_at_authz_table.py b/lib/galaxy/model/migrate/versions/0010_hda_display_at_authz_table.py index eba0bcdaa7d..c86bae70402 100644 --- a/lib/galaxy/model/migrate/versions/0010_hda_display_at_authz_table.py +++ b/lib/galaxy/model/migrate/versions/0010_hda_display_at_authz_table.py @@ -30,7 +30,6 @@ log.addHandler( handler ) from galaxy.model.custom_types import * metadata = MetaData() -#db_session = scoped_session( sessionmaker( bind=migrate_engine, autoflush=False, autocommit=True ) ) def display_migration_details(): print "========================================" diff --git a/lib/galaxy/model/migrate/versions/0011_v0010_mysql_index_fix.py b/lib/galaxy/model/migrate/versions/0011_v0010_mysql_index_fix.py index d7c1e1ad021..896c6495ab9 100644 --- a/lib/galaxy/model/migrate/versions/0011_v0010_mysql_index_fix.py +++ b/lib/galaxy/model/migrate/versions/0011_v0010_mysql_index_fix.py @@ -25,7 +25,6 @@ log.addHandler( handler ) from galaxy.model.custom_types import * metadata = MetaData() -#db_session = scoped_session( sessionmaker( bind=migrate_engine, autoflush=False, autocommit=True ) ) def display_migration_details(): print "========================================" diff --git a/lib/galaxy/model/migrate/versions/0017_library_item_indexes.py b/lib/galaxy/model/migrate/versions/0017_library_item_indexes.py index 6fee9758eec..3737256f5a4 100644 --- a/lib/galaxy/model/migrate/versions/0017_library_item_indexes.py +++ b/lib/galaxy/model/migrate/versions/0017_library_item_indexes.py @@ -16,7 +16,6 @@ handler.setFormatter( formatter ) log.addHandler( handler ) metadata = MetaData() -#db_session = scoped_session( sessionmaker( bind=migrate_engine, autoflush=False, autocommit=True ) ) def display_migration_details(): print "========================================" diff --git a/lib/galaxy/model/migrate/versions/0020_library_upload_job.py b/lib/galaxy/model/migrate/versions/0020_library_upload_job.py index edcb5c64af1..255b31c3f81 100644 --- a/lib/galaxy/model/migrate/versions/0020_library_upload_job.py +++ b/lib/galaxy/model/migrate/versions/0020_library_upload_job.py @@ -18,7 +18,6 @@ handler.setFormatter( formatter ) log.addHandler( handler ) metadata = MetaData() -#db_session = scoped_session( sessionmaker( bind=migrate_engine, autoflush=False, autocommit=True ) ) def display_migration_details(): print "" diff --git a/lib/galaxy/model/migrate/versions/0025_user_info.py b/lib/galaxy/model/migrate/versions/0025_user_info.py index d8d141b0e95..633e6bdc1da 100644 --- a/lib/galaxy/model/migrate/versions/0025_user_info.py +++ b/lib/galaxy/model/migrate/versions/0025_user_info.py @@ -20,7 +20,6 @@ handler.setFormatter( formatter ) log.addHandler( handler ) metadata = MetaData() -#db_session = scoped_session( sessionmaker( bind=migrate_engine, autoflush=False, autocommit=True ) ) def display_migration_details(): print "========================================" diff --git a/lib/galaxy/model/migrate/versions/0033_published_cols_for_histories_and_workflows.py b/lib/galaxy/model/migrate/versions/0033_published_cols_for_histories_and_workflows.py index a6caf68958b..e873e1121d3 100644 --- a/lib/galaxy/model/migrate/versions/0033_published_cols_for_histories_and_workflows.py +++ b/lib/galaxy/model/migrate/versions/0033_published_cols_for_histories_and_workflows.py @@ -12,7 +12,6 @@ import logging log = logging.getLogger( __name__ ) metadata = MetaData() -#db_session = scoped_session( sessionmaker( bind=migrate_engine, autoflush=False, autocommit=True ) ) def upgrade(migrate_engine): metadata.bind = migrate_engine diff --git a/lib/galaxy/model/migrate/versions/0034_page_user_share_association.py b/lib/galaxy/model/migrate/versions/0034_page_user_share_association.py index 2e6b39f38aa..7c378318c20 100644 --- a/lib/galaxy/model/migrate/versions/0034_page_user_share_association.py +++ b/lib/galaxy/model/migrate/versions/0034_page_user_share_association.py @@ -11,7 +11,6 @@ import logging log = logging.getLogger( __name__ ) metadata = MetaData() -#db_session = scoped_session( sessionmaker( bind=migrate_engine, autoflush=False, autocommit=True ) ) PageUserShareAssociation_table = Table( "page_user_share_association", metadata, Column( "id", Integer, primary_key=True ), diff --git a/lib/galaxy/model/migrate/versions/0035_item_annotations_and_workflow_step_tags.py b/lib/galaxy/model/migrate/versions/0035_item_annotations_and_workflow_step_tags.py index cdeb265970f..5ef9d0d5b9b 100644 --- a/lib/galaxy/model/migrate/versions/0035_item_annotations_and_workflow_step_tags.py +++ b/lib/galaxy/model/migrate/versions/0035_item_annotations_and_workflow_step_tags.py @@ -11,7 +11,6 @@ import logging log = logging.getLogger( __name__ ) metadata = MetaData() -#db_session = scoped_session( sessionmaker( bind=migrate_engine, autoflush=False, autocommit=True ) ) # Annotation tables. diff --git a/lib/galaxy/model/migrate/versions/0040_page_annotations.py b/lib/galaxy/model/migrate/versions/0040_page_annotations.py index 1c53c1e534e..db717def49a 100644 --- a/lib/galaxy/model/migrate/versions/0040_page_annotations.py +++ b/lib/galaxy/model/migrate/versions/0040_page_annotations.py @@ -11,7 +11,6 @@ import logging log = logging.getLogger( __name__ ) metadata = MetaData() -#db_session = scoped_session( sessionmaker( bind=migrate_engine, autoflush=False, autocommit=True ) ) PageAnnotationAssociation_table = Table( "page_annotation_association", metadata, Column( "id", Integer, primary_key=True ), diff --git a/lib/galaxy/model/migrate/versions/0041_workflow_invocation.py b/lib/galaxy/model/migrate/versions/0041_workflow_invocation.py index 380cde72a29..228ad54ed97 100644 --- a/lib/galaxy/model/migrate/versions/0041_workflow_invocation.py +++ b/lib/galaxy/model/migrate/versions/0041_workflow_invocation.py @@ -15,7 +15,6 @@ import datetime now = datetime.datetime.utcnow metadata = MetaData() -#db_session = scoped_session( sessionmaker( bind=migrate_engine, autoflush=False, autocommit=True ) ) WorkflowInvocation_table = Table( "workflow_invocation", metadata, Column( "id", Integer, primary_key=True ), diff --git a/lib/galaxy/model/migrate/versions/0046_post_job_actions.py b/lib/galaxy/model/migrate/versions/0046_post_job_actions.py index 3f730122f7f..9bdfaf2c3c7 100644 --- a/lib/galaxy/model/migrate/versions/0046_post_job_actions.py +++ b/lib/galaxy/model/migrate/versions/0046_post_job_actions.py @@ -18,7 +18,6 @@ import datetime now = datetime.datetime.utcnow metadata = MetaData() -#db_session = scoped_session( sessionmaker( bind=migrate_engine, autoflush=False, autocommit=True ) ) PostJobAction_table = Table("post_job_action", metadata, Column("id", Integer, primary_key=True), diff --git a/lib/galaxy/model/migrate/versions/0053_item_ratings.py b/lib/galaxy/model/migrate/versions/0053_item_ratings.py index bb0433a859f..0352c1b69c6 100644 --- a/lib/galaxy/model/migrate/versions/0053_item_ratings.py +++ b/lib/galaxy/model/migrate/versions/0053_item_ratings.py @@ -11,7 +11,6 @@ import logging log = logging.getLogger( __name__ ) metadata = MetaData() -#db_session = scoped_session( sessionmaker( bind=migrate_engine, autoflush=False, autocommit=True ) ) # Rating tables. diff --git a/lib/galaxy/model/migrate/versions/0056_workflow_outputs.py b/lib/galaxy/model/migrate/versions/0056_workflow_outputs.py index cbe05f1b8d4..a3be38eeabc 100644 --- a/lib/galaxy/model/migrate/versions/0056_workflow_outputs.py +++ b/lib/galaxy/model/migrate/versions/0056_workflow_outputs.py @@ -15,7 +15,6 @@ log = logging.getLogger( __name__ ) from galaxy.model.custom_types import * metadata = MetaData() -#db_session = scoped_session( sessionmaker( bind=migrate_engine, autoflush=False, autocommit=True ) ) WorkflowOutput_table = Table( "workflow_output", metadata, Column( "id", Integer, primary_key=True ), diff --git a/lib/galaxy/model/migrate/versions/0058_history_import_export.py b/lib/galaxy/model/migrate/versions/0058_history_import_export.py index 96b615f41e0..842a0895642 100644 --- a/lib/galaxy/model/migrate/versions/0058_history_import_export.py +++ b/lib/galaxy/model/migrate/versions/0058_history_import_export.py @@ -11,7 +11,6 @@ import logging log = logging.getLogger( __name__ ) metadata = MetaData() -#db_session = scoped_session( sessionmaker( bind=migrate_engine, autoflush=False, autocommit=True ) ) # Table to add. diff --git a/lib/galaxy/model/migrate/versions/0061_tasks.py b/lib/galaxy/model/migrate/versions/0061_tasks.py index 77d0145e936..e7d5d398cd8 100644 --- a/lib/galaxy/model/migrate/versions/0061_tasks.py +++ b/lib/galaxy/model/migrate/versions/0061_tasks.py @@ -12,7 +12,6 @@ import logging log = logging.getLogger( __name__ ) metadata = MetaData() -#db_session = scoped_session( sessionmaker( bind=migrate_engine, autoflush=False, autocommit=True ) ) now = datetime.datetime.utcnow Task_table = Table( "task", metadata, diff --git a/lib/galaxy/model/migrate/versions/0062_user_openid_table.py b/lib/galaxy/model/migrate/versions/0062_user_openid_table.py index 779ed1528bd..54192ab8a86 100644 --- a/lib/galaxy/model/migrate/versions/0062_user_openid_table.py +++ b/lib/galaxy/model/migrate/versions/0062_user_openid_table.py @@ -15,7 +15,6 @@ import logging log = logging.getLogger( __name__ ) metadata = MetaData() -#db_session = scoped_session( sessionmaker( bind=migrate_engine, autoflush=False, autocommit=True ) ) # Table to add diff --git a/lib/galaxy/model/migrate/versions/0063_sequencer_table.py b/lib/galaxy/model/migrate/versions/0063_sequencer_table.py index de6b895a45f..81875278bed 100644 --- a/lib/galaxy/model/migrate/versions/0063_sequencer_table.py +++ b/lib/galaxy/model/migrate/versions/0063_sequencer_table.py @@ -17,7 +17,6 @@ import logging log = logging.getLogger( __name__ ) metadata = MetaData() -#db_session = scoped_session( sessionmaker( bind=migrate_engine, autoflush=False, autocommit=True ) ) # Table to add Sequencer_table = Table( 'sequencer', metadata, diff --git a/lib/galaxy/model/migrate/versions/0066_deferred_job_and_transfer_job_tables.py b/lib/galaxy/model/migrate/versions/0066_deferred_job_and_transfer_job_tables.py index 2b4f4233dc8..8cb0a5590d7 100644 --- a/lib/galaxy/model/migrate/versions/0066_deferred_job_and_transfer_job_tables.py +++ b/lib/galaxy/model/migrate/versions/0066_deferred_job_and_transfer_job_tables.py @@ -17,7 +17,6 @@ import logging log = logging.getLogger( __name__ ) metadata = MetaData() -#db_session = scoped_session( sessionmaker( bind=migrate_engine, autoflush=False, autocommit=True ) ) # Table to add diff --git a/lib/galaxy/model/migrate/versions/0070_add_info_column_to_deferred_job_table.py b/lib/galaxy/model/migrate/versions/0070_add_info_column_to_deferred_job_table.py index a053325104f..3f7f72d243d 100644 --- a/lib/galaxy/model/migrate/versions/0070_add_info_column_to_deferred_job_table.py +++ b/lib/galaxy/model/migrate/versions/0070_add_info_column_to_deferred_job_table.py @@ -11,7 +11,6 @@ import logging log = logging.getLogger( __name__ ) metadata = MetaData() -#db_session = scoped_session( sessionmaker( bind=migrate_engine, autoflush=False, autocommit=True ) ) def upgrade(migrate_engine): metadata.bind = migrate_engine diff --git a/lib/galaxy/model/migrate/versions/0071_add_history_and_workflow_to_sample.py b/lib/galaxy/model/migrate/versions/0071_add_history_and_workflow_to_sample.py index b76b24ac48f..710f10e6bae 100644 --- a/lib/galaxy/model/migrate/versions/0071_add_history_and_workflow_to_sample.py +++ b/lib/galaxy/model/migrate/versions/0071_add_history_and_workflow_to_sample.py @@ -12,7 +12,6 @@ import logging log = logging.getLogger( __name__ ) metadata = MetaData() -#db_session = scoped_session( sessionmaker( bind=migrate_engine, autoflush=False, autocommit=True ) ) def upgrade(migrate_engine): metadata.bind = migrate_engine diff --git a/lib/galaxy/model/migrate/versions/0072_add_pid_and_socket_columns_to_transfer_job_table.py b/lib/galaxy/model/migrate/versions/0072_add_pid_and_socket_columns_to_transfer_job_table.py index 78cfdd55f5e..9dbeea7d91d 100644 --- a/lib/galaxy/model/migrate/versions/0072_add_pid_and_socket_columns_to_transfer_job_table.py +++ b/lib/galaxy/model/migrate/versions/0072_add_pid_and_socket_columns_to_transfer_job_table.py @@ -11,7 +11,6 @@ import logging log = logging.getLogger( __name__ ) metadata = MetaData() -#db_session = scoped_session( sessionmaker( bind=migrate_engine, autoflush=False, autocommit=True ) ) def upgrade(migrate_engine): metadata.bind = migrate_engine diff --git a/lib/galaxy/model/migrate/versions/0073_add_ldda_to_implicit_conversion_table.py b/lib/galaxy/model/migrate/versions/0073_add_ldda_to_implicit_conversion_table.py index 4c6ad256321..e6ba0ea03b3 100644 --- a/lib/galaxy/model/migrate/versions/0073_add_ldda_to_implicit_conversion_table.py +++ b/lib/galaxy/model/migrate/versions/0073_add_ldda_to_implicit_conversion_table.py @@ -11,7 +11,6 @@ import logging log = logging.getLogger( __name__ ) metadata = MetaData() -#db_session = scoped_session( sessionmaker( bind=migrate_engine, autoflush=False, autocommit=True ) ) def upgrade(migrate_engine): metadata.bind = migrate_engine diff --git a/lib/galaxy/model/migrate/versions/0077_create_tool_tag_association_table.py b/lib/galaxy/model/migrate/versions/0077_create_tool_tag_association_table.py index 88e3febd142..38a36bbcad3 100644 --- a/lib/galaxy/model/migrate/versions/0077_create_tool_tag_association_table.py +++ b/lib/galaxy/model/migrate/versions/0077_create_tool_tag_association_table.py @@ -16,7 +16,6 @@ import logging log = logging.getLogger( __name__ ) metadata = MetaData() -#db_session = scoped_session( sessionmaker( bind=migrate_engine, autoflush=False, autocommit=True ) ) # Table to add diff --git a/lib/galaxy/model/migrate/versions/0079_input_library_to_job_table.py b/lib/galaxy/model/migrate/versions/0079_input_library_to_job_table.py index 2241f92f25a..6e9c0af394e 100644 --- a/lib/galaxy/model/migrate/versions/0079_input_library_to_job_table.py +++ b/lib/galaxy/model/migrate/versions/0079_input_library_to_job_table.py @@ -11,7 +11,6 @@ import logging log = logging.getLogger( __name__ ) metadata = MetaData() -#db_session = scoped_session( sessionmaker( bind=migrate_engine, autoflush=False, autocommit=True ) ) JobToInputLibraryDatasetAssociation_table = Table( "job_to_input_library_dataset", metadata, Column( "id", Integer, primary_key=True ), diff --git a/lib/galaxy/model/migrate/versions/0081_add_tool_version_to_hda_ldda.py b/lib/galaxy/model/migrate/versions/0081_add_tool_version_to_hda_ldda.py index 170a2f6dd24..6efec998795 100644 --- a/lib/galaxy/model/migrate/versions/0081_add_tool_version_to_hda_ldda.py +++ b/lib/galaxy/model/migrate/versions/0081_add_tool_version_to_hda_ldda.py @@ -10,7 +10,6 @@ from migrate.changeset import * from galaxy.model.custom_types import * metadata = MetaData() -#db_session = scoped_session( sessionmaker( bind=migrate_engine, autoflush=False, autocommit=True ) ) def upgrade(migrate_engine): metadata.bind = migrate_engine diff --git a/lib/galaxy/model/migrate/versions/0083_add_prepare_files_to_task.py b/lib/galaxy/model/migrate/versions/0083_add_prepare_files_to_task.py index a1d13a51d8d..0a0b75f293e 100644 --- a/lib/galaxy/model/migrate/versions/0083_add_prepare_files_to_task.py +++ b/lib/galaxy/model/migrate/versions/0083_add_prepare_files_to_task.py @@ -11,7 +11,6 @@ import logging log = logging.getLogger( __name__ ) metadata = MetaData() -#db_session = scoped_session( sessionmaker( bind=migrate_engine, autoflush=False, autocommit=True ) ) def upgrade(migrate_engine): metadata.bind = migrate_engine diff --git a/lib/galaxy/model/migrate/versions/0084_add_ldda_id_to_implicit_conversion_table.py b/lib/galaxy/model/migrate/versions/0084_add_ldda_id_to_implicit_conversion_table.py index c17616f5cd4..f3ae10e6a20 100644 --- a/lib/galaxy/model/migrate/versions/0084_add_ldda_id_to_implicit_conversion_table.py +++ b/lib/galaxy/model/migrate/versions/0084_add_ldda_id_to_implicit_conversion_table.py @@ -11,7 +11,6 @@ import logging log = logging.getLogger( __name__ ) metadata = MetaData() -#db_session = scoped_session( sessionmaker( bind=migrate_engine, autoflush=False, autocommit=True ) ) def upgrade(migrate_engine): metadata.bind = migrate_engine diff --git a/lib/galaxy/model/migrate/versions/0085_add_task_info.py b/lib/galaxy/model/migrate/versions/0085_add_task_info.py index 770187718ca..92ccc6d2d78 100644 --- a/lib/galaxy/model/migrate/versions/0085_add_task_info.py +++ b/lib/galaxy/model/migrate/versions/0085_add_task_info.py @@ -12,7 +12,6 @@ log = logging.getLogger( __name__ ) from galaxy.model.custom_types import TrimmedString metadata = MetaData() -#db_session = scoped_session( sessionmaker( bind=migrate_engine, autoflush=False, autocommit=True ) ) def upgrade(migrate_engine): metadata.bind = migrate_engine diff --git a/lib/galaxy/model/migrate/versions/0087_tool_id_guid_map_table.py b/lib/galaxy/model/migrate/versions/0087_tool_id_guid_map_table.py index 60abd129cb0..d14f0562e7a 100644 --- a/lib/galaxy/model/migrate/versions/0087_tool_id_guid_map_table.py +++ b/lib/galaxy/model/migrate/versions/0087_tool_id_guid_map_table.py @@ -22,7 +22,6 @@ handler.setFormatter( formatter ) log.addHandler( handler ) metadata = MetaData() -#db_session = scoped_session( sessionmaker( bind=migrate_engine, autoflush=False, autocommit=True ) ) ToolIdGuidMap_table = Table( "tool_id_guid_map", metadata, Column( "id", Integer, primary_key=True ), diff --git a/lib/galaxy/model/migrate/versions/0094_add_job_handler_col.py b/lib/galaxy/model/migrate/versions/0094_add_job_handler_col.py index 731538bbc8c..15f3dee9013 100644 --- a/lib/galaxy/model/migrate/versions/0094_add_job_handler_col.py +++ b/lib/galaxy/model/migrate/versions/0094_add_job_handler_col.py @@ -14,7 +14,6 @@ log = logging.getLogger( __name__ ) from galaxy.model.custom_types import * metadata = MetaData() -#db_session = scoped_session( sessionmaker( bind=migrate_engine, autoflush=False, autocommit=True ) ) # Column to add. handler_col = Column( "handler", TrimmedString(255), index=True ) diff --git a/lib/galaxy/model/migrate/versions/0095_hda_subsets.py b/lib/galaxy/model/migrate/versions/0095_hda_subsets.py index d1a168399c8..0dedcf2895a 100644 --- a/lib/galaxy/model/migrate/versions/0095_hda_subsets.py +++ b/lib/galaxy/model/migrate/versions/0095_hda_subsets.py @@ -11,7 +11,6 @@ import logging log = logging.getLogger( __name__ ) metadata = MetaData() -#db_session = scoped_session( sessionmaker( bind=migrate_engine, autoflush=False, autocommit=True ) ) # Table to add. diff --git a/lib/galaxy/model/migrate/versions/0097_add_ctx_rev_column.py b/lib/galaxy/model/migrate/versions/0097_add_ctx_rev_column.py index a22c493dd95..4d2acc5551a 100644 --- a/lib/galaxy/model/migrate/versions/0097_add_ctx_rev_column.py +++ b/lib/galaxy/model/migrate/versions/0097_add_ctx_rev_column.py @@ -22,7 +22,6 @@ handler.setFormatter( formatter ) log.addHandler( handler ) metadata = MetaData() -#db_session = scoped_session( sessionmaker( bind=migrate_engine, autoflush=False, autocommit=True ) ) def upgrade(migrate_engine): metadata.bind = migrate_engine diff --git a/lib/galaxy/model/migrate/versions/0102_add_tool_dependency_status_columns.py b/lib/galaxy/model/migrate/versions/0102_add_tool_dependency_status_columns.py index f089dc3d50d..f177ab69a1b 100644 --- a/lib/galaxy/model/migrate/versions/0102_add_tool_dependency_status_columns.py +++ b/lib/galaxy/model/migrate/versions/0102_add_tool_dependency_status_columns.py @@ -22,7 +22,6 @@ handler.setFormatter( formatter ) log.addHandler( handler ) metadata = MetaData() -#db_session = scoped_session( sessionmaker( bind=migrate_engine, autoflush=False, autocommit=True ) ) def upgrade(migrate_engine): metadata.bind = migrate_engine diff --git a/lib/galaxy/model/migrate/versions/0106_add_missing_indexes.py b/lib/galaxy/model/migrate/versions/0106_add_missing_indexes.py index ced488710c1..529eb93dbe9 100644 --- a/lib/galaxy/model/migrate/versions/0106_add_missing_indexes.py +++ b/lib/galaxy/model/migrate/versions/0106_add_missing_indexes.py @@ -19,7 +19,6 @@ handler.setFormatter( formatter ) log.addHandler( handler ) metadata = MetaData() -#db_session = scoped_session( sessionmaker( bind=migrate_engine, autoflush=False, autocommit=True ) ) indexes = ( ( "ix_metadata_file_lda_id", 'metadata_file', 'lda_id' ), # 0003 diff --git a/lib/galaxy/model/migrate/versions/0107_add_exit_code_to_job_and_task.py b/lib/galaxy/model/migrate/versions/0107_add_exit_code_to_job_and_task.py index 5ffa773e217..6c9b73b55b1 100644 --- a/lib/galaxy/model/migrate/versions/0107_add_exit_code_to_job_and_task.py +++ b/lib/galaxy/model/migrate/versions/0107_add_exit_code_to_job_and_task.py @@ -14,7 +14,6 @@ log = logging.getLogger( __name__ ) from galaxy.model.custom_types import * metadata = MetaData() -#db_session = scoped_session( sessionmaker( bind=migrate_engine, autoflush=False, autocommit=True ) ) # There was a bug when only one column was used for both tables, # so create separate columns. From e63c57306f1cad16f9369abcf1ba5494e599257c Mon Sep 17 00:00:00 2001 From: Dannon Baker Date: Tue, 30 Apr 2013 19:18:37 -0400 Subject: [PATCH 273/523] Migration 0062 -- fix db_session/migrate_engine change. --- lib/galaxy/model/migrate/versions/0062_user_openid_table.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/galaxy/model/migrate/versions/0062_user_openid_table.py b/lib/galaxy/model/migrate/versions/0062_user_openid_table.py index 54192ab8a86..aae4702e203 100644 --- a/lib/galaxy/model/migrate/versions/0062_user_openid_table.py +++ b/lib/galaxy/model/migrate/versions/0062_user_openid_table.py @@ -41,7 +41,7 @@ def upgrade(migrate_engine): ix_name = 'ix_galaxy_user_openid_openid' if migrate_engine.name == 'mysql': i = "ALTER TABLE galaxy_user_openid ADD UNIQUE INDEX ( openid( 255 ) )" - db_session.execute( i ) + migrate_engine.execute( i ) else: i = Index( ix_name, UserOpenID_table.c.openid, unique=True ) try: From d143f53c74514937ff24dbca3589ecda6decc4a4 Mon Sep 17 00:00:00 2001 From: Dannon Baker Date: Tue, 30 Apr 2013 19:54:45 -0400 Subject: [PATCH 274/523] Migration script 0102, skip dropping uninstalled column in tool_dependency table under sqlite -- sqlalchemy_migrate check constraint boolean issue causes total failure, dropping of the table. --- .../0102_add_tool_dependency_status_columns.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/lib/galaxy/model/migrate/versions/0102_add_tool_dependency_status_columns.py b/lib/galaxy/model/migrate/versions/0102_add_tool_dependency_status_columns.py index f177ab69a1b..54eb23d2fb0 100644 --- a/lib/galaxy/model/migrate/versions/0102_add_tool_dependency_status_columns.py +++ b/lib/galaxy/model/migrate/versions/0102_add_tool_dependency_status_columns.py @@ -43,10 +43,14 @@ def upgrade(migrate_engine): assert col is ToolDependency_table.c.error_message except Exception, e: print "Adding error_message column to the tool_dependency table failed: %s" % str( e ) - try: - ToolDependency_table.c.uninstalled.drop() - except Exception, e: - print "Dropping uninstalled column from the tool_dependency table failed: %s" % str( e ) + + if migrate_engine.name != 'sqlite': + #This breaks in sqlite due to failure to drop check constraint. + # TODO move to alembic. + try: + ToolDependency_table.c.uninstalled.drop() + except Exception, e: + print "Dropping uninstalled column from the tool_dependency table failed: %s" % str( e ) def downgrade(migrate_engine): metadata.bind = migrate_engine metadata.reflect() From 3f4a35b40f3f4419381ffb6161fc4768930bd6cd Mon Sep 17 00:00:00 2001 From: Dannon Baker Date: Tue, 30 Apr 2013 20:18:29 -0400 Subject: [PATCH 275/523] Prevent ambiguous util import in requests_common.py, clean up other imports. --- .../webapps/galaxy/controllers/requests_common.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/lib/galaxy/webapps/galaxy/controllers/requests_common.py b/lib/galaxy/webapps/galaxy/controllers/requests_common.py index 549ebe99b32..ffc436ac832 100644 --- a/lib/galaxy/webapps/galaxy/controllers/requests_common.py +++ b/lib/galaxy/webapps/galaxy/controllers/requests_common.py @@ -1,11 +1,13 @@ -from galaxy.web.base.controller import * +import csv +import logging +import re +from galaxy import web +from galaxy.web.base.controller import BaseUIController, UsesFormDefinitionsMixin from galaxy.web.framework.helpers import time_ago, iff, grids -from galaxy.model.orm import * +from galaxy.model.orm import select, func, and_ from galaxy import model, util -from galaxy.util.odict import odict -from galaxy.web.form_builder import * +from galaxy.web.form_builder import TextField, CheckboxField, build_select_field, SelectField from galaxy.security.validate_user_input import validate_email -import logging, os, csv log = logging.getLogger( __name__ ) From 3a842894ca9a2cf5862c063be47cbb5bc86f9cba Mon Sep 17 00:00:00 2001 From: Dannon Baker Date: Tue, 30 Apr 2013 20:19:00 -0400 Subject: [PATCH 276/523] Sort imports. --- .../webapps/galaxy/controllers/requests_common.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/lib/galaxy/webapps/galaxy/controllers/requests_common.py b/lib/galaxy/webapps/galaxy/controllers/requests_common.py index ffc436ac832..95b2d36d07a 100644 --- a/lib/galaxy/webapps/galaxy/controllers/requests_common.py +++ b/lib/galaxy/webapps/galaxy/controllers/requests_common.py @@ -1,13 +1,12 @@ import csv import logging import re -from galaxy import web -from galaxy.web.base.controller import BaseUIController, UsesFormDefinitionsMixin -from galaxy.web.framework.helpers import time_ago, iff, grids -from galaxy.model.orm import select, func, and_ -from galaxy import model, util -from galaxy.web.form_builder import TextField, CheckboxField, build_select_field, SelectField +from galaxy import model, util, web +from galaxy.model.orm import and_, func, select from galaxy.security.validate_user_input import validate_email +from galaxy.web.base.controller import BaseUIController, UsesFormDefinitionsMixin +from galaxy.web.form_builder import build_select_field, CheckboxField, SelectField, TextField +from galaxy.web.framework.helpers import grids, iff, time_ago log = logging.getLogger( __name__ ) From f6f49f9c3c9881a67f7e1a839f992b215a718c0d Mon Sep 17 00:00:00 2001 From: Dannon Baker Date: Tue, 30 Apr 2013 20:22:46 -0400 Subject: [PATCH 277/523] Async controller -- clean up imports, remove *'s, fix potentially ambiguous util import. --- lib/galaxy/webapps/galaxy/controllers/async.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/galaxy/webapps/galaxy/controllers/async.py b/lib/galaxy/webapps/galaxy/controllers/async.py index 6653c10d361..45da3ab9844 100644 --- a/lib/galaxy/webapps/galaxy/controllers/async.py +++ b/lib/galaxy/webapps/galaxy/controllers/async.py @@ -2,12 +2,12 @@ Upload class """ -from galaxy.web.base.controller import * +import logging, urllib -from galaxy import jobs, util, datatypes, web - -import logging, urllib, sys -from galaxy.util.hash_util import * +from galaxy import jobs, util, web +from galaxy.util import Params +from galaxy.util.hash_util import hmac_new +from galaxy.web.base.controller import BaseUIController log = logging.getLogger( __name__ ) @@ -32,7 +32,7 @@ class ASync( BaseUIController ): return trans.response.send_redirect( "/index" ) history = trans.get_history( create=True ) - params = util.Params(kwd, sanitize=False) + params = Params(kwd, sanitize=False) STATUS = params.STATUS URL = params.URL data_id = params.data_id From c9450ed8346603f84d6192d302951f7397d58aa6 Mon Sep 17 00:00:00 2001 From: Greg Von Kuster Date: Tue, 30 Apr 2013 20:49:20 -0400 Subject: [PATCH 278/523] Filter out deleted and deprecated repositories from the new tool shed lists for reviewing repositories with tool, and filter out repositories with missing tool components from the list that shows failing tool tests. --- lib/tool_shed/grids/repository_grids.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/lib/tool_shed/grids/repository_grids.py b/lib/tool_shed/grids/repository_grids.py index 37a15758dfc..2a2c15f5390 100644 --- a/lib/tool_shed/grids/repository_grids.py +++ b/lib/tool_shed/grids/repository_grids.py @@ -537,7 +537,9 @@ class RepositoriesMissingToolTestComponentsGrid( RepositoryGrid ): def build_initial_query( self, trans, **kwd ): # Filter by latest installable revisions that contain tools with missing tool test components. revision_clause_list = [] - for repository in trans.sa_session.query( model.Repository ): + for repository in trans.sa_session.query( model.Repository ) \ + .filter( and_( model.Repository.table.c.deprecated == False, + model.Repository.table.c.deleted == False ) ): changeset_revision = filter_by_latest_downloadable_changeset_revision_that_has_missing_tool_test_components( trans, repository ) if changeset_revision: revision_clause_list.append( model.RepositoryMetadata.table.c.changeset_revision == changeset_revision ) @@ -614,7 +616,9 @@ class RepositoriesWithFailingToolTestsGrid( RepositoryGrid ): def build_initial_query( self, trans, **kwd ): # Filter by latest installable revisions that contain tools with at least 1 failing tool test. revision_clause_list = [] - for repository in trans.sa_session.query( model.Repository ): + for repository in trans.sa_session.query( model.Repository ) \ + .filter( and_( model.Repository.table.c.deprecated == False, + model.Repository.table.c.deleted == False ) ): changeset_revision = filter_by_latest_downloadable_changeset_revision_that_has_failing_tool_tests( trans, repository ) if changeset_revision: revision_clause_list.append( model.RepositoryMetadata.table.c.changeset_revision == changeset_revision ) @@ -692,7 +696,9 @@ class RepositoriesWithNoFailingToolTestsGrid( RepositoryGrid ): # We have the list of repositories that the current user is authorized to update, so filter further by latest installable revisions that contain # tools with at least 1 failing tool test. revision_clause_list = [] - for repository in trans.sa_session.query( model.Repository ): + for repository in trans.sa_session.query( model.Repository ) \ + .filter( and_( model.Repository.table.c.deprecated == False, + model.Repository.table.c.deleted == False ) ): changeset_revision = filter_by_latest_downloadable_changeset_revision_that_has_no_failing_tool_tests( trans, repository ) if changeset_revision: revision_clause_list.append( model.RepositoryMetadata.table.c.changeset_revision == changeset_revision ) @@ -1345,7 +1351,7 @@ def filter_by_latest_downloadable_changeset_revision_that_has_failing_tool_tests repo = hg.repository( suc.get_configured_ui(), repository.repo_path( trans.app ) ) tip_ctx = str( repo.changectx( repo.changelog.tip() ) ) repository_metadata = get_latest_installable_repository_metadata_if_it_includes_tools( trans, repository ) - if repository_metadata and not repository_metadata.tools_functionally_correct: + if repository_metadata and not repository_metadata.missing_test_components and not repository_metadata.tools_functionally_correct: return repository_metadata.changeset_revision return None From 57c89d742ea40fb4113e20cb370477c12fb46026 Mon Sep 17 00:00:00 2001 From: Dannon Baker Date: Tue, 30 Apr 2013 23:24:55 -0400 Subject: [PATCH 279/523] Functional test setup fix for sqlite -- pool_size is an invalid option in sqlalchemy7+. --- scripts/functional_tests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/functional_tests.py b/scripts/functional_tests.py index 43a181881aa..16e7da4672d 100644 --- a/scripts/functional_tests.py +++ b/scripts/functional_tests.py @@ -220,6 +220,7 @@ def main(): global_conf = None if not database_connection.startswith( 'sqlite://' ): kwargs[ 'database_engine_option_max_overflow' ] = '20' + kwargs[ 'database_engine_option_pool_size' ] = '10' if tool_dependency_dir is not None: kwargs[ 'tool_dependency_dir' ] = tool_dependency_dir if use_distributed_object_store: @@ -230,7 +231,6 @@ def main(): id_secret = 'changethisinproductiontoo', template_path = "templates", database_connection = database_connection, - database_engine_option_pool_size = '10', file_path = file_path, new_file_path = new_file_path, tool_path = tool_path, From 362f8e634c5f18770827cccccbe277cbcfab97fe Mon Sep 17 00:00:00 2001 From: Dannon Baker Date: Wed, 1 May 2013 00:04:56 -0400 Subject: [PATCH 280/523] Admin controller -- fix ambiguous 'util' import; remove all import *'s and refactor --- .../webapps/galaxy/controllers/admin.py | 48 ++++++++++--------- 1 file changed, 26 insertions(+), 22 deletions(-) diff --git a/lib/galaxy/webapps/galaxy/controllers/admin.py b/lib/galaxy/webapps/galaxy/controllers/admin.py index fdeb64c7b07..526dbc999fe 100644 --- a/lib/galaxy/webapps/galaxy/controllers/admin.py +++ b/lib/galaxy/webapps/galaxy/controllers/admin.py @@ -1,20 +1,24 @@ -from galaxy.web.base.controller import * -from galaxy.web.base.controllers.admin import Admin +import imp +import logging +import os + +import galaxy.util from galaxy import model -from galaxy.model.orm import * -from galaxy.web.framework.helpers import time_ago, iff, grids -from galaxy.tools.search import ToolBoxSearch -from galaxy.tools import ToolSection, json_fix -from galaxy.util import parse_xml, inflector +from galaxy import web from galaxy.actions.admin import AdminActions -from galaxy.web.params import QuotaParamParser -from galaxy.exceptions import * -from galaxy.util.odict import * -from tool_shed.util import encoding_util -from tool_shed.util import common_util -import galaxy.datatypes.registry -import logging, imp, subprocess +from galaxy.exceptions import MessageException from galaxy.util import sanitize_text +from galaxy.util.odict import odict +from galaxy.web import url_for +from galaxy.web.base.controller import BaseUIController, UsesQuotaMixin +from galaxy.web.base.controllers.admin import Admin +from galaxy.web.framework.helpers import grids, time_ago +from galaxy.web.params import QuotaParamParser +from tool_shed.util import common_util +from tool_shed.util import encoding_util +# from galaxy.model.orm import * +# from galaxy.util.odict import * +# import galaxy.datatypes.registry log = logging.getLogger( __name__ ) @@ -638,7 +642,7 @@ class AdminGalaxy( BaseUIController, Admin, AdminActions, UsesQuotaMixin, QuotaP if listify: quota = [] messages = [] - for id in util.listify( params.id ): + for id in galaxy.util.listify( params.id ): try: quota.append( self.get_quota( trans, id ) ) except MessageException, e: @@ -701,7 +705,7 @@ class AdminGalaxy( BaseUIController, Admin, AdminActions, UsesQuotaMixin, QuotaP def check_for_tool_dependencies( self, trans, migration_stage ): # Get the 000x_tools.xml file associated with migration_stage. tools_xml_file_path = os.path.abspath( os.path.join( trans.app.config.root, 'scripts', 'migrate_tools', '%04d_tools.xml' % migration_stage ) ) - tree = util.parse_xml( tools_xml_file_path ) + tree = galaxy.util.parse_xml( tools_xml_file_path ) root = tree.getroot() tool_shed = root.get( 'name' ) tool_shed_url = self.get_tool_shed_url_from_tools_xml_file_path( trans, tool_shed ) @@ -729,8 +733,8 @@ class AdminGalaxy( BaseUIController, Admin, AdminActions, UsesQuotaMixin, QuotaP @web.expose @web.require_admin def review_tool_migration_stages( self, trans, **kwd ): - message = util.restore_text( kwd.get( 'message', '' ) ) - status = util.restore_text( kwd.get( 'status', 'done' ) ) + message = galaxy.util.restore_text( kwd.get( 'message', '' ) ) + status = galaxy.util.restore_text( kwd.get( 'status', 'done' ) ) migration_stages_dict = odict() migration_modules = [] migration_scripts_dir = os.path.abspath( os.path.join( trans.app.config.root, 'lib', 'tool_shed', 'galaxy_install', 'migrate', 'versions' ) ) @@ -760,12 +764,12 @@ class AdminGalaxy( BaseUIController, Admin, AdminActions, UsesQuotaMixin, QuotaP @web.expose @web.require_admin def view_datatypes_registry( self, trans, **kwd ): - message = util.restore_text( kwd.get( 'message', '' ) ) - status = util.restore_text( kwd.get( 'status', 'done' ) ) + message = galaxy.util.restore_text( kwd.get( 'message', '' ) ) + status = galaxy.util.restore_text( kwd.get( 'status', 'done' ) ) return trans.fill_template( 'admin/view_datatypes_registry.mako', message=message, status=status ) @web.expose @web.require_admin def view_tool_data_tables( self, trans, **kwd ): - message = util.restore_text( kwd.get( 'message', '' ) ) - status = util.restore_text( kwd.get( 'status', 'done' ) ) + message = galaxy.util.restore_text( kwd.get( 'message', '' ) ) + status = galaxy.util.restore_text( kwd.get( 'status', 'done' ) ) return trans.fill_template( 'admin/view_data_tables_registry.mako', message=message, status=status ) From 0de70d7f96ba73cc1f1907cc82463e6fc00a5212 Mon Sep 17 00:00:00 2001 From: Dannon Baker Date: Wed, 1 May 2013 00:05:14 -0400 Subject: [PATCH 281/523] History controller -- fix ambiguous 'util' import; remove all import *'s and refactor --- .../webapps/galaxy/controllers/history.py | 61 +++++++++---------- 1 file changed, 30 insertions(+), 31 deletions(-) diff --git a/lib/galaxy/webapps/galaxy/controllers/history.py b/lib/galaxy/webapps/galaxy/controllers/history.py index 7efa95db134..ae79773b55f 100644 --- a/lib/galaxy/webapps/galaxy/controllers/history.py +++ b/lib/galaxy/webapps/galaxy/controllers/history.py @@ -1,22 +1,21 @@ -from galaxy.web.base.controller import * -from galaxy.web.framework.helpers import time_ago, iff, grids +import logging +from cgi import escape + +# from sqlalchemy.sql.expression import and_, func + +import galaxy.util +from galaxy import model +from galaxy import web from galaxy.datatypes.data import nice_size -from galaxy import model, util +from galaxy.model.item_attrs import UsesAnnotations, UsesItemRatings +from galaxy.model.orm import and_, eagerload_all, func from galaxy.util import Params from galaxy.util.odict import odict -from galaxy.model.mapping import desc -from galaxy.model.orm import * -from galaxy.model.item_attrs import * -from galaxy.util.json import * from galaxy.util.sanitize_html import sanitize_html -from galaxy.tools.parameters.basic import UnvalidatedValue -from galaxy.tools.actions import upload_common -from galaxy.tags.tag_handler import GalaxyTagHandler -from sqlalchemy.sql.expression import ClauseElement, func -from sqlalchemy.sql import select -import webhelpers, logging, operator, os, tempfile, subprocess, shutil, tarfile -from datetime import datetime -from cgi import escape +from galaxy.web import error, url_for +from galaxy.web.base.controller import BaseUIController, SharableMixin, UsesHistoryDatasetAssociationMixin, UsesHistoryMixin +from galaxy.web.base.controller import ERROR, INFO, SUCCESS, WARNING +from galaxy.web.framework.helpers import grids, iff, time_ago log = logging.getLogger( __name__ ) @@ -238,7 +237,7 @@ class HistoryController( BaseUIController, SharableMixin, UsesAnnotations, UsesI show_deleted=history.deleted, use_panels=False ) ) #return self.view( trans, id=kwargs['id'], show_deleted=history.deleted, use_panels=False ) - history_ids = util.listify( kwargs.get( 'id', [] ) ) + history_ids = galaxy.util.listify( kwargs.get( 'id', [] ) ) # Display no message by default status, message = None, None refresh_history = False @@ -396,10 +395,10 @@ class HistoryController( BaseUIController, SharableMixin, UsesAnnotations, UsesI @web.require_login( "work with shared histories" ) def list_shared( self, trans, **kwargs ): """List histories shared with current user by others""" - msg = util.restore_text( kwargs.get( 'msg', '' ) ) + msg = galaxy.util.restore_text( kwargs.get( 'msg', '' ) ) status = message = None if 'operation' in kwargs: - ids = util.listify( kwargs.get( 'id', [] ) ) + ids = galaxy.util.listify( kwargs.get( 'id', [] ) ) operation = kwargs['operation'].lower() if operation == "view": # Display history. @@ -572,7 +571,7 @@ class HistoryController( BaseUIController, SharableMixin, UsesAnnotations, UsesI @web.expose def unhide_datasets( self, trans, current=False, ids=None ): """Unhide the datasets in the active history -- this does not require a logged in user.""" - if not ids and util.string_as_bool( current ): + if not ids and galaxy.util.string_as_bool( current ): histories = [ trans.get_history() ] refresh_frames = ['history'] else: @@ -586,7 +585,7 @@ class HistoryController( BaseUIController, SharableMixin, UsesAnnotations, UsesI @web.expose def resume_paused_jobs( self, trans, current=False, ids=None ): """Resume paused jobs the active history -- this does not require a logged in user.""" - if not ids and util.string_as_bool( current ): + if not ids and galaxy.util.string_as_bool( current ): histories = [ trans.get_history() ] refresh_frames = ['history'] else: @@ -873,10 +872,10 @@ class HistoryController( BaseUIController, SharableMixin, UsesAnnotations, UsesI and ( not history_to_view.importable ) ): error( "Either you are not allowed to view this history or the owner of this history has not made it accessible." ) # View history. - show_deleted = util.string_as_bool( show_deleted ) + show_deleted = galaxy.util.string_as_bool( show_deleted ) datasets = self.get_history_datasets( trans, history_to_view, show_deleted=show_deleted ) try: - use_panels = util.string_as_bool( use_panels ) + use_panels = galaxy.util.string_as_bool( use_panels ) except: pass # already a bool return trans.stream_template_mako( "history/view.mako", @@ -926,7 +925,7 @@ class HistoryController( BaseUIController, SharableMixin, UsesAnnotations, UsesI session = trans.sa_session # Id values take precedence over histories passed in; last resort is current history. if id: - ids = util.listify( id ) + ids = galaxy.util.listify( id ) if ids: histories = [ self.get_history( trans, history_id ) for history_id in ids ] elif not histories: @@ -986,12 +985,12 @@ class HistoryController( BaseUIController, SharableMixin, UsesAnnotations, UsesI # TODO: we have too many error messages floating around in here - we need # to incorporate the messaging system used by the libraries that will display # a message on any page. - err_msg = util.restore_text( params.get( 'err_msg', '' ) ) + err_msg = galaxy.util.restore_text( params.get( 'err_msg', '' ) ) if not email: if not id: # Default to the current history id = trans.security.encode_id( trans.history.id ) - id = util.listify( id ) + id = galaxy.util.listify( id ) send_to_err = err_msg histories = [] for history_id in id: @@ -1099,13 +1098,13 @@ class HistoryController( BaseUIController, SharableMixin, UsesAnnotations, UsesI if not id: # Default to the current history id = trans.security.encode_id( trans.history.id ) - id = util.listify( id ) + id = galaxy.util.listify( id ) send_to_err = "" histories = [] for history_id in id: histories.append( self.get_history( trans, history_id ) ) send_to_users = [] - for email_address in util.listify( email ): + for email_address in galaxy.util.listify( email ): email_address = email_address.strip() if email_address: if email_address == user.email: @@ -1267,8 +1266,8 @@ class HistoryController( BaseUIController, SharableMixin, UsesAnnotations, UsesI if not history.user: return trans.show_error_message( "You must save your history before renaming it." ) id = trans.security.encode_id( history.id ) - id = util.listify( id ) - name = util.listify( name ) + id = galaxy.util.listify( id ) + name = galaxy.util.listify( name ) histories = [] cur_names = [] for history_id in id: @@ -1311,7 +1310,7 @@ class HistoryController( BaseUIController, SharableMixin, UsesAnnotations, UsesI if id is None: histories = [ trans.history ] else: - ids = util.listify( id ) + ids = galaxy.util.listify( id ) histories = [] for history_id in ids: history = self.get_history( trans, history_id, check_ownership=False ) @@ -1362,7 +1361,7 @@ class HistoryController( BaseUIController, SharableMixin, UsesAnnotations, UsesI #TODO: allow id for more flexibility? (the following doesn't work if anonymous...) #history = self.get_history( trans, id, check_ownership=True, check_accessible=True, deleted=None ) if hda_ids: - unencoded_hda_ids = [ trans.security.decode_id( hda_id ) for hda_id in util.listify( hda_ids ) ] + unencoded_hda_ids = [ trans.security.decode_id( hda_id ) for hda_id in galaxy.util.listify( hda_ids ) ] #TODO: this gets all - should narrow query by using hda_ids here - no way with this current method hdas = self.get_history_datasets( trans, history, show_deleted=False, show_hidden=True, show_purged=False ) From 2c450019c26cf2ceabe79a1ce0ec2caeb71e02f0 Mon Sep 17 00:00:00 2001 From: Dannon Baker Date: Wed, 1 May 2013 00:14:41 -0400 Subject: [PATCH 282/523] LibraryAdmin controller - fix ambiguous util imports, remove * imports. --- .../galaxy/controllers/library_admin.py | 38 ++++++++++--------- 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/lib/galaxy/webapps/galaxy/controllers/library_admin.py b/lib/galaxy/webapps/galaxy/controllers/library_admin.py index 2f05646b35f..704aa28f82a 100644 --- a/lib/galaxy/webapps/galaxy/controllers/library_admin.py +++ b/lib/galaxy/webapps/galaxy/controllers/library_admin.py @@ -1,9 +1,11 @@ -import sys -from galaxy import model, util -from galaxy.web.base.controller import * -from galaxy.web.framework.helpers import time_ago, iff, grids -from galaxy.model.orm import * +import galaxy.model +import galaxy.util +from galaxy import web +from galaxy.web.base.controller import BaseUIController +from galaxy.web.framework.helpers import grids, time_ago from library_common import get_comptypes, lucene_search, whoosh_search +# from galaxy.model.orm import * + # Older py compatibility try: set() @@ -31,7 +33,7 @@ class LibraryListGrid( grids.Grid ): return "" # Grid definition title = "Data Libraries" - model_class = model.Library + model_class = galaxy.model.Library template='/admin/library/grid.mako' default_sort_key = "name" columns = [ @@ -90,7 +92,7 @@ class LibraryAdmin( BaseUIController ): self.library_list_grid.operations = [] if 'f-deleted' in kwd: if kwd[ 'f-deleted' ] != 'All': - if util.string_as_bool( kwd[ 'f-deleted' ] ): + if galaxy.util.string_as_bool( kwd[ 'f-deleted' ] ): # We're viewing deleted data libraries, so add a GridOperation # enabling one or more of them to be undeleted. self.library_list_grid.operations = [ @@ -134,8 +136,8 @@ class LibraryAdmin( BaseUIController ): indexed_search_enabled = False if indexed_search_enabled: comptypes = get_comptypes( trans ) - show_deleted = util.string_as_bool( kwd.get( 'show_deleted', False ) ) - use_panels = util.string_as_bool( kwd.get( 'use_panels', False ) ) + show_deleted = galaxy.util.string_as_bool( kwd.get( 'show_deleted', False ) ) + use_panels = galaxy.util.string_as_bool( kwd.get( 'use_panels', False ) ) return trans.fill_template( '/library/common/library_dataset_search_results.mako', cntrller='library_admin', search_term=search_term, @@ -150,13 +152,13 @@ class LibraryAdmin( BaseUIController ): @web.expose @web.require_admin def create_library( self, trans, **kwd ): - params = util.Params( kwd ) - message = util.restore_text( params.get( 'message', '' ) ) + params = galaxy.util.Params( kwd ) + message = galaxy.util.restore_text( params.get( 'message', '' ) ) status = params.get( 'status', 'done' ) if params.get( 'create_library_button', False ): - name = util.restore_text( params.get( 'name', 'No name' ) ) - description = util.restore_text( params.get( 'description', '' ) ) - synopsis = util.restore_text( params.get( 'synopsis', '' ) ) + name = galaxy.util.restore_text( params.get( 'name', 'No name' ) ) + description = galaxy.util.restore_text( params.get( 'description', '' ) ) + synopsis = galaxy.util.restore_text( params.get( 'synopsis', '' ) ) if synopsis in [ 'None', None ]: synopsis = '' library = trans.app.model.Library( name=name, description=description, synopsis=synopsis ) @@ -169,7 +171,7 @@ class LibraryAdmin( BaseUIController ): action='browse_library', cntrller='library_admin', id=trans.security.encode_id( library.id ), - message=util.sanitize_text( message ), + message=galaxy.util.sanitize_text( message ), status='done' ) ) return trans.fill_template( '/admin/library/new_library.mako', message=message, status=status ) @web.expose @@ -198,7 +200,7 @@ class LibraryAdmin( BaseUIController ): # TODO: change this function to purge_library_item, behaving similar to delete_library_item # assuming we want the ability to purge libraries. # This function is currently only used by the functional tests. - params = util.Params( kwd ) + params = galaxy.util.Params( kwd ) library = trans.sa_session.query( trans.app.model.Library ).get( trans.security.decode_id( params.id ) ) def purge_folder( library_folder ): for lf in library_folder.folders: @@ -228,7 +230,7 @@ class LibraryAdmin( BaseUIController ): message = "Library '%s' has not been marked deleted, so it cannot be purged" % ( library.name ) return trans.response.send_redirect( web.url_for( controller='library_admin', action='browse_libraries', - message=util.sanitize_text( message ), + message=galaxy.util.sanitize_text( message ), status='error' ) ) else: purge_folder( library.root_folder ) @@ -238,5 +240,5 @@ class LibraryAdmin( BaseUIController ): message = "Library '%s' and all of its contents have been purged, datasets will be removed from disk via the cleanup_datasets script" % library.name return trans.response.send_redirect( web.url_for( controller='library_admin', action='browse_libraries', - message=util.sanitize_text( message ), + message=galaxy.util.sanitize_text( message ), status='done' ) ) From c2682127b5c232c6f642532dcc8c99b533d4f2f7 Mon Sep 17 00:00:00 2001 From: Dannon Baker Date: Wed, 1 May 2013 00:16:08 -0400 Subject: [PATCH 283/523] LibraryAdmin: Remove python 2.5 'set' compatability cruft. --- lib/galaxy/webapps/galaxy/controllers/library_admin.py | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/lib/galaxy/webapps/galaxy/controllers/library_admin.py b/lib/galaxy/webapps/galaxy/controllers/library_admin.py index 704aa28f82a..eb831d22e4d 100644 --- a/lib/galaxy/webapps/galaxy/controllers/library_admin.py +++ b/lib/galaxy/webapps/galaxy/controllers/library_admin.py @@ -1,18 +1,14 @@ +import logging + import galaxy.model import galaxy.util + from galaxy import web from galaxy.web.base.controller import BaseUIController from galaxy.web.framework.helpers import grids, time_ago from library_common import get_comptypes, lucene_search, whoosh_search # from galaxy.model.orm import * -# Older py compatibility -try: - set() -except: - from sets import Set as set - -import logging log = logging.getLogger( __name__ ) class LibraryListGrid( grids.Grid ): From c886153f17ec52bed1c0ba99157f5ada3cfb541b Mon Sep 17 00:00:00 2001 From: Dannon Baker Date: Wed, 1 May 2013 00:24:08 -0400 Subject: [PATCH 284/523] Remove import * in base admin controller. Also fixes ambiguous 'util' --- lib/galaxy/web/base/controllers/admin.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/lib/galaxy/web/base/controllers/admin.py b/lib/galaxy/web/base/controllers/admin.py index d932a095fc6..799e12521f7 100644 --- a/lib/galaxy/web/base/controllers/admin.py +++ b/lib/galaxy/web/base/controllers/admin.py @@ -1,12 +1,11 @@ import logging from datetime import datetime, timedelta -from string import punctuation as PUNCTUATION - -from galaxy import web, util -from galaxy.model.orm import * - +from galaxy import util, web +from galaxy.model.orm import and_, func, or_ from galaxy.util import inflector from galaxy.web.form_builder import CheckboxField +from string import punctuation as PUNCTUATION + log = logging.getLogger( __name__ ) @@ -894,7 +893,7 @@ class Admin( object ): def name_autocomplete_data( self, trans, q=None, limit=None, timestamp=None ): """Return autocomplete data for user emails""" ac_data = "" - for user in trans.sa_session.query( User ).filter_by( deleted=False ).filter( func.lower( User.email ).like( q.lower() + "%" ) ): + for user in trans.sa_session.query( trans.app.model.User ).filter_by( deleted=False ).filter( func.lower( trans.app.model.User.email ).like( q.lower() + "%" ) ): ac_data = ac_data + user.email + "\n" return ac_data @web.expose From bdf853dbc5150fcba76d83812c59e3bf3d14a1a8 Mon Sep 17 00:00:00 2001 From: Dannon Baker Date: Wed, 1 May 2013 00:24:56 -0400 Subject: [PATCH 285/523] Remove commented cruft left over from refactoring. --- lib/galaxy/webapps/galaxy/controllers/admin.py | 3 --- lib/galaxy/webapps/galaxy/controllers/history.py | 2 -- 2 files changed, 5 deletions(-) diff --git a/lib/galaxy/webapps/galaxy/controllers/admin.py b/lib/galaxy/webapps/galaxy/controllers/admin.py index 526dbc999fe..d9f399eb32e 100644 --- a/lib/galaxy/webapps/galaxy/controllers/admin.py +++ b/lib/galaxy/webapps/galaxy/controllers/admin.py @@ -16,9 +16,6 @@ from galaxy.web.framework.helpers import grids, time_ago from galaxy.web.params import QuotaParamParser from tool_shed.util import common_util from tool_shed.util import encoding_util -# from galaxy.model.orm import * -# from galaxy.util.odict import * -# import galaxy.datatypes.registry log = logging.getLogger( __name__ ) diff --git a/lib/galaxy/webapps/galaxy/controllers/history.py b/lib/galaxy/webapps/galaxy/controllers/history.py index ae79773b55f..ed4648313c6 100644 --- a/lib/galaxy/webapps/galaxy/controllers/history.py +++ b/lib/galaxy/webapps/galaxy/controllers/history.py @@ -1,8 +1,6 @@ import logging from cgi import escape -# from sqlalchemy.sql.expression import and_, func - import galaxy.util from galaxy import model from galaxy import web From 71611d37bc321d13a0ee8c268defd5e996f831f3 Mon Sep 17 00:00:00 2001 From: Dannon Baker Date: Wed, 1 May 2013 00:51:30 -0400 Subject: [PATCH 286/523] Refactor import *'s in library_common; fixes ambiguous util in namespace --- .../galaxy/controllers/library_common.py | 33 ++++++++++++------- 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/lib/galaxy/webapps/galaxy/controllers/library_common.py b/lib/galaxy/webapps/galaxy/controllers/library_common.py index a82f7d77d4d..a0c47d49bc3 100644 --- a/lib/galaxy/webapps/galaxy/controllers/library_common.py +++ b/lib/galaxy/webapps/galaxy/controllers/library_common.py @@ -1,17 +1,26 @@ -import os, os.path, shutil, urllib, StringIO, re, gzip, tempfile, shutil, zipfile, copy, glob, string, urllib2 -from galaxy.web.base.controller import * -from galaxy import util, jobs -from galaxy.datatypes import sniff -from galaxy.security import RBACAgent -from galaxy.util.json import to_json_string -from galaxy.tools.actions import upload_common -from galaxy.model.orm import * -from galaxy.util.streamball import StreamBall -from galaxy.util import inflector -from galaxy.web.form_builder import AddressField, CheckboxField, SelectField, TextArea, TextField, WorkflowField, WorkflowMappingField, HistoryField -import logging, tempfile, zipfile, tarfile, os, sys, operator +import glob +import logging +import operator +import os +import os.path +import string +import sys +import tarfile +import tempfile +import urllib +import urllib2 +import zipfile +from galaxy import util, web +from galaxy.web import url_for from galaxy.eggs import require from galaxy.security import Action +from galaxy.tools.actions import upload_common +from galaxy.util import inflector +from galaxy.util.json import to_json_string +from galaxy.web.base.controller import BaseUIController, UsesFormDefinitionsMixin +from galaxy.web.form_builder import AddressField, CheckboxField, SelectField, build_select_field +from galaxy.model.orm import and_, eagerload_all + # Whoosh is compatible with Python 2.5+ Try to import Whoosh and set flag to indicate whether tool search is enabled. try: require( "Whoosh" ) From 011a1a18d25fa95d34ec88db46ae98c3ebd119ac Mon Sep 17 00:00:00 2001 From: Dannon Baker Date: Wed, 1 May 2013 00:52:57 -0400 Subject: [PATCH 287/523] Eliminate year old debugging code for library timing. --- .../webapps/galaxy/controllers/library_common.py | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/lib/galaxy/webapps/galaxy/controllers/library_common.py b/lib/galaxy/webapps/galaxy/controllers/library_common.py index a0c47d49bc3..ea307c4bb12 100644 --- a/lib/galaxy/webapps/galaxy/controllers/library_common.py +++ b/lib/galaxy/webapps/galaxy/controllers/library_common.py @@ -2606,25 +2606,16 @@ def datasets_for_lddas( trans, lddas ): return datasets def active_folders_and_library_datasets( trans, folder ): - # SM: TODO: Eliminate timing code - from datetime import datetime, timedelta - query_start = datetime.now() folders = active_folders( trans, folder ) library_datasets = trans.sa_session.query( trans.model.LibraryDataset ) \ .filter( and_( trans.model.LibraryDataset.table.c.deleted == False, trans.model.LibraryDataset.table.c.folder_id == folder.id ) ) \ .order_by( trans.model.LibraryDataset.table.c._name ) \ .all() - query_end = datetime.now() - query_delta = query_end - query_start - #log.debug( "active_folders_and_library_datasets: %d.%.6d" % - # ( query_delta.seconds, query_delta.microseconds ) ) return folders, library_datasets def activatable_folders_and_library_datasets( trans, folder ): folders = activatable_folders( trans, folder ) - from datetime import datetime, timedelta - query_start = datetime.now() library_datasets = trans.sa_session.query( trans.model.LibraryDataset ) \ .filter( trans.model.LibraryDataset.table.c.folder_id == folder.id ) \ .join( ( trans.model.LibraryDatasetDatasetAssociation.table, @@ -2634,11 +2625,8 @@ def activatable_folders_and_library_datasets( trans, folder ): .filter( trans.model.Dataset.table.c.deleted == False ) \ .order_by( trans.model.LibraryDataset.table.c._name ) \ .all() - query_end = datetime.now() - query_delta = query_end - query_start - log.debug( "activatable_folders_and_library_datasets: %d.%.6d" % - ( query_delta.seconds, query_delta.microseconds ) ) return folders, library_datasets + def branch_deleted( folder ): # Return True if a folder belongs to a branch that has been deleted if folder.deleted: From a2de4b4fc52e59beb8efd099d5282d5624130fba Mon Sep 17 00:00:00 2001 From: Dave Bouvier Date: Wed, 1 May 2013 10:23:13 -0400 Subject: [PATCH 288/523] Update repository metadata generation to provide the file path for tool functional test output files. Update script that checks for tool functional tests and test data to use the updated metadata structure. --- .../check_repositories_for_functional_tests.py | 13 +++---------- lib/tool_shed/util/metadata_util.py | 2 ++ 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/lib/tool_shed/scripts/check_repositories_for_functional_tests.py b/lib/tool_shed/scripts/check_repositories_for_functional_tests.py index a13b3cbe881..225d29df7d9 100644 --- a/lib/tool_shed/scripts/check_repositories_for_functional_tests.py +++ b/lib/tool_shed/scripts/check_repositories_for_functional_tests.py @@ -387,16 +387,9 @@ def check_for_missing_test_files( test_definition, test_data_path ): missing_test_files = [] required_test_files = [] for test_dict in test_definition: - for input_file in test_dict[ 'required_files' ]: - if input_file not in required_test_files: - required_test_files.append( input_file ) - for output in test_dict[ 'outputs' ]: - fieldname, filename = output - # In rare cases, the filename may be None. If that is the case, skip that output definition. - if filename is None: - continue - if filename not in required_test_files: - required_test_files.append( filename ) + for required_file in test_dict[ 'required_files' ]: + if required_file not in required_test_files: + required_test_files.append( required_file ) # Make sure each specified file actually does exist in the test data path of the cloned repository. for required_file in required_test_files: required_file_full_path = os.path.join( test_data_path, required_file ) diff --git a/lib/tool_shed/util/metadata_util.py b/lib/tool_shed/util/metadata_util.py index cc39c6f2148..678878d3929 100644 --- a/lib/tool_shed/util/metadata_util.py +++ b/lib/tool_shed/util/metadata_util.py @@ -825,6 +825,8 @@ def generate_tool_metadata( tool_config, tool, repository_clone_url, metadata_di for output in ttb.outputs: name, file_name, extra = output outputs.append( ( name, suc.strip_path( file_name ) if file_name else None ) ) + if file_name not in required_files and file_name is not None: + required_files.append( file_name ) test_dict = dict( name=ttb.name, required_files=required_files, inputs=inputs, From 90922ee0db090f8dc6c6efddb59f5d9fa106b278 Mon Sep 17 00:00:00 2001 From: Greg Von Kuster Date: Wed, 1 May 2013 13:42:47 -0400 Subject: [PATCH 289/523] Move the My invalid tools menu item in the tool shed to a grid that filters to the latest metadata revision. --- .../webapps/tool_shed/api/repositories.py | 2 +- .../tool_shed/controllers/repository.py | 102 +- lib/tool_shed/grids/repository_grids.py | 940 +++++++++++------- lib/tool_shed/util/shed_util_common.py | 22 +- templates/webapps/tool_shed/admin/index.mako | 50 - templates/webapps/tool_shed/index.mako | 17 +- .../repository/browse_invalid_tools.mako | 44 - .../display_tool_functional_test_results.mako | 309 ------ 8 files changed, 636 insertions(+), 850 deletions(-) delete mode 100644 templates/webapps/tool_shed/repository/browse_invalid_tools.mako delete mode 100644 templates/webapps/tool_shed/repository/display_tool_functional_test_results.mako diff --git a/lib/galaxy/webapps/tool_shed/api/repositories.py b/lib/galaxy/webapps/tool_shed/api/repositories.py index 62c3e5ab747..aa1bd845aac 100644 --- a/lib/galaxy/webapps/tool_shed/api/repositories.py +++ b/lib/galaxy/webapps/tool_shed/api/repositories.py @@ -48,7 +48,7 @@ class RepositoriesController( BaseAPIController ): encoded_repository_id = trans.security.encode_id( repository.id ) repo_dir = repository.repo_path( trans.app ) repo = hg.repository( suc.get_configured_ui(), repo_dir ) - ordered_installable_revisions = suc.get_ordered_downloadable_changeset_revisions( repository, repo ) + ordered_installable_revisions = suc.get_ordered_metadata_changeset_revisions( repository, repo, downloadable=True ) return ordered_installable_revisions except Exception, e: message = "Error in the Tool Shed repositories API in get_ordered_installable_revisions: %s" % str( e ) diff --git a/lib/galaxy/webapps/tool_shed/controllers/repository.py b/lib/galaxy/webapps/tool_shed/controllers/repository.py index 2c036743771..9c3f35258f7 100644 --- a/lib/galaxy/webapps/tool_shed/controllers/repository.py +++ b/lib/galaxy/webapps/tool_shed/controllers/repository.py @@ -61,12 +61,14 @@ class RepositoryController( BaseUIController, common_util.ItemRatings ): my_writable_repositories_grid = repository_grids.MyWritableRepositoriesGrid() my_writable_repositories_missing_tool_test_components_grid = repository_grids.MyWritableRepositoriesMissingToolTestComponentsGrid() my_writable_repositories_with_failing_tool_tests_grid = repository_grids.MyWritableRepositoriesWithFailingToolTestsGrid() + my_writable_repositories_with_invalid_tools_grid = repository_grids.MyWritableRepositoriesWithInvalidToolsGrid() my_writable_repositories_with_no_failing_tool_tests_grid = repository_grids.MyWritableRepositoriesWithNoFailingToolTestsGrid() repositories_by_user_grid = repository_grids.RepositoriesByUserGrid() repositories_i_own_grid = repository_grids.RepositoriesIOwnGrid() repositories_in_category_grid = repository_grids.RepositoriesInCategoryGrid() repositories_missing_tool_test_components_grid = repository_grids.RepositoriesMissingToolTestComponentsGrid() repositories_with_failing_tool_tests_grid = repository_grids.RepositoriesWithFailingToolTestsGrid() + repositories_with_invalid_tools_grid = repository_grids.RepositoriesWithInvalidToolsGrid() repositories_with_no_failing_tool_tests_grid = repository_grids.RepositoriesWithNoFailingToolTestsGrid() repository_dependencies_grid = repository_grids.RepositoryDependenciesGrid() repository_grid = repository_grids.RepositoryGrid() @@ -140,47 +142,6 @@ class RepositoryController( BaseUIController, common_util.ItemRatings ): changeset_revision=selected_changeset_revision ) ) return self.deprecated_repositories_i_own_grid( trans, **kwd ) - @web.expose - def browse_invalid_tools( self, trans, **kwd ): - params = util.Params( kwd ) - message = util.restore_text( params.get( 'message', '' ) ) - status = params.get( 'status', 'done' ) - cntrller = params.get( 'cntrller', 'repository' ) - is_admin = trans.user_is_admin() - invalid_tools_dict = odict() - if is_admin and cntrller == 'admin': - for repository in trans.sa_session.query( trans.model.Repository ) \ - .filter( trans.model.Repository.table.c.deleted == False ) \ - .order_by( trans.model.Repository.table.c.name ): - # A repository's metadata_revisions are those that ignore the value of the repository_metadata.downloadable column. - for downloadable_revision in repository.metadata_revisions: - metadata = downloadable_revision.metadata - invalid_tools = metadata.get( 'invalid_tools', [] ) - for invalid_tool_config in invalid_tools: - invalid_tools_dict[ invalid_tool_config ] = ( repository.id, - repository.name, - repository.user.username, - downloadable_revision.changeset_revision ) - else: - for repository in trans.sa_session.query( trans.model.Repository ) \ - .filter( and_( trans.model.Repository.table.c.deleted == False, - trans.model.Repository.table.c.user_id == trans.user.id ) ) \ - .order_by( trans.model.Repository.table.c.name ): - for downloadable_revision in repository.metadata_revisions: - metadata = downloadable_revision.metadata - if metadata: - invalid_tools = metadata.get( 'invalid_tools', [] ) - for invalid_tool_config in invalid_tools: - invalid_tools_dict[ invalid_tool_config ] = ( repository.id, - repository.name, - repository.user.username, - downloadable_revision.changeset_revision ) - return trans.fill_template( '/webapps/tool_shed/repository/browse_invalid_tools.mako', - cntrller=cntrller, - invalid_tools_dict=invalid_tools_dict, - message=message, - status=status ) - @web.expose def browse_my_writable_repositories( self, trans, **kwd ): if 'operation' in kwd: @@ -264,6 +225,34 @@ class RepositoryController( BaseUIController, common_util.ItemRatings ): kwd[ 'status' ] = 'warning' return self.my_writable_repositories_with_failing_tool_tests_grid( trans, **kwd ) + @web.expose + def browse_my_writable_repositories_with_invalid_tools( self, trans, **kwd ): + if 'operation' in kwd: + operation = kwd[ 'operation' ].lower() + if operation == "view_or_manage_repository": + return trans.response.send_redirect( web.url_for( controller='repository', + action='view_or_manage_repository', + **kwd ) ) + elif operation == "repositories_by_user": + return trans.response.send_redirect( web.url_for( controller='repository', + action='browse_repositories_by_user', + **kwd ) ) + elif operation in [ 'mark as deprecated', 'mark as not deprecated' ]: + kwd[ 'mark_deprecated' ] = operation == 'mark as deprecated' + return trans.response.send_redirect( web.url_for( controller='repository', + action='deprecate', + **kwd ) ) + if 'message' not in kwd: + message = 'This list contains repositories that match the following criteria:
    ' + message += '
      ' + message += '
    • you are authorized to update them
    • ' + message += '
    • the latest metadata revision contains at least 1 invalid tool
    • ' + message += '
    ' + message += 'Click the tool config file name to see why the tool is invalid.' + kwd[ 'message' ] = message + kwd[ 'status' ] = 'warning' + return self.my_writable_repositories_with_invalid_tools_grid( trans, **kwd ) + @web.expose def browse_my_writable_repositories_with_no_failing_tool_tests( self, trans, **kwd ): if 'operation' in kwd: @@ -491,6 +480,33 @@ class RepositoryController( BaseUIController, common_util.ItemRatings ): kwd[ 'status' ] = 'warning' return self.repositories_with_failing_tool_tests_grid( trans, **kwd ) + @web.expose + def browse_repositories_with_invalid_tools( self, trans, **kwd ): + if 'operation' in kwd: + operation = kwd[ 'operation' ].lower() + if operation == "view_or_manage_repository": + return trans.response.send_redirect( web.url_for( controller='repository', + action='view_or_manage_repository', + **kwd ) ) + elif operation == "repositories_by_user": + return trans.response.send_redirect( web.url_for( controller='repository', + action='browse_repositories_by_user', + **kwd ) ) + elif operation in [ 'mark as deprecated', 'mark as not deprecated' ]: + kwd[ 'mark_deprecated' ] = operation == 'mark as deprecated' + return trans.response.send_redirect( web.url_for( controller='repository', + action='deprecate', + **kwd ) ) + if 'message' not in kwd: + message = 'This list contains repositories that match the following criteria:
    ' + message += '
      ' + message += '
    • the latest metadata revision contains at least 1 invalid tool
    • ' + message += '
    ' + message += 'Click the tool config file name to see why the tool is invalid.' + kwd[ 'message' ] = message + kwd[ 'status' ] = 'warning' + return self.repositories_with_invalid_tools_grid( trans, **kwd ) + @web.expose def browse_repositories_with_no_failing_tool_tests( self, trans, **kwd ): if 'operation' in kwd: @@ -1976,7 +1992,7 @@ class RepositoryController( BaseUIController, common_util.ItemRatings ): is_malicious = repository_metadata.malicious else: # There is no repository_metadata defined for the changeset_revision, so see if it was defined in a previous changeset in the changelog. - previous_changeset_revision = suc.get_previous_downloadable_changeset_revision( repository, repo, changeset_revision ) + previous_changeset_revision = suc.get_previous_metadata_changeset_revision( repository, repo, changeset_revision, downloadable=False ) if previous_changeset_revision != suc.INITIAL_CHANGELOG_HASH: repository_metadata = suc.get_repository_metadata_by_changeset_revision( trans, id, previous_changeset_revision ) if repository_metadata: @@ -2161,7 +2177,7 @@ class RepositoryController( BaseUIController, common_util.ItemRatings ): repo_dir = repository.repo_path( trans.app ) repo = hg.repository( suc.get_configured_ui(), repo_dir ) # Get the lower bound changeset revision. - lower_bound_changeset_revision = suc.get_previous_downloadable_changeset_revision( repository, repo, changeset_revision ) + lower_bound_changeset_revision = suc.get_previous_metadata_changeset_revision( repository, repo, changeset_revision, downloadable=True ) # Build the list of changeset revision hashes. changeset_hashes = [] for changeset in suc.reversed_lower_upper_bounded_changelog( repo, lower_bound_changeset_revision, changeset_revision ): diff --git a/lib/tool_shed/grids/repository_grids.py b/lib/tool_shed/grids/repository_grids.py index 2a2c15f5390..35f1a1fdfbd 100644 --- a/lib/tool_shed/grids/repository_grids.py +++ b/lib/tool_shed/grids/repository_grids.py @@ -73,48 +73,6 @@ class CategoryGrid( grids.Grid ): use_paging = False -class ValidCategoryGrid( CategoryGrid ): - - - class RepositoriesColumn( grids.TextColumn ): - - def get_value( self, trans, grid, category ): - if category.repositories: - viewable_repositories = 0 - for rca in category.repositories: - repository = rca.repository - if not repository.deleted and not repository.deprecated and repository.downloadable_revisions: - viewable_repositories += 1 - return viewable_repositories - return 0 - - title = "Categories of valid repositories" - model_class = model.Category - template='/webapps/tool_shed/category/valid_grid.mako' - default_sort_key = "name" - columns = [ - CategoryGrid.NameColumn( "Name", - key="Category.name", - link=( lambda item: dict( operation="valid_repositories_by_category", id=item.id ) ), - attach_popup=False ), - CategoryGrid.DescriptionColumn( "Description", - key="Category.description", - attach_popup=False ), - # Columns that are valid for filtering but are not visible. - RepositoriesColumn( "Valid repositories", - model_class=model.Repository, - attach_popup=False ) - ] - # Override these - default_filter = {} - global_actions = [] - operations = [] - standard_filters = [] - num_rows_per_page = 50 - preserve_state = False - use_paging = False - - class RepositoryGrid( grids.Grid ): @@ -248,7 +206,7 @@ class RepositoryGrid( grids.Grid ): NameColumn( "Name", key="name", link=( lambda item: dict( operation="view_or_manage_repository", id=item.id ) ), - attach_popup=True ), + attach_popup=False ), DescriptionColumn( "Synopsis", key="description", attach_popup=False ), @@ -294,6 +252,195 @@ class RepositoryGrid( grids.Grid ): .outerjoin( model.Category.table ) +class EmailAlertsRepositoryGrid( RepositoryGrid ): + columns = [ + RepositoryGrid.NameColumn( "Name", + key="name", + link=( lambda item: dict( operation="view_or_manage_repository", id=item.id ) ), + attach_popup=False ), + RepositoryGrid.DescriptionColumn( "Synopsis", + key="description", + attach_popup=False ), + RepositoryGrid.UserColumn( "Owner", + model_class=model.User, + link=( lambda item: dict( operation="repositories_by_user", id=item.id ) ), + attach_popup=False, + key="User.username" ), + RepositoryGrid.EmailAlertsColumn( "Alert", attach_popup=False ), + # Columns that are valid for filtering but are not visible. + grids.DeletedColumn( "Deleted", + key="deleted", + visible=False, + filterable="advanced" ) + ] + operations = [] + global_actions = [ + grids.GridAction( "User preferences", dict( controller='user', action='index', cntrller='repository' ) ) + ] + + +class MatchedRepositoryGrid( grids.Grid ): + # This grid filters out repositories that have been marked as deleted or deprecated. + + + class NameColumn( grids.TextColumn ): + + def get_value( self, trans, grid, repository_metadata ): + return repository_metadata.repository.name + + + class DescriptionColumn( grids.TextColumn ): + + def get_value( self, trans, grid, repository_metadata ): + return repository_metadata.repository.description + + + class RevisionColumn( grids.TextColumn ): + + def get_value( self, trans, grid, repository_metadata ): + return repository_metadata.changeset_revision + + + class UserColumn( grids.TextColumn ): + + def get_value( self, trans, grid, repository_metadata ): + if repository_metadata.repository.user: + return repository_metadata.repository.user.username + return 'no user' + + # Grid definition + title = "Matching repositories" + model_class = model.RepositoryMetadata + template='/webapps/tool_shed/repository/grid.mako' + default_sort_key = "Repository.name" + columns = [ + NameColumn( "Repository name", + link=( lambda item: dict( operation="view_or_manage_repository", id=item.id ) ), + attach_popup=True ), + DescriptionColumn( "Synopsis", + attach_popup=False ), + RevisionColumn( "Revision" ), + UserColumn( "Owner", + model_class=model.User, + attach_popup=False ) + ] + operations = [ grids.GridOperation( "Install to Galaxy", allow_multiple=True ) ] + standard_filters = [] + default_filter = {} + num_rows_per_page = 50 + preserve_state = False + use_paging = False + + def build_initial_query( self, trans, **kwd ): + match_tuples = kwd.get( 'match_tuples', [] ) + clause_list = [] + if match_tuples: + for match_tuple in match_tuples: + repository_id, changeset_revision = match_tuple + clause_list.append( "%s=%d and %s='%s'" % ( model.RepositoryMetadata.table.c.repository_id, + int( repository_id ), + model.RepositoryMetadata.table.c.changeset_revision, + changeset_revision ) ) + return trans.sa_session.query( model.RepositoryMetadata ) \ + .join( model.Repository ) \ + .filter( and_( model.Repository.table.c.deleted == False, + model.Repository.table.c.deprecated == False ) ) \ + .join( model.User.table ) \ + .filter( or_( *clause_list ) ) \ + .order_by( model.Repository.name ) + # Return an empty query + return trans.sa_session.query( model.RepositoryMetadata ) \ + .filter( model.RepositoryMetadata.id < 0 ) + + +class InstallMatchedRepositoryGrid( MatchedRepositoryGrid ): + columns = [ col for col in MatchedRepositoryGrid.columns ] + # Override the NameColumn + columns[ 0 ] = MatchedRepositoryGrid.NameColumn( "Name", + link=( lambda item: dict( operation="view_or_manage_repository", id=item.id ) ), + attach_popup=False ) + + +class MyWritableRepositoriesGrid( RepositoryGrid ): + # This grid filters out repositories that have been marked as either deprecated or deleted. + title = 'Repositories I can change' + columns = [ + RepositoryGrid.NameColumn( "Name", + key="name", + link=( lambda item: dict( operation="view_or_manage_repository", id=item.id ) ), + attach_popup=False ), + RepositoryGrid.MetadataRevisionColumn( "Metadata Revisions" ), + RepositoryGrid.ToolsFunctionallyCorrectColumn( "Tools Verified" ), + RepositoryGrid.UserColumn( "Owner", + model_class=model.User, + link=( lambda item: dict( operation="repositories_by_user", id=item.id ) ), + attach_popup=False, + key="User.username" ) + ] + columns.append( grids.MulticolFilterColumn( "Search repository name", + cols_to_filter=[ columns[ 0 ] ], + key="free-text-search", + visible=False, + filterable="standard" ) ) + operations = [] + use_paging = False + + def build_initial_query( self, trans, **kwd ): + # TODO: improve performance by adding a db table associating users with repositories for which they have write access. + username = trans.user.username + clause_list = [] + for repository in trans.sa_session.query( model.Repository ) \ + .filter( and_( model.Repository.table.c.deprecated == False, + model.Repository.table.c.deleted == False ) ): + allow_push = repository.allow_push( trans.app ) + if allow_push: + allow_push_usernames = allow_push.split( ',' ) + if username in allow_push_usernames: + clause_list.append( model.Repository.table.c.id == repository.id ) + if clause_list: + return trans.sa_session.query( model.Repository ) \ + .filter( or_( *clause_list ) ) \ + .join( model.User.table ) + # Return an empty query. + return trans.sa_session.query( model.Repository ) \ + .filter( model.Repository.table.c.id < 0 ) + + +class RepositoriesByUserGrid( RepositoryGrid ): + title = "Repositories by user" + columns = [ + RepositoryGrid.NameColumn( "Name", + key="name", + link=( lambda item: dict( operation="view_or_manage_repository", id=item.id ) ), + attach_popup=False ), + RepositoryGrid.DescriptionColumn( "Synopsis", + key="description", + attach_popup=False ), + RepositoryGrid.MetadataRevisionColumn( "Metadata Revisions" ), + RepositoryGrid.ToolsFunctionallyCorrectColumn( "Tools Verified" ), + RepositoryGrid.CategoryColumn( "Category", + model_class=model.Category, + key="Category.name", + attach_popup=False ) + ] + operations = [] + standard_filters = [] + default_filter = dict( deleted="False" ) + num_rows_per_page = 50 + preserve_state = False + use_paging = False + + def build_initial_query( self, trans, **kwd ): + decoded_user_id = trans.security.decode_id( kwd[ 'user_id' ] ) + return trans.sa_session.query( model.Repository ) \ + .filter( and_( model.Repository.table.c.deleted == False, + model.Repository.table.c.deprecated == False, + model.Repository.table.c.user_id == decoded_user_id ) ) \ + .join( model.User.table ) \ + .outerjoin( model.RepositoryCategoryAssociation.table ) \ + .outerjoin( model.Category.table ) + + class RepositoriesInCategoryGrid( RepositoryGrid ): title = "Category" @@ -346,48 +493,13 @@ class RepositoriesInCategoryGrid( RepositoryGrid ): .outerjoin( model.Category.table ) -class RepositoriesByUserGrid( RepositoryGrid ): - title = "Repositories by user" - columns = [ - RepositoryGrid.NameColumn( "Name", - key="name", - link=( lambda item: dict( operation="view_or_manage_repository", id=item.id ) ), - attach_popup=False ), - RepositoryGrid.DescriptionColumn( "Synopsis", - key="description", - attach_popup=False ), - RepositoryGrid.MetadataRevisionColumn( "Metadata Revisions" ), - RepositoryGrid.ToolsFunctionallyCorrectColumn( "Tools Verified" ), - RepositoryGrid.CategoryColumn( "Category", - model_class=model.Category, - key="Category.name", - attach_popup=False ) - ] - operations = [] - standard_filters = [] - default_filter = dict( deleted="False" ) - num_rows_per_page = 50 - preserve_state = False - use_paging = False - - def build_initial_query( self, trans, **kwd ): - decoded_user_id = trans.security.decode_id( kwd[ 'user_id' ] ) - return trans.sa_session.query( model.Repository ) \ - .filter( and_( model.Repository.table.c.deleted == False, - model.Repository.table.c.deprecated == False, - model.Repository.table.c.user_id == decoded_user_id ) ) \ - .join( model.User.table ) \ - .outerjoin( model.RepositoryCategoryAssociation.table ) \ - .outerjoin( model.Category.table ) - - class RepositoriesIOwnGrid( RepositoryGrid ): title = "Repositories I own" columns = [ RepositoryGrid.NameColumn( "Name", key="name", link=( lambda item: dict( operation="view_or_manage_repository", id=item.id ) ), - attach_popup=True ), + attach_popup=False ), RepositoryGrid.MetadataRevisionColumn( "Metadata Revisions" ), RepositoryGrid.ToolsFunctionallyCorrectColumn( "Tools Verified" ), RepositoryGrid.DeprecatedColumn( "Deprecated" ) @@ -409,110 +521,8 @@ class RepositoriesIOwnGrid( RepositoryGrid ): .outerjoin( model.Category.table ) -class DeprecatedRepositoriesIOwnGrid( RepositoriesIOwnGrid ): - title = "Deprecated repositories I own" - columns = [ - RepositoriesIOwnGrid.NameColumn( "Name", - key="name", - link=( lambda item: dict( operation="view_or_manage_repository", id=item.id ) ), - attach_popup=True ), - RepositoriesIOwnGrid.MetadataRevisionColumn( "Metadata Revisions" ), - RepositoryGrid.ToolsFunctionallyCorrectColumn( "Tools Verified" ), - RepositoriesIOwnGrid.CategoryColumn( "Category", - model_class=model.Category, - key="Category.name", - attach_popup=False ), - ] - columns.append( grids.MulticolFilterColumn( "Search repository name", - cols_to_filter=[ columns[0] ], - key="free-text-search", - visible=False, - filterable="standard" ) ) - use_paging = False - - def build_initial_query( self, trans, **kwd ): - return trans.sa_session.query( model.Repository ) \ - .filter( and_( model.Repository.table.c.deleted == False, - model.Repository.table.c.user_id == trans.user.id, - model.Repository.table.c.deprecated == True ) ) \ - .join( model.User.table ) \ - .outerjoin( model.RepositoryCategoryAssociation.table ) \ - .outerjoin( model.Category.table ) - - -class EmailAlertsRepositoryGrid( RepositoryGrid ): - columns = [ - RepositoryGrid.NameColumn( "Name", - key="name", - link=( lambda item: dict( operation="view_or_manage_repository", id=item.id ) ), - attach_popup=False ), - RepositoryGrid.DescriptionColumn( "Synopsis", - key="description", - attach_popup=False ), - RepositoryGrid.UserColumn( "Owner", - model_class=model.User, - link=( lambda item: dict( operation="repositories_by_user", id=item.id ) ), - attach_popup=False, - key="User.username" ), - RepositoryGrid.EmailAlertsColumn( "Alert", attach_popup=False ), - # Columns that are valid for filtering but are not visible. - grids.DeletedColumn( "Deleted", - key="deleted", - visible=False, - filterable="advanced" ) - ] - operations = [] - global_actions = [ - grids.GridAction( "User preferences", dict( controller='user', action='index', cntrller='repository' ) ) - ] - - -class MyWritableRepositoriesGrid( RepositoryGrid ): - # This grid filters out repositories that have been marked as either deprecated or deleted. - title = 'Repositories I can change' - columns = [ - RepositoryGrid.NameColumn( "Name", - key="name", - link=( lambda item: dict( operation="view_or_manage_repository", id=item.id ) ), - attach_popup=True ), - RepositoryGrid.MetadataRevisionColumn( "Metadata Revisions" ), - RepositoryGrid.ToolsFunctionallyCorrectColumn( "Tools Verified" ), - RepositoryGrid.UserColumn( "Owner", - model_class=model.User, - link=( lambda item: dict( operation="repositories_by_user", id=item.id ) ), - attach_popup=False, - key="User.username" ) - ] - columns.append( grids.MulticolFilterColumn( "Search repository name", - cols_to_filter=[ columns[ 0 ] ], - key="free-text-search", - visible=False, - filterable="standard" ) ) - operations = [] - use_paging = False - - def build_initial_query( self, trans, **kwd ): - # TODO: improve performance by adding a db table associating users with repositories for which they have write access. - username = trans.user.username - clause_list = [] - for repository in trans.sa_session.query( model.Repository ) \ - .filter( and_( model.Repository.table.c.deprecated == False, - model.Repository.table.c.deleted == False ) ): - allow_push = repository.allow_push( trans.app ) - if allow_push: - allow_push_usernames = allow_push.split( ',' ) - if username in allow_push_usernames: - clause_list.append( model.Repository.table.c.id == repository.id ) - if clause_list: - return trans.sa_session.query( model.Repository ) \ - .filter( or_( *clause_list ) ) \ - .join( model.User.table ) - # Return an empty query. - return trans.sa_session.query( model.Repository ) \ - .filter( model.Repository.table.c.id < 0 ) - - class RepositoriesMissingToolTestComponentsGrid( RepositoryGrid ): + # This grid displays only the latest installable revision of each repository. title = "Repositories with missing tool test components" columns = [ RepositoryGrid.NameColumn( "Name", @@ -545,6 +555,8 @@ class RepositoriesMissingToolTestComponentsGrid( RepositoryGrid ): revision_clause_list.append( model.RepositoryMetadata.table.c.changeset_revision == changeset_revision ) if revision_clause_list: return trans.sa_session.query( model.Repository ) \ + .filter( and_( model.Repository.table.c.deprecated == False, + model.Repository.table.c.deleted == False ) ) \ .join( model.RepositoryMetadata ) \ .filter( or_( *revision_clause_list ) ) \ .join( model.User.table ) @@ -554,6 +566,7 @@ class RepositoriesMissingToolTestComponentsGrid( RepositoryGrid ): class MyWritableRepositoriesMissingToolTestComponentsGrid( RepositoriesMissingToolTestComponentsGrid ): + # This grid displays only the latest installable revision of each repository. title = "Repositories I can change with missing tool test components" columns = [ col for col in RepositoriesMissingToolTestComponentsGrid.columns ] operations = [] @@ -576,12 +589,16 @@ class MyWritableRepositoriesMissingToolTestComponentsGrid( RepositoriesMissingTo # tools with missing tool test components. revision_clause_list = [] for repository in trans.sa_session.query( model.Repository ) \ + .filter( and_( model.Repository.table.c.deprecated == False, + model.Repository.table.c.deleted == False ) ) \ .filter( or_( *user_clause_list ) ): changeset_revision = filter_by_latest_downloadable_changeset_revision_that_has_missing_tool_test_components( trans, repository ) if changeset_revision: revision_clause_list.append( model.RepositoryMetadata.table.c.changeset_revision == changeset_revision ) if revision_clause_list: return trans.sa_session.query( model.Repository ) \ + .filter( and_( model.Repository.table.c.deprecated == False, + model.Repository.table.c.deleted == False ) ) \ .join( model.User.table ) \ .filter( or_( *user_clause_list ) ) \ .join( model.RepositoryMetadata ) \ @@ -591,7 +608,39 @@ class MyWritableRepositoriesMissingToolTestComponentsGrid( RepositoriesMissingTo .filter( model.Repository.table.c.id < 0 ) +class DeprecatedRepositoriesIOwnGrid( RepositoriesIOwnGrid ): + title = "Deprecated repositories I own" + columns = [ + RepositoriesIOwnGrid.NameColumn( "Name", + key="name", + link=( lambda item: dict( operation="view_or_manage_repository", id=item.id ) ), + attach_popup=False ), + RepositoriesIOwnGrid.MetadataRevisionColumn( "Metadata Revisions" ), + RepositoryGrid.ToolsFunctionallyCorrectColumn( "Tools Verified" ), + RepositoriesIOwnGrid.CategoryColumn( "Category", + model_class=model.Category, + key="Category.name", + attach_popup=False ), + ] + columns.append( grids.MulticolFilterColumn( "Search repository name", + cols_to_filter=[ columns[0] ], + key="free-text-search", + visible=False, + filterable="standard" ) ) + use_paging = False + + def build_initial_query( self, trans, **kwd ): + return trans.sa_session.query( model.Repository ) \ + .filter( and_( model.Repository.table.c.deleted == False, + model.Repository.table.c.user_id == trans.user.id, + model.Repository.table.c.deprecated == True ) ) \ + .join( model.User.table ) \ + .outerjoin( model.RepositoryCategoryAssociation.table ) \ + .outerjoin( model.Category.table ) + + class RepositoriesWithFailingToolTestsGrid( RepositoryGrid ): + # This grid displays only the latest installable revision of each repository. title = "Repositories with failing tool tests" columns = [ RepositoryGrid.NameColumn( "Name", @@ -624,6 +673,8 @@ class RepositoriesWithFailingToolTestsGrid( RepositoryGrid ): revision_clause_list.append( model.RepositoryMetadata.table.c.changeset_revision == changeset_revision ) if revision_clause_list: return trans.sa_session.query( model.Repository ) \ + .filter( and_( model.Repository.table.c.deprecated == False, + model.Repository.table.c.deleted == False ) ) \ .join( model.RepositoryMetadata ) \ .filter( or_( *revision_clause_list ) ) \ .join( model.User.table ) @@ -633,6 +684,7 @@ class RepositoriesWithFailingToolTestsGrid( RepositoryGrid ): class MyWritableRepositoriesWithFailingToolTestsGrid( RepositoriesWithFailingToolTestsGrid ): + # This grid displays only the latest installable revision of each repository. title = "Repositories I can change with failing tool tests" columns = [ col for col in RepositoriesWithFailingToolTestsGrid.columns ] operations = [] @@ -655,12 +707,16 @@ class MyWritableRepositoriesWithFailingToolTestsGrid( RepositoriesWithFailingToo # tools with at least 1 failing tool test. revision_clause_list = [] for repository in trans.sa_session.query( model.Repository ) \ + .filter( and_( model.Repository.table.c.deprecated == False, + model.Repository.table.c.deleted == False ) ) \ .filter( or_( *user_clause_list ) ): changeset_revision = filter_by_latest_downloadable_changeset_revision_that_has_failing_tool_tests( trans, repository ) if changeset_revision: revision_clause_list.append( model.RepositoryMetadata.table.c.changeset_revision == changeset_revision ) if revision_clause_list: return trans.sa_session.query( model.Repository ) \ + .filter( and_( model.Repository.table.c.deprecated == False, + model.Repository.table.c.deleted == False ) ) \ .join( model.User.table ) \ .filter( or_( *user_clause_list ) ) \ .join( model.RepositoryMetadata ) \ @@ -671,6 +727,7 @@ class MyWritableRepositoriesWithFailingToolTestsGrid( RepositoriesWithFailingToo class RepositoriesWithNoFailingToolTestsGrid( RepositoryGrid ): + # This grid displays only the latest installable revision of each repository. title = "Repositories with no failing tool tests" columns = [ RepositoryGrid.NameColumn( "Name", @@ -704,6 +761,8 @@ class RepositoriesWithNoFailingToolTestsGrid( RepositoryGrid ): revision_clause_list.append( model.RepositoryMetadata.table.c.changeset_revision == changeset_revision ) if revision_clause_list: return trans.sa_session.query( model.Repository ) \ + .filter( and_( model.Repository.table.c.deprecated == False, + model.Repository.table.c.deleted == False ) ) \ .join( model.RepositoryMetadata ) \ .filter( or_( *revision_clause_list ) ) \ .join( model.User.table ) @@ -713,6 +772,7 @@ class RepositoriesWithNoFailingToolTestsGrid( RepositoryGrid ): class MyWritableRepositoriesWithNoFailingToolTestsGrid( RepositoriesWithNoFailingToolTestsGrid ): + # This grid displays only the latest installable revision of each repository. title = "Repositories I can change with no failing tool tests" columns = [ col for col in RepositoriesWithNoFailingToolTestsGrid.columns ] operations = [] @@ -735,12 +795,16 @@ class MyWritableRepositoriesWithNoFailingToolTestsGrid( RepositoriesWithNoFailin # at least 1 tool, no missing tool test components, and no failing tool tests. revision_clause_list = [] for repository in trans.sa_session.query( model.Repository ) \ + .filter( and_( model.Repository.table.c.deprecated == False, + model.Repository.table.c.deleted == False ) ) \ .filter( or_( *user_clause_list ) ): changeset_revision = filter_by_latest_downloadable_changeset_revision_that_has_no_failing_tool_tests( trans, repository ) if changeset_revision: revision_clause_list.append( model.RepositoryMetadata.table.c.changeset_revision == changeset_revision ) if revision_clause_list: return trans.sa_session.query( model.Repository ) \ + .filter( and_( model.Repository.table.c.deprecated == False, + model.Repository.table.c.deleted == False ) ) \ .join( model.User.table ) \ .filter( or_( *user_clause_list ) ) \ .join( model.RepositoryMetadata ) \ @@ -750,179 +814,110 @@ class MyWritableRepositoriesWithNoFailingToolTestsGrid( RepositoriesWithNoFailin .filter( model.Repository.table.c.id < 0 ) -class ValidRepositoryGrid( RepositoryGrid ): - # This grid filters out repositories that have been marked as either deleted or deprecated. +class RepositoriesWithInvalidToolsGrid( RepositoryGrid ): + # This grid displays only the latest installable revision of each repository. - class CategoryColumn( grids.TextColumn ): - - def get_value( self, trans, grid, repository ): - rval = '
      ' - if repository.categories: - for rca in repository.categories: - rval += '
    • %s
    • ' \ - % ( trans.security.encode_id( rca.category.id ), rca.category.name ) - else: - rval += '
    • not set
    • ' - rval += '
    ' - return rval - - - class RepositoryCategoryColumn( grids.GridColumn ): - - def filter( self, trans, user, query, column_filter ): - """Modify query to filter by category.""" - if column_filter == "All": - return query - return query.filter( model.Category.name == column_filter ) - - - class InstallableRevisionColumn( grids.GridColumn ): + class InvalidToolConfigColumn( grids.GridColumn ): def __init__( self, col_name ): grids.GridColumn.__init__( self, col_name ) def get_value( self, trans, grid, repository ): - """Display a SelectField whose options are the changeset_revision strings of all download-able revisions of this repository.""" - select_field = grids_util.build_changeset_revision_select_field( trans, repository, downloadable=True ) - if len( select_field.options ) > 1: - return select_field.get_html() - elif len( select_field.options ) == 1: - return select_field.options[ 0 ][ 0 ] - return '' + # At the time this grid is displayed we know that the received repository will have invalid tools in it's latest changeset revision + # that has associated metadata. + val = '' + repository_metadata = get_latest_repository_metadata_if_it_includes_invalid_tools( trans, repository ) + metadata = repository_metadata.metadata + invalid_tools = metadata.get( 'invalid_tools', [] ) + if invalid_tools: + for invalid_tool_config in invalid_tools: + href_str = '%s' % \ + ( trans.security.encode_id( repository.id ), invalid_tool_config, repository_metadata.changeset_revision, invalid_tool_config ) + val += href_str + val += '
    ' + val = val.rstrip( '
    ' ) + return val - title = "Valid repositories" - columns = [ + title = "Repositories with invalid tools" + columns = [ + InvalidToolConfigColumn( "Tool config" ), RepositoryGrid.NameColumn( "Name", key="name", - attach_popup=True ), - RepositoryGrid.DescriptionColumn( "Synopsis", - key="description", - attach_popup=False ), - InstallableRevisionColumn( "Installable Revisions" ), - RepositoryGrid.ToolsFunctionallyCorrectColumn( "Tools Verified" ), - RepositoryGrid.UserColumn( "Owner", - model_class=model.User, + link=( lambda item: dict( operation="view_or_manage_repository", id=item.id ) ), attach_popup=False ), - # Columns that are valid for filtering but are not visible. - RepositoryCategoryColumn( "Category", - model_class=model.Category, - key="Category.name", - visible=False ) + RepositoryGrid.LatestInstallableRevisionColumn( "Latest Metadata Revision" ), + RepositoryGrid.UserColumn( "Owner", + key="User.username", + model_class=model.User, + link=( lambda item: dict( operation="repositories_by_user", id=item.id ) ), + attach_popup=False ) ] - columns.append( grids.MulticolFilterColumn( "Search repository name, description", - cols_to_filter=[ columns[0], columns[1] ], - key="free-text-search", - visible=False, - filterable="standard" ) ) operations = [] use_paging = False def build_initial_query( self, trans, **kwd ): - if 'id' in kwd: - # The user is browsing categories of valid repositories, so filter the request by the received id, which is a category id. + # Filter by latest metadata revisions that contain invalid tools. + revision_clause_list = [] + for repository in trans.sa_session.query( model.Repository ) \ + .filter( and_( model.Repository.table.c.deprecated == False, + model.Repository.table.c.deleted == False ) ): + changeset_revision = filter_by_latest_metadata_changeset_revision_that_has_invalid_tools( trans, repository ) + if changeset_revision: + revision_clause_list.append( model.RepositoryMetadata.table.c.changeset_revision == changeset_revision ) + if revision_clause_list: return trans.sa_session.query( model.Repository ) \ - .filter( and_( model.Repository.table.c.deleted == False, - model.Repository.table.c.deprecated == False ) ) \ - .join( model.RepositoryMetadata.table ) \ - .join( model.User.table ) \ - .join( model.RepositoryCategoryAssociation.table ) \ - .join( model.Category.table ) \ - .filter( and_( model.Category.table.c.id == trans.security.decode_id( kwd[ 'id' ] ), - model.RepositoryMetadata.table.c.downloadable == True ) ) - # The user performed a free text search on the ValidCategoryGrid. + .filter( and_( model.Repository.table.c.deprecated == False, + model.Repository.table.c.deleted == False ) ) \ + .join( model.RepositoryMetadata ) \ + .filter( or_( *revision_clause_list ) ) \ + .join( model.User.table ) + # Return an empty query. return trans.sa_session.query( model.Repository ) \ - .filter( and_( model.Repository.table.c.deleted == False, - model.Repository.table.c.deprecated == False ) ) \ - .join( model.RepositoryMetadata.table ) \ - .join( model.User.table ) \ - .outerjoin( model.RepositoryCategoryAssociation.table ) \ - .outerjoin( model.Category.table ) \ - .filter( model.RepositoryMetadata.table.c.downloadable == True ) + .filter( model.Repository.table.c.id < 0 ) -class MatchedRepositoryGrid( grids.Grid ): - # This grid filters out repositories that have been marked as deprecated. - - - class NameColumn( grids.TextColumn ): - - def get_value( self, trans, grid, repository_metadata ): - return repository_metadata.repository.name - - - class DescriptionColumn( grids.TextColumn ): - - def get_value( self, trans, grid, repository_metadata ): - return repository_metadata.repository.description - - - class RevisionColumn( grids.TextColumn ): - - def get_value( self, trans, grid, repository_metadata ): - return repository_metadata.changeset_revision - - - class UserColumn( grids.TextColumn ): - - def get_value( self, trans, grid, repository_metadata ): - if repository_metadata.repository.user: - return repository_metadata.repository.user.username - return 'no user' - - # Grid definition - title = "Matching repositories" - model_class = model.RepositoryMetadata - template='/webapps/tool_shed/repository/grid.mako' - default_sort_key = "Repository.name" - columns = [ - NameColumn( "Repository name", - link=( lambda item: dict( operation="view_or_manage_repository", id=item.id ) ), - attach_popup=True ), - DescriptionColumn( "Synopsis", - attach_popup=False ), - RevisionColumn( "Revision" ), - UserColumn( "Owner", - model_class=model.User, - attach_popup=False ) - ] - operations = [ - grids.GridOperation( "Install to Galaxy", allow_multiple=True ) - ] - standard_filters = [] - default_filter = {} - num_rows_per_page = 50 - preserve_state = False +class MyWritableRepositoriesWithInvalidToolsGrid( RepositoriesWithInvalidToolsGrid ): + # This grid displays only the latest installable revision of each repository. + title = "Repositories I can change with invalid tools" + columns = [ col for col in RepositoriesWithInvalidToolsGrid.columns ] + operations = [] use_paging = False def build_initial_query( self, trans, **kwd ): - match_tuples = kwd.get( 'match_tuples', [] ) - clause_list = [] - if match_tuples: - for match_tuple in match_tuples: - repository_id, changeset_revision = match_tuple - clause_list.append( "%s=%d and %s='%s'" % ( model.RepositoryMetadata.table.c.repository_id, - int( repository_id ), - model.RepositoryMetadata.table.c.changeset_revision, - changeset_revision ) ) - return trans.sa_session.query( model.RepositoryMetadata ) \ - .join( model.Repository ) \ - .filter( and_( model.Repository.table.c.deleted == False, - model.Repository.table.c.deprecated == False ) ) \ - .join( model.User.table ) \ - .filter( or_( *clause_list ) ) \ - .order_by( model.Repository.name ) - # Return an empty query - return trans.sa_session.query( model.RepositoryMetadata ) \ - .filter( model.RepositoryMetadata.id < 0 ) - - -class InstallMatchedRepositoryGrid( MatchedRepositoryGrid ): - columns = [ col for col in MatchedRepositoryGrid.columns ] - # Override the NameColumn - columns[ 0 ] = MatchedRepositoryGrid.NameColumn( "Name", - link=( lambda item: dict( operation="view_or_manage_repository", id=item.id ) ), - attach_popup=False ) + # First get all repositories that the current user is authorized to update. + username = trans.user.username + user_clause_list = [] + for repository in trans.sa_session.query( model.Repository ) \ + .filter( and_( model.Repository.table.c.deprecated == False, + model.Repository.table.c.deleted == False ) ): + allow_push = repository.allow_push( trans.app ) + if allow_push: + allow_push_usernames = allow_push.split( ',' ) + if username in allow_push_usernames: + user_clause_list.append( model.Repository.table.c.id == repository.id ) + if user_clause_list: + # We have the list of repositories that the current user is authorized to update, so filter further by latest metadata revisions that contain + # invalid tools. + revision_clause_list = [] + for repository in trans.sa_session.query( model.Repository ) \ + .filter( and_( model.Repository.table.c.deprecated == False, + model.Repository.table.c.deleted == False ) ) \ + .filter( or_( *user_clause_list ) ): + changeset_revision = filter_by_latest_metadata_changeset_revision_that_has_invalid_tools( trans, repository ) + if changeset_revision: + revision_clause_list.append( model.RepositoryMetadata.table.c.changeset_revision == changeset_revision ) + if revision_clause_list: + return trans.sa_session.query( model.Repository ) \ + .filter( and_( model.Repository.table.c.deprecated == False, + model.Repository.table.c.deleted == False ) ) \ + .join( model.User.table ) \ + .filter( or_( *user_clause_list ) ) \ + .join( model.RepositoryMetadata ) \ + .filter( or_( *revision_clause_list ) ) + # Return an empty query. + return trans.sa_session.query( model.Repository ) \ + .filter( model.Repository.table.c.id < 0 ) class RepositoryMetadataGrid( grids.Grid ): @@ -1136,6 +1131,73 @@ class RepositoryDependenciesGrid( RepositoryMetadataGrid ): .join( model.User.table ) +class DatatypesGrid( RepositoryMetadataGrid ): + + + class DatatypesColumn( grids.TextColumn ): + + def get_value( self, trans, grid, repository_metadata ): + datatype_str = '' + if repository_metadata: + metadata = repository_metadata.metadata + if metadata: + datatype_dicts = metadata.get( 'datatypes', [] ) + if datatype_dicts: + # Create tuples of the attributes we want so we can sort them by extension. + datatype_tups = [] + for datatype_dict in datatype_dicts: + # Example: {"display_in_upload": "true", "dtype": "galaxy.datatypes.blast:BlastXml", "extension": "blastxml", "mimetype": "application/xml"} + extension = datatype_dict.get( 'extension', '' ) + dtype = datatype_dict.get( 'dtype', '' ) + mimetype = datatype_dict.get( 'mimetype', '' ) + display_in_upload = datatype_dict.get( 'display_in_upload', False ) + # For now we'll just display extension and dtype. + if extension and dtype: + datatype_tups.append( ( extension, dtype ) ) + sorted_datatype_tups = sorted( datatype_tups, key=lambda datatype_tup: datatype_tup[ 0 ] ) + num_datatype_tups = len( sorted_datatype_tups ) + for index, datatype_tup in enumerate( sorted_datatype_tups ): + extension = datatype_tup[ 0 ] + dtype = datatype_tup[ 1 ] + datatype_str += '' % trans.security.encode_id( repository_metadata.id ) + datatype_str += '%s: %s' % ( escape_html( extension ), escape_html( dtype ) ) + datatype_str += '' + if index < num_datatype_tups - 1: + datatype_str += '
    ' + return datatype_str + + title = "Custom datatypes in this tool shed" + default_sort_key = "Repository.name" + columns = [ + DatatypesColumn( "Datatype extension and class", + attach_popup=False ), + RepositoryMetadataGrid.RepositoryNameColumn( "Repository name", + model_class=model.Repository, + link=( lambda item: dict( operation="view_or_manage_repository", id=item.id ) ), + attach_popup=False, + key="Repository.name" ), + RepositoryMetadataGrid.RepositoryOwnerColumn( "Owner", + model_class=model.User, + attach_popup=False, + key="User.username" ), + RepositoryMetadataGrid.ChangesetRevisionColumn( "Revision", + attach_popup=False ) + ] + columns.append( grids.MulticolFilterColumn( "Search repository name, owner", + cols_to_filter=[ columns[1], columns[2] ], + key="free-text-search", + visible=False, + filterable="standard" ) ) + + def build_initial_query( self, trans, **kwd ): + return trans.sa_session.query( model.RepositoryMetadata ) \ + .join( model.Repository ) \ + .filter( and_( model.RepositoryMetadata.table.c.includes_datatypes == True, + model.Repository.table.c.deleted == False, + model.Repository.table.c.deprecated == False ) ) \ + .join( model.User.table ) + + class ToolDependenciesGrid( RepositoryMetadataGrid ): @@ -1275,71 +1337,137 @@ class ToolsGrid( RepositoryMetadataGrid ): .join( model.User.table ) -class DatatypesGrid( RepositoryMetadataGrid ): +class ValidCategoryGrid( CategoryGrid ): - class DatatypesColumn( grids.TextColumn ): + class RepositoriesColumn( grids.TextColumn ): - def get_value( self, trans, grid, repository_metadata ): - datatype_str = '' - if repository_metadata: - metadata = repository_metadata.metadata - if metadata: - datatype_dicts = metadata.get( 'datatypes', [] ) - if datatype_dicts: - # Create tuples of the attributes we want so we can sort them by extension. - datatype_tups = [] - for datatype_dict in datatype_dicts: - # Example: {"display_in_upload": "true", "dtype": "galaxy.datatypes.blast:BlastXml", "extension": "blastxml", "mimetype": "application/xml"} - extension = datatype_dict.get( 'extension', '' ) - dtype = datatype_dict.get( 'dtype', '' ) - mimetype = datatype_dict.get( 'mimetype', '' ) - display_in_upload = datatype_dict.get( 'display_in_upload', False ) - # For now we'll just display extension and dtype. - if extension and dtype: - datatype_tups.append( ( extension, dtype ) ) - sorted_datatype_tups = sorted( datatype_tups, key=lambda datatype_tup: datatype_tup[ 0 ] ) - num_datatype_tups = len( sorted_datatype_tups ) - for index, datatype_tup in enumerate( sorted_datatype_tups ): - extension = datatype_tup[ 0 ] - dtype = datatype_tup[ 1 ] - datatype_str += '' % trans.security.encode_id( repository_metadata.id ) - datatype_str += '%s: %s' % ( escape_html( extension ), escape_html( dtype ) ) - datatype_str += '' - if index < num_datatype_tups - 1: - datatype_str += '
    ' - return datatype_str + def get_value( self, trans, grid, category ): + if category.repositories: + viewable_repositories = 0 + for rca in category.repositories: + repository = rca.repository + if not repository.deleted and not repository.deprecated and repository.downloadable_revisions: + viewable_repositories += 1 + return viewable_repositories + return 0 - title = "Custom datatypes in this tool shed" - default_sort_key = "Repository.name" + title = "Categories of valid repositories" + model_class = model.Category + template='/webapps/tool_shed/category/valid_grid.mako' + default_sort_key = "name" columns = [ - DatatypesColumn( "Datatype extension and class", - attach_popup=False ), - RepositoryMetadataGrid.RepositoryNameColumn( "Repository name", - model_class=model.Repository, - link=( lambda item: dict( operation="view_or_manage_repository", id=item.id ) ), - attach_popup=False, - key="Repository.name" ), - RepositoryMetadataGrid.RepositoryOwnerColumn( "Owner", - model_class=model.User, - attach_popup=False, - key="User.username" ), - RepositoryMetadataGrid.ChangesetRevisionColumn( "Revision", - attach_popup=False ) + CategoryGrid.NameColumn( "Name", + key="Category.name", + link=( lambda item: dict( operation="valid_repositories_by_category", id=item.id ) ), + attach_popup=False ), + CategoryGrid.DescriptionColumn( "Description", + key="Category.description", + attach_popup=False ), + # Columns that are valid for filtering but are not visible. + RepositoriesColumn( "Valid repositories", + model_class=model.Repository, + attach_popup=False ) ] - columns.append( grids.MulticolFilterColumn( "Search repository name, owner", - cols_to_filter=[ columns[1], columns[2] ], + # Override these + default_filter = {} + global_actions = [] + operations = [] + standard_filters = [] + num_rows_per_page = 50 + preserve_state = False + use_paging = False + + +class ValidRepositoryGrid( RepositoryGrid ): + # This grid filters out repositories that have been marked as either deleted or deprecated. + + + class CategoryColumn( grids.TextColumn ): + + def get_value( self, trans, grid, repository ): + rval = '
      ' + if repository.categories: + for rca in repository.categories: + rval += '
    • %s
    • ' \ + % ( trans.security.encode_id( rca.category.id ), rca.category.name ) + else: + rval += '
    • not set
    • ' + rval += '
    ' + return rval + + + class RepositoryCategoryColumn( grids.GridColumn ): + + def filter( self, trans, user, query, column_filter ): + """Modify query to filter by category.""" + if column_filter == "All": + return query + return query.filter( model.Category.name == column_filter ) + + + class InstallableRevisionColumn( grids.GridColumn ): + + def __init__( self, col_name ): + grids.GridColumn.__init__( self, col_name ) + + def get_value( self, trans, grid, repository ): + """Display a SelectField whose options are the changeset_revision strings of all download-able revisions of this repository.""" + select_field = grids_util.build_changeset_revision_select_field( trans, repository, downloadable=True ) + if len( select_field.options ) > 1: + return select_field.get_html() + elif len( select_field.options ) == 1: + return select_field.options[ 0 ][ 0 ] + return '' + + title = "Valid repositories" + columns = [ + RepositoryGrid.NameColumn( "Name", + key="name", + attach_popup=False ), + RepositoryGrid.DescriptionColumn( "Synopsis", + key="description", + attach_popup=False ), + InstallableRevisionColumn( "Installable Revisions" ), + RepositoryGrid.ToolsFunctionallyCorrectColumn( "Tools Verified" ), + RepositoryGrid.UserColumn( "Owner", + model_class=model.User, + attach_popup=False ), + # Columns that are valid for filtering but are not visible. + RepositoryCategoryColumn( "Category", + model_class=model.Category, + key="Category.name", + visible=False ) + ] + columns.append( grids.MulticolFilterColumn( "Search repository name, description", + cols_to_filter=[ columns[0], columns[1] ], key="free-text-search", visible=False, filterable="standard" ) ) + operations = [] + use_paging = False def build_initial_query( self, trans, **kwd ): - return trans.sa_session.query( model.RepositoryMetadata ) \ - .join( model.Repository ) \ - .filter( and_( model.RepositoryMetadata.table.c.includes_datatypes == True, - model.Repository.table.c.deleted == False, + if 'id' in kwd: + # The user is browsing categories of valid repositories, so filter the request by the received id, which is a category id. + return trans.sa_session.query( model.Repository ) \ + .filter( and_( model.Repository.table.c.deleted == False, + model.Repository.table.c.deprecated == False ) ) \ + .join( model.RepositoryMetadata.table ) \ + .join( model.User.table ) \ + .join( model.RepositoryCategoryAssociation.table ) \ + .join( model.Category.table ) \ + .filter( and_( model.Category.table.c.id == trans.security.decode_id( kwd[ 'id' ] ), + model.RepositoryMetadata.table.c.downloadable == True ) ) + # The user performed a free text search on the ValidCategoryGrid. + return trans.sa_session.query( model.Repository ) \ + .filter( and_( model.Repository.table.c.deleted == False, model.Repository.table.c.deprecated == False ) ) \ - .join( model.User.table ) + .join( model.RepositoryMetadata.table ) \ + .join( model.User.table ) \ + .outerjoin( model.RepositoryCategoryAssociation.table ) \ + .outerjoin( model.Category.table ) \ + .filter( model.RepositoryMetadata.table.c.downloadable == True ) # ------ utility methods ------------------- @@ -1380,8 +1508,46 @@ def filter_by_latest_downloadable_changeset_revision_that_has_no_failing_tool_te return repository_metadata.changeset_revision return None +def filter_by_latest_metadata_changeset_revision_that_has_invalid_tools( trans, repository ): + """ + Inspect the latest changeset revision with associated metadata for the received repository to see if it has invalid tools. + """ + encoded_repository_id = trans.security.encode_id( repository.id ) + repo = hg.repository( suc.get_configured_ui(), repository.repo_path( trans.app ) ) + tip_ctx = str( repo.changectx( repo.changelog.tip() ) ) + repository_metadata = get_latest_repository_metadata_if_it_includes_invalid_tools( trans, repository ) + if repository_metadata: + return repository_metadata.changeset_revision + return None + +def get_latest_repository_metadata_if_it_includes_invalid_tools( trans, repository ): + """Return the latest repository_metadata record for the received repository that contains invalid tools if one exists.""" + encoded_repository_id = trans.security.encode_id( repository.id ) + repo = hg.repository( suc.get_configured_ui(), repository.repo_path( trans.app ) ) + tip_ctx = str( repo.changectx( repo.changelog.tip() ) ) + repository_metadata = None + try: + repository_metadata = suc.get_repository_metadata_by_changeset_revision( trans, encoded_repository_id, tip_ctx ) + if repository_metadata: + metadata = repository_metadata.metadata + if metadata and 'invalid_tools' in metadata: + return repository_metadata + return None + return None + except: + latest_installable_revision = suc.get_previous_metadata_changeset_revision( repository, repo, tip_ctx, downloadable=False ) + if latest_installable_revision == suc.INITIAL_CHANGELOG_HASH: + return None + repository_metadata = suc.get_repository_metadata_by_changeset_revision( trans, encoded_repository_id, latest_installable_revision ) + if repository_metadata: + metadata = repository_metadata.metadata + if metadata and 'invalid_tools' in metadata: + return repository_metadata + return None + return None + def get_latest_installable_repository_metadata_if_it_includes_tools( trans, repository ): - """Return the latest installable repository_metadata record for the received repository if one exists.""" + """Return the latest installable repository_metadata record for the received repository that contains valid tools if one exists.""" encoded_repository_id = trans.security.encode_id( repository.id ) repo = hg.repository( suc.get_configured_ui(), repository.repo_path( trans.app ) ) tip_ctx = str( repo.changectx( repo.changelog.tip() ) ) @@ -1392,7 +1558,7 @@ def get_latest_installable_repository_metadata_if_it_includes_tools( trans, repo return repository_metadata return None except: - latest_installable_revision = suc.get_previous_downloadable_changeset_revision( repository, repo, tip_ctx ) + latest_installable_revision = suc.get_previous_metadata_changeset_revision( repository, repo, tip_ctx, downloadable=True ) if latest_installable_revision == suc.INITIAL_CHANGELOG_HASH: return None repository_metadata = suc.get_repository_metadata_by_changeset_revision( trans, encoded_repository_id, latest_installable_revision ) diff --git a/lib/tool_shed/util/shed_util_common.py b/lib/tool_shed/util/shed_util_common.py index e7848353fcc..41670fb48bd 100644 --- a/lib/tool_shed/util/shed_util_common.py +++ b/lib/tool_shed/util/shed_util_common.py @@ -520,7 +520,7 @@ def get_next_downloadable_changeset_revision( repository, repo, after_changeset_ Return the installable changeset_revision in the repository changelog after the changeset to which after_changeset_revision refers. If there isn't one, return None. """ - changeset_revisions = get_ordered_downloadable_changeset_revisions( repository, repo ) + changeset_revisions = get_ordered_metadata_changeset_revisions( repository, repo, downloadable=True ) if len( changeset_revisions ) == 1: changeset_revision = changeset_revisions[ 0 ] if changeset_revision == after_changeset_revision: @@ -560,10 +560,14 @@ def get_or_create_tool_shed_repository( trans, tool_shed, name, owner, changeset dist_to_shed=False ) return repository -def get_ordered_downloadable_changeset_revisions( repository, repo ): - """Return an ordered list of changeset_revisions defined by a repository changelog.""" +def get_ordered_metadata_changeset_revisions( repository, repo, downloadable=True ): + """Return an ordered list of changeset_revisions that are associated with metadata where order is defined by the repository changelog.""" + if downloadable: + metadata_revisions = repository.downloadable_revisions + else: + metadata_revisions = repository.metadata_revisions changeset_tups = [] - for repository_metadata in repository.downloadable_revisions: + for repository_metadata in metadata_revisions: changeset_revision = repository_metadata.changeset_revision ctx = get_changectx_for_changeset( repo, changeset_revision ) if ctx: @@ -575,12 +579,12 @@ def get_ordered_downloadable_changeset_revisions( repository, repo ): sorted_changeset_revisions = [ changeset_tup[ 1 ] for changeset_tup in sorted_changeset_tups ] return sorted_changeset_revisions -def get_previous_downloadable_changeset_revision( repository, repo, before_changeset_revision ): +def get_previous_metadata_changeset_revision( repository, repo, before_changeset_revision, downloadable=True ): """ - Return the installable changeset_revision in the repository changelog prior to the changeset to which before_changeset_revision - refers. If there isn't one, return the hash value of an empty repository changelog, INITIAL_CHANGELOG_HASH. + Return the changeset_revision in the repository changelog that has associated metadata prior to the changeset to which + before_changeset_revision refers. If there isn't one, return the hash value of an empty repository changelog, INITIAL_CHANGELOG_HASH. """ - changeset_revisions = get_ordered_downloadable_changeset_revisions( repository, repo ) + changeset_revisions = get_ordered_metadata_changeset_revisions( repository, repo, downloadable=downloadable ) if len( changeset_revisions ) == 1: changeset_revision = changeset_revisions[ 0 ] if changeset_revision == before_changeset_revision: @@ -1091,7 +1095,7 @@ def reversed_lower_upper_bounded_changelog( repo, excluded_lower_bounds_changese """ # To set excluded_lower_bounds_changeset_revision, calling methods should do the following, where the value of changeset_revision # is a downloadable changeset_revision. - # excluded_lower_bounds_changeset_revision = get_previous_downloadable_changeset_revision( repository, repo, changeset_revision ) + # excluded_lower_bounds_changeset_revision = get_previous_metadata_changeset_revision( repository, repo, changeset_revision, downloadable=? ) if excluded_lower_bounds_changeset_revision == INITIAL_CHANGELOG_HASH: appending_started = True else: diff --git a/templates/webapps/tool_shed/admin/index.mako b/templates/webapps/tool_shed/admin/index.mako index 461e142e218..f28396822a8 100644 --- a/templates/webapps/tool_shed/admin/index.mako +++ b/templates/webapps/tool_shed/admin/index.mako @@ -60,58 +60,8 @@ -
    - %if can_review_repositories: -
    -
    - Reviewing Repositories -
    - -
    -
    - Reviewing Repositories With Tools -
    - - %endif
    Categories diff --git a/templates/webapps/tool_shed/index.mako b/templates/webapps/tool_shed/index.mako index f9fead49179..a3a0260f30d 100644 --- a/templates/webapps/tool_shed/index.mako +++ b/templates/webapps/tool_shed/index.mako @@ -113,16 +113,16 @@ My writable repositories
    @@ -164,13 +164,16 @@ diff --git a/templates/webapps/tool_shed/repository/browse_invalid_tools.mako b/templates/webapps/tool_shed/repository/browse_invalid_tools.mako deleted file mode 100644 index e116f69ebfb..00000000000 --- a/templates/webapps/tool_shed/repository/browse_invalid_tools.mako +++ /dev/null @@ -1,44 +0,0 @@ -<%inherit file="/base.mako"/> -<%namespace file="/message.mako" import="render_msg" /> - -%if message: - ${render_msg( message, status )} -%endif - -
    - %if invalid_tools_dict: -
    Invalid tools - click the tool config file name to see why the tool is invalid
    -
    - - - - - - - - %for invalid_tool_config, repository_tup in invalid_tools_dict.items(): - <% repository_id, repository_name, repository_owner, changeset_revision = repository_tup %> - - - - - - - %endfor -
    Tool configRepository nameRepository ownerChangeset revision
    - - ${invalid_tool_config} - - ${repository_name | h}${repository_owner | h}${changeset_revision | h}
    -
    -
    - %else: -
    - %if cntrller == 'admin' and trans.user_is_admin(): - No repositories in this tool shed contain invalid tools. - %else: - None of your repositories contain invalid tools. - %endif -
    - %endif -
    diff --git a/templates/webapps/tool_shed/repository/display_tool_functional_test_results.mako b/templates/webapps/tool_shed/repository/display_tool_functional_test_results.mako deleted file mode 100644 index f7c0b51d8bc..00000000000 --- a/templates/webapps/tool_shed/repository/display_tool_functional_test_results.mako +++ /dev/null @@ -1,309 +0,0 @@ -<%inherit file="/base.mako"/> -<%namespace file="/message.mako" import="render_msg" /> -<%namespace file="/webapps/tool_shed/common/common.mako" import="*" /> -<%namespace file="/webapps/tool_shed/repository/common.mako" import="*" /> - -<%! - def inherit(context): - if context.get('use_panels'): - return '/webapps/tool_shed/base_panels.mako' - else: - return '/base.mako' -%> - -<%inherit file="${inherit(context)}"/> - -<%def name="render_functional_test_text( functional_test_text )"> - <% - from tool_shed.util.shed_util_common import to_safe_string - %> - - - -
    ${ to_safe_string( functional_test_text, to_html=True ) }
    - - -<% - from galaxy.web.framework.helpers import time_ago - - changeset_revision = repository_metadata.changeset_revision - has_metadata = repository.metadata_revisions - has_readme = metadata and 'readme' in metadata - is_admin = trans.user_is_admin() - is_new = repository.is_new( trans.app ) - is_deprecated = repository.deprecated - - can_browse_contents = trans.webapp.name == 'tool_shed' and not is_new - can_contact_owner = trans.user and trans.user != repository.user - can_download = not is_new and ( not is_malicious or can_push ) - can_manage = is_admin or repository.user == trans.user - can_push = trans.app.security_agent.can_push( trans.app, trans.user, repository ) - can_rate = repository.user != trans.user - can_review_repository = has_metadata and not is_deprecated and trans.app.security_agent.user_can_review_repositories( trans.user ) - can_upload = can_push - can_view_change_log = trans.webapp.name == 'tool_shed' and not is_new - if repository_metadata.tool_test_results: - # The tool_test_results will contain a dictionary that includes information about the test environment even if all tests passed and the - # repository_metadata.tools_functionally_correct column is set to True. - tool_test_results = repository_metadata.tool_test_results - test_environment_dict = tool_test_results.get( 'test_environment', None ) - missing_test_components = tool_test_results.get( 'missing_test_components', [] ) - failed_tests = tool_test_results.get( 'failed_tests', [] ) - passed_tests = tool_test_results.get( 'passed_tests', [] ) - else: - tool_test_results = None - test_environment_dict = {} - missing_test_components = [] - failed_tests = [] - passed_tests = [] - - if can_push: - browse_label = 'Browse or delete repository tip files' - else: - browse_label = 'Browse repository tip files' -%> - -

    - - -%if message: - ${render_msg( message, status )} -%endif - -
    -
    Repository revision
    -
    -
    - - %if can_view_change_log: - ${revision_label} - %else: - ${revision_label} - %endif -
    -
    -
    -

    -%if can_download: -

    -
    Repository '${repository.name}'
    -
    -
    - - ${render_clone_str( repository )} -
    -
    -
    -%else: - Repository name:
    - ${repository.name} -%endif -%if missing_test_components or tool_test_results or passed_tests: -

    -

    -
    Tool functional test results
    -
    -
    - - ${time_ago( repository_metadata.time_last_tested ) | h} -
    -
    - - -
    Tool Shed environment
    -
    -
    - - ${test_environment_dict.get( 'tool_shed_revision', 'unknown' ) | h} -
    -
    -
    - - ${test_environment_dict.get( 'tool_shed_database_version', 'unknown' ) | h} -
    -
    -
    - - ${test_environment_dict.get( 'tool_shed_mercurial_version', 'unknown' ) | h} -
    -
    -
    - - -
    Galaxy environment
    -
    -
    - - ${test_environment_dict.get( 'galaxy_revision', 'unknown' ) | h} -
    -
    -
    - - ${test_environment_dict.get( 'galaxy_database_version', 'unknown' ) | h} -
    -
    -
    - - ${test_environment_dict.get( 'architecture', 'unknown' ) | h} -
    -
    -
    - - ${test_environment_dict.get( 'system', 'unknown' ) | h} -
    -
    -
    - - ${test_environment_dict.get( 'python_version', 'unknown' ) | h} -
    -
    - %if failed_tests: -
    - - -
    Tests that failed
    -
    -
    - - %for test_results_dict in failed_tests: - <% - test_id = test_results_dict.get( 'test_id', 'unknown' ) - tool_id = test_results_dict.get( 'tool_id', 'unknown' ) - tool_version = test_results_dict.get( 'tool_version', 'unknown' ) - test_status = 'Test failed' - stderr = test_results_dict.get( 'stderr', '' ) - traceback = test_results_dict.get( 'traceback', '' ) - %> - - - - - - - - - - - - - - - - - - - - %endfor -
    Tool id: ${tool_id} version: ${tool_version}
    Test id${test_id}
    Status${test_status}
    Stderr${render_functional_test_text( stderr )}
    Traceback${render_functional_test_text( traceback )}
    -
    -
    - %endif - %if passed_tests: -
    - - -
    Tests that passed successfully
    -
    -
    - - %for test_results_dict in passed_tests: - <% - test_id = test_results_dict.get( 'test_id', 'unknown' ) - tool_id = test_results_dict.get( 'tool_id', 'unknown' ) - tool_version = test_results_dict.get( 'tool_version', 'unknown' ) - test_status = 'Test passed' - %> - - - - - - - - - - - - %endfor -
    Tool id: ${tool_id} version: ${tool_version}
    Test id${test_id}
    Status${test_status}
    -
    - %endif - %if missing_test_components: -
    - - -
    Invalid tests
    -
    -
    - - %for test_results_dict in missing_test_components: - <% - guid = test_results_dict.get( 'tool_guid', None ) - tool_id = test_results_dict.get( 'tool_id', None ) - tool_version = test_results_dict.get( 'tool_version', None ) - missing_components = test_results_dict.get( 'missing_components', None ) - %> - %if tool_id or tool_version: - - - - %endif - %if missing_components: - - - - - %endif - %endfor -
    Tool id: ${tool_id} version: ${tool_version}
    Reason test is invalid${render_functional_test_text( missing_components )}
    -
    - %endif -
    -
    -%endif From 00903796c5438c6ae88f837c2d449b547a42c7d4 Mon Sep 17 00:00:00 2001 From: Dannon Baker Date: Wed, 1 May 2013 14:01:35 -0400 Subject: [PATCH 290/523] Silence log.debug outputs for get_history() returning None, since it's both valid and fairly common. --- lib/galaxy/web/framework/__init__.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/lib/galaxy/web/framework/__init__.py b/lib/galaxy/web/framework/__init__.py index 2671f12a1b7..7203b6159a0 100644 --- a/lib/galaxy/web/framework/__init__.py +++ b/lib/galaxy/web/framework/__init__.py @@ -822,9 +822,6 @@ class GalaxyWebTransaction( base.DefaultWebTransaction ): history = self.galaxy_session.current_history if not history and util.string_as_bool( create ): history = self.new_history() - else: - # Perhaps a bot is running a tool without having logged in to get a history - log.debug( "This request returned None from get_history(): %s" % self.request.browser_url ) return history def set_history( self, history ): From b3f509da1c80feaf67f60200f7b10c0ca3028249 Mon Sep 17 00:00:00 2001 From: Jeremy Goecks Date: Wed, 1 May 2013 14:04:23 -0400 Subject: [PATCH 291/523] Visualization framework: (1) fix disappearing Y axis labels for line tracks and (2) update dataset dict to correspond with new description. --- lib/galaxy/web/base/controller.py | 19 ++++++++++--------- static/scripts/viz/trackster/tracks.js | 23 ++++++++++++++--------- 2 files changed, 24 insertions(+), 18 deletions(-) diff --git a/lib/galaxy/web/base/controller.py b/lib/galaxy/web/base/controller.py index 0bb1032ba3e..08f1e6677b6 100644 --- a/lib/galaxy/web/base/controller.py +++ b/lib/galaxy/web/base/controller.py @@ -703,17 +703,18 @@ class UsesVisualizationMixin( UsesHistoryDatasetAssociationMixin, # -- Validate config. -- if vis.type == 'trackster': - def unpack_track( track_json ): + def unpack_track( track_dict ): """ Unpack a track from its json. """ + dataset_dict = track_dict[ 'dataset' ] return { - "dataset_id": trans.security.decode_id( track_json['dataset_id'] ), - "hda_ldda": track_json.get('hda_ldda', 'hda'), - "name": track_json['name'], - "track_type": track_json['track_type'], - "prefs": track_json['prefs'], - "mode": track_json['mode'], - "filters": track_json['filters'], - "tool_state": track_json['tool_state'] + "dataset_id": trans.security.decode_id( dataset_dict['id'] ), + "hda_ldda": dataset_dict.get('hda_ldda', 'hda'), + "name": track_dict['name'], + "track_type": track_dict['track_type'], + "prefs": track_dict['prefs'], + "mode": track_dict['mode'], + "filters": track_dict['filters'], + "tool_state": track_dict['tool_state'] } def unpack_collection( collection_json ): diff --git a/static/scripts/viz/trackster/tracks.js b/static/scripts/viz/trackster/tracks.js index 0882699d369..9cae1df0389 100644 --- a/static/scripts/viz/trackster/tracks.js +++ b/static/scripts/viz/trackster/tracks.js @@ -2765,14 +2765,16 @@ extend(TiledTrack.prototype, Drawable.prototype, Track.prototype, { */ to_dict: function() { return { - "track_type": this.get_type(), - "name": this.name, - "hda_ldda": this.dataset.get('hda_ldda'), - "dataset_id": this.dataset.id, - "prefs": this.prefs, - "mode": this.mode, - "filters": this.filters_manager.to_dict(), - "tool_state": (this.tool ? this.tool.state_dict() : {}) + track_type: this.get_type(), + name: this.name, + dataset: { + id: this.dataset.id, + hda_ldda: this.dataset.get('hda_ldda') + }, + prefs: this.prefs, + mode: this.mode, + filters: this.filters_manager.to_dict(), + tool_state: (this.tool ? this.tool.state_dict() : {}) }; }, /** @@ -2943,10 +2945,13 @@ extend(TiledTrack.prototype, Drawable.prototype, Track.prototype, { // // If using SummaryTree tiles, show max and make it editable. // - this.container_div.find(".yaxislabel").remove(); var track = this, first_tile = tiles[0]; if (first_tile instanceof SummaryTreeTile) { + // Remove old max. + this.container_div.find(".yaxislabel").remove(); + + // Add new max. var max_val = (this.prefs.histogram_max ? this.prefs.histogram_max : first_tile.max_val), max_label = $("
    ").text(max_val).make_text_editable({ num_cols: 12, From 78d107527429b82e66e726f8b18150613be7fd4e Mon Sep 17 00:00:00 2001 From: Jeremy Goecks Date: Wed, 1 May 2013 15:08:25 -0400 Subject: [PATCH 292/523] Trackster: make composite track draw helper compatible with dd4b953. --- static/scripts/viz/trackster/tracks.js | 35 +++++++++++++++++--------- 1 file changed, 23 insertions(+), 12 deletions(-) diff --git a/static/scripts/viz/trackster/tracks.js b/static/scripts/viz/trackster/tracks.js index 9cae1df0389..904131eb289 100644 --- a/static/scripts/viz/trackster/tracks.js +++ b/static/scripts/viz/trackster/tracks.js @@ -3349,18 +3349,26 @@ extend(CompositeTrack.prototype, TiledTrack.prototype, { draw_helper: function(force, region, resolution, parent_element, w_scale, kwargs) { // FIXME: this function is similar to TiledTrack.draw_helper -- can the two be merged/refactored? + + // SHARED block with TiledTrack.draw_helper(): + var track = this, - key = this._gen_tile_cache_key(w_scale, region); + key = this._gen_tile_cache_key(w_scale, region), + is_tile = function(o) { return (o && 'track' in o); }; // Init kwargs if necessary to avoid having to check if kwargs defined. if (!kwargs) { kwargs = {}; } // Check tile cache, if found show existing tile in correct position var tile = (force ? undefined : track.tile_cache.get_elt(key)); - if (tile) { - track.show_tile(tile, parent_element, w_scale); + if (tile) { + if (is_tile(tile)) { + track.show_tile(tile, parent_element, w_scale); + } return tile; } + + // UNIQUE block. // Try to get drawables' data. var all_data = [], @@ -3447,17 +3455,20 @@ extend(CompositeTrack.prototype, TiledTrack.prototype, { this.show_tile(tile, parent_element, w_scale); return tile; } - - // Can't draw now, so trigger another redraw when the data is ready - var can_draw = $.Deferred(), - track = this; + + // SHARED (somewhat) block with TiledTrack.draw_helper() + + // Can't draw now, so put Deferred in cache and draw tile when data is available. + var tile_drawn = $.Deferred(); + track = this; + track.tile_cache.set_elt(key, tile_drawn); $.when.apply($, all_data).then(function() { - view.request_redraw(false, false, false, track); - can_draw.resolve(); + // Draw tile--force to clear Deferred from cache--and resolve. + tile = track.draw_helper(true, region, resolution, parent_element, w_scale, kwargs); + tile_drawn.resolve(tile); }); - // Returned Deferred that is resolved when tile can be drawn. - return can_draw; + return tile_drawn; }, /** @@ -3534,7 +3545,7 @@ extend(CompositeTrack.prototype, TiledTrack.prototype, { for (var i = 0; i < tiles.length; i++) { var tile = tiles[i]; if (tile.html_elt.find("canvas").height() !== max_height) { - this.draw_helper(true, tile.index, tile.resolution, tile.html_elt.parent(), w_scale, { height: max_height } ); + this.draw_helper(true, tile.region, tile.resolution, tile.html_elt.parent(), w_scale, { height: max_height } ); tile.html_elt.remove(); } } From 476530873b9757745d59fe4eb40af3199fe8ddb2 Mon Sep 17 00:00:00 2001 From: Dannon Baker Date: Wed, 1 May 2013 16:28:34 -0400 Subject: [PATCH 293/523] Remove * import, fixes duplicate 'util' import from sqlalchemy --- lib/galaxy/quota/__init__.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/lib/galaxy/quota/__init__.py b/lib/galaxy/quota/__init__.py index 2752f2a6f89..2769b6806ed 100644 --- a/lib/galaxy/quota/__init__.py +++ b/lib/galaxy/quota/__init__.py @@ -2,11 +2,8 @@ Galaxy Quotas """ -import logging, socket, operator -from datetime import datetime, timedelta -from galaxy import util -from galaxy.util.bunch import Bunch -from galaxy.model.orm import * +import logging +import galaxy.util log = logging.getLogger(__name__) @@ -88,7 +85,7 @@ class QuotaAgent( NoQuotaAgent ): rval = 0 if nice_size: if rval is not None: - rval = util.nice_size( rval ) + rval = galaxy.util.nice_size( rval ) else: rval = 'unlimited' return rval From 28eba9efe0efdb109265b6b730f5f250c326d1c4 Mon Sep 17 00:00:00 2001 From: Dannon Baker Date: Wed, 1 May 2013 16:29:18 -0400 Subject: [PATCH 294/523] Fix quota manager to flush properly when 'flush_needed' is set. --- lib/galaxy/quota/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/galaxy/quota/__init__.py b/lib/galaxy/quota/__init__.py index 2769b6806ed..99f99bea19f 100644 --- a/lib/galaxy/quota/__init__.py +++ b/lib/galaxy/quota/__init__.py @@ -146,7 +146,7 @@ class QuotaAgent( NoQuotaAgent ): flush_needed = False for a in quota.users + quota.groups: self.sa_session.delete( a ) - flush_neeeded = True + flush_needed = True if flush_needed: self.sa_session.flush() for user in users: From 16d3a7aa12b7fa8247c02ffc4eee260130ece755 Mon Sep 17 00:00:00 2001 From: Dannon Baker Date: Wed, 1 May 2013 16:59:29 -0400 Subject: [PATCH 295/523] Remove spurious 'Tool did not define error code...' debug statement -- not useful, almost nothing defines one. --- lib/galaxy/jobs/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/galaxy/jobs/__init__.py b/lib/galaxy/jobs/__init__.py index 3545c9b9fb4..d7edef3b41f 100644 --- a/lib/galaxy/jobs/__init__.py +++ b/lib/galaxy/jobs/__init__.py @@ -1174,8 +1174,8 @@ class JobWrapper( object ): # the job has an error, and the job is ok otherwise. else: # TODO: Add in the tool and job id: - log.debug( "Tool did not define exit code or stdio handling; " - + "checking stderr for success" ) + # log.debug( "Tool did not define exit code or stdio handling; " + # + "checking stderr for success" ) if stderr: success = False else: From 3c2a33be79c18b7ad7e7923e256288c90cc31490 Mon Sep 17 00:00:00 2001 From: Greg Von Kuster Date: Wed, 1 May 2013 17:02:32 -0400 Subject: [PATCH 296/523] First pass at standardizing the Repository Actions menu in the tool shed. --- .../tool_shed/controllers/repository.py | 57 ------- .../common/repository_actions_menu.mako | 142 ++++++++++++++++++ .../repository/browse_repository.mako | 77 ++-------- .../tool_shed/repository/contact_owner.mako | 49 +----- .../tool_shed/repository/find_tools.mako | 7 +- .../tool_shed/repository/find_workflows.mako | 7 +- .../repository/manage_repository.mako | 65 +------- .../preview_tools_in_changeset.mako | 28 +--- .../tool_shed/repository/rate_repository.mako | 47 +----- .../tool_shed/repository/tool_form.mako | 73 +-------- .../webapps/tool_shed/repository/upload.mako | 25 +-- .../tool_shed/repository/view_changelog.mako | 41 +---- .../tool_shed/repository/view_changeset.mako | 46 +----- .../tool_shed/repository/view_repository.mako | 74 +-------- .../repository/view_tool_metadata.mako | 81 +--------- .../tool_shed/repository/view_workflow.mako | 56 +------ .../repository_review/browse_review.mako | 14 +- .../repository_review/edit_review.mako | 14 +- .../reviews_of_changeset_revision.mako | 38 +---- .../reviews_of_repository.mako | 39 +---- .../select_previous_review.mako | 41 +---- 21 files changed, 204 insertions(+), 817 deletions(-) create mode 100644 templates/webapps/tool_shed/common/repository_actions_menu.mako diff --git a/lib/galaxy/webapps/tool_shed/controllers/repository.py b/lib/galaxy/webapps/tool_shed/controllers/repository.py index 9c3f35258f7..5d946097df5 100644 --- a/lib/galaxy/webapps/tool_shed/controllers/repository.py +++ b/lib/galaxy/webapps/tool_shed/controllers/repository.py @@ -545,13 +545,11 @@ class RepositoryController( BaseUIController, common_util.ItemRatings ): repo = hg.repository( suc.get_configured_ui(), repository.repo_path( trans.app ) ) # Update repository files for browsing. suc.update_repository( repo ) - is_malicious = suc.changeset_is_malicious( trans, id, repository.tip( trans.app ) ) metadata = self.get_metadata( trans, id, repository.tip( trans.app ) ) return trans.fill_template( '/webapps/tool_shed/repository/browse_repository.mako', repository=repository, metadata=metadata, commit_message=commit_message, - is_malicious=is_malicious, message=message, status=status ) @@ -941,7 +939,6 @@ class RepositoryController( BaseUIController, common_util.ItemRatings ): if message: status = 'error' tool_state = self.__new_state( trans ) - is_malicious = suc.changeset_is_malicious( trans, repository_id, repository.tip( trans.app ) ) metadata = self.get_metadata( trans, repository_id, changeset_revision ) try: return trans.fill_template( "/webapps/tool_shed/repository/tool_form.mako", @@ -950,7 +947,6 @@ class RepositoryController( BaseUIController, common_util.ItemRatings ): changeset_revision=changeset_revision, tool=tool, tool_state=tool_state, - is_malicious=is_malicious, message=message, status=status ) except Exception, e: @@ -1779,7 +1775,6 @@ class RepositoryController( BaseUIController, common_util.ItemRatings ): status = params.get( 'status', 'error' ) repository, tool, error_message = tool_util.load_tool_from_changeset_revision( trans, repository_id, changeset_revision, tool_config ) tool_state = self.__new_state( trans ) - is_malicious = suc.changeset_is_malicious( trans, repository_id, repository.tip( trans.app ) ) invalid_file_tups = [] if tool: invalid_file_tups = tool_util.check_tool_input_params( trans.app, @@ -1797,7 +1792,6 @@ class RepositoryController( BaseUIController, common_util.ItemRatings ): changeset_revision=changeset_revision, tool=tool, tool_state=tool_state, - is_malicious=is_malicious, message=message, status='error' ) except Exception, e: @@ -2024,17 +2018,6 @@ class RepositoryController( BaseUIController, common_util.ItemRatings ): malicious_check_box = CheckboxField( 'malicious', checked=is_malicious ) categories = suc.get_categories( trans ) selected_categories = [ rca.category_id for rca in repository.categories ] - # Determine if the current changeset revision has been reviewed by the current user. - reviewed_by_user = review_util.changeset_revision_reviewed_by_user( trans, trans.user, repository, changeset_revision ) - if reviewed_by_user: - review = review_util.get_review_by_repository_id_changeset_revision_user_id( trans=trans, - repository_id=id, - changeset_revision=changeset_revision, - user_id=trans.security.encode_id( trans.user.id ) ) - review_id = trans.security.encode_id( review.id ) - else: - review_id = None - can_browse_repository_reviews = review_util.can_browse_repository_reviews( trans, repository ) containers_dict = container_util.build_repository_containers_for_tool_shed( trans, repository, changeset_revision, repository_dependencies, repository_metadata ) return trans.fill_template( '/webapps/tool_shed/repository/manage_repository.mako', repo_name=repo_name, @@ -2047,8 +2030,6 @@ class RepositoryController( BaseUIController, common_util.ItemRatings ): containers_dict=containers_dict, repository_metadata=repository_metadata, changeset_revision=changeset_revision, - reviewed_by_user=reviewed_by_user, - review_id=review_id, changeset_revision_select_field=changeset_revision_select_field, revision_label=revision_label, selected_categories=selected_categories, @@ -2059,8 +2040,6 @@ class RepositoryController( BaseUIController, common_util.ItemRatings ): num_ratings=num_ratings, alerts_check_box=alerts_check_box, malicious_check_box=malicious_check_box, - is_malicious=is_malicious, - can_browse_repository_reviews=can_browse_repository_reviews, message=message, status=status ) @@ -2214,7 +2193,6 @@ class RepositoryController( BaseUIController, common_util.ItemRatings ): avg_rating, num_ratings = self.get_ave_item_rating_data( trans.sa_session, repository, webapp_model=trans.model ) display_reviews = util.string_as_bool( params.get( 'display_reviews', False ) ) rra = self.get_user_item_rating( trans.sa_session, trans.user, repository, webapp_model=trans.model ) - is_malicious = suc.changeset_is_malicious( trans, id, repository.tip( trans.app ) ) metadata = self.get_metadata( trans, id, repository.tip( trans.app ) ) return trans.fill_template( '/webapps/tool_shed/repository/rate_repository.mako', repository=repository, @@ -2223,7 +2201,6 @@ class RepositoryController( BaseUIController, common_util.ItemRatings ): display_reviews=display_reviews, num_ratings=num_ratings, rra=rra, - is_malicious=is_malicious, message=message, status=status ) @@ -2403,12 +2380,10 @@ class RepositoryController( BaseUIController, common_util.ItemRatings ): else: message = "Select at least 1 file to delete from the repository before clicking Delete selected files." status = "error" - is_malicious = suc.changeset_is_malicious( trans, id, repository.tip( trans.app ) ) return trans.fill_template( '/webapps/tool_shed/repository/browse_repository.mako', repo=repo, repository=repository, commit_message=commit_message, - is_malicious=is_malicious, message=message, status=status ) @@ -2679,13 +2654,11 @@ class RepositoryController( BaseUIController, common_util.ItemRatings ): 'has_metadata' : has_metadata } # Make sure we'll view latest changeset first. changesets.insert( 0, change_dict ) - is_malicious = suc.changeset_is_malicious( trans, id, repository.tip( trans.app ) ) metadata = self.get_metadata( trans, id, repository.tip( trans.app ) ) return trans.fill_template( '/webapps/tool_shed/repository/view_changelog.mako', repository=repository, metadata=metadata, changesets=changesets, - is_malicious=is_malicious, message=message, status=status ) @@ -2711,7 +2684,6 @@ class RepositoryController( BaseUIController, common_util.ItemRatings ): diffs = [] for diff in patch.diff( repo, node1=ctx_parent.node(), node2=ctx.node() ): diffs.append( suc.to_safe_string( diff, to_html=True ) ) - is_malicious = suc.changeset_is_malicious( trans, id, repository.tip( trans.app ) ) metadata = self.get_metadata( trans, id, ctx_str ) return trans.fill_template( '/webapps/tool_shed/repository/view_changeset.mako', repository=repository, @@ -2726,7 +2698,6 @@ class RepositoryController( BaseUIController, common_util.ItemRatings ): ignored=ignored, clean=clean, diffs=diffs, - is_malicious=is_malicious, message=message, status=status ) @@ -2814,18 +2785,7 @@ class RepositoryController( BaseUIController, common_util.ItemRatings ): else: message += malicious_error status = 'error' - # Determine if the current changeset revision has been reviewed by the current user. - reviewed_by_user = review_util.changeset_revision_reviewed_by_user( trans, trans.user, repository, changeset_revision ) - if reviewed_by_user: - review = review_util.get_review_by_repository_id_changeset_revision_user_id( trans=trans, - repository_id=id, - changeset_revision=changeset_revision, - user_id=trans.security.encode_id( trans.user.id ) ) - review_id = trans.security.encode_id( review.id ) - else: - review_id = None containers_dict = container_util.build_repository_containers_for_tool_shed( trans, repository, changeset_revision, repository_dependencies, repository_metadata ) - can_browse_repository_reviews = review_util.can_browse_repository_reviews( trans, repository ) return trans.fill_template( '/webapps/tool_shed/repository/view_repository.mako', repo=repo, repository=repository, @@ -2837,12 +2797,8 @@ class RepositoryController( BaseUIController, common_util.ItemRatings ): num_ratings=num_ratings, alerts_check_box=alerts_check_box, changeset_revision=changeset_revision, - reviewed_by_user=reviewed_by_user, - review_id=review_id, changeset_revision_select_field=changeset_revision_select_field, revision_label=revision_label, - is_malicious=is_malicious, - can_browse_repository_reviews=can_browse_repository_reviews, message=message, status=status ) @@ -2898,22 +2854,12 @@ class RepositoryController( BaseUIController, common_util.ItemRatings ): else: repository_metadata_id = None metadata = None - is_malicious = suc.changeset_is_malicious( trans, repository_id, repository.tip( trans.app ) ) changeset_revision_select_field = grids_util.build_changeset_revision_select_field( trans, repository, selected_value=changeset_revision, add_id_to_name=False, downloadable=False ) trans.app.config.tool_data_path = original_tool_data_path - reviewed_by_user = review_util.changeset_revision_reviewed_by_user( trans, trans.user, repository, changeset_revision ) - if reviewed_by_user: - review = review_util.get_review_by_repository_id_changeset_revision_user_id( trans=trans, - repository_id=repository_id, - changeset_revision=changeset_revision, - user_id=trans.security.encode_id( trans.user.id ) ) - review_id = trans.security.encode_id( review.id ) - else: - review_id = None return trans.fill_template( "/webapps/tool_shed/repository/view_tool_metadata.mako", repository=repository, repository_metadata_id=repository_metadata_id, @@ -2924,9 +2870,6 @@ class RepositoryController( BaseUIController, common_util.ItemRatings ): changeset_revision=changeset_revision, revision_label=revision_label, changeset_revision_select_field=changeset_revision_select_field, - is_malicious=is_malicious, - reviewed_by_user=reviewed_by_user, - review_id=review_id, message=message, status=status ) diff --git a/templates/webapps/tool_shed/common/repository_actions_menu.mako b/templates/webapps/tool_shed/common/repository_actions_menu.mako new file mode 100644 index 00000000000..6001bcb4bce --- /dev/null +++ b/templates/webapps/tool_shed/common/repository_actions_menu.mako @@ -0,0 +1,142 @@ +<%inherit file="/base.mako"/> + +<%def name="render_tool_shed_repository_actions( repository, repo=None, metadata=None, changeset_revision=None )"> + <% + from tool_shed.util.review_util import can_browse_repository_reviews, changeset_revision_reviewed_by_user, get_review_by_repository_id_changeset_revision_user_id + from tool_shed.util.shed_util_common import changeset_is_malicious + + has_metadata = repository.metadata_revisions + has_readme = metadata and 'readme' in metadata + is_admin = trans.user_is_admin() + is_deprecated = repository.deprecated + is_new = repository.is_new( trans.app ) + is_malicious = changeset_is_malicious( trans, trans.security.encode_id( repository.id ), repository.tip( trans.app ) ) + + can_browse_contents = not is_new + can_browse_reviews = can_browse_repository_reviews( trans, repository ) + can_contact_owner = trans.user and trans.user != repository.user + can_deprecate = not is_new and trans.user and ( is_admin or repository.user == trans.user ) and not is_deprecated + can_push = not is_deprecated and trans.app.security_agent.can_push( trans.app, trans.user, repository ) + can_download = not is_deprecated and not is_new and ( not is_malicious or can_push ) + if not is_deprecated: + if repo and len( repo ) > 0: + can_reset_all_metadata = True + else: + can_reset_all_metadata = False + else: + can_reset_all_metadata = False + can_upload = can_push and not is_deprecated + can_rate = not is_new and not is_deprecated and trans.user and repository.user != trans.user + if changeset_revision: + can_review_repository = has_metadata and not is_deprecated and trans.app.security_agent.user_can_review_repositories( trans.user ) + reviewed_by_user = changeset_revision_reviewed_by_user( trans, trans.user, repository, changeset_revision ) + else: + can_review_repository = False + reviewed_by_user = False + + # Determine if the current changeset revision has been reviewed by the current user. + if reviewed_by_user: + review = get_review_by_repository_id_changeset_revision_user_id( trans=trans, + repository_id=trans.security.encode_id( repository.id ), + changeset_revision=changeset_revision, + user_id=trans.security.encode_id( trans.user.id ) ) + review_id = trans.security.encode_id( review.id ) + else: + review_id = None + + can_set_metadata = not is_new and not is_deprecated + if changeset_revision: + if changeset_revision == repository.tip( trans.app ): + changeset_revision_is_repository_tip = True + else: + changeset_revision_is_repository_tip = False + else: + changeset_revision_is_repository_tip = False + can_set_malicious = metadata and can_set_metadata and is_admin and changeset_revision_is_repository_tip + can_undeprecate = trans.user and ( is_admin or repository.user == trans.user ) and is_deprecated + can_manage = is_admin or repository.user == trans.user + can_view_change_log = not is_new + + if can_push: + browse_label = 'Browse or delete repository tip files' + else: + browse_label = 'Browse repository tip files' + %> + +

    + + + +<%def name="render_galaxy_repository_actions( repository=None )"> +

    + + diff --git a/templates/webapps/tool_shed/repository/browse_repository.mako b/templates/webapps/tool_shed/repository/browse_repository.mako index 88845d426b8..38a503f8b07 100644 --- a/templates/webapps/tool_shed/repository/browse_repository.mako +++ b/templates/webapps/tool_shed/repository/browse_repository.mako @@ -1,23 +1,9 @@ <%inherit file="/base.mako"/> <%namespace file="/message.mako" import="render_msg" /> +<%namespace file="/webapps/tool_shed/common/repository_actions_menu.mako" import="render_tool_shed_repository_actions" /> <%namespace file="/webapps/tool_shed/common/common.mako" import="*" /> <%namespace file="/webapps/tool_shed/repository/common.mako" import="*" /> -<% - from galaxy.web.framework.helpers import time_ago - is_admin = trans.user_is_admin() - is_new = repository.is_new( trans.app ) - can_contact_owner = trans.user and trans.user != repository.user - can_push = trans.app.security_agent.can_push( trans.app, trans.user, repository ) - can_upload = can_push and ( not repository.deprecated ) - can_download = not is_new and ( not is_malicious or can_push ) - can_browse_contents = not is_new - can_rate = trans.user and repository.user != trans.user - can_manage = is_admin or repository.user == trans.user - can_view_change_log = not is_new - has_readme = metadata and 'readme' in metadata -%> - <%! def inherit(context): if context.get('use_panels'): @@ -30,30 +16,6 @@ <%def name="stylesheets()"> ${parent.stylesheets()} ${h.css( "jquery.rating", "dynatree_skin/ui.dynatree" )} - <%def name="javascripts()"> @@ -62,35 +24,14 @@ ${common_javascripts(repository)} -

    - +<% + is_new = repository.is_new( trans.app ) + can_push = trans.app.security_agent.can_push( trans.app, trans.user, repository ) + can_download = not is_new and ( not is_malicious or can_push ) + can_browse_contents = not is_new +%> + +${render_tool_shed_repository_actions( repository )} %if message: ${render_msg( message, status )} diff --git a/templates/webapps/tool_shed/repository/contact_owner.mako b/templates/webapps/tool_shed/repository/contact_owner.mako index 849cbad4550..4dc3b2e6d23 100644 --- a/templates/webapps/tool_shed/repository/contact_owner.mako +++ b/templates/webapps/tool_shed/repository/contact_owner.mako @@ -1,23 +1,6 @@ <%inherit file="/base.mako"/> <%namespace file="/message.mako" import="render_msg" /> - -<% - is_admin = trans.user_is_admin() - is_new = repository.is_new( trans.app ) - can_contact_owner = trans.user and trans.user != repository.user - can_push = trans.app.security_agent.can_push( trans.app, trans.user, repository ) - can_upload = can_push - can_download = not is_new and ( not is_malicious or can_push ) - can_browse_contents = not is_new - can_rate = repository.user != trans.user - can_manage = is_admin or repository.user == trans.user - can_view_change_log = not is_new - if can_push: - browse_label = 'Browse or delete repository tip files' - else: - browse_label = 'Browse repository tip files' - has_readme = metadata and 'readme' in metadata -%> +<%namespace file="/webapps/tool_shed/common/repository_actions_menu.mako" import="render_tool_shed_repository_actions" /> <%! def inherit(context): @@ -28,35 +11,7 @@ %> <%inherit file="${inherit(context)}"/> -

    - +${render_tool_shed_repository_actions( repository )} %if message: ${render_msg( message, status )} diff --git a/templates/webapps/tool_shed/repository/find_tools.mako b/templates/webapps/tool_shed/repository/find_tools.mako index 181d344a334..7bcacfcee0b 100644 --- a/templates/webapps/tool_shed/repository/find_tools.mako +++ b/templates/webapps/tool_shed/repository/find_tools.mako @@ -1,5 +1,6 @@ <%inherit file="/base.mako"/> <%namespace file="/message.mako" import="render_msg" /> +<%namespace file="/webapps/tool_shed/common/repository_actions_menu.mako" import="render_galaxy_repository_actions" /> <%! def inherit(context): @@ -11,11 +12,7 @@ <%inherit file="${inherit(context)}"/> %if trans.webapp.name == 'galaxy': -

    - + ${render_galaxy_repository_actions( repository=None )} %endif %if message: diff --git a/templates/webapps/tool_shed/repository/find_workflows.mako b/templates/webapps/tool_shed/repository/find_workflows.mako index 648e563935b..f4f36915b8b 100644 --- a/templates/webapps/tool_shed/repository/find_workflows.mako +++ b/templates/webapps/tool_shed/repository/find_workflows.mako @@ -1,5 +1,6 @@ <%inherit file="/base.mako"/> <%namespace file="/message.mako" import="render_msg" /> +<%namespace file="/webapps/tool_shed/common/repository_actions_menu.mako" import="render_galaxy_repository_actions" /> <%! def inherit(context): @@ -11,11 +12,7 @@ <%inherit file="${inherit(context)}"/> %if trans.webapp.name == 'galaxy': -

    - + ${render_galaxy_repository_actions( repository=None )} %endif %if message: diff --git a/templates/webapps/tool_shed/repository/manage_repository.mako b/templates/webapps/tool_shed/repository/manage_repository.mako index e43ae00fb51..636b5c2e1f6 100644 --- a/templates/webapps/tool_shed/repository/manage_repository.mako +++ b/templates/webapps/tool_shed/repository/manage_repository.mako @@ -2,6 +2,7 @@ <%namespace file="/message.mako" import="render_msg" /> <%namespace file="/webapps/tool_shed/common/common.mako" import="*" /> <%namespace file="/webapps/tool_shed/repository/common.mako" import="*" /> +<%namespace file="/webapps/tool_shed/common/repository_actions_menu.mako" import="render_tool_shed_repository_actions" /> <% from galaxy.web.framework.helpers import time_ago @@ -12,26 +13,14 @@ is_new = repository.is_new( trans.app ) is_deprecated = repository.deprecated - can_browse_contents = not is_new - can_contact_owner = trans.user and trans.user != repository.user - can_deprecate = not is_new and trans.user and ( is_admin or repository.user == trans.user ) and not is_deprecated can_push = not is_deprecated and trans.app.security_agent.can_push( trans.app, trans.user, repository ) can_download = not is_deprecated and not is_new and ( not is_malicious or can_push ) - can_rate = not is_new and not is_deprecated and trans.user and repository.user != trans.user - can_reset_all_metadata = not is_deprecated and len( repo ) > 0 can_review_repository = has_metadata and not is_deprecated and trans.app.security_agent.user_can_review_repositories( trans.user ) can_set_metadata = not is_new and not is_deprecated changeset_revision_is_repository_tip = changeset_revision == repository.tip( trans.app ) can_set_malicious = metadata and can_set_metadata and is_admin and changeset_revision_is_repository_tip - can_undeprecate = trans.user and ( is_admin or repository.user == trans.user ) and is_deprecated - can_upload = can_push can_view_change_log = not is_new - if can_push: - browse_label = 'Browse or delete repository tip files' - else: - browse_label = 'Browse repository tip files' - if changeset_revision_is_repository_tip: tip_str = 'repository tip' sharable_link_label = 'Sharable link to this repository:' @@ -62,57 +51,7 @@ ${container_javascripts()} -

    - +${render_tool_shed_repository_actions( repository, repo=repo, metadata=metadata, changeset_revision=changeset_revision )} %if message: ${render_msg( message, status )} diff --git a/templates/webapps/tool_shed/repository/preview_tools_in_changeset.mako b/templates/webapps/tool_shed/repository/preview_tools_in_changeset.mako index f7d11a62131..5c7ae8784b1 100644 --- a/templates/webapps/tool_shed/repository/preview_tools_in_changeset.mako +++ b/templates/webapps/tool_shed/repository/preview_tools_in_changeset.mako @@ -2,22 +2,7 @@ <%namespace file="/message.mako" import="render_msg" /> <%namespace file="/webapps/tool_shed/common/common.mako" import="*" /> <%namespace file="/webapps/tool_shed/repository/common.mako" import="*" /> - -<% - is_new = repository.is_new( trans.app ) - can_contact_owner = trans.user and trans.user != repository.user - can_push = trans.app.security_agent.can_push( trans.app, trans.user, repository ) - can_rate = not is_new and trans.user and repository.user != trans.user - can_upload = can_push - can_download = not is_new and ( not is_malicious or can_push ) - can_browse_contents = not is_new - can_view_change_log = not is_new - if can_push: - browse_label = 'Browse or delete repository tip files' - else: - browse_label = 'Browse repository tip files' - has_readme = metadata and 'readme' in metadata -%> +<%namespace file="/webapps/tool_shed/common/repository_actions_menu.mako" import="render_galaxy_repository_actions" /> <%! def inherit(context): @@ -39,16 +24,7 @@ ${container_javascripts()} -

    - +${render_galaxy_repository_actions( repository=repository )} %if message: ${render_msg( message, status )} diff --git a/templates/webapps/tool_shed/repository/rate_repository.mako b/templates/webapps/tool_shed/repository/rate_repository.mako index a9892483e67..799f9811c9a 100644 --- a/templates/webapps/tool_shed/repository/rate_repository.mako +++ b/templates/webapps/tool_shed/repository/rate_repository.mako @@ -1,25 +1,13 @@ <%namespace file="/message.mako" import="render_msg" /> <%namespace file="/webapps/tool_shed/common/common.mako" import="*" /> <%namespace file="/webapps/tool_shed/repository/common.mako" import="*" /> +<%namespace file="/webapps/tool_shed/common/repository_actions_menu.mako" import="render_tool_shed_repository_actions" /> <% from galaxy.web.framework.helpers import time_ago - from urllib import quote_plus - is_admin = trans.user_is_admin() is_new = repository.is_new( trans.app ) - can_contact_owner = trans.user and trans.user != repository.user can_push = trans.app.security_agent.can_push( trans.app, trans.user, repository ) - can_upload = can_push can_download = not is_new and ( not is_malicious or can_push ) - can_browse_contents = not is_new - can_rate = repository.user != trans.user - can_manage = is_admin or repository.user == trans.user - can_view_change_log = not is_new - if can_push: - browse_label = 'Browse or delete repository tip files' - else: - browse_label = 'Browse repository tip files' - has_readme = metadata and 'readme' in metadata %> <%! @@ -45,38 +33,7 @@ ${render_msg( message, status )} %endif -

    - +${render_tool_shed_repository_actions( repository, repo=None, metadata=None, changeset_revision=None )} %if repository.user != trans.user:
    diff --git a/templates/webapps/tool_shed/repository/tool_form.mako b/templates/webapps/tool_shed/repository/tool_form.mako index 4a2de4153ce..38fb537af68 100644 --- a/templates/webapps/tool_shed/repository/tool_form.mako +++ b/templates/webapps/tool_shed/repository/tool_form.mako @@ -1,27 +1,12 @@ <%inherit file="/base.mako"/> <%namespace file="/message.mako" import="render_msg" /> +<%namespace file="/webapps/tool_shed/common/repository_actions_menu.mako" import="*" /> <% from galaxy.util.expressions import ExpressionContext from galaxy import util from galaxy.tools.parameters.basic import DataToolParameter, ColumnListParameter, GenomeBuildParameter, SelectToolParameter from galaxy.web.form_builder import SelectField - - is_admin = trans.user_is_admin() - is_new = repository.is_new( trans.app ) - can_contact_owner = trans.user and trans.user != repository.user - can_push = trans.app.security_agent.can_push( trans.app, trans.user, repository ) - can_upload = can_push - can_download = not is_new and ( not is_malicious or can_push ) - can_browse_contents = not is_new - can_rate = trans.user and repository.user != trans.user - can_manage = is_admin or repository.user == trans.user - can_view_change_log = not is_new - if can_push: - browse_label = 'Browse or delete repository tip files' - else: - browse_label = 'Browse repository tip files' - has_readme = metadata and 'readme' in metadata %> @@ -119,56 +104,12 @@
    -

    - - + %if trans.webapp.name == 'galaxy': + ${render_galaxy_repository_actions( repository=repository )} + %else: + ${render_tool_shed_repository_actions( repository, repo=None, metadata=None, changeset_revision=None )} + %endif + %if message: ${render_msg( message, status )} %endif diff --git a/templates/webapps/tool_shed/repository/upload.mako b/templates/webapps/tool_shed/repository/upload.mako index 8c3932da76b..c70f85c6b82 100644 --- a/templates/webapps/tool_shed/repository/upload.mako +++ b/templates/webapps/tool_shed/repository/upload.mako @@ -1,14 +1,9 @@ <%namespace file="/message.mako" import="render_msg" /> <%namespace file="/webapps/tool_shed/repository/common.mako" import="*" /> +<%namespace file="/webapps/tool_shed/common/repository_actions_menu.mako" import="render_tool_shed_repository_actions" /> <% - is_admin = trans.user_is_admin() is_new = repository.is_new( trans.app ) - can_browse_contents = not is_new - can_browse_contents = not is_new - can_rate = repository.user != trans.user - can_manage = is_admin or repository.user == trans.user - can_view_change_log = not is_new %> <%! @@ -43,23 +38,7 @@ ${render_msg( message, status )} %endif -

    - +${render_tool_shed_repository_actions( repository=repository)}
    Upload a single file or a tarball
    diff --git a/templates/webapps/tool_shed/repository/view_changelog.mako b/templates/webapps/tool_shed/repository/view_changelog.mako index 9c424fbc277..02b0bd4c594 100644 --- a/templates/webapps/tool_shed/repository/view_changelog.mako +++ b/templates/webapps/tool_shed/repository/view_changelog.mako @@ -2,23 +2,13 @@ <%namespace file="/message.mako" import="render_msg" /> <%namespace file="/webapps/tool_shed/common/common.mako" import="*" /> <%namespace file="/webapps/tool_shed/repository/common.mako" import="render_clone_str" /> +<%namespace file="/webapps/tool_shed/common/repository_actions_menu.mako" import="render_tool_shed_repository_actions" /> <% from galaxy.web.framework.helpers import time_ago - is_admin = trans.user_is_admin() is_new = repository.is_new( trans.app ) - can_contact_owner = trans.user and trans.user != repository.user - can_browse_contents = not is_new - can_manage = is_admin or trans.user == repository.user can_push = trans.app.security_agent.can_push( trans.app, trans.user, repository ) - can_rate = trans.user and repository.user != trans.user - can_upload = can_push can_download = not is_new and ( not is_malicious or can_push ) - if can_push: - browse_label = 'Browse or delete repository tip files' - else: - browse_label = 'Browse repository tip files' - has_readme = metadata and 'readme' in metadata %> <%! @@ -40,34 +30,7 @@ ${h.js( "libs/jquery/jquery.rating" )} -

    - +${render_tool_shed_repository_actions( repository=repository )} %if message: ${render_msg( message, status )} diff --git a/templates/webapps/tool_shed/repository/view_changeset.mako b/templates/webapps/tool_shed/repository/view_changeset.mako index e5b33df4ceb..be95ae8cb3f 100644 --- a/templates/webapps/tool_shed/repository/view_changeset.mako +++ b/templates/webapps/tool_shed/repository/view_changeset.mako @@ -2,24 +2,12 @@ <%namespace file="/message.mako" import="render_msg" /> <%namespace file="/webapps/tool_shed/common/common.mako" import="*" /> <%namespace file="/webapps/tool_shed/repository/common.mako" import="render_clone_str" /> +<%namespace file="/webapps/tool_shed/common/repository_actions_menu.mako" import="render_tool_shed_repository_actions" /> <% - from galaxy.web.framework.helpers import time_ago - is_admin = trans.user_is_admin() is_new = repository.is_new( trans.app ) - can_contact_owner = trans.user and trans.user != repository.user - can_browse_contents = not is_new - can_rate = trans.user and repository.user != trans.user - can_manage = is_admin or trans.user == repository.user can_push = trans.app.security_agent.can_push( trans.app, trans.user, repository ) - can_view_change_log = not is_new - can_upload = can_push can_download = not is_new and ( not is_malicious or can_push ) - if can_push: - browse_label = 'Browse or delete repository tip files' - else: - browse_label = 'Browse repository tip files' - has_readme = metadata and 'readme' in metadata %> <%! @@ -41,37 +29,7 @@ ${h.js( "libs/jquery/jquery.rating" )} -

    - +${render_tool_shed_repository_actions( repository=repository )} %if message: ${render_msg( message, status )} diff --git a/templates/webapps/tool_shed/repository/view_repository.mako b/templates/webapps/tool_shed/repository/view_repository.mako index 3a78e64c489..872b884b3d0 100644 --- a/templates/webapps/tool_shed/repository/view_repository.mako +++ b/templates/webapps/tool_shed/repository/view_repository.mako @@ -2,34 +2,21 @@ <%namespace file="/message.mako" import="render_msg" /> <%namespace file="/webapps/tool_shed/common/common.mako" import="*" /> <%namespace file="/webapps/tool_shed/repository/common.mako" import="*" /> +<%namespace file="/webapps/tool_shed/common/repository_actions_menu.mako" import="*" /> <% from galaxy.web.framework.helpers import time_ago from tool_shed.util.shed_util_common import to_safe_string - if metadata: - has_readme = 'readme' in metadata - else: - has_readme = False - has_metadata = repository.metadata_revisions is_new = repository.is_new( trans.app ) is_deprecated = repository.deprecated can_browse_contents = trans.webapp.name == 'tool_shed' and not is_new - can_contact_owner = trans.user and trans.user != repository.user - can_download = not is_deprecated and not is_new and ( not is_malicious or can_push ) can_push = not is_deprecated and trans.app.security_agent.can_push( trans.app, trans.user, repository ) - can_rate = not is_deprecated and not is_new and trans.user and repository.user != trans.user - can_review_repository = has_metadata and not is_deprecated and trans.app.security_agent.user_can_review_repositories( trans.user ) - can_upload = can_push + can_download = not is_deprecated and not is_new and ( not is_malicious or can_push ) can_view_change_log = trans.webapp.name == 'tool_shed' and not is_new changeset_revision_is_repository_tip = changeset_revision == repository.tip( trans.app ) - if can_push: - browse_label = 'Browse or delete repository tip files' - else: - browse_label = 'Browse repository tip files' - if changeset_revision_is_repository_tip: tip_str = 'repository tip' sharable_link_label = 'Sharable link to this repository:' @@ -60,58 +47,11 @@ ${container_javascripts()} -

    - +%if trans.webapp.name == 'tool_shed': + ${render_tool_shed_repository_actions( repository=repository, changeset_revision=changeset_revision )} +%else: + ${render_galaxy_repository_actions( repository=repository )} +%endif %if message: ${render_msg( message, status )} diff --git a/templates/webapps/tool_shed/repository/view_tool_metadata.mako b/templates/webapps/tool_shed/repository/view_tool_metadata.mako index e7da70057ca..729aae74cd5 100644 --- a/templates/webapps/tool_shed/repository/view_tool_metadata.mako +++ b/templates/webapps/tool_shed/repository/view_tool_metadata.mako @@ -2,33 +2,14 @@ <%namespace file="/message.mako" import="render_msg" /> <%namespace file="/webapps/tool_shed/common/common.mako" import="*" /> <%namespace file="/webapps/tool_shed/repository/common.mako" import="*" /> +<%namespace file="/webapps/tool_shed/common/repository_actions_menu.mako" import="*" /> <% - from galaxy.web.framework.helpers import time_ago - from urllib import quote_plus - - has_metadata = repository.metadata_revisions - has_readme = metadata and 'readme' in metadata - is_admin = trans.user_is_admin() is_new = repository.is_new( trans.app ) - is_deprecated = repository.deprecated - can_browse_contents = trans.webapp.name == 'tool_shed' and not is_new - can_contact_owner = trans.user and trans.user != repository.user - can_download = not is_new and ( not is_malicious or can_push ) - can_manage = is_admin or repository.user == trans.user can_push = trans.app.security_agent.can_push( trans.app, trans.user, repository ) - can_rate = repository.user != trans.user - can_review_repository = has_metadata and not is_deprecated and trans.app.security_agent.user_can_review_repositories( trans.user ) - can_upload = can_push + can_download = not is_new and ( not is_malicious or can_push ) can_view_change_log = trans.webapp.name == 'tool_shed' and not is_new - # TODO: fix the following when the install and test buildbot is functional. - #can_view_tool_test_results = tool_test_results is not None - - if can_push: - browse_label = 'Browse or delete repository tip files' - else: - browse_label = 'Browse repository tip files' %> <%! @@ -40,59 +21,11 @@ %> <%inherit file="${inherit(context)}"/> -

    - +%if trans.webapp.name == 'tool_shed': + ${render_tool_shed_repository_actions( repository=repository, changeset_revision=changeset_revision )} +%else: + ${render_galaxy_repository_actions( repository=repository )} +%endif %if message: ${render_msg( message, status )} diff --git a/templates/webapps/tool_shed/repository/view_workflow.mako b/templates/webapps/tool_shed/repository/view_workflow.mako index 2ce3bd06e89..175220d1d97 100644 --- a/templates/webapps/tool_shed/repository/view_workflow.mako +++ b/templates/webapps/tool_shed/repository/view_workflow.mako @@ -2,27 +2,10 @@ <%namespace file="/message.mako" import="render_msg" /> <%namespace file="/webapps/tool_shed/common/common.mako" import="*" /> <%namespace file="/webapps/tool_shed/repository/common.mako" import="*" /> +<%namespace file="/webapps/tool_shed/common/repository_actions_menu.mako" import="render_tool_shed_repository_actions" /> <% - from galaxy.web.framework.helpers import time_ago from tool_shed.util.encoding_util import tool_shed_encode - - is_admin = trans.user_is_admin() - is_new = repository.is_new( trans.app ) - can_manage = is_admin or trans.user == repository.user - can_contact_owner = trans.user and trans.user != repository.user - can_push = trans.app.security_agent.can_push( trans.app, trans.user, repository ) - can_upload = can_push - can_download = not is_new and ( not is_malicious or can_push ) - can_browse_contents = not is_new - can_rate = not is_new and trans.user and repository.user != trans.user - can_view_change_log = not is_new - if can_push: - browse_label = 'Browse or delete repository tip files' - else: - browse_label = 'Browse repository tip files' - - #
  • Install repository to Galaxy
  • %> <%! @@ -32,6 +15,7 @@ else: return '/base.mako' %> + <%inherit file="${inherit(context)}"/> <%def name="render_workflow( workflow_name, repository_metadata_id )"> @@ -39,41 +23,7 @@ -

    - +${render_tool_shed_repository_actions( repository=repository )} %if message: ${render_msg( message, status )} diff --git a/templates/webapps/tool_shed/repository_review/browse_review.mako b/templates/webapps/tool_shed/repository_review/browse_review.mako index ff7081d754d..94509649e12 100644 --- a/templates/webapps/tool_shed/repository_review/browse_review.mako +++ b/templates/webapps/tool_shed/repository_review/browse_review.mako @@ -1,12 +1,12 @@ <%inherit file="/base.mako"/> <%namespace file="/message.mako" import="render_msg" /> <%namespace file="/webapps/tool_shed/common/common.mako" import="*" /> +<%namespace file="/webapps/tool_shed/common/repository_actions_menu.mako" import="render_tool_shed_repository_actions" /> <% from galaxy.web.form_builder import CheckboxField from galaxy.webapps.tool_shed.util.container_util import STRSEP from tool_shed.util.shed_util_common import to_safe_string - can_manage_repository = is_admin or repository.user == trans.user %> <%def name="stylesheets()"> @@ -18,17 +18,7 @@ ${h.js( "libs/jquery/jquery.rating" )} -

    - +${render_tool_shed_repository_actions( repository=repository, changeset_revision=review.changeset_revision )} %if message: ${render_msg( message, status )} diff --git a/templates/webapps/tool_shed/repository_review/edit_review.mako b/templates/webapps/tool_shed/repository_review/edit_review.mako index dd8e118f688..46aa44c3d19 100644 --- a/templates/webapps/tool_shed/repository_review/edit_review.mako +++ b/templates/webapps/tool_shed/repository_review/edit_review.mako @@ -1,12 +1,12 @@ <%inherit file="/base.mako"/> <%namespace file="/message.mako" import="render_msg" /> <%namespace file="/webapps/tool_shed/common/common.mako" import="*" /> +<%namespace file="/webapps/tool_shed/common/repository_actions_menu.mako" import="render_tool_shed_repository_actions" /> <% from galaxy.web.form_builder import CheckboxField from tool_shed.grids.util import build_approved_select_field from galaxy.webapps.tool_shed.util.container_util import STRSEP - can_manage_repository = is_admin or repository.user == trans.user %> <%def name="stylesheets()"> @@ -18,17 +18,7 @@ ${h.js( "libs/jquery/jquery.rating" )} -

    - +${render_tool_shed_repository_actions( repository=repository, changeset_revision=review.changeset_revision )} %if message: ${render_msg( message, status )} diff --git a/templates/webapps/tool_shed/repository_review/reviews_of_changeset_revision.mako b/templates/webapps/tool_shed/repository_review/reviews_of_changeset_revision.mako index 4adc6e51ab6..1822defd0ab 100644 --- a/templates/webapps/tool_shed/repository_review/reviews_of_changeset_revision.mako +++ b/templates/webapps/tool_shed/repository_review/reviews_of_changeset_revision.mako @@ -2,22 +2,12 @@ <%namespace file="/message.mako" import="render_msg" /> <%namespace file="/webapps/tool_shed/common/common.mako" import="*" /> <%namespace file="/webapps/tool_shed/repository/common.mako" import="*" /> +<%namespace file="/webapps/tool_shed/common/repository_actions_menu.mako" import="render_tool_shed_repository_actions" /> <% from tool_shed.grids.util import build_approved_select_field from galaxy.webapps.tool_shed.util.container_util import STRSEP - is_admin = trans.user_is_admin() - is_new = repository.is_new( trans.app ) - can_browse_contents = not is_new - can_contact_owner = trans.user and trans.user != repository.user - can_manage = is_admin or repository.user == trans.user - can_push = trans.app.security_agent.can_push( trans.app, trans.user, repository ) - can_rate = not is_new and trans.user and repository.user != trans.user - can_view_change_log = not is_new - if can_push: - browse_label = 'Browse or delete repository tip files' - else: - browse_label = 'Browse repository tip files' + if installable: installable_str = 'yes' else: @@ -44,29 +34,7 @@ ${common_javascripts(repository)} -

    - +${render_tool_shed_repository_actions( repository=repository, changeset_revision=changeset_revision )} %if message: ${render_msg( message, status )} diff --git a/templates/webapps/tool_shed/repository_review/reviews_of_repository.mako b/templates/webapps/tool_shed/repository_review/reviews_of_repository.mako index a20853cf349..d5f06a10f00 100644 --- a/templates/webapps/tool_shed/repository_review/reviews_of_repository.mako +++ b/templates/webapps/tool_shed/repository_review/reviews_of_repository.mako @@ -2,26 +2,13 @@ <%namespace file="/message.mako" import="render_msg" /> <%namespace file="/webapps/tool_shed/common/common.mako" import="*" /> <%namespace file="/webapps/tool_shed/repository/common.mako" import="*" /> +<%namespace file="/webapps/tool_shed/common/repository_actions_menu.mako" import="render_tool_shed_repository_actions" /> <% - is_admin = trans.user_is_admin() - is_new = repository.is_new( trans.app ) - can_browse_contents = not is_new - can_contact_owner = trans.user and trans.user != repository.user - can_manage = is_admin or repository.user == trans.user - can_push = trans.app.security_agent.can_push( trans.app, trans.user, repository ) - can_rate = not is_new and trans.user and repository.user != trans.user - can_view_change_log = not is_new - if can_push: - browse_label = 'Browse or delete repository tip files' - else: - browse_label = 'Browse repository tip files' if mine: title = "My reviews of repository '%s'" % repository.name - review_revision_label = "Manage my review of this revision" else: title = "All reviews of repository '%s'" % repository.name - review_revision_label = "Inspect reviews of this revision" %> <%! @@ -39,29 +26,7 @@ ${common_javascripts(repository)} -

    - +${render_tool_shed_repository_actions( repository=repository )} %if message: ${render_msg( message, status )} diff --git a/templates/webapps/tool_shed/repository_review/select_previous_review.mako b/templates/webapps/tool_shed/repository_review/select_previous_review.mako index f25cba95e13..28cd33fddb3 100644 --- a/templates/webapps/tool_shed/repository_review/select_previous_review.mako +++ b/templates/webapps/tool_shed/repository_review/select_previous_review.mako @@ -2,22 +2,7 @@ <%namespace file="/message.mako" import="render_msg" /> <%namespace file="/webapps/tool_shed/common/common.mako" import="*" /> <%namespace file="/webapps/tool_shed/repository/common.mako" import="*" /> - -<% - is_admin = trans.user_is_admin() - is_new = repository.is_new( trans.app ) - can_browse_contents = not is_new - can_contact_owner = trans.user and trans.user != repository.user - can_manage = is_admin or repository.user == trans.user - can_push = trans.app.security_agent.can_push( trans.app, trans.user, repository ) - can_rate = not is_new and trans.user and repository.user != trans.user - can_view_change_log = not is_new - if can_push: - browse_label = 'Browse or delete repository tip files' - else: - browse_label = 'Browse repository tip files' - can_review_repositories = trans.app.security_agent.user_can_review_repositories( trans.user ) -%> +<%namespace file="/webapps/tool_shed/common/repository_actions_menu.mako" import="render_tool_shed_repository_actions" /> <%! def inherit(context): @@ -38,29 +23,7 @@ ${common_javascripts(repository)} -

    - +${render_tool_shed_repository_actions( repository=repository )} %if message: ${render_msg( message, status )} From bdfdb6b7cf6af65284a89a7403415beae84fc897 Mon Sep 17 00:00:00 2001 From: Dannon Baker Date: Wed, 1 May 2013 17:13:42 -0400 Subject: [PATCH 297/523] Remove duplicate imports from tags controller --- lib/galaxy/webapps/galaxy/controllers/tag.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/lib/galaxy/webapps/galaxy/controllers/tag.py b/lib/galaxy/webapps/galaxy/controllers/tag.py index 8a891e4ab38..f8ab2b88674 100644 --- a/lib/galaxy/webapps/galaxy/controllers/tag.py +++ b/lib/galaxy/webapps/galaxy/controllers/tag.py @@ -9,9 +9,6 @@ from galaxy.web.base.controller import BaseUIController, UsesTagsMixin from sqlalchemy.sql import select from sqlalchemy.sql.expression import and_, func -from galaxy import web -from galaxy.web.base.controller import BaseUIController, UsesTagsMixin - import logging log = logging.getLogger( __name__ ) From 19dded7b331d83de768a5d851664e77032235843 Mon Sep 17 00:00:00 2001 From: Dannon Baker Date: Wed, 1 May 2013 22:32:57 -0400 Subject: [PATCH 298/523] library_common: Fix StreamBall import mangled previously. --- lib/galaxy/webapps/galaxy/controllers/library_common.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/galaxy/webapps/galaxy/controllers/library_common.py b/lib/galaxy/webapps/galaxy/controllers/library_common.py index ea307c4bb12..12f49c346c9 100644 --- a/lib/galaxy/webapps/galaxy/controllers/library_common.py +++ b/lib/galaxy/webapps/galaxy/controllers/library_common.py @@ -17,6 +17,7 @@ from galaxy.security import Action from galaxy.tools.actions import upload_common from galaxy.util import inflector from galaxy.util.json import to_json_string +from galaxy.util.streamball import StreamBall from galaxy.web.base.controller import BaseUIController, UsesFormDefinitionsMixin from galaxy.web.form_builder import AddressField, CheckboxField, SelectField, build_select_field from galaxy.model.orm import and_, eagerload_all @@ -1805,13 +1806,13 @@ class LibraryCommon( BaseUIController, UsesFormDefinitionsMixin ): archive.add = lambda x, y: archive.write( x, y.encode('CP437') ) elif action == 'tgz': if trans.app.config.upstream_gzip: - archive = util.streamball.StreamBall( 'w|' ) + archive = StreamBall( 'w|' ) outext = 'tar' else: - archive = util.streamball.StreamBall( 'w|gz' ) + archive = StreamBall( 'w|gz' ) outext = 'tgz' elif action == 'tbz': - archive = util.streamball.StreamBall( 'w|bz2' ) + archive = StreamBall( 'w|bz2' ) outext = 'tbz2' elif action == 'ngxzip': archive = NgxZip( trans.app.config.nginx_x_archive_files_base ) From 1241ab0276f6408bb2f5f282bab5be13765b19de Mon Sep 17 00:00:00 2001 From: Dannon Baker Date: Wed, 1 May 2013 23:16:57 -0400 Subject: [PATCH 299/523] Tag controller: Fix abstract query to correctly map to item_class.id instead of strings. This resolves 'ambiguous column' issues. --- lib/galaxy/webapps/galaxy/controllers/tag.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/galaxy/webapps/galaxy/controllers/tag.py b/lib/galaxy/webapps/galaxy/controllers/tag.py index f8ab2b88674..dc1b7417acb 100644 --- a/lib/galaxy/webapps/galaxy/controllers/tag.py +++ b/lib/galaxy/webapps/galaxy/controllers/tag.py @@ -203,5 +203,5 @@ class TagsController ( BaseUIController, UsesTagsMixin ): Get an item based on type and id. """ item_class = self.get_tag_handler( trans ).item_tag_assoc_info[item_class_name].item_class - item = trans.sa_session.query( item_class ).filter( "id=" + str( id ) )[0] + item = trans.sa_session.query( item_class ).filter( item_class.id == id)[0] return item From f9792d3a57376f92d40f7f1a843179d3199fb679 Mon Sep 17 00:00:00 2001 From: Dannon Baker Date: Wed, 1 May 2013 23:19:36 -0400 Subject: [PATCH 300/523] Library Security, update query for retrieving items. --- lib/galaxy/security/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/galaxy/security/__init__.py b/lib/galaxy/security/__init__.py index 4567786237e..ee1ee67c72b 100644 --- a/lib/galaxy/security/__init__.py +++ b/lib/galaxy/security/__init__.py @@ -939,9 +939,9 @@ class GalaxyRBACAgent( RBACAgent ): in_roles = [ self.sa_session.query( self.model.Role ).get( x ) for x in listify( kwd.get( k + '_in', [] ) ) ] if v == self.permitted_actions.DATASET_ACCESS and in_roles: if library: - item = self.model.Library.get( item_id ) + item = self.sa_session.query( self.model.Library ).get( item_id ) else: - item = self.model.Dataset.get( item_id ) + item = self.sa_session.query( self.model.Dataset ).get( item_id ) if ( library and not self.library_is_public( item ) ) or ( not library and not self.dataset_is_public( item ) ): # Ensure that roles being associated with DATASET_ACCESS are a subset of the legitimate roles # derived from the roles associated with the access permission on item if it's not public. This From 155b87febea7aa5859c8be23310e0642676f27c8 Mon Sep 17 00:00:00 2001 From: Dannon Baker Date: Thu, 2 May 2013 00:41:04 -0400 Subject: [PATCH 301/523] Update toolshed migrations to work with recent sqlalchemy-migrate. --- .../migrate/versions/0001_initial_tables.py | 7 +- .../versions/0002_add_tool_suite_column.py | 15 ++-- ...03_review_and_review_association_tables.py | 10 ++- .../versions/0004_repository_tables.py | 10 ++- .../versions/0005_drop_tool_related_tables.py | 7 +- .../versions/0006_add_email_alerts_column.py | 9 ++- ...ng_description_times_downloaded_columns.py | 13 ++-- .../0008_add_repository_metadata_table.py | 9 ++- .../versions/0009_add_malicious_column.py | 15 ++-- .../0010_add_new_repo_alert_column.py | 17 +++-- .../versions/0011_add_tool_versions_column.py | 9 ++- .../versions/0012_add_downloadable_column.py | 13 ++-- .../versions/0013_add_review_tables.py | 73 ++++++++++--------- .../versions/0014_add_deprecated_column.py | 13 ++-- .../versions/0015_add_api_keys_table.py | 11 ++- ...ols_functionally_correct_errors_columns.py | 25 ++++--- ...ty_columns_to_repository_metadata_table.py | 29 ++++---- ...18_add_repository_metadata_flag_columns.py | 15 ++-- 18 files changed, 160 insertions(+), 140 deletions(-) diff --git a/lib/galaxy/webapps/tool_shed/model/migrate/versions/0001_initial_tables.py b/lib/galaxy/webapps/tool_shed/model/migrate/versions/0001_initial_tables.py index 00cc5dec61e..0cd20fafc6c 100644 --- a/lib/galaxy/webapps/tool_shed/model/migrate/versions/0001_initial_tables.py +++ b/lib/galaxy/webapps/tool_shed/model/migrate/versions/0001_initial_tables.py @@ -20,7 +20,7 @@ formatter = logging.Formatter( format ) handler.setFormatter( formatter ) log.addHandler( handler ) -metadata = MetaData( migrate_engine ) +metadata = MetaData() User_table = Table( "galaxy_user", metadata, Column( "id", Integer, primary_key=True), @@ -145,10 +145,11 @@ ToolAnnotationAssociation_table = Table( "tool_annotation_association", metadata Column( "user_id", Integer, ForeignKey( "galaxy_user.id" ), index=True ), Column( "annotation", TEXT, index=True) ) -def upgrade(): +def upgrade( migrate_engine ): print __doc__ + metadata.bind = migrate_engine metadata.create_all() -def downgrade(): +def downgrade( migrate_engine ): # Operations to reverse the above upgrade go here. pass diff --git a/lib/galaxy/webapps/tool_shed/model/migrate/versions/0002_add_tool_suite_column.py b/lib/galaxy/webapps/tool_shed/model/migrate/versions/0002_add_tool_suite_column.py index 6a7840fcdd7..477f6e349bf 100644 --- a/lib/galaxy/webapps/tool_shed/model/migrate/versions/0002_add_tool_suite_column.py +++ b/lib/galaxy/webapps/tool_shed/model/migrate/versions/0002_add_tool_suite_column.py @@ -16,10 +16,10 @@ formatter = logging.Formatter( format ) handler.setFormatter( formatter ) log.addHandler( handler ) -metadata = MetaData( migrate_engine ) -db_session = scoped_session( sessionmaker( bind=migrate_engine, autoflush=False, autocommit=True ) ) +metadata = MetaData() -def upgrade(): +def upgrade( migrate_engine ): + metadata.bind = migrate_engine print __doc__ metadata.reflect() # Create and initialize imported column in job table. @@ -27,19 +27,20 @@ def upgrade(): c = Column( "suite", Boolean, default=False, index=True ) try: # Create - c.create( Tool_table ) + c.create( Tool_table, index_name = 'ix_tool_suite') assert c is Tool_table.c.suite # Initialize. if migrate_engine.name == 'mysql' or migrate_engine.name == 'sqlite': default_false = "0" - elif migrate_engine.name == 'postgres': + elif migrate_engine.name in ['postgresql', 'postgres']: default_false = "false" - db_session.execute( "UPDATE tool SET suite=%s" % default_false ) + migrate_engine.execute( "UPDATE tool SET suite=%s" % default_false ) except Exception, e: print "Adding suite column to the tool table failed: %s" % str( e ) log.debug( "Adding suite column to the tool table failed: %s" % str( e ) ) -def downgrade(): +def downgrade( migrate_engine ): + metadata.bind = migrate_engine metadata.reflect() # Drop suite column from tool table. Tool_table = Table( "tool", metadata, autoload=True ) diff --git a/lib/galaxy/webapps/tool_shed/model/migrate/versions/0003_review_and_review_association_tables.py b/lib/galaxy/webapps/tool_shed/model/migrate/versions/0003_review_and_review_association_tables.py index 17318d35966..137b0c5404a 100644 --- a/lib/galaxy/webapps/tool_shed/model/migrate/versions/0003_review_and_review_association_tables.py +++ b/lib/galaxy/webapps/tool_shed/model/migrate/versions/0003_review_and_review_association_tables.py @@ -22,8 +22,7 @@ log.addHandler( handler ) # Need our custom types, but don't import anything else from model from galaxy.model.custom_types import * -metadata = MetaData( migrate_engine ) -db_session = scoped_session( sessionmaker( bind=migrate_engine, autoflush=False, autocommit=True ) ) +metadata = MetaData() ToolRatingAssociation_table = Table( "tool_rating_association", metadata, Column( "id", Integer, primary_key=True ), @@ -34,16 +33,19 @@ ToolRatingAssociation_table = Table( "tool_rating_association", metadata, Column( "rating", Integer, index=True ), Column( "comment", TEXT ) ) -def upgrade(): +def upgrade(migrate_engine): print __doc__ + metadata.bind = migrate_engine # Load existing tables metadata.reflect() try: ToolRatingAssociation_table.create() except Exception, e: log.debug( "Creating tool_rating_association table failed: %s" % str( e ) ) -def downgrade(): + +def downgrade(migrate_engine): # Load existing tables + metadata.bind = migrate_engine metadata.reflect() try: ToolRatingAssociation_table.drop() diff --git a/lib/galaxy/webapps/tool_shed/model/migrate/versions/0004_repository_tables.py b/lib/galaxy/webapps/tool_shed/model/migrate/versions/0004_repository_tables.py index b15bfcd5b0b..86ab02a7b57 100644 --- a/lib/galaxy/webapps/tool_shed/model/migrate/versions/0004_repository_tables.py +++ b/lib/galaxy/webapps/tool_shed/model/migrate/versions/0004_repository_tables.py @@ -22,8 +22,7 @@ log.addHandler( handler ) # Need our custom types, but don't import anything else from model from galaxy.model.custom_types import * -metadata = MetaData( migrate_engine ) -db_session = scoped_session( sessionmaker( bind=migrate_engine, autoflush=False, autocommit=True ) ) +metadata = MetaData() Repository_table = Table( "repository", metadata, Column( "id", Integer, primary_key=True ), @@ -49,9 +48,10 @@ RepositoryCategoryAssociation_table = Table( "repository_category_association", Column( "repository_id", Integer, ForeignKey( "repository.id" ), index=True ), Column( "category_id", Integer, ForeignKey( "category.id" ), index=True ) ) -def upgrade(): +def upgrade(migrate_engine): print __doc__ # Load existing tables + metadata.bind = migrate_engine metadata.reflect() try: Repository_table.create() @@ -65,8 +65,10 @@ def upgrade(): RepositoryCategoryAssociation_table.create() except Exception, e: log.debug( "Creating repository_category_association table failed: %s" % str( e ) ) -def downgrade(): + +def downgrade(migrate_engine): # Load existing tables + metadata.bind = migrate_engine metadata.reflect() try: Repository_table.drop() diff --git a/lib/galaxy/webapps/tool_shed/model/migrate/versions/0005_drop_tool_related_tables.py b/lib/galaxy/webapps/tool_shed/model/migrate/versions/0005_drop_tool_related_tables.py index e15738c9f83..37e947224c0 100644 --- a/lib/galaxy/webapps/tool_shed/model/migrate/versions/0005_drop_tool_related_tables.py +++ b/lib/galaxy/webapps/tool_shed/model/migrate/versions/0005_drop_tool_related_tables.py @@ -24,12 +24,12 @@ log.addHandler( handler ) # Need our custom types, but don't import anything else from model from galaxy.model.custom_types import * -metadata = MetaData( migrate_engine ) -db_session = scoped_session( sessionmaker( bind=migrate_engine, autoflush=False, autocommit=True ) ) +metadata = MetaData() -def upgrade(): +def upgrade(migrate_engine): print __doc__ # Load existing tables + metadata.bind = migrate_engine metadata.reflect() # Load and then drop the tool_category_association table try: @@ -96,6 +96,7 @@ def upgrade(): log.debug( "Dropping tool table failed: %s" % str( e ) ) def downgrade(): # Load existing tables + metadata.bind = migrate_engine metadata.reflect() # We've lost all of our data, so downgrading is useless. However, we'll # at least re-create the dropped tables. diff --git a/lib/galaxy/webapps/tool_shed/model/migrate/versions/0006_add_email_alerts_column.py b/lib/galaxy/webapps/tool_shed/model/migrate/versions/0006_add_email_alerts_column.py index 02c3e8b2831..3fc912546a9 100644 --- a/lib/galaxy/webapps/tool_shed/model/migrate/versions/0006_add_email_alerts_column.py +++ b/lib/galaxy/webapps/tool_shed/model/migrate/versions/0006_add_email_alerts_column.py @@ -19,11 +19,11 @@ formatter = logging.Formatter( format ) handler.setFormatter( formatter ) log.addHandler( handler ) -metadata = MetaData( migrate_engine ) -db_session = scoped_session( sessionmaker( bind=migrate_engine, autoflush=False, autocommit=True ) ) +metadata = MetaData() -def upgrade(): +def upgrade(migrate_engine): print __doc__ + metadata.bind = migrate_engine metadata.reflect() # Create and initialize imported column in job table. Repository_table = Table( "repository", metadata, autoload=True ) @@ -36,7 +36,8 @@ def upgrade(): print "Adding email_alerts column to the repository table failed: %s" % str( e ) log.debug( "Adding email_alerts column to the repository table failed: %s" % str( e ) ) -def downgrade(): +def downgrade(migrate_engine): + metadata.bind = migrate_engine metadata.reflect() # Drop email_alerts column from repository table. Repository_table = Table( "repository", metadata, autoload=True ) diff --git a/lib/galaxy/webapps/tool_shed/model/migrate/versions/0007_add_long_description_times_downloaded_columns.py b/lib/galaxy/webapps/tool_shed/model/migrate/versions/0007_add_long_description_times_downloaded_columns.py index eb4578f02b3..06c9c40463c 100644 --- a/lib/galaxy/webapps/tool_shed/model/migrate/versions/0007_add_long_description_times_downloaded_columns.py +++ b/lib/galaxy/webapps/tool_shed/model/migrate/versions/0007_add_long_description_times_downloaded_columns.py @@ -19,11 +19,11 @@ formatter = logging.Formatter( format ) handler.setFormatter( formatter ) log.addHandler( handler ) -metadata = MetaData( migrate_engine ) -db_session = scoped_session( sessionmaker( bind=migrate_engine, autoflush=False, autocommit=True ) ) +metadata = MetaData() -def upgrade(): +def upgrade(migrate_engine): print __doc__ + metadata.bind = migrate_engine metadata.reflect() # Create and initialize imported column in job table. Repository_table = Table( "repository", metadata, autoload=True ) @@ -46,11 +46,12 @@ def upgrade(): log.debug( "Adding times_downloaded column to the repository table failed: %s" % str( e ) ) cmd = "UPDATE repository SET long_description = ''" - db_session.execute( cmd ) + migrate_engine.execute( cmd ) cmd = "UPDATE repository SET times_downloaded = 0" - db_session.execute( cmd ) + migrate_engine.execute( cmd ) -def downgrade(): +def downgrade(migrate_engine): + metadata.bind = migrate_engine metadata.reflect() # Drop email_alerts column from repository table. Repository_table = Table( "repository", metadata, autoload=True ) diff --git a/lib/galaxy/webapps/tool_shed/model/migrate/versions/0008_add_repository_metadata_table.py b/lib/galaxy/webapps/tool_shed/model/migrate/versions/0008_add_repository_metadata_table.py index 36b23f2e7b0..fcdf1372ab2 100644 --- a/lib/galaxy/webapps/tool_shed/model/migrate/versions/0008_add_repository_metadata_table.py +++ b/lib/galaxy/webapps/tool_shed/model/migrate/versions/0008_add_repository_metadata_table.py @@ -21,8 +21,7 @@ formatter = logging.Formatter( format ) handler.setFormatter( formatter ) log.addHandler( handler ) -metadata = MetaData( migrate_engine ) -db_session = scoped_session( sessionmaker( bind=migrate_engine, autoflush=False, autocommit=True ) ) +metadata = MetaData() RepositoryMetadata_table = Table( "repository_metadata", metadata, Column( "id", Integer, primary_key=True ), @@ -32,8 +31,9 @@ RepositoryMetadata_table = Table( "repository_metadata", metadata, Column( "changeset_revision", TrimmedString( 255 ), index=True ), Column( "metadata", JSONType, nullable=True ) ) -def upgrade(): +def upgrade(migrate_engine): print __doc__ + metadata.bind = migrate_engine metadata.reflect() # Create repository_metadata table. try: @@ -42,7 +42,8 @@ def upgrade(): print str(e) log.debug( "Creating repository_metadata table failed: %s" % str( e ) ) -def downgrade(): +def downgrade(migrate_engine): + metadata.bind = migrate_engine metadata.reflect() # Drop repository_metadata table. try: diff --git a/lib/galaxy/webapps/tool_shed/model/migrate/versions/0009_add_malicious_column.py b/lib/galaxy/webapps/tool_shed/model/migrate/versions/0009_add_malicious_column.py index a8d16a645d9..f2c1fb390dd 100644 --- a/lib/galaxy/webapps/tool_shed/model/migrate/versions/0009_add_malicious_column.py +++ b/lib/galaxy/webapps/tool_shed/model/migrate/versions/0009_add_malicious_column.py @@ -16,30 +16,31 @@ formatter = logging.Formatter( format ) handler.setFormatter( formatter ) log.addHandler( handler ) -metadata = MetaData( migrate_engine ) -db_session = scoped_session( sessionmaker( bind=migrate_engine, autoflush=False, autocommit=True ) ) +metadata = MetaData() -def upgrade(): +def upgrade( migrate_engine ): print __doc__ + metadata.bind = migrate_engine metadata.reflect() # Create and initialize imported column in job table. Repository_metadata_table = Table( "repository_metadata", metadata, autoload=True ) c = Column( "malicious", Boolean, default=False, index=True ) try: # Create - c.create( Repository_metadata_table ) + c.create( Repository_metadata_table, index_name = "ix_repository_metadata_malicious") assert c is Repository_metadata_table.c.malicious # Initialize. if migrate_engine.name == 'mysql' or migrate_engine.name == 'sqlite': default_false = "0" - elif migrate_engine.name == 'postgres': + elif migrate_engine.name in ['postgresql', 'postgres']: default_false = "false" - db_session.execute( "UPDATE repository_metadata SET malicious=%s" % default_false ) + migrate_engine.execute( "UPDATE repository_metadata SET malicious=%s" % default_false ) except Exception, e: print "Adding malicious column to the repository_metadata table failed: %s" % str( e ) log.debug( "Adding malicious column to the repository_metadata table failed: %s" % str( e ) ) -def downgrade(): +def downgrade( migrate_engine ): + metadata.bind = migrate_engine metadata.reflect() # Drop malicious column from repository_metadata table. Repository_metadata_table = Table( "repository_metadata", metadata, autoload=True ) diff --git a/lib/galaxy/webapps/tool_shed/model/migrate/versions/0010_add_new_repo_alert_column.py b/lib/galaxy/webapps/tool_shed/model/migrate/versions/0010_add_new_repo_alert_column.py index 2238e267821..0264af93cce 100644 --- a/lib/galaxy/webapps/tool_shed/model/migrate/versions/0010_add_new_repo_alert_column.py +++ b/lib/galaxy/webapps/tool_shed/model/migrate/versions/0010_add_new_repo_alert_column.py @@ -16,30 +16,33 @@ formatter = logging.Formatter( format ) handler.setFormatter( formatter ) log.addHandler( handler ) -metadata = MetaData( migrate_engine ) -db_session = scoped_session( sessionmaker( bind=migrate_engine, autoflush=False, autocommit=True ) ) +metadata = MetaData() -def upgrade(): +def upgrade(migrate_engine): print __doc__ + metadata.bind = migrate_engine metadata.reflect() # Create and initialize imported column in job table. User_table = Table( "galaxy_user", metadata, autoload=True ) c = Column( "new_repo_alert", Boolean, default=False, index=True ) try: # Create - c.create( User_table ) + c.create( User_table, index_name="ix_galaxy_user_new_repo_alert") assert c is User_table.c.new_repo_alert # Initialize. if migrate_engine.name == 'mysql' or migrate_engine.name == 'sqlite': default_false = "0" - elif migrate_engine.name == 'postgres': + elif migrate_engine.name in ['postgresql', 'postgres']: default_false = "false" - db_session.execute( "UPDATE galaxy_user SET new_repo_alert=%s" % default_false ) + else: + log.debug("unknown migrate_engine dialect") + migrate_engine.execute( "UPDATE galaxy_user SET new_repo_alert=%s" % default_false ) except Exception, e: print "Adding new_repo_alert column to the galaxy_user table failed: %s" % str( e ) log.debug( "Adding new_repo_alert column to the galaxy_user table failed: %s" % str( e ) ) -def downgrade(): +def downgrade(migrate_engine): + metadata.bind = migrate_engine metadata.reflect() # Drop new_repo_alert column from galaxy_user table. User_table = Table( "galaxy_user", metadata, autoload=True ) diff --git a/lib/galaxy/webapps/tool_shed/model/migrate/versions/0011_add_tool_versions_column.py b/lib/galaxy/webapps/tool_shed/model/migrate/versions/0011_add_tool_versions_column.py index 67c4dd18165..02f6242b2df 100644 --- a/lib/galaxy/webapps/tool_shed/model/migrate/versions/0011_add_tool_versions_column.py +++ b/lib/galaxy/webapps/tool_shed/model/migrate/versions/0011_add_tool_versions_column.py @@ -21,11 +21,11 @@ formatter = logging.Formatter( format ) handler.setFormatter( formatter ) log.addHandler( handler ) -metadata = MetaData( migrate_engine ) -db_session = scoped_session( sessionmaker( bind=migrate_engine, autoflush=False, autocommit=True ) ) +metadata = MetaData() -def upgrade(): +def upgrade(migrate_engine): print __doc__ + metadata.bind = migrate_engine metadata.reflect() RepositoryMetadata_table = Table( "repository_metadata", metadata, autoload=True ) c = Column( "tool_versions", JSONType, nullable=True ) @@ -36,7 +36,8 @@ def upgrade(): except Exception, e: print "Adding tool_versions column to the repository_metadata table failed: %s" % str( e ) -def downgrade(): +def downgrade(migrate_engine): + metadata.bind = migrate_engine metadata.reflect() # Drop new_repo_alert column from galaxy_user table. RepositoryMetadata_table = Table( "repository_metadata", metadata, autoload=True ) diff --git a/lib/galaxy/webapps/tool_shed/model/migrate/versions/0012_add_downloadable_column.py b/lib/galaxy/webapps/tool_shed/model/migrate/versions/0012_add_downloadable_column.py index dc9589854b3..1aa79a34702 100644 --- a/lib/galaxy/webapps/tool_shed/model/migrate/versions/0012_add_downloadable_column.py +++ b/lib/galaxy/webapps/tool_shed/model/migrate/versions/0012_add_downloadable_column.py @@ -16,11 +16,11 @@ formatter = logging.Formatter( format ) handler.setFormatter( formatter ) log.addHandler( handler ) -metadata = MetaData( migrate_engine ) -db_session = scoped_session( sessionmaker( bind=migrate_engine, autoflush=False, autocommit=True ) ) +metadata = MetaData() -def upgrade(): +def upgrade(migrate_engine): print __doc__ + metadata.bind = migrate_engine metadata.reflect() # Create and initialize imported column in job table. RepositoryMetadata_table = Table( "repository_metadata", metadata, autoload=True ) @@ -32,13 +32,14 @@ def upgrade(): # Initialize. if migrate_engine.name == 'mysql' or migrate_engine.name == 'sqlite': default_true = "1" - elif migrate_engine.name == 'postgres': + elif migrate_engine.name in ['postgresql', 'postgres']: default_true = "true" - db_session.execute( "UPDATE repository_metadata SET downloadable=%s" % default_true ) + migrate_engine.execute( "UPDATE repository_metadata SET downloadable=%s" % default_true ) except Exception, e: print "Adding downloadable column to the repository_metadata table failed: %s" % str( e ) -def downgrade(): +def downgrade(migrate_engine): + metadata.bind = migrate_engine metadata.reflect() # Drop downloadable column from repository_metadata table. RepositoryMetadata_table = Table( "repository_metadata", metadata, autoload=True ) diff --git a/lib/galaxy/webapps/tool_shed/model/migrate/versions/0013_add_review_tables.py b/lib/galaxy/webapps/tool_shed/model/migrate/versions/0013_add_review_tables.py index b7b13c43d53..5925dd8bb5e 100644 --- a/lib/galaxy/webapps/tool_shed/model/migrate/versions/0013_add_review_tables.py +++ b/lib/galaxy/webapps/tool_shed/model/migrate/versions/0013_add_review_tables.py @@ -17,32 +17,31 @@ formatter = logging.Formatter( format ) handler.setFormatter( formatter ) log.addHandler( handler ) -metadata = MetaData( migrate_engine ) -db_session = scoped_session( sessionmaker( bind=migrate_engine, autoflush=False, autocommit=True ) ) +metadata = MetaData() IUC = 'Intergalactic Utilities Commission' NOW = datetime.datetime.utcnow REVIEWER = 'Repository Reviewer' ROLE_TYPE = 'system' -def nextval( table, col='id' ): - if migrate_engine.name == 'postgres': +def nextval(migrate_engine, table, col='id' ): + if migrate_engine.name in ['postgresql', 'postgres']: return "nextval('%s_%s_seq')" % ( table, col ) elif migrate_engine.name == 'mysql' or migrate_engine.name == 'sqlite': return "null" else: raise Exception( 'Unable to convert data for unknown database type: %s' % migrate_engine.name ) -def localtimestamp(): - if migrate_engine.name == 'postgres' or migrate_engine.name == 'mysql': +def localtimestamp(migrate_engine): + if migrate_engine.name in ['postgresql', 'postgres'] or migrate_engine.name == 'mysql': return "LOCALTIMESTAMP" elif migrate_engine.name == 'sqlite': return "current_date || ' ' || current_time" else: raise Exception( 'Unable to convert data for unknown database type: %s' % db ) -def boolean_false(): - if migrate_engine.name == 'postgres' or migrate_engine.name == 'mysql': +def boolean_false(migrate_engine): + if migrate_engine.name in ['postgresql', 'postgres'] or migrate_engine.name == 'mysql': return False elif migrate_engine.name == 'sqlite': return 0 @@ -77,8 +76,9 @@ Component_table = Table( "component", metadata, Column( "name", TrimmedString( 255 ) ), Column( "description", TEXT ) ) -def upgrade(): +def upgrade(migrate_engine): print __doc__ + metadata.bind = migrate_engine metadata.reflect() # Create new review tables. try: @@ -107,41 +107,41 @@ def upgrade(): for tup in zip( names, descriptions ): name, description = tup cmd = "INSERT INTO component VALUES (" - cmd += "%s, " % nextval( 'component' ) + cmd += "%s, " % nextval(migrate_engine, 'component' ) cmd += "'%s', " % name cmd += "'%s' " % description cmd += ");" - db_session.execute( cmd ) + migrate_engine.execute( cmd ) # Insert a REVIEWER role into the role table. cmd = "INSERT INTO role VALUES (" - cmd += "%s, " % nextval( 'role' ) - cmd += "%s, " % localtimestamp() - cmd += "%s, " % localtimestamp() + cmd += "%s, " % nextval(migrate_engine, 'role' ) + cmd += "%s, " % localtimestamp(migrate_engine) + cmd += "%s, " % localtimestamp(migrate_engine) cmd += "'%s', " % REVIEWER cmd += "'A user or group member with this role can review repositories.', " cmd += "'%s', " % ROLE_TYPE - cmd += "%s" % boolean_false() + cmd += "%s" % boolean_false(migrate_engine) cmd += ");" - db_session.execute( cmd ) + migrate_engine.execute( cmd ) # Get the id of the REVIEWER role. cmd = "SELECT id FROM role WHERE name = '%s' and type = '%s';" % ( REVIEWER, ROLE_TYPE ) - row = db_session.execute( cmd ).fetchone() + row = migrate_engine.execute( cmd ).fetchone() if row: role_id = row[ 0 ] else: role_id = None # Insert an IUC group into the galaxy_group table. cmd = "INSERT INTO galaxy_group VALUES (" - cmd += "%s, " % nextval( 'galaxy_group' ) - cmd += "%s, " % localtimestamp() - cmd += "%s, " % localtimestamp() + cmd += "%s, " % nextval(migrate_engine, 'galaxy_group' ) + cmd += "%s, " % localtimestamp(migrate_engine) + cmd += "%s, " % localtimestamp(migrate_engine) cmd += "'%s', " % IUC - cmd += "%s" % boolean_false() + cmd += "%s" % boolean_false(migrate_engine) cmd += ");" - db_session.execute( cmd ) + migrate_engine.execute( cmd ) # Get the id of the IUC group. cmd = "SELECT id FROM galaxy_group WHERE name = '%s';" % ( IUC ) - row = db_session.execute( cmd ).fetchone() + row = migrate_engine.execute( cmd ).fetchone() if row: group_id = row[ 0 ] else: @@ -149,15 +149,16 @@ def upgrade(): if group_id and role_id: # Insert a group_role_association for the IUC group and the REVIEWER role. cmd = "INSERT INTO group_role_association VALUES (" - cmd += "%s, " % nextval( 'group_role_association' ) + cmd += "%s, " % nextval(migrate_engine, 'group_role_association' ) cmd += "%d, " % int( group_id ) cmd += "%d, " % int( role_id ) - cmd += "%s, " % localtimestamp() - cmd += "%s " % localtimestamp() + cmd += "%s, " % localtimestamp(migrate_engine) + cmd += "%s " % localtimestamp(migrate_engine) cmd += ");" - db_session.execute( cmd ) + migrate_engine.execute( cmd ) -def downgrade(): +def downgrade(migrate_engine): + metadata.bind = migrate_engine metadata.reflect() # Drop review tables. try: @@ -177,36 +178,36 @@ def downgrade(): log.debug( "Dropping component table failed: %s" % str( e ) ) # Get the id of the REVIEWER group. cmd = "SELECT id FROM galaxy_group WHERE name = '%s';" % ( IUC ) - row = db_session.execute( cmd ).fetchone() + row = migrate_engine.execute( cmd ).fetchone() if row: group_id = row[ 0 ] else: group_id = None # Get the id of the REVIEWER role. cmd = "SELECT id FROM role WHERE name = '%s' and type = '%s';" % ( REVIEWER, ROLE_TYPE ) - row = db_session.execute( cmd ).fetchone() + row = migrate_engine.execute( cmd ).fetchone() if row: role_id = row[ 0 ] else: role_id = None # See if we have at least 1 user cmd = "SELECT * FROM galaxy_user;" - users = db_session.execute( cmd ).fetchall() + users = migrate_engine.execute( cmd ).fetchall() if role_id: if users: # Delete all UserRoleAssociations for the REVIEWER role. cmd = "DELETE FROM user_role_association WHERE role_id = %d;" % int( role_id ) - db_session.execute( cmd ) + migrate_engine.execute( cmd ) if group_id: # Delete all UserGroupAssociations for members of the IUC group. cmd = "DELETE FROM user_group_association WHERE group_id = %d;" % int( group_id ) - db_session.execute( cmd ) + migrate_engine.execute( cmd ) # Delete all GroupRoleAssociations for the IUC group and the REVIEWER role. cmd = "DELETE FROM group_role_association WHERE group_id = %d and role_id = %d;" % ( int( group_id ), int( role_id ) ) - db_session.execute( cmd ) + migrate_engine.execute( cmd ) # Delete the IUC group from the galaxy_group table. cmd = "DELETE FROM galaxy_group WHERE id = %d;" % int( group_id ) - db_session.execute( cmd ) + migrate_engine.execute( cmd ) # Delete the REVIEWER role from the role table. cmd = "DELETE FROM role WHERE id = %d;" % int( role_id ) - db_session.execute( cmd ) + migrate_engine.execute( cmd ) diff --git a/lib/galaxy/webapps/tool_shed/model/migrate/versions/0014_add_deprecated_column.py b/lib/galaxy/webapps/tool_shed/model/migrate/versions/0014_add_deprecated_column.py index e0e8de4ff53..a570d3286b9 100644 --- a/lib/galaxy/webapps/tool_shed/model/migrate/versions/0014_add_deprecated_column.py +++ b/lib/galaxy/webapps/tool_shed/model/migrate/versions/0014_add_deprecated_column.py @@ -19,11 +19,11 @@ formatter = logging.Formatter( format ) handler.setFormatter( formatter ) log.addHandler( handler ) -metadata = MetaData( migrate_engine ) -db_session = scoped_session( sessionmaker( bind=migrate_engine, autoflush=False, autocommit=True ) ) +metadata = MetaData() -def upgrade(): +def upgrade(migrate_engine): print __doc__ + metadata.bind = migrate_engine metadata.reflect() # Create and initialize imported column in job table. Repository_table = Table( "repository", metadata, autoload=True ) @@ -35,14 +35,15 @@ def upgrade(): # Initialize. if migrate_engine.name == 'mysql' or migrate_engine.name == 'sqlite': default_false = "0" - elif migrate_engine.name == 'postgres': + elif migrate_engine.name in ['postgresql', 'postgres']: default_false = "false" - db_session.execute( "UPDATE repository SET deprecated=%s" % default_false ) + migrate_engine.execute( "UPDATE repository SET deprecated=%s" % default_false ) except Exception, e: print "Adding deprecated column to the repository table failed: %s" % str( e ) log.debug( "Adding deprecated column to the repository table failed: %s" % str( e ) ) -def downgrade(): +def downgrade(migrate_engine): + metadata.bind = migrate_engine metadata.reflect() # Drop email_alerts column from repository table. Repository_table = Table( "repository", metadata, autoload=True ) diff --git a/lib/galaxy/webapps/tool_shed/model/migrate/versions/0015_add_api_keys_table.py b/lib/galaxy/webapps/tool_shed/model/migrate/versions/0015_add_api_keys_table.py index 9cdd40abc49..9f37d343c41 100644 --- a/lib/galaxy/webapps/tool_shed/model/migrate/versions/0015_add_api_keys_table.py +++ b/lib/galaxy/webapps/tool_shed/model/migrate/versions/0015_add_api_keys_table.py @@ -18,10 +18,7 @@ log.addHandler( handler ) now = datetime.datetime.utcnow -metadata = MetaData( migrate_engine ) -db_session = scoped_session( sessionmaker( bind=migrate_engine, autoflush=False, autocommit=True ) ) - -metadata = MetaData( migrate_engine ) +metadata = MetaData() APIKeys_table = Table( "api_keys", metadata, Column( "id", Integer, primary_key=True ), @@ -29,16 +26,18 @@ APIKeys_table = Table( "api_keys", metadata, Column( "user_id", Integer, ForeignKey( "galaxy_user.id" ), index=True ), Column( "key", TrimmedString( 32 ), index=True, unique=True ) ) -def upgrade(): +def upgrade(migrate_engine): print __doc__ + metadata.bind = migrate_engine metadata.reflect() try: APIKeys_table.create() except Exception, e: log.debug( "Creating api_keys table failed: %s" % str( e ) ) -def downgrade(): +def downgrade(migrate_engine): # Load existing tables + metadata.bind = migrate_engine metadata.reflect() try: APIKeys_table.drop() diff --git a/lib/galaxy/webapps/tool_shed/model/migrate/versions/0016_add_do_not_test_tools_functionally_correct_errors_columns.py b/lib/galaxy/webapps/tool_shed/model/migrate/versions/0016_add_do_not_test_tools_functionally_correct_errors_columns.py index b96ad311945..ef45bb2228d 100644 --- a/lib/galaxy/webapps/tool_shed/model/migrate/versions/0016_add_do_not_test_tools_functionally_correct_errors_columns.py +++ b/lib/galaxy/webapps/tool_shed/model/migrate/versions/0016_add_do_not_test_tools_functionally_correct_errors_columns.py @@ -19,46 +19,46 @@ formatter = logging.Formatter( format ) handler.setFormatter( formatter ) log.addHandler( handler ) -metadata = MetaData( migrate_engine ) -db_session = scoped_session( sessionmaker( bind=migrate_engine, autoflush=False, autocommit=True ) ) +metadata = MetaData() -def upgrade(): +def upgrade(migrate_engine): print __doc__ + metadata.bind = migrate_engine metadata.reflect() # Create and initialize tools_functionally_correct, do_not_test, time_last_tested, and tool_test_errors columns in repository_metadata table. RepositoryMetadata_table = Table( "repository_metadata", metadata, autoload=True ) c = Column( "tools_functionally_correct", Boolean, default=False, index=True ) try: # Create tools_functionally_correct column - c.create( RepositoryMetadata_table ) + c.create( RepositoryMetadata_table, index_name="ix_repository_metadata_tfc" ) assert c is RepositoryMetadata_table.c.tools_functionally_correct # Initialize. if migrate_engine.name == 'mysql' or migrate_engine.name == 'sqlite': default_false = "0" - elif migrate_engine.name == 'postgres': + elif migrate_engine.name in ['postgresql', 'postgres']: default_false = "false" - db_session.execute( "UPDATE repository_metadata SET tools_functionally_correct=%s" % default_false ) + migrate_engine.execute( "UPDATE repository_metadata SET tools_functionally_correct=%s" % default_false ) except Exception, e: print "Adding tools_functionally_correct column to the repository_metadata table failed: %s" % str( e ) log.debug( "Adding tools_functionally_correct column to the repository_metadata table failed: %s" % str( e ) ) c = Column( "do_not_test", Boolean, default=False, index=True ) try: # Create do_not_test column - c.create( RepositoryMetadata_table ) + c.create( RepositoryMetadata_table, index_name="ix_repository_metadata_dnt") assert c is RepositoryMetadata_table.c.do_not_test # Initialize. if migrate_engine.name == 'mysql' or migrate_engine.name == 'sqlite': default_false = "0" - elif migrate_engine.name == 'postgres': + elif migrate_engine.name in ['postgresql', 'postgres']: default_false = "false" - db_session.execute( "UPDATE repository_metadata SET do_not_test=%s" % default_false ) + migrate_engine.execute( "UPDATE repository_metadata SET do_not_test=%s" % default_false ) except Exception, e: print "Adding do_not_test column to the repository_metadata table failed: %s" % str( e ) log.debug( "Adding do_not_test column to the repository_metadata table failed: %s" % str( e ) ) c = Column( "time_last_tested", DateTime, default=None, nullable=True ) try: # Create time_last_tested column - c.create( RepositoryMetadata_table ) + c.create( RepositoryMetadata_table, index_name="ix_repository_metadata_tlt") assert c is RepositoryMetadata_table.c.time_last_tested except Exception, e: print "Adding time_last_tested column to the repository_metadata table failed: %s" % str( e ) @@ -66,13 +66,14 @@ def upgrade(): c = Column( "tool_test_errors", JSONType, nullable=True ) try: # Create tool_test_errors column - c.create( RepositoryMetadata_table ) + c.create( RepositoryMetadata_table, index_name="ix_repository_metadata_tte") assert c is RepositoryMetadata_table.c.tool_test_errors except Exception, e: print "Adding tool_test_errors column to the repository_metadata table failed: %s" % str( e ) log.debug( "Adding tool_test_errors column to the repository_metadata table failed: %s" % str( e ) ) -def downgrade(): +def downgrade(migrate_engine): + metadata.bind = migrate_engine metadata.reflect() # Drop tool_test_errors, time_last_tested, do_not_test, and tools_functionally_correct columns from repository_metadata table. RepositoryMetadata_table = Table( "repository_metadata", metadata, autoload=True ) diff --git a/lib/galaxy/webapps/tool_shed/model/migrate/versions/0017_add_galaxy_utility_columns_to_repository_metadata_table.py b/lib/galaxy/webapps/tool_shed/model/migrate/versions/0017_add_galaxy_utility_columns_to_repository_metadata_table.py index dce46af2902..debb7fc3161 100644 --- a/lib/galaxy/webapps/tool_shed/model/migrate/versions/0017_add_galaxy_utility_columns_to_repository_metadata_table.py +++ b/lib/galaxy/webapps/tool_shed/model/migrate/versions/0017_add_galaxy_utility_columns_to_repository_metadata_table.py @@ -20,11 +20,11 @@ formatter = logging.Formatter( format ) handler.setFormatter( formatter ) log.addHandler( handler ) -metadata = MetaData( migrate_engine ) -db_session = scoped_session( sessionmaker( bind=migrate_engine, autoflush=False, autocommit=True ) ) +metadata = MetaData() -def upgrade(): +def upgrade(migrate_engine): print __doc__ + metadata.bind = migrate_engine metadata.reflect() # Initialize. if migrate_engine.name == 'mysql' or migrate_engine.name == 'sqlite': @@ -37,49 +37,50 @@ def upgrade(): # Create tools_functionally_correct column c = Column( "includes_datatypes", Boolean, default=False, index=True ) try: - c.create( RepositoryMetadata_table ) + c.create( RepositoryMetadata_table, index_name="ix_repository_metadata_inc_datatypes") assert c is RepositoryMetadata_table.c.includes_datatypes - db_session.execute( "UPDATE repository_metadata SET includes_datatypes=%s" % default_false ) + migrate_engine.execute( "UPDATE repository_metadata SET includes_datatypes=%s" % default_false ) except Exception, e: print "Adding includes_datatypes column to the repository_metadata table failed: %s" % str( e ) # Create includes_datatypes column c = Column( "has_repository_dependencies", Boolean, default=False, index=True ) try: - c.create( RepositoryMetadata_table ) + c.create( RepositoryMetadata_table, index_name="ix_repository_metadata_has_repo_deps") assert c is RepositoryMetadata_table.c.has_repository_dependencies - db_session.execute( "UPDATE repository_metadata SET has_repository_dependencies=%s" % default_false ) + migrate_engine.execute( "UPDATE repository_metadata SET has_repository_dependencies=%s" % default_false ) except Exception, e: print "Adding has_repository_dependencies column to the repository_metadata table failed: %s" % str( e ) # Create includes_tools column c = Column( "includes_tools", Boolean, default=False, index=True ) try: - c.create( RepositoryMetadata_table ) + c.create( RepositoryMetadata_table, index_name="ix_repository_metadata_inc_tools") assert c is RepositoryMetadata_table.c.includes_tools - db_session.execute( "UPDATE repository_metadata SET includes_tools=%s" % default_false ) + migrate_engine.execute( "UPDATE repository_metadata SET includes_tools=%s" % default_false ) except Exception, e: print "Adding includes_tools column to the repository_metadata table failed: %s" % str( e ) # Create includes_tool_dependencies column c = Column( "includes_tool_dependencies", Boolean, default=False, index=True ) try: - c.create( RepositoryMetadata_table ) + c.create( RepositoryMetadata_table, index_name="ix_repository_metadata_inc_tool_deps") assert c is RepositoryMetadata_table.c.includes_tool_dependencies - db_session.execute( "UPDATE repository_metadata SET includes_tool_dependencies=%s" % default_false ) + migrate_engine.execute( "UPDATE repository_metadata SET includes_tool_dependencies=%s" % default_false ) except Exception, e: print "Adding includes_tool_dependencies column to the repository_metadata table failed: %s" % str( e ) # Create includes_workflows column c = Column( "includes_workflows", Boolean, default=False, index=True ) try: - c.create( RepositoryMetadata_table ) + c.create( RepositoryMetadata_table, index_name="ix_repository_metadata_inc_workflows") assert c is RepositoryMetadata_table.c.includes_workflows - db_session.execute( "UPDATE repository_metadata SET includes_workflows=%s" % default_false ) + migrate_engine.execute( "UPDATE repository_metadata SET includes_workflows=%s" % default_false ) except Exception, e: print "Adding includes_workflows column to the repository_metadata table failed: %s" % str( e ) -def downgrade(): +def downgrade(migrate_engine): + metadata.bind = migrate_engine metadata.reflect() # Drop tool_test_errors, time_last_tested, do_not_test, and tools_functionally_correct columns from repository_metadata table. RepositoryMetadata_table = Table( "repository_metadata", metadata, autoload=True ) diff --git a/lib/galaxy/webapps/tool_shed/model/migrate/versions/0018_add_repository_metadata_flag_columns.py b/lib/galaxy/webapps/tool_shed/model/migrate/versions/0018_add_repository_metadata_flag_columns.py index 3d5caf57c1f..ee65901ac5a 100644 --- a/lib/galaxy/webapps/tool_shed/model/migrate/versions/0018_add_repository_metadata_flag_columns.py +++ b/lib/galaxy/webapps/tool_shed/model/migrate/versions/0018_add_repository_metadata_flag_columns.py @@ -20,16 +20,16 @@ formatter = logging.Formatter( format ) handler.setFormatter( formatter ) log.addHandler( handler ) -metadata = MetaData( migrate_engine ) -db_session = scoped_session( sessionmaker( bind=migrate_engine, autoflush=False, autocommit=True ) ) +metadata = MetaData() -def upgrade(): +def upgrade(migrate_engine): print __doc__ + metadata.bind = migrate_engine metadata.reflect() # Initialize. if migrate_engine.name == 'mysql' or migrate_engine.name == 'sqlite': default_false = "0" - elif migrate_engine.name == 'postgres': + elif migrate_engine.name in ['postgresql', 'postgres']: default_false = "false" try: @@ -38,7 +38,7 @@ def upgrade(): RepositoryMetadata_table = None log.debug( "Failed loading table repository_metadata." ) - if RepositoryMetadata_table: + if RepositoryMetadata_table is not None: # Drop the tool_test_errors column from the repository_metadata table as it is poorly named. It will be replaced with the new # tool_test_results column. try: @@ -58,13 +58,14 @@ def upgrade(): # Create the missing_test_components column. c = Column( "missing_test_components", Boolean, default=False, index=True ) try: - c.create( RepositoryMetadata_table ) + c.create( RepositoryMetadata_table, index_name="ix_repository_metadata_mtc") assert c is RepositoryMetadata_table.c.missing_test_components - db_session.execute( "UPDATE repository_metadata SET missing_test_components=%s" % default_false ) + migrate_engine.execute( "UPDATE repository_metadata SET missing_test_components=%s" % default_false ) except Exception, e: print "Adding missing_test_components column to the repository_metadata table failed: %s" % str( e ) def downgrade(): + metadata.bind = migrate_engine metadata.reflect() # Drop missing_test_components and tool_test_results from the repository_metadata table and add tool_test_errors to the repository_metadata table. RepositoryMetadata_table = Table( "repository_metadata", metadata, autoload=True ) From 8c615aef4e4ce844205787d936e16b85cb56e352 Mon Sep 17 00:00:00 2001 From: Dannon Baker Date: Thu, 2 May 2013 00:44:01 -0400 Subject: [PATCH 302/523] Strip trailing whitespace from toolshed migrations. --- .../migrate/versions/0001_initial_tables.py | 6 +++--- .../versions/0002_add_tool_suite_column.py | 4 ++-- .../0003_review_and_review_association_tables.py | 4 ++-- .../versions/0005_drop_tool_related_tables.py | 4 ++-- .../versions/0006_add_email_alerts_column.py | 2 +- ..._long_description_times_downloaded_columns.py | 2 +- .../versions/0009_add_malicious_column.py | 4 ++-- .../versions/0010_add_new_repo_alert_column.py | 4 ++-- .../versions/0011_add_tool_versions_column.py | 2 +- .../versions/0012_add_downloadable_column.py | 4 ++-- .../migrate/versions/0013_add_review_tables.py | 2 +- .../versions/0014_add_deprecated_column.py | 4 ++-- .../migrate/versions/0015_add_api_keys_table.py | 2 +- ..._tools_functionally_correct_errors_columns.py | 6 +++--- ...ility_columns_to_repository_metadata_table.py | 16 ++++++++-------- .../0018_add_repository_metadata_flag_columns.py | 10 +++++----- 16 files changed, 38 insertions(+), 38 deletions(-) diff --git a/lib/galaxy/webapps/tool_shed/model/migrate/versions/0001_initial_tables.py b/lib/galaxy/webapps/tool_shed/model/migrate/versions/0001_initial_tables.py index 0cd20fafc6c..19b79a64a44 100644 --- a/lib/galaxy/webapps/tool_shed/model/migrate/versions/0001_initial_tables.py +++ b/lib/galaxy/webapps/tool_shed/model/migrate/versions/0001_initial_tables.py @@ -49,7 +49,7 @@ Role_table = Table( "role", metadata, Column( "type", String( 40 ), index=True ), Column( "deleted", Boolean, index=True, default=False ) ) -UserGroupAssociation_table = Table( "user_group_association", metadata, +UserGroupAssociation_table = Table( "user_group_association", metadata, Column( "id", Integer, primary_key=True ), Column( "user_id", Integer, ForeignKey( "galaxy_user.id" ), index=True ), Column( "group_id", Integer, ForeignKey( "galaxy_group.id" ), index=True ), @@ -83,7 +83,7 @@ GalaxySession_table = Table( "galaxy_session", metadata, Column( "prev_session_id", Integer ) # saves a reference to the previous session so we have a way to chain them together ) -Tool_table = Table( "tool", metadata, +Tool_table = Table( "tool", metadata, Column( "id", Integer, primary_key=True ), Column( "guid", TrimmedString( 255 ), index=True, unique=True ), Column( "tool_id", TrimmedString( 255 ), index=True ), @@ -127,7 +127,7 @@ Tag_table = Table( "tag", metadata, Column( "id", Integer, primary_key=True ), Column( "type", Integer ), Column( "parent_id", Integer, ForeignKey( "tag.id" ) ), - Column( "name", TrimmedString(255) ), + Column( "name", TrimmedString(255) ), UniqueConstraint( "name" ) ) ToolTagAssociation_table = Table( "tool_tag_association", metadata, diff --git a/lib/galaxy/webapps/tool_shed/model/migrate/versions/0002_add_tool_suite_column.py b/lib/galaxy/webapps/tool_shed/model/migrate/versions/0002_add_tool_suite_column.py index 477f6e349bf..d4979e2ddd2 100644 --- a/lib/galaxy/webapps/tool_shed/model/migrate/versions/0002_add_tool_suite_column.py +++ b/lib/galaxy/webapps/tool_shed/model/migrate/versions/0002_add_tool_suite_column.py @@ -30,7 +30,7 @@ def upgrade( migrate_engine ): c.create( Tool_table, index_name = 'ix_tool_suite') assert c is Tool_table.c.suite # Initialize. - if migrate_engine.name == 'mysql' or migrate_engine.name == 'sqlite': + if migrate_engine.name == 'mysql' or migrate_engine.name == 'sqlite': default_false = "0" elif migrate_engine.name in ['postgresql', 'postgres']: default_false = "false" @@ -38,7 +38,7 @@ def upgrade( migrate_engine ): except Exception, e: print "Adding suite column to the tool table failed: %s" % str( e ) log.debug( "Adding suite column to the tool table failed: %s" % str( e ) ) - + def downgrade( migrate_engine ): metadata.bind = migrate_engine metadata.reflect() diff --git a/lib/galaxy/webapps/tool_shed/model/migrate/versions/0003_review_and_review_association_tables.py b/lib/galaxy/webapps/tool_shed/model/migrate/versions/0003_review_and_review_association_tables.py index 137b0c5404a..fe4b19200b3 100644 --- a/lib/galaxy/webapps/tool_shed/model/migrate/versions/0003_review_and_review_association_tables.py +++ b/lib/galaxy/webapps/tool_shed/model/migrate/versions/0003_review_and_review_association_tables.py @@ -41,7 +41,7 @@ def upgrade(migrate_engine): try: ToolRatingAssociation_table.create() except Exception, e: - log.debug( "Creating tool_rating_association table failed: %s" % str( e ) ) + log.debug( "Creating tool_rating_association table failed: %s" % str( e ) ) def downgrade(migrate_engine): # Load existing tables @@ -50,4 +50,4 @@ def downgrade(migrate_engine): try: ToolRatingAssociation_table.drop() except Exception, e: - log.debug( "Dropping tool_rating_association table failed: %s" % str( e ) ) + log.debug( "Dropping tool_rating_association table failed: %s" % str( e ) ) diff --git a/lib/galaxy/webapps/tool_shed/model/migrate/versions/0005_drop_tool_related_tables.py b/lib/galaxy/webapps/tool_shed/model/migrate/versions/0005_drop_tool_related_tables.py index 37e947224c0..152653997aa 100644 --- a/lib/galaxy/webapps/tool_shed/model/migrate/versions/0005_drop_tool_related_tables.py +++ b/lib/galaxy/webapps/tool_shed/model/migrate/versions/0005_drop_tool_related_tables.py @@ -1,6 +1,6 @@ """ Drops the tool, tool_category_association, event, tool_event_association, tool_rating_association, -tool_tag_association and tool_annotation_association tables since they are no longer used in the +tool_tag_association and tool_annotation_association tables since they are no longer used in the next-gen tool shed. """ from sqlalchemy import * @@ -107,7 +107,7 @@ def downgrade(): Column( "state", TrimmedString( 255 ), index=True ), Column( "comment", TEXT ) ) - Tool_table = Table( "tool", metadata, + Tool_table = Table( "tool", metadata, Column( "id", Integer, primary_key=True ), Column( "guid", TrimmedString( 255 ), index=True, unique=True ), Column( "tool_id", TrimmedString( 255 ), index=True ), diff --git a/lib/galaxy/webapps/tool_shed/model/migrate/versions/0006_add_email_alerts_column.py b/lib/galaxy/webapps/tool_shed/model/migrate/versions/0006_add_email_alerts_column.py index 3fc912546a9..2f50fd33039 100644 --- a/lib/galaxy/webapps/tool_shed/model/migrate/versions/0006_add_email_alerts_column.py +++ b/lib/galaxy/webapps/tool_shed/model/migrate/versions/0006_add_email_alerts_column.py @@ -35,7 +35,7 @@ def upgrade(migrate_engine): except Exception, e: print "Adding email_alerts column to the repository table failed: %s" % str( e ) log.debug( "Adding email_alerts column to the repository table failed: %s" % str( e ) ) - + def downgrade(migrate_engine): metadata.bind = migrate_engine metadata.reflect() diff --git a/lib/galaxy/webapps/tool_shed/model/migrate/versions/0007_add_long_description_times_downloaded_columns.py b/lib/galaxy/webapps/tool_shed/model/migrate/versions/0007_add_long_description_times_downloaded_columns.py index 06c9c40463c..51c841f2efd 100644 --- a/lib/galaxy/webapps/tool_shed/model/migrate/versions/0007_add_long_description_times_downloaded_columns.py +++ b/lib/galaxy/webapps/tool_shed/model/migrate/versions/0007_add_long_description_times_downloaded_columns.py @@ -49,7 +49,7 @@ def upgrade(migrate_engine): migrate_engine.execute( cmd ) cmd = "UPDATE repository SET times_downloaded = 0" migrate_engine.execute( cmd ) - + def downgrade(migrate_engine): metadata.bind = migrate_engine metadata.reflect() diff --git a/lib/galaxy/webapps/tool_shed/model/migrate/versions/0009_add_malicious_column.py b/lib/galaxy/webapps/tool_shed/model/migrate/versions/0009_add_malicious_column.py index f2c1fb390dd..4cf4b2c0cbf 100644 --- a/lib/galaxy/webapps/tool_shed/model/migrate/versions/0009_add_malicious_column.py +++ b/lib/galaxy/webapps/tool_shed/model/migrate/versions/0009_add_malicious_column.py @@ -30,7 +30,7 @@ def upgrade( migrate_engine ): c.create( Repository_metadata_table, index_name = "ix_repository_metadata_malicious") assert c is Repository_metadata_table.c.malicious # Initialize. - if migrate_engine.name == 'mysql' or migrate_engine.name == 'sqlite': + if migrate_engine.name == 'mysql' or migrate_engine.name == 'sqlite': default_false = "0" elif migrate_engine.name in ['postgresql', 'postgres']: default_false = "false" @@ -38,7 +38,7 @@ def upgrade( migrate_engine ): except Exception, e: print "Adding malicious column to the repository_metadata table failed: %s" % str( e ) log.debug( "Adding malicious column to the repository_metadata table failed: %s" % str( e ) ) - + def downgrade( migrate_engine ): metadata.bind = migrate_engine metadata.reflect() diff --git a/lib/galaxy/webapps/tool_shed/model/migrate/versions/0010_add_new_repo_alert_column.py b/lib/galaxy/webapps/tool_shed/model/migrate/versions/0010_add_new_repo_alert_column.py index 0264af93cce..9652e129c12 100644 --- a/lib/galaxy/webapps/tool_shed/model/migrate/versions/0010_add_new_repo_alert_column.py +++ b/lib/galaxy/webapps/tool_shed/model/migrate/versions/0010_add_new_repo_alert_column.py @@ -30,7 +30,7 @@ def upgrade(migrate_engine): c.create( User_table, index_name="ix_galaxy_user_new_repo_alert") assert c is User_table.c.new_repo_alert # Initialize. - if migrate_engine.name == 'mysql' or migrate_engine.name == 'sqlite': + if migrate_engine.name == 'mysql' or migrate_engine.name == 'sqlite': default_false = "0" elif migrate_engine.name in ['postgresql', 'postgres']: default_false = "false" @@ -40,7 +40,7 @@ def upgrade(migrate_engine): except Exception, e: print "Adding new_repo_alert column to the galaxy_user table failed: %s" % str( e ) log.debug( "Adding new_repo_alert column to the galaxy_user table failed: %s" % str( e ) ) - + def downgrade(migrate_engine): metadata.bind = migrate_engine metadata.reflect() diff --git a/lib/galaxy/webapps/tool_shed/model/migrate/versions/0011_add_tool_versions_column.py b/lib/galaxy/webapps/tool_shed/model/migrate/versions/0011_add_tool_versions_column.py index 02f6242b2df..c09b894ef8c 100644 --- a/lib/galaxy/webapps/tool_shed/model/migrate/versions/0011_add_tool_versions_column.py +++ b/lib/galaxy/webapps/tool_shed/model/migrate/versions/0011_add_tool_versions_column.py @@ -35,7 +35,7 @@ def upgrade(migrate_engine): assert c is RepositoryMetadata_table.c.tool_versions except Exception, e: print "Adding tool_versions column to the repository_metadata table failed: %s" % str( e ) - + def downgrade(migrate_engine): metadata.bind = migrate_engine metadata.reflect() diff --git a/lib/galaxy/webapps/tool_shed/model/migrate/versions/0012_add_downloadable_column.py b/lib/galaxy/webapps/tool_shed/model/migrate/versions/0012_add_downloadable_column.py index 1aa79a34702..4746fe91a62 100644 --- a/lib/galaxy/webapps/tool_shed/model/migrate/versions/0012_add_downloadable_column.py +++ b/lib/galaxy/webapps/tool_shed/model/migrate/versions/0012_add_downloadable_column.py @@ -30,14 +30,14 @@ def upgrade(migrate_engine): c.create( RepositoryMetadata_table ) assert c is RepositoryMetadata_table.c.downloadable # Initialize. - if migrate_engine.name == 'mysql' or migrate_engine.name == 'sqlite': + if migrate_engine.name == 'mysql' or migrate_engine.name == 'sqlite': default_true = "1" elif migrate_engine.name in ['postgresql', 'postgres']: default_true = "true" migrate_engine.execute( "UPDATE repository_metadata SET downloadable=%s" % default_true ) except Exception, e: print "Adding downloadable column to the repository_metadata table failed: %s" % str( e ) - + def downgrade(migrate_engine): metadata.bind = migrate_engine metadata.reflect() diff --git a/lib/galaxy/webapps/tool_shed/model/migrate/versions/0013_add_review_tables.py b/lib/galaxy/webapps/tool_shed/model/migrate/versions/0013_add_review_tables.py index 5925dd8bb5e..7e7a4bbb6b2 100644 --- a/lib/galaxy/webapps/tool_shed/model/migrate/versions/0013_add_review_tables.py +++ b/lib/galaxy/webapps/tool_shed/model/migrate/versions/0013_add_review_tables.py @@ -46,7 +46,7 @@ def boolean_false(migrate_engine): elif migrate_engine.name == 'sqlite': return 0 else: - raise Exception( 'Unable to convert data for unknown database type: %s' % db ) + raise Exception( 'Unable to convert data for unknown database type: %s' % db ) RepositoryReview_table = Table( "repository_review", metadata, Column( "id", Integer, primary_key=True ), diff --git a/lib/galaxy/webapps/tool_shed/model/migrate/versions/0014_add_deprecated_column.py b/lib/galaxy/webapps/tool_shed/model/migrate/versions/0014_add_deprecated_column.py index a570d3286b9..7742ba88fc1 100644 --- a/lib/galaxy/webapps/tool_shed/model/migrate/versions/0014_add_deprecated_column.py +++ b/lib/galaxy/webapps/tool_shed/model/migrate/versions/0014_add_deprecated_column.py @@ -33,7 +33,7 @@ def upgrade(migrate_engine): c.create( Repository_table ) assert c is Repository_table.c.deprecated # Initialize. - if migrate_engine.name == 'mysql' or migrate_engine.name == 'sqlite': + if migrate_engine.name == 'mysql' or migrate_engine.name == 'sqlite': default_false = "0" elif migrate_engine.name in ['postgresql', 'postgres']: default_false = "false" @@ -41,7 +41,7 @@ def upgrade(migrate_engine): except Exception, e: print "Adding deprecated column to the repository table failed: %s" % str( e ) log.debug( "Adding deprecated column to the repository table failed: %s" % str( e ) ) - + def downgrade(migrate_engine): metadata.bind = migrate_engine metadata.reflect() diff --git a/lib/galaxy/webapps/tool_shed/model/migrate/versions/0015_add_api_keys_table.py b/lib/galaxy/webapps/tool_shed/model/migrate/versions/0015_add_api_keys_table.py index 9f37d343c41..7105c9381e7 100644 --- a/lib/galaxy/webapps/tool_shed/model/migrate/versions/0015_add_api_keys_table.py +++ b/lib/galaxy/webapps/tool_shed/model/migrate/versions/0015_add_api_keys_table.py @@ -7,7 +7,7 @@ from sqlalchemy.orm import * from migrate import * from migrate.changeset import * from galaxy.model.custom_types import * - + log = logging.getLogger( __name__ ) log.setLevel(logging.DEBUG) handler = logging.StreamHandler( sys.stdout ) diff --git a/lib/galaxy/webapps/tool_shed/model/migrate/versions/0016_add_do_not_test_tools_functionally_correct_errors_columns.py b/lib/galaxy/webapps/tool_shed/model/migrate/versions/0016_add_do_not_test_tools_functionally_correct_errors_columns.py index ef45bb2228d..b77a3363157 100644 --- a/lib/galaxy/webapps/tool_shed/model/migrate/versions/0016_add_do_not_test_tools_functionally_correct_errors_columns.py +++ b/lib/galaxy/webapps/tool_shed/model/migrate/versions/0016_add_do_not_test_tools_functionally_correct_errors_columns.py @@ -33,7 +33,7 @@ def upgrade(migrate_engine): c.create( RepositoryMetadata_table, index_name="ix_repository_metadata_tfc" ) assert c is RepositoryMetadata_table.c.tools_functionally_correct # Initialize. - if migrate_engine.name == 'mysql' or migrate_engine.name == 'sqlite': + if migrate_engine.name == 'mysql' or migrate_engine.name == 'sqlite': default_false = "0" elif migrate_engine.name in ['postgresql', 'postgres']: default_false = "false" @@ -47,7 +47,7 @@ def upgrade(migrate_engine): c.create( RepositoryMetadata_table, index_name="ix_repository_metadata_dnt") assert c is RepositoryMetadata_table.c.do_not_test # Initialize. - if migrate_engine.name == 'mysql' or migrate_engine.name == 'sqlite': + if migrate_engine.name == 'mysql' or migrate_engine.name == 'sqlite': default_false = "0" elif migrate_engine.name in ['postgresql', 'postgres']: default_false = "false" @@ -71,7 +71,7 @@ def upgrade(migrate_engine): except Exception, e: print "Adding tool_test_errors column to the repository_metadata table failed: %s" % str( e ) log.debug( "Adding tool_test_errors column to the repository_metadata table failed: %s" % str( e ) ) - + def downgrade(migrate_engine): metadata.bind = migrate_engine metadata.reflect() diff --git a/lib/galaxy/webapps/tool_shed/model/migrate/versions/0017_add_galaxy_utility_columns_to_repository_metadata_table.py b/lib/galaxy/webapps/tool_shed/model/migrate/versions/0017_add_galaxy_utility_columns_to_repository_metadata_table.py index debb7fc3161..c37e3d05031 100644 --- a/lib/galaxy/webapps/tool_shed/model/migrate/versions/0017_add_galaxy_utility_columns_to_repository_metadata_table.py +++ b/lib/galaxy/webapps/tool_shed/model/migrate/versions/0017_add_galaxy_utility_columns_to_repository_metadata_table.py @@ -27,13 +27,13 @@ def upgrade(migrate_engine): metadata.bind = migrate_engine metadata.reflect() # Initialize. - if migrate_engine.name == 'mysql' or migrate_engine.name == 'sqlite': + if migrate_engine.name == 'mysql' or migrate_engine.name == 'sqlite': default_false = "0" elif migrate_engine.name == 'postgres': default_false = "false" # Create and initialize tools_functionally_correct, do_not_test, time_last_tested, and tool_test_errors columns in repository_metadata table. RepositoryMetadata_table = Table( "repository_metadata", metadata, autoload=True ) - + # Create tools_functionally_correct column c = Column( "includes_datatypes", Boolean, default=False, index=True ) try: @@ -42,7 +42,7 @@ def upgrade(migrate_engine): migrate_engine.execute( "UPDATE repository_metadata SET includes_datatypes=%s" % default_false ) except Exception, e: print "Adding includes_datatypes column to the repository_metadata table failed: %s" % str( e ) - + # Create includes_datatypes column c = Column( "has_repository_dependencies", Boolean, default=False, index=True ) try: @@ -51,7 +51,7 @@ def upgrade(migrate_engine): migrate_engine.execute( "UPDATE repository_metadata SET has_repository_dependencies=%s" % default_false ) except Exception, e: print "Adding has_repository_dependencies column to the repository_metadata table failed: %s" % str( e ) - + # Create includes_tools column c = Column( "includes_tools", Boolean, default=False, index=True ) try: @@ -60,7 +60,7 @@ def upgrade(migrate_engine): migrate_engine.execute( "UPDATE repository_metadata SET includes_tools=%s" % default_false ) except Exception, e: print "Adding includes_tools column to the repository_metadata table failed: %s" % str( e ) - + # Create includes_tool_dependencies column c = Column( "includes_tool_dependencies", Boolean, default=False, index=True ) try: @@ -69,7 +69,7 @@ def upgrade(migrate_engine): migrate_engine.execute( "UPDATE repository_metadata SET includes_tool_dependencies=%s" % default_false ) except Exception, e: print "Adding includes_tool_dependencies column to the repository_metadata table failed: %s" % str( e ) - + # Create includes_workflows column c = Column( "includes_workflows", Boolean, default=False, index=True ) try: @@ -84,13 +84,13 @@ def downgrade(migrate_engine): metadata.reflect() # Drop tool_test_errors, time_last_tested, do_not_test, and tools_functionally_correct columns from repository_metadata table. RepositoryMetadata_table = Table( "repository_metadata", metadata, autoload=True ) - + # Drop the includes_workflows column. try: RepositoryMetadata_table.c.includes_workflows.drop() except Exception, e: print "Dropping column includes_workflows from the repository_metadata table failed: %s" % str( e ) - + # Drop the includes_tool_dependencies column. try: RepositoryMetadata_table.c.includes_tool_dependencies.drop() diff --git a/lib/galaxy/webapps/tool_shed/model/migrate/versions/0018_add_repository_metadata_flag_columns.py b/lib/galaxy/webapps/tool_shed/model/migrate/versions/0018_add_repository_metadata_flag_columns.py index ee65901ac5a..7085c87fab0 100644 --- a/lib/galaxy/webapps/tool_shed/model/migrate/versions/0018_add_repository_metadata_flag_columns.py +++ b/lib/galaxy/webapps/tool_shed/model/migrate/versions/0018_add_repository_metadata_flag_columns.py @@ -27,11 +27,11 @@ def upgrade(migrate_engine): metadata.bind = migrate_engine metadata.reflect() # Initialize. - if migrate_engine.name == 'mysql' or migrate_engine.name == 'sqlite': + if migrate_engine.name == 'mysql' or migrate_engine.name == 'sqlite': default_false = "0" elif migrate_engine.name in ['postgresql', 'postgres']: default_false = "false" - + try: RepositoryMetadata_table = Table( "repository_metadata", metadata, autoload=True ) except NoSuchTableError: @@ -54,7 +54,7 @@ def upgrade(migrate_engine): assert c is RepositoryMetadata_table.c.tool_test_results except Exception, e: print "Adding tool_test_results column to the repository_metadata table failed: %s" % str( e ) - + # Create the missing_test_components column. c = Column( "missing_test_components", Boolean, default=False, index=True ) try: @@ -69,13 +69,13 @@ def downgrade(): metadata.reflect() # Drop missing_test_components and tool_test_results from the repository_metadata table and add tool_test_errors to the repository_metadata table. RepositoryMetadata_table = Table( "repository_metadata", metadata, autoload=True ) - + # Drop the missing_test_components column. try: RepositoryMetadata_table.c.missing_test_components.drop() except Exception, e: print "Dropping column missing_test_components from the repository_metadata table failed: %s" % str( e ) - + # Drop the tool_test_results column. try: RepositoryMetadata_table.c.tool_test_results.drop() From 081104e26277cd9ecada4de0d6dd0a9c6fb43a76 Mon Sep 17 00:00:00 2001 From: Dannon Baker Date: Thu, 2 May 2013 00:46:28 -0400 Subject: [PATCH 303/523] Strip trailing whitespace from galaxy migrations. --- .../migrate/versions/0001_initial_tables.py | 20 +-- .../versions/0002_metadata_file_table.py | 2 +- .../versions/0003_security_and_libraries.py | 18 +- .../versions/0005_cleanup_datasets_fix.py | 170 +++++++++--------- .../migrate/versions/0008_galaxy_forms.py | 32 ++-- .../0010_hda_display_at_authz_table.py | 6 +- .../versions/0011_v0010_mysql_index_fix.py | 6 +- .../migrate/versions/0012_user_address.py | 2 +- ...0013_change_lib_item_templates_to_forms.py | 2 +- .../model/migrate/versions/0014_pages.py | 2 +- .../model/migrate/versions/0015_tagging.py | 8 +- .../versions/0016_v0015_mysql_index_fix.py | 4 +- .../versions/0017_library_item_indexes.py | 4 +- .../versions/0019_request_library_folder.py | 4 +- ...0023_page_published_and_deleted_columns.py | 10 +- .../0024_page_slug_unique_constraint.py | 4 +- .../migrate/versions/0026_cloud_tables.py | 26 +-- .../migrate/versions/0027_request_events.py | 14 +- .../0028_external_metadata_file_override.py | 4 +- .../versions/0030_history_slug_column.py | 6 +- .../0031_community_and_workflow_tags.py | 16 +- .../0032_stored_workflow_slug_column.py | 8 +- ...lished_cols_for_histories_and_workflows.py | 12 +- .../0034_page_user_share_association.py | 6 +- ...item_annotations_and_workflow_step_tags.py | 28 +-- ...column_to_library_template_assoc_tables.py | 4 +- .../migrate/versions/0037_samples_library.py | 18 +- ...column_to_library_template_assoc_tables.py | 14 +- ...39_add_synopsis_column_to_library_table.py | 2 +- .../migrate/versions/0040_page_annotations.py | 8 +- .../versions/0041_workflow_invocation.py | 4 +- ...0044_add_notify_column_to_request_table.py | 2 +- .../versions/0050_drop_cloud_tables.py | 20 +-- .../versions/0052_sample_dataset_table.py | 12 +- .../migrate/versions/0053_item_ratings.py | 30 ++-- .../versions/0054_visualization_dbkey.py | 6 +- .../migrate/versions/0057_request_notify.py | 12 +- .../versions/0058_history_import_export.py | 6 +- .../versions/0059_sample_dataset_file_path.py | 8 +- .../versions/0060_history_archive_import.py | 18 +- .../model/migrate/versions/0061_tasks.py | 2 +- .../versions/0062_user_openid_table.py | 6 +- .../migrate/versions/0063_sequencer_table.py | 8 +- ...0065_add_name_to_form_fields_and_values.py | 8 +- ...66_deferred_job_and_transfer_job_tables.py | 8 +- .../versions/0067_populate_sequencer_table.py | 102 +++++------ .../0069_rename_sequencer_form_type.py | 4 +- ..._purged_column_to_library_dataset_table.py | 2 +- .../0076_fix_form_values_data_corruption.py | 4 +- .../0077_create_tool_tag_association_table.py | 6 +- .../migrate/versions/0080_quota_tables.py | 4 +- .../0081_add_tool_version_to_hda_ldda.py | 6 +- ..._add_tool_shed_repository_table_columns.py | 2 +- .../versions/0087_tool_id_guid_map_table.py | 2 +- .../versions/0091_add_tool_version_tables.py | 8 +- .../versions/0092_add_migrate_tools_table.py | 4 +- .../versions/0093_add_job_params_col.py | 8 +- .../versions/0094_add_job_handler_col.py | 8 +- .../migrate/versions/0095_hda_subsets.py | 8 +- .../migrate/versions/0096_openid_provider.py | 2 +- .../0098_genome_index_tool_data_table.py | 4 +- .../versions/0108_add_extended_metadata.py | 8 +- .../migrate/versions/0110_add_dataset_uuid.py | 8 +- .../versions/0111_add_job_destinations.py | 2 +- 64 files changed, 401 insertions(+), 401 deletions(-) diff --git a/lib/galaxy/model/migrate/versions/0001_initial_tables.py b/lib/galaxy/model/migrate/versions/0001_initial_tables.py index a51fb6b7295..b3371ecb36a 100644 --- a/lib/galaxy/model/migrate/versions/0001_initial_tables.py +++ b/lib/galaxy/model/migrate/versions/0001_initial_tables.py @@ -20,7 +20,7 @@ User_table = Table( "galaxy_user", metadata, Column( "email", TrimmedString( 255 ), nullable=False ), Column( "password", TrimmedString( 40 ), nullable=False ), Column( "external", Boolean, default=False ) ) - + History_table = Table( "history", metadata, Column( "id", Integer, primary_key=True), Column( "create_time", DateTime, default=now ), @@ -32,7 +32,7 @@ History_table = Table( "history", metadata, Column( "purged", Boolean, index=True, default=False ), Column( "genome_build", TrimmedString( 40 ) ) ) -HistoryDatasetAssociation_table = Table( "history_dataset_association", metadata, +HistoryDatasetAssociation_table = Table( "history_dataset_association", metadata, Column( "id", Integer, primary_key=True ), Column( "history_id", Integer, ForeignKey( "history.id" ), index=True ), Column( "dataset_id", Integer, ForeignKey( "dataset.id" ), index=True ), @@ -51,7 +51,7 @@ HistoryDatasetAssociation_table = Table( "history_dataset_association", metadata Column( "deleted", Boolean, index=True, default=False ), Column( "visible", Boolean ) ) -Dataset_table = Table( "dataset", metadata, +Dataset_table = Table( "dataset", metadata, Column( "id", Integer, primary_key=True ), Column( "create_time", DateTime, default=now ), Column( "update_time", DateTime, index=True, default=now, onupdate=now ), @@ -89,7 +89,7 @@ Job_table = Table( "job", metadata, Column( "tool_version", TEXT, default="1.0.0" ), Column( "state", String( 64 ) ), Column( "info", TrimmedString( 255 ) ), - Column( "command_line", TEXT ), + Column( "command_line", TEXT ), Column( "param_filename", String( 1024 ) ), Column( "runner_name", String( 255 ) ), Column( "stdout", TEXT ), @@ -98,26 +98,26 @@ Job_table = Table( "job", metadata, Column( "session_id", Integer, ForeignKey( "galaxy_session.id" ), index=True, nullable=True ), Column( "job_runner_name", String( 255 ) ), Column( "job_runner_external_id", String( 255 ) ) ) - + JobParameter_table = Table( "job_parameter", metadata, Column( "id", Integer, primary_key=True ), Column( "job_id", Integer, ForeignKey( "job.id" ), index=True ), Column( "name", String(255) ), Column( "value", TEXT ) ) - + JobToInputDatasetAssociation_table = Table( "job_to_input_dataset", metadata, Column( "id", Integer, primary_key=True ), Column( "job_id", Integer, ForeignKey( "job.id" ), index=True ), Column( "dataset_id", Integer, ForeignKey( "history_dataset_association.id" ), index=True ), Column( "name", String(255) ) ) - + JobToOutputDatasetAssociation_table = Table( "job_to_output_dataset", metadata, Column( "id", Integer, primary_key=True ), Column( "job_id", Integer, ForeignKey( "job.id" ), index=True ), Column( "dataset_id", Integer, ForeignKey( "history_dataset_association.id" ), index=True ), Column( "name", String(255) ) ) - -Event_table = Table( "event", metadata, + +Event_table = Table( "event", metadata, Column( "id", Integer, primary_key=True ), Column( "create_time", DateTime, default=now ), Column( "update_time", DateTime, default=now, onupdate=now ), @@ -200,7 +200,7 @@ StoredWorkflowUserShareAssociation_table = Table( "stored_workflow_user_share_co StoredWorkflowMenuEntry_table = Table( "stored_workflow_menu_entry", metadata, Column( "id", Integer, primary_key=True ), Column( "stored_workflow_id", Integer, ForeignKey( "stored_workflow.id" ), index=True ), - Column( "user_id", Integer, ForeignKey( "galaxy_user.id" ), index=True ), + Column( "user_id", Integer, ForeignKey( "galaxy_user.id" ), index=True ), Column( "order_index", Integer ) ) def upgrade(migrate_engine): diff --git a/lib/galaxy/model/migrate/versions/0002_metadata_file_table.py b/lib/galaxy/model/migrate/versions/0002_metadata_file_table.py index 0531ea72db4..aef94d00334 100644 --- a/lib/galaxy/model/migrate/versions/0002_metadata_file_table.py +++ b/lib/galaxy/model/migrate/versions/0002_metadata_file_table.py @@ -13,7 +13,7 @@ log = logging.getLogger( __name__ ) metadata = MetaData() # New table in changeset 1568:0b022adfdc34 -MetadataFile_table = Table( "metadata_file", metadata, +MetadataFile_table = Table( "metadata_file", metadata, Column( "id", Integer, primary_key=True ), Column( "name", TEXT ), Column( "hda_id", Integer, ForeignKey( "history_dataset_association.id" ), index=True, nullable=True ), diff --git a/lib/galaxy/model/migrate/versions/0003_security_and_libraries.py b/lib/galaxy/model/migrate/versions/0003_security_and_libraries.py index 96931dd72c9..3e3597a51eb 100644 --- a/lib/galaxy/model/migrate/versions/0003_security_and_libraries.py +++ b/lib/galaxy/model/migrate/versions/0003_security_and_libraries.py @@ -29,7 +29,7 @@ Group_table = Table( "galaxy_group", metadata, Column( "name", String( 255 ), index=True, unique=True ), Column( "deleted", Boolean, index=True, default=False ) ) -UserGroupAssociation_table = Table( "user_group_association", metadata, +UserGroupAssociation_table = Table( "user_group_association", metadata, Column( "id", Integer, primary_key=True ), Column( "user_id", Integer, ForeignKey( "galaxy_user.id" ), index=True ), Column( "group_id", Integer, ForeignKey( "galaxy_group.id" ), index=True ), @@ -129,7 +129,7 @@ DefaultHistoryPermissions_table = Table( "default_history_permissions", metadata Column( "action", TEXT ), Column( "role_id", Integer, ForeignKey( "role.id" ), index=True ) ) -LibraryDataset_table = Table( "library_dataset", metadata, +LibraryDataset_table = Table( "library_dataset", metadata, Column( "id", Integer, primary_key=True ), Column( "library_dataset_dataset_association_id", Integer, ForeignKey( "library_dataset_dataset_association.id", use_alter=True, name="library_dataset_dataset_association_id_fk" ), nullable=True, index=True ),#current version of dataset, if null, there is not a current version selected Column( "folder_id", Integer, ForeignKey( "library_folder.id" ), index=True ), @@ -140,7 +140,7 @@ LibraryDataset_table = Table( "library_dataset", metadata, Column( "info", TrimmedString( 255 ), key="_info" ), #when not None/null this will supercede display in library (but not when imported into user's history?) Column( "deleted", Boolean, index=True, default=False ) ) -LibraryDatasetDatasetAssociation_table = Table( "library_dataset_dataset_association", metadata, +LibraryDatasetDatasetAssociation_table = Table( "library_dataset_dataset_association", metadata, Column( "id", Integer, primary_key=True ), Column( "library_dataset_id", Integer, ForeignKey( "library_dataset.id" ), index=True ), Column( "dataset_id", Integer, ForeignKey( "dataset.id" ), index=True ), @@ -197,7 +197,7 @@ LibraryItemInfoTemplateElement_table = Table( "library_item_info_template_elemen Column( "options", JSONType() ), Column( "library_item_info_template_id", Integer, ForeignKey( "library_item_info_template.id" ) ) ) Index( "ix_liite_library_item_info_template_id", LibraryItemInfoTemplateElement_table.c.library_item_info_template_id ) - + LibraryItemInfoTemplate_table = Table( "library_item_info_template", metadata, Column( "id", Integer, primary_key=True ), Column( "create_time", DateTime, default=now ), @@ -332,7 +332,7 @@ def upgrade(migrate_engine): elif migrate_engine.name == 'sqlite': return 0 else: - raise Exception( 'Unable to convert data for unknown database type: %s' % db ) + raise Exception( 'Unable to convert data for unknown database type: %s' % db ) # Add 2 new columns to the galaxy_user table try: @@ -381,8 +381,8 @@ def upgrade(migrate_engine): log.debug( "Adding column 'lda_id' to metadata_file table failed: %s" % ( str( e ) ) ) # Add 1 new column to the stored_workflow table - changeset 2328 try: - StoredWorkflow_table = Table( "stored_workflow", metadata, - Column( "latest_workflow_id", Integer, + StoredWorkflow_table = Table( "stored_workflow", metadata, + Column( "latest_workflow_id", Integer, ForeignKey( "workflow.id", use_alter=True, name='stored_workflow_latest_workflow_id_fk' ), index=True ), autoload=True, extend_existing=True ) except NoSuchTableError: @@ -457,7 +457,7 @@ def upgrade(migrate_engine): if users: cmd = "SELECT * FROM role;" roles = db_session.execute( cmd ).fetchall() - if not roles: + if not roles: # Create private roles for each user - pass 1 cmd = \ "INSERT INTO role " + \ @@ -503,7 +503,7 @@ def upgrade(migrate_engine): "ORDER BY galaxy_user.id;" cmd = cmd % nextval('default_user_permissions') db_session.execute( cmd ) - # Create default history permissions for each active history associated with a user + # Create default history permissions for each active history associated with a user cmd = \ "INSERT INTO default_history_permissions " + \ diff --git a/lib/galaxy/model/migrate/versions/0005_cleanup_datasets_fix.py b/lib/galaxy/model/migrate/versions/0005_cleanup_datasets_fix.py index 69e54f89ebd..5b2272378f2 100644 --- a/lib/galaxy/model/migrate/versions/0005_cleanup_datasets_fix.py +++ b/lib/galaxy/model/migrate/versions/0005_cleanup_datasets_fix.py @@ -94,7 +94,7 @@ class Dataset( object ): file_name = property( get_file_name, set_file_name ) @property def extra_files_path( self ): - if self._extra_files_path: + if self._extra_files_path: path = self._extra_files_path else: path = os.path.join( self.file_path, "dataset_%d_files" % self.id ) @@ -136,7 +136,7 @@ class DatasetInstance( object ): """A base class for all 'dataset instances', HDAs, LDAs, etc""" states = Dataset.states permitted_actions = Dataset.permitted_actions - def __init__( self, id=None, hid=None, name=None, info=None, blurb=None, peek=None, extension=None, + def __init__( self, id=None, hid=None, name=None, info=None, blurb=None, peek=None, extension=None, dbkey=None, metadata=None, history=None, dataset=None, deleted=False, designation=None, parent_id=None, validation_errors=None, visible=True, create_dataset = False ): self.name = name or "Unnamed dataset" @@ -197,9 +197,9 @@ class DatasetInstance( object ): return dbkey[0] def set_dbkey( self, value ): if "dbkey" in self.datatype.metadata_spec: - if not isinstance(value, list): + if not isinstance(value, list): self.metadata.dbkey = [value] - else: + else: self.metadata.dbkey = value dbkey = property( get_dbkey, set_dbkey ) def change_datatype( self, new_ext ): @@ -298,11 +298,11 @@ class DatasetInstance( object ): class HistoryDatasetAssociation( DatasetInstance ): - def __init__( self, - hid = None, - history = None, - copied_from_history_dataset_association = None, - copied_from_library_dataset_dataset_association = None, + def __init__( self, + hid = None, + history = None, + copied_from_history_dataset_association = None, + copied_from_library_dataset_dataset_association = None, **kwd ): DatasetInstance.__init__( self, **kwd ) self.hid = hid @@ -311,17 +311,17 @@ class HistoryDatasetAssociation( DatasetInstance ): self.copied_from_history_dataset_association = copied_from_history_dataset_association self.copied_from_library_dataset_dataset_association = copied_from_library_dataset_dataset_association def copy( self, copy_children = False, parent_id = None, target_history = None ): - hda = HistoryDatasetAssociation( hid=self.hid, - name=self.name, - info=self.info, - blurb=self.blurb, - peek=self.peek, - extension=self.extension, - dbkey=self.dbkey, - dataset = self.dataset, - visible=self.visible, - deleted=self.deleted, - parent_id=parent_id, + hda = HistoryDatasetAssociation( hid=self.hid, + name=self.name, + info=self.info, + blurb=self.blurb, + peek=self.peek, + extension=self.extension, + dbkey=self.dbkey, + dataset = self.dataset, + visible=self.visible, + deleted=self.deleted, + parent_id=parent_id, copied_from_history_dataset_association=self, history = target_history ) context.add( hda ) @@ -342,21 +342,21 @@ class HistoryDatasetAssociation( DatasetInstance ): # The replace_dataset param ( when not None ) refers to a LibraryDataset that is being replaced with a new version. library_dataset = replace_dataset else: - # If replace_dataset is None, the Library level permissions will be taken from the folder and applied to the new + # If replace_dataset is None, the Library level permissions will be taken from the folder and applied to the new # LibraryDataset, and the current user's DefaultUserPermissions will be applied to the associated Dataset. library_dataset = LibraryDataset( folder=target_folder, name=self.name, info=self.info ) context.add( library_dataset ) context.flush() - ldda = LibraryDatasetDatasetAssociation( name=self.name, + ldda = LibraryDatasetDatasetAssociation( name=self.name, info=self.info, - blurb=self.blurb, - peek=self.peek, - extension=self.extension, - dbkey=self.dbkey, - dataset=self.dataset, + blurb=self.blurb, + peek=self.peek, + extension=self.extension, + dbkey=self.dbkey, + dataset=self.dataset, library_dataset=library_dataset, - visible=self.visible, - deleted=self.deleted, + visible=self.visible, + deleted=self.deleted, parent_id=parent_id, copied_from_history_dataset_association=self, user=self.history.user ) @@ -401,16 +401,16 @@ class LibraryDatasetDatasetAssociation( DatasetInstance ): self.user = user def to_history_dataset_association( self, target_history, parent_id=None ): hid = target_history._next_hid() - hda = HistoryDatasetAssociation( name=self.name, + hda = HistoryDatasetAssociation( name=self.name, info=self.info, - blurb=self.blurb, - peek=self.peek, - extension=self.extension, - dbkey=self.dbkey, - dataset=self.dataset, - visible=self.visible, - deleted=self.deleted, - parent_id=parent_id, + blurb=self.blurb, + peek=self.peek, + extension=self.extension, + dbkey=self.dbkey, + dataset=self.dataset, + visible=self.visible, + deleted=self.deleted, + parent_id=parent_id, copied_from_library_dataset_dataset_association=self, history=target_history, hid=hid ) @@ -424,16 +424,16 @@ class LibraryDatasetDatasetAssociation( DatasetInstance ): context.flush() return hda def copy( self, copy_children = False, parent_id = None, target_folder = None ): - ldda = LibraryDatasetDatasetAssociation( name=self.name, - info=self.info, - blurb=self.blurb, - peek=self.peek, - extension=self.extension, - dbkey=self.dbkey, - dataset=self.dataset, - visible=self.visible, - deleted=self.deleted, - parent_id=parent_id, + ldda = LibraryDatasetDatasetAssociation( name=self.name, + info=self.info, + blurb=self.blurb, + peek=self.peek, + extension=self.extension, + dbkey=self.dbkey, + dataset=self.dataset, + visible=self.visible, + deleted=self.deleted, + parent_id=parent_id, copied_from_library_dataset_dataset_association=self, folder=target_folder ) context.add( ldda ) @@ -513,7 +513,7 @@ class LibraryDataset( object ): ##tables -Dataset.table = Table( "dataset", metadata, +Dataset.table = Table( "dataset", metadata, Column( "id", Integer, primary_key=True ), Column( "create_time", DateTime, default=now ), Column( "update_time", DateTime, index=True, default=now, onupdate=now ), @@ -527,7 +527,7 @@ Dataset.table = Table( "dataset", metadata, -HistoryDatasetAssociation.table = Table( "history_dataset_association", metadata, +HistoryDatasetAssociation.table = Table( "history_dataset_association", metadata, Column( "id", Integer, primary_key=True ), Column( "dataset_id", Integer, ForeignKey( "dataset.id" ), index=True ), Column( "create_time", DateTime, default=now ), @@ -547,7 +547,7 @@ HistoryDatasetAssociation.table = Table( "history_dataset_association", metadata Column( "visible", Boolean ) ) -LibraryDatasetDatasetAssociation.table = Table( "library_dataset_dataset_association", metadata, +LibraryDatasetDatasetAssociation.table = Table( "library_dataset_dataset_association", metadata, Column( "id", Integer, primary_key=True ), Column( "library_dataset_id", Integer, ForeignKey( "library_dataset.id" ), index=True ), Column( "dataset_id", Integer, ForeignKey( "dataset.id" ), index=True ), @@ -567,7 +567,7 @@ LibraryDatasetDatasetAssociation.table = Table( "library_dataset_dataset_associa Column( "visible", Boolean ), Column( "message", TrimmedString( 255 ) ) ) -LibraryDataset.table = Table( "library_dataset", metadata, +LibraryDataset.table = Table( "library_dataset", metadata, Column( "id", Integer, primary_key=True ), Column( "library_dataset_dataset_association_id", Integer, ForeignKey( "library_dataset_dataset_association.id", use_alter=True, name="library_dataset_dataset_association_id_fk" ), nullable=True, index=True ),#current version of dataset, if null, there is not a current version selected Column( "order_id", Integer ), @@ -583,69 +583,69 @@ LibraryDataset.table = Table( "library_dataset", metadata, assign_mapper( context, Dataset, Dataset.table, - properties=dict( - history_associations=relation( - HistoryDatasetAssociation, + properties=dict( + history_associations=relation( + HistoryDatasetAssociation, primaryjoin=( Dataset.table.c.id == HistoryDatasetAssociation.table.c.dataset_id ) ), - active_history_associations=relation( - HistoryDatasetAssociation, + active_history_associations=relation( + HistoryDatasetAssociation, primaryjoin=( ( Dataset.table.c.id == HistoryDatasetAssociation.table.c.dataset_id ) & ( HistoryDatasetAssociation.table.c.deleted == False ) ) ), - library_associations=relation( - LibraryDatasetDatasetAssociation, + library_associations=relation( + LibraryDatasetDatasetAssociation, primaryjoin=( Dataset.table.c.id == LibraryDatasetDatasetAssociation.table.c.dataset_id ) ), - active_library_associations=relation( - LibraryDatasetDatasetAssociation, + active_library_associations=relation( + LibraryDatasetDatasetAssociation, primaryjoin=( ( Dataset.table.c.id == LibraryDatasetDatasetAssociation.table.c.dataset_id ) & ( LibraryDatasetDatasetAssociation.table.c.deleted == False ) ) ) ) ) assign_mapper( context, HistoryDatasetAssociation, HistoryDatasetAssociation.table, - properties=dict( - dataset=relation( - Dataset, + properties=dict( + dataset=relation( + Dataset, primaryjoin=( Dataset.table.c.id == HistoryDatasetAssociation.table.c.dataset_id ), lazy=False ), # .history defined in History mapper - copied_to_history_dataset_associations=relation( - HistoryDatasetAssociation, + copied_to_history_dataset_associations=relation( + HistoryDatasetAssociation, primaryjoin=( HistoryDatasetAssociation.table.c.copied_from_history_dataset_association_id == HistoryDatasetAssociation.table.c.id ), backref=backref( "copied_from_history_dataset_association", primaryjoin=( HistoryDatasetAssociation.table.c.copied_from_history_dataset_association_id == HistoryDatasetAssociation.table.c.id ), remote_side=[HistoryDatasetAssociation.table.c.id], uselist=False ) ), - copied_to_library_dataset_dataset_associations=relation( - LibraryDatasetDatasetAssociation, + copied_to_library_dataset_dataset_associations=relation( + LibraryDatasetDatasetAssociation, primaryjoin=( HistoryDatasetAssociation.table.c.copied_from_library_dataset_dataset_association_id == LibraryDatasetDatasetAssociation.table.c.id ), backref=backref( "copied_from_history_dataset_association", primaryjoin=( HistoryDatasetAssociation.table.c.copied_from_library_dataset_dataset_association_id == LibraryDatasetDatasetAssociation.table.c.id ), remote_side=[LibraryDatasetDatasetAssociation.table.c.id], uselist=False ) ), - children=relation( - HistoryDatasetAssociation, + children=relation( + HistoryDatasetAssociation, primaryjoin=( HistoryDatasetAssociation.table.c.parent_id == HistoryDatasetAssociation.table.c.id ), backref=backref( "parent", primaryjoin=( HistoryDatasetAssociation.table.c.parent_id == HistoryDatasetAssociation.table.c.id ), remote_side=[HistoryDatasetAssociation.table.c.id], uselist=False ) ), - visible_children=relation( - HistoryDatasetAssociation, + visible_children=relation( + HistoryDatasetAssociation, primaryjoin=( ( HistoryDatasetAssociation.table.c.parent_id == HistoryDatasetAssociation.table.c.id ) & ( HistoryDatasetAssociation.table.c.visible == True ) ) ) ) ) assign_mapper( context, LibraryDatasetDatasetAssociation, LibraryDatasetDatasetAssociation.table, - properties=dict( + properties=dict( dataset=relation( Dataset ), library_dataset = relation( LibraryDataset, primaryjoin=( LibraryDatasetDatasetAssociation.table.c.library_dataset_id == LibraryDataset.table.c.id ) ), - copied_to_library_dataset_dataset_associations=relation( - LibraryDatasetDatasetAssociation, + copied_to_library_dataset_dataset_associations=relation( + LibraryDatasetDatasetAssociation, primaryjoin=( LibraryDatasetDatasetAssociation.table.c.copied_from_library_dataset_dataset_association_id == LibraryDatasetDatasetAssociation.table.c.id ), backref=backref( "copied_from_library_dataset_dataset_association", primaryjoin=( LibraryDatasetDatasetAssociation.table.c.copied_from_library_dataset_dataset_association_id == LibraryDatasetDatasetAssociation.table.c.id ), remote_side=[LibraryDatasetDatasetAssociation.table.c.id] ) ), - copied_to_history_dataset_associations=relation( - HistoryDatasetAssociation, + copied_to_history_dataset_associations=relation( + HistoryDatasetAssociation, primaryjoin=( HistoryDatasetAssociation.table.c.copied_from_library_dataset_dataset_association_id == LibraryDatasetDatasetAssociation.table.c.id ), backref=backref( "copied_from_library_dataset_dataset_association", primaryjoin=( HistoryDatasetAssociation.table.c.copied_from_library_dataset_dataset_association_id == LibraryDatasetDatasetAssociation.table.c.id ), remote_side=[LibraryDatasetDatasetAssociation.table.c.id], uselist=False ) ), - children=relation( - LibraryDatasetDatasetAssociation, + children=relation( + LibraryDatasetDatasetAssociation, primaryjoin=( LibraryDatasetDatasetAssociation.table.c.parent_id == LibraryDatasetDatasetAssociation.table.c.id ), backref=backref( "parent", primaryjoin=( LibraryDatasetDatasetAssociation.table.c.parent_id == LibraryDatasetDatasetAssociation.table.c.id ), remote_side=[LibraryDatasetDatasetAssociation.table.c.id] ) ), - visible_children=relation( - LibraryDatasetDatasetAssociation, + visible_children=relation( + LibraryDatasetDatasetAssociation, primaryjoin=( ( LibraryDatasetDatasetAssociation.table.c.parent_id == LibraryDatasetDatasetAssociation.table.c.id ) & ( LibraryDatasetDatasetAssociation.table.c.visible == True ) ) ) ) ) assign_mapper( context, LibraryDataset, LibraryDataset.table, - properties=dict( + properties=dict( library_dataset_dataset_association=relation( LibraryDatasetDatasetAssociation, primaryjoin=( LibraryDataset.table.c.library_dataset_dataset_association_id == LibraryDatasetDatasetAssociation.table.c.id ) ), expired_datasets = relation( LibraryDatasetDatasetAssociation, foreign_keys=[LibraryDataset.table.c.id,LibraryDataset.table.c.library_dataset_dataset_association_id ], primaryjoin=( ( LibraryDataset.table.c.id == LibraryDatasetDatasetAssociation.table.c.library_dataset_id ) & ( not_( LibraryDataset.table.c.library_dataset_dataset_association_id == LibraryDatasetDatasetAssociation.table.c.id ) ) ), viewonly=True, uselist=True ) ) ) @@ -678,7 +678,7 @@ def upgrade(migrate_engine): context.flush() log.debug( "%i items affected, and restored." % ( affected_items ) ) log.debug( "Time elapsed: %s" % ( time.time() - start_time ) ) - + #fix share before hda log.debug( "Fixing a discrepancy concerning cleaning up deleted history items shared before HDAs." ) dataset_by_filename = {} @@ -692,7 +692,7 @@ def upgrade(migrate_engine): if guessed_dataset and dataset.file_name != guessed_dataset.file_name:#not os.path.samefile( dataset.file_name, guessed_dataset.file_name ): guessed_dataset = None dataset_by_filename[ dataset.file_name ] = guessed_dataset - + if guessed_dataset is not None and guessed_dataset.id != dataset.id: #could we have a self referential dataset? for dataset_instance in dataset.history_associations + dataset.library_associations: dataset_instance.dataset = guessed_dataset @@ -708,4 +708,4 @@ def upgrade(migrate_engine): def downgrade(migrate_engine): metadata.bind = migrate_engine log.debug( "Downgrade is not possible." ) - + diff --git a/lib/galaxy/model/migrate/versions/0008_galaxy_forms.py b/lib/galaxy/model/migrate/versions/0008_galaxy_forms.py index 67d7485b177..208e5fe423b 100644 --- a/lib/galaxy/model/migrate/versions/0008_galaxy_forms.py +++ b/lib/galaxy/model/migrate/versions/0008_galaxy_forms.py @@ -111,7 +111,7 @@ SampleEvent_table = Table('sample_event', metadata, Column( "id", Integer, primary_key=True), Column( "create_time", DateTime, default=now ), Column( "update_time", DateTime, default=now, onupdate=now ), - Column( "sample_id", Integer, ForeignKey( "sample.id" ), index=True ), + Column( "sample_id", Integer, ForeignKey( "sample.id" ), index=True ), Column( "sample_state_id", Integer, ForeignKey( "sample_state.id" ), index=True ), Column( "comment", TEXT ) ) @@ -126,7 +126,7 @@ def upgrade(migrate_engine): try: FormDefinitionCurrent_table.create() except Exception, e: - log.debug( "Creating form_definition_current table failed: %s" % str( e ) ) + log.debug( "Creating form_definition_current table failed: %s" % str( e ) ) try: FormDefinition_table.create() except Exception, e: @@ -144,27 +144,27 @@ def upgrade(migrate_engine): try: FormValues_table.create() except Exception, e: - log.debug( "Creating form_values table failed: %s" % str( e ) ) + log.debug( "Creating form_values table failed: %s" % str( e ) ) try: RequestType_table.create() except Exception, e: - log.debug( "Creating request_type table failed: %s" % str( e ) ) + log.debug( "Creating request_type table failed: %s" % str( e ) ) try: Request_table.create() except Exception, e: - log.debug( "Creating request table failed: %s" % str( e ) ) + log.debug( "Creating request table failed: %s" % str( e ) ) try: Sample_table.create() except Exception, e: - log.debug( "Creating sample table failed: %s" % str( e ) ) + log.debug( "Creating sample table failed: %s" % str( e ) ) try: SampleState_table.create() except Exception, e: - log.debug( "Creating sample_state table failed: %s" % str( e ) ) + log.debug( "Creating sample_state table failed: %s" % str( e ) ) try: SampleEvent_table.create() except Exception, e: - log.debug( "Creating sample_event table failed: %s" % str( e ) ) + log.debug( "Creating sample_event table failed: %s" % str( e ) ) def downgrade(migrate_engine): db_session = scoped_session( sessionmaker( bind=migrate_engine, autoflush=False, autocommit=True ) ) @@ -174,32 +174,32 @@ def downgrade(migrate_engine): try: FormDefinition_table.drop() except Exception, e: - log.debug( "Dropping form_definition table failed: %s" % str( e ) ) + log.debug( "Dropping form_definition table failed: %s" % str( e ) ) try: FormDefinitionCurrent_table.drop() except Exception, e: - log.debug( "Dropping form_definition_current table failed: %s" % str( e ) ) + log.debug( "Dropping form_definition_current table failed: %s" % str( e ) ) try: FormValues_table.drop() except Exception, e: - log.debug( "Dropping form_values table failed: %s" % str( e ) ) + log.debug( "Dropping form_values table failed: %s" % str( e ) ) try: Request_table.drop() except Exception, e: - log.debug( "Dropping request table failed: %s" % str( e ) ) + log.debug( "Dropping request table failed: %s" % str( e ) ) try: RequestType_table.drop() except Exception, e: - log.debug( "Dropping request_type table failed: %s" % str( e ) ) + log.debug( "Dropping request_type table failed: %s" % str( e ) ) try: Sample_table.drop() except Exception, e: - log.debug( "Dropping sample table failed: %s" % str( e ) ) + log.debug( "Dropping sample table failed: %s" % str( e ) ) try: SampleState_table.drop() except Exception, e: - log.debug( "Dropping sample_state table failed: %s" % str( e ) ) + log.debug( "Dropping sample_state table failed: %s" % str( e ) ) try: SampleEvent_table.drop() except Exception, e: - log.debug( "Dropping sample_event table failed: %s" % str( e ) ) + log.debug( "Dropping sample_event table failed: %s" % str( e ) ) diff --git a/lib/galaxy/model/migrate/versions/0010_hda_display_at_authz_table.py b/lib/galaxy/model/migrate/versions/0010_hda_display_at_authz_table.py index c86bae70402..42a6060b0e3 100644 --- a/lib/galaxy/model/migrate/versions/0010_hda_display_at_authz_table.py +++ b/lib/galaxy/model/migrate/versions/0010_hda_display_at_authz_table.py @@ -5,7 +5,7 @@ If using mysql, this script will display the following error, which is corrected migration script: history_dataset_association_display_at_authorization table failed: (OperationalError) -(1059, "Identifier name 'ix_history_dataset_association_display_at_authorization_update_time' +(1059, "Identifier name 'ix_history_dataset_association_display_at_authorization_update_time' is too long """ from sqlalchemy import * @@ -58,7 +58,7 @@ def upgrade(migrate_engine): try: HistoryDatasetAssociationDisplayAtAuthorization_table.create() except Exception, e: - log.debug( "Creating history_dataset_association_display_at_authorization table failed: %s" % str( e ) ) + log.debug( "Creating history_dataset_association_display_at_authorization table failed: %s" % str( e ) ) def downgrade(migrate_engine): metadata.bind = migrate_engine @@ -67,4 +67,4 @@ def downgrade(migrate_engine): try: HistoryDatasetAssociationDisplayAtAuthorization_table.drop() except Exception, e: - log.debug( "Dropping history_dataset_association_display_at_authorization table failed: %s" % str( e ) ) + log.debug( "Dropping history_dataset_association_display_at_authorization table failed: %s" % str( e ) ) diff --git a/lib/galaxy/model/migrate/versions/0011_v0010_mysql_index_fix.py b/lib/galaxy/model/migrate/versions/0011_v0010_mysql_index_fix.py index 896c6495ab9..62e836c0cb0 100644 --- a/lib/galaxy/model/migrate/versions/0011_v0010_mysql_index_fix.py +++ b/lib/galaxy/model/migrate/versions/0011_v0010_mysql_index_fix.py @@ -1,5 +1,5 @@ """ -This script fixes a problem introduced in 0010_hda_display_at_atuhz_table.py. MySQL has a +This script fixes a problem introduced in 0010_hda_display_at_atuhz_table.py. MySQL has a name length limit and thus the index "ix_hdadaa_history_dataset_association_id" has to be manually created. """ @@ -51,7 +51,7 @@ def upgrade(migrate_engine): try: i.create() except Exception, e: - log.debug( "Adding index 'ix_hdadaa_history_dataset_association_id' to table 'history_dataset_association_display_at_authorization' table failed: %s" % str( e ) ) + log.debug( "Adding index 'ix_hdadaa_history_dataset_association_id' to table 'history_dataset_association_display_at_authorization' table failed: %s" % str( e ) ) def downgrade(migrate_engine): metadata.bind = migrate_engine @@ -62,4 +62,4 @@ def downgrade(migrate_engine): try: i.drop() except Exception, e: - log.debug( "Removing index 'ix_hdadaa_history_dataset_association_id' from table 'history_dataset_association_display_at_authorization' table failed: %s" % str( e ) ) + log.debug( "Removing index 'ix_hdadaa_history_dataset_association_id' from table 'history_dataset_association_display_at_authorization' table failed: %s" % str( e ) ) diff --git a/lib/galaxy/model/migrate/versions/0012_user_address.py b/lib/galaxy/model/migrate/versions/0012_user_address.py index da1dab09c7e..861255b4dcb 100644 --- a/lib/galaxy/model/migrate/versions/0012_user_address.py +++ b/lib/galaxy/model/migrate/versions/0012_user_address.py @@ -60,7 +60,7 @@ def upgrade(migrate_engine): try: UserAddress_table.create() except Exception, e: - log.debug( "Creating user_address table failed: %s" % str( e ) ) + log.debug( "Creating user_address table failed: %s" % str( e ) ) # Add 1 column to the request_type table try: RequestType_table = Table( "request_type", metadata, autoload=True ) diff --git a/lib/galaxy/model/migrate/versions/0013_change_lib_item_templates_to_forms.py b/lib/galaxy/model/migrate/versions/0013_change_lib_item_templates_to_forms.py index b9e303aece7..a9fa0a54346 100644 --- a/lib/galaxy/model/migrate/versions/0013_change_lib_item_templates_to_forms.py +++ b/lib/galaxy/model/migrate/versions/0013_change_lib_item_templates_to_forms.py @@ -67,7 +67,7 @@ LibraryDatasetDatasetInfoAssociation_table = Table( 'library_dataset_dataset_inf Column( "library_dataset_dataset_association_id", Integer, ForeignKey( "library_dataset_dataset_association.id" ), nullable=True, index=True ), Column( "form_definition_id", Integer, ForeignKey( "form_definition.id" ), index=True ), Column( "form_values_id", Integer, ForeignKey( "form_values.id" ), index=True ) ) - + def upgrade(migrate_engine): metadata.bind = migrate_engine display_migration_details() diff --git a/lib/galaxy/model/migrate/versions/0014_pages.py b/lib/galaxy/model/migrate/versions/0014_pages.py index 23e0ab105c4..1f8baab216d 100644 --- a/lib/galaxy/model/migrate/versions/0014_pages.py +++ b/lib/galaxy/model/migrate/versions/0014_pages.py @@ -48,7 +48,7 @@ def upgrade(migrate_engine): PageRevision_table.create() except: log.debug( "Could not create page_revision table" ) - + # Add 1 column to the user table User_table = Table( "galaxy_user", metadata, autoload=True ) col = Column( 'username', String(255), index=True, unique=True, default=False ) diff --git a/lib/galaxy/model/migrate/versions/0015_tagging.py b/lib/galaxy/model/migrate/versions/0015_tagging.py index 75a0d7c880a..22dea9dd5c9 100644 --- a/lib/galaxy/model/migrate/versions/0015_tagging.py +++ b/lib/galaxy/model/migrate/versions/0015_tagging.py @@ -35,14 +35,14 @@ def display_migration_details(): print "(OperationalError) (1059, 'Identifier name " print "'ix_history_dataset_association_tag_association_history_dataset_association_id'" print "is too long)" - + # New tables to support tagging of histories, datasets, and history-dataset associations. Tag_table = Table( "tag", metadata, Column( "id", Integer, primary_key=True ), Column( "type", Integer ), Column( "parent_id", Integer, ForeignKey( "tag.id" ) ), - Column( "name", TrimmedString(255) ), + Column( "name", TrimmedString(255) ), UniqueConstraint( "name" ) ) HistoryTagAssociation_table = Table( "history_tag_association", metadata, @@ -51,7 +51,7 @@ HistoryTagAssociation_table = Table( "history_tag_association", metadata, Column( "user_tname", TrimmedString(255), index=True), Column( "value", TrimmedString(255), index=True), Column( "user_value", TrimmedString(255), index=True) ) - + DatasetTagAssociation_table = Table( "dataset_tag_association", metadata, Column( "dataset_id", Integer, ForeignKey( "dataset.id" ), index=True ), Column( "tag_id", Integer, ForeignKey( "tag.id" ), index=True ), @@ -90,7 +90,7 @@ def upgrade(migrate_engine): except Exception, e: print str(e) log.debug( "Creating history_dataset_association_tag_association table failed: %s" % str( e ) ) - + def downgrade(migrate_engine): metadata.bind = migrate_engine metadata.reflect() diff --git a/lib/galaxy/model/migrate/versions/0016_v0015_mysql_index_fix.py b/lib/galaxy/model/migrate/versions/0016_v0015_mysql_index_fix.py index 3ca40b4cee7..12412b094d7 100644 --- a/lib/galaxy/model/migrate/versions/0016_v0015_mysql_index_fix.py +++ b/lib/galaxy/model/migrate/versions/0016_v0015_mysql_index_fix.py @@ -23,7 +23,7 @@ def display_migration_details(): print "This script fixes a problem introduced in 0015_tagging.py. MySQL has a" print "name length limit and thus the index 'ix_hda_ta_history_dataset_association_id'" print "has to be manually created." - + HistoryDatasetAssociationTagAssociation_table = Table( "history_dataset_association_tag_association", metadata, Column( "history_dataset_association_id", Integer, ForeignKey( "history_dataset_association.id" ), index=True ), Column( "tag_id", Integer, ForeignKey( "tag.id" ), index=True ), @@ -41,7 +41,7 @@ def upgrade(migrate_engine): except Exception, e: print str(e) log.debug( "Adding index 'ix_hdata_history_dataset_association_id' to table 'history_dataset_association_tag_association' table failed: %s" % str( e ) ) - + def downgrade(migrate_engine): metadata.bind = migrate_engine metadata.reflect() diff --git a/lib/galaxy/model/migrate/versions/0017_library_item_indexes.py b/lib/galaxy/model/migrate/versions/0017_library_item_indexes.py index 3737256f5a4..317eb725c0c 100644 --- a/lib/galaxy/model/migrate/versions/0017_library_item_indexes.py +++ b/lib/galaxy/model/migrate/versions/0017_library_item_indexes.py @@ -16,13 +16,13 @@ handler.setFormatter( formatter ) log.addHandler( handler ) metadata = MetaData() - + def display_migration_details(): print "========================================" print "This script adds 3 indexes to table columns: library_folder.name," print "library_dataset.name, library_dataset_dataset_association.name." print "========================================" - + def upgrade(migrate_engine): metadata.bind = migrate_engine LibraryFolder_table = Table( "library_folder", metadata, autoload=True ) diff --git a/lib/galaxy/model/migrate/versions/0019_request_library_folder.py b/lib/galaxy/model/migrate/versions/0019_request_library_folder.py index 9f15abe2c90..2e596c30ca8 100644 --- a/lib/galaxy/model/migrate/versions/0019_request_library_folder.py +++ b/lib/galaxy/model/migrate/versions/0019_request_library_folder.py @@ -23,7 +23,7 @@ def display_migration_details(): print "========================================" print """This script creates a request.folder_id column which is a foreign key to the library_folder table. This also adds a 'type' and 'layout' column -to the form_definition table.""" +to the form_definition table.""" print "========================================" def upgrade(migrate_engine): @@ -73,7 +73,7 @@ def upgrade(migrate_engine): except Exception, e: log.debug( "Adding column 'type' to form_definition table failed: %s" % ( str( e ) ) ) try: - col = Column( "layout", JSONType()) + col = Column( "layout", JSONType()) col.create( FormDefinition_table ) assert col is FormDefinition_table.c.layout except Exception, e: diff --git a/lib/galaxy/model/migrate/versions/0023_page_published_and_deleted_columns.py b/lib/galaxy/model/migrate/versions/0023_page_published_and_deleted_columns.py index 181b6067da5..8de454e4a82 100644 --- a/lib/galaxy/model/migrate/versions/0023_page_published_and_deleted_columns.py +++ b/lib/galaxy/model/migrate/versions/0023_page_published_and_deleted_columns.py @@ -14,17 +14,17 @@ metadata = MetaData() def upgrade(migrate_engine): metadata.bind = migrate_engine - + print __doc__ metadata.reflect() - + Page_table = Table( "page", metadata, autoload=True ) - + c = Column( "published", Boolean, index=True, default=False ) c.create( Page_table, index_name = 'ix_page_published' ) assert c is Page_table.c.published - - c = Column( "deleted", Boolean, index=True, default=False ) + + c = Column( "deleted", Boolean, index=True, default=False ) c.create( Page_table, index_name='ix_page_deleted') assert c is Page_table.c.deleted diff --git a/lib/galaxy/model/migrate/versions/0024_page_slug_unique_constraint.py b/lib/galaxy/model/migrate/versions/0024_page_slug_unique_constraint.py index 57f8acfaed1..861d2a3e45d 100644 --- a/lib/galaxy/model/migrate/versions/0024_page_slug_unique_constraint.py +++ b/lib/galaxy/model/migrate/versions/0024_page_slug_unique_constraint.py @@ -25,10 +25,10 @@ def upgrade(migrate_engine): try: # Sqlite doesn't support .alter, so we need to drop an recreate - + i = Index( "ix_page_slug", Page_table.c.slug ) i.drop() - + i = Index( "ix_page_slug", Page_table.c.slug, unique=False ) i.create() diff --git a/lib/galaxy/model/migrate/versions/0026_cloud_tables.py b/lib/galaxy/model/migrate/versions/0026_cloud_tables.py index 6682273a9b1..062528aa8ae 100644 --- a/lib/galaxy/model/migrate/versions/0026_cloud_tables.py +++ b/lib/galaxy/model/migrate/versions/0026_cloud_tables.py @@ -17,8 +17,8 @@ def display_migration_details(): print "========================================" print "This script adds tables needed for Galaxy cloud functionality." print "========================================" - -CloudImage_table = Table( "cloud_image", metadata, + +CloudImage_table = Table( "cloud_image", metadata, Column( "id", Integer, primary_key=True ), Column( "create_time", DateTime, default=now ), Column( "update_time", DateTime, default=now, onupdate=now ), @@ -30,7 +30,7 @@ CloudImage_table = Table( "cloud_image", metadata, Column( "deleted", Boolean, default=False ) ) """ UserConfiguredInstance (UCI) table """ -UCI_table = Table( "cloud_uci", metadata, +UCI_table = Table( "cloud_uci", metadata, Column( "id", Integer, primary_key=True ), Column( "create_time", DateTime, default=now ), Column( "update_time", DateTime, default=now, onupdate=now ), @@ -45,7 +45,7 @@ UCI_table = Table( "cloud_uci", metadata, Column( "launch_time", DateTime ), Column( "deleted", Boolean, default=False ) ) -CloudInstance_table = Table( "cloud_instance", metadata, +CloudInstance_table = Table( "cloud_instance", metadata, Column( "id", Integer, primary_key=True ), Column( "create_time", DateTime, default=now ), Column( "update_time", DateTime, default=now, onupdate=now ), @@ -64,7 +64,7 @@ CloudInstance_table = Table( "cloud_instance", metadata, Column( "security_group", TEXT ), Column( "availability_zone", TEXT ) ) -CloudStore_table = Table( "cloud_store", metadata, +CloudStore_table = Table( "cloud_store", metadata, Column( "id", Integer, primary_key=True ), Column( "create_time", DateTime, default=now ), Column( "update_time", DateTime, default=now, onupdate=now ), @@ -81,7 +81,7 @@ CloudStore_table = Table( "cloud_store", metadata, Column( "error", TEXT ), Column( "deleted", Boolean, default=False ) ) -CloudSnapshot_table = Table( "cloud_snapshot", metadata, +CloudSnapshot_table = Table( "cloud_snapshot", metadata, Column( "id", Integer, primary_key=True ), Column( "create_time", DateTime, default=now ), Column( "update_time", DateTime, default=now, onupdate=now ), @@ -94,7 +94,7 @@ CloudSnapshot_table = Table( "cloud_snapshot", metadata, Column( "error", TEXT ), Column( "deleted", Boolean, default=False ) ) -CloudUserCredentials_table = Table( "cloud_user_credentials", metadata, +CloudUserCredentials_table = Table( "cloud_user_credentials", metadata, Column( "id", Integer, primary_key=True ), Column( "create_time", DateTime, default=now ), Column( "update_time", DateTime, default=now, onupdate=now ), @@ -105,7 +105,7 @@ CloudUserCredentials_table = Table( "cloud_user_credentials", metadata, Column( "secret_key", TEXT ), Column( "deleted", Boolean, default=False ) ) -CloudProvider_table = Table( "cloud_provider", metadata, +CloudProvider_table = Table( "cloud_provider", metadata, Column( "id", Integer, primary_key=True ), Column( "create_time", DateTime, default=now ), Column( "update_time", DateTime, default=now, onupdate=now ), @@ -132,7 +132,7 @@ def upgrade(migrate_engine): display_migration_details() # Load existing tables metadata.reflect() - + CloudProvider_table.create() CloudUserCredentials_table.create() @@ -142,17 +142,17 @@ def upgrade(migrate_engine): CloudInstance_table.create() CloudStore_table.create() CloudSnapshot_table.create() - + def downgrade(migrate_engine): metadata.bind = migrate_engine metadata.reflect() - + CloudInstance_table.drop() CloudSnapshot_table.drop() CloudStore_table.drop() UCI_table.drop() - CloudImage_table.drop() + CloudImage_table.drop() - CloudUserCredentials_table.drop() + CloudUserCredentials_table.drop() CloudProvider_table.drop() diff --git a/lib/galaxy/model/migrate/versions/0027_request_events.py b/lib/galaxy/model/migrate/versions/0027_request_events.py index 65a7f5c7b33..dc683f6e337 100644 --- a/lib/galaxy/model/migrate/versions/0027_request_events.py +++ b/lib/galaxy/model/migrate/versions/0027_request_events.py @@ -1,5 +1,5 @@ """ -This migration script adds the request_event table and +This migration script adds the request_event table and removes the state field in the request table """ # Need our custom types, but don't import anything else from model @@ -30,7 +30,7 @@ metadata = MetaData() def display_migration_details(): print "========================================" - print "This migration script adds the request_event table and" + print "This migration script adds the request_event table and" print "removes the state field in the request table" print "========================================" @@ -39,7 +39,7 @@ RequestEvent_table = Table('request_event', metadata, Column( "id", Integer, primary_key=True), Column( "create_time", DateTime, default=now ), Column( "update_time", DateTime, default=now, onupdate=now ), - Column( "request_id", Integer, ForeignKey( "request.id" ), index=True ), + Column( "request_id", Integer, ForeignKey( "request.id" ), index=True ), Column( "state", TrimmedString( 255 ), index=True ), Column( "comment", TEXT ) ) @@ -77,10 +77,10 @@ def upgrade(migrate_engine): "request.id AS request_id," + \ "request.state AS state," + \ "'%s' AS comment " + \ - "FROM request;" + "FROM request;" cmd = cmd % ( nextval('request_event'), localtimestamp(), localtimestamp(), 'Imported from request table') migrate_engine.execute( cmd ) - + if migrate_engine.name != 'sqlite': # Delete the state column try: @@ -92,8 +92,8 @@ def upgrade(migrate_engine): try: Request_table.c.state.drop() except Exception, e: - log.debug( "Deleting column 'state' to request table failed: %s" % ( str( e ) ) ) - + log.debug( "Deleting column 'state' to request table failed: %s" % ( str( e ) ) ) + def downgrade(migrate_engine): metadata.bind = migrate_engine pass diff --git a/lib/galaxy/model/migrate/versions/0028_external_metadata_file_override.py b/lib/galaxy/model/migrate/versions/0028_external_metadata_file_override.py index 508a89741d9..fbcce08f7d2 100644 --- a/lib/galaxy/model/migrate/versions/0028_external_metadata_file_override.py +++ b/lib/galaxy/model/migrate/versions/0028_external_metadata_file_override.py @@ -1,6 +1,6 @@ """ -This script adds the filename_override_metadata column to the JobExternalOutputMetadata table, -allowing existing metadata files to be written when using external metadata and a cluster +This script adds the filename_override_metadata column to the JobExternalOutputMetadata table, +allowing existing metadata files to be written when using external metadata and a cluster set up with read-only access to database/files """ from sqlalchemy import * diff --git a/lib/galaxy/model/migrate/versions/0030_history_slug_column.py b/lib/galaxy/model/migrate/versions/0030_history_slug_column.py index 8a426e66f61..a16c0d26789 100644 --- a/lib/galaxy/model/migrate/versions/0030_history_slug_column.py +++ b/lib/galaxy/model/migrate/versions/0030_history_slug_column.py @@ -13,12 +13,12 @@ metadata = MetaData() def upgrade(migrate_engine): metadata.bind = migrate_engine - + print __doc__ metadata.reflect() - + History_table = Table( "history", metadata, autoload=True ) - + # Create slug column. c = Column( "slug", TEXT, index=True ) c.create( History_table , index_name='ix_history_slug') diff --git a/lib/galaxy/model/migrate/versions/0031_community_and_workflow_tags.py b/lib/galaxy/model/migrate/versions/0031_community_and_workflow_tags.py index c982b844c87..46a4a714a98 100644 --- a/lib/galaxy/model/migrate/versions/0031_community_and_workflow_tags.py +++ b/lib/galaxy/model/migrate/versions/0031_community_and_workflow_tags.py @@ -24,7 +24,7 @@ StoredWorkflowTagAssociation_table = Table( "stored_workflow_tag_association", m Column( "user_tname", Unicode(255), index=True), Column( "value", Unicode(255), index=True), Column( "user_value", Unicode(255), index=True) ) - + WorkflowTagAssociation_table = Table( "workflow_tag_association", metadata, Column( "id", Integer, primary_key=True ), Column( "workflow_id", Integer, ForeignKey( "workflow.id" ), index=True ), @@ -62,7 +62,7 @@ def upgrade(migrate_engine): # Populate column so that user_id is the id of the user who owns the history (and, up to now, was the only person able to tag the history). if c is HistoryTagAssociation_table.c.user_id: - migrate_engine.execute( + migrate_engine.execute( "UPDATE history_tag_association SET user_id=( SELECT user_id FROM history WHERE history_tag_association.history_id = history.id )" ) @@ -92,7 +92,7 @@ def upgrade(migrate_engine): # Populate column so that user_id is the id of the user who owns the history_dataset_association (and, up to now, was the only person able to tag the page). if c is HistoryDatasetAssociationTagAssociation_table.c.user_id: - migrate_engine.execute( + migrate_engine.execute( "UPDATE history_dataset_association_tag_association SET user_id=( SELECT history.user_id FROM history, history_dataset_association WHERE history_dataset_association.history_id = history.id AND history_dataset_association.id = history_dataset_association_tag_association.history_dataset_association_id)" ) if migrate_engine.name != 'sqlite': @@ -120,7 +120,7 @@ def upgrade(migrate_engine): # Populate column so that user_id is the id of the user who owns the page (and, up to now, was the only person able to tag the page). if c is PageTagAssociation_table.c.user_id: - migrate_engine.execute( + migrate_engine.execute( "UPDATE page_tag_association SET user_id=( SELECT user_id FROM page WHERE page_tag_association.page_id = page.id )" ) @@ -130,13 +130,13 @@ def upgrade(migrate_engine): except Exception, e: print str(e) log.debug( "Creating stored_workflow_tag_association table failed: %s" % str( e ) ) - + # Create workflow_tag_association table. try: WorkflowTagAssociation_table.create() except Exception, e: print str(e) - log.debug( "Creating workflow_tag_association table failed: %s" % str( e ) ) + log.debug( "Creating workflow_tag_association table failed: %s" % str( e ) ) def downgrade(migrate_engine): metadata.bind = migrate_engine @@ -149,7 +149,7 @@ def downgrade(migrate_engine): except Exception, e: print str(e) log.debug( "Dropping column user_id from history_tag_association table failed: %s" % str( e ) ) - + # Drop user_id column from history_dataset_association_tag_association table. HistoryDatasetAssociationTagAssociation_table = Table( "history_dataset_association_tag_association", metadata, autoload=True ) @@ -173,7 +173,7 @@ def downgrade(migrate_engine): except Exception, e: print str(e) log.debug( "Dropping stored_workflow_tag_association table failed: %s" % str( e ) ) - + # Drop workflow_tag_association table. try: WorkflowTagAssociation_table.drop() diff --git a/lib/galaxy/model/migrate/versions/0032_stored_workflow_slug_column.py b/lib/galaxy/model/migrate/versions/0032_stored_workflow_slug_column.py index 40ee762858b..fc9de3e8701 100644 --- a/lib/galaxy/model/migrate/versions/0032_stored_workflow_slug_column.py +++ b/lib/galaxy/model/migrate/versions/0032_stored_workflow_slug_column.py @@ -13,12 +13,12 @@ metadata = MetaData() def upgrade(migrate_engine): metadata.bind = migrate_engine - + print __doc__ metadata.reflect() - + StoredWorkflow_table = Table( "stored_workflow", metadata, autoload=True ) - + if migrate_engine.name != 'sqlite': # Create slug column. c = Column( "slug", TEXT, index=True ) @@ -28,7 +28,7 @@ def upgrade(migrate_engine): c = Column( "slug", TEXT ) c.create( StoredWorkflow_table ) assert c is StoredWorkflow_table.c.slug - + # Create slug index. if migrate_engine.name != 'sqlite': try: diff --git a/lib/galaxy/model/migrate/versions/0033_published_cols_for_histories_and_workflows.py b/lib/galaxy/model/migrate/versions/0033_published_cols_for_histories_and_workflows.py index e873e1121d3..dc580d67140 100644 --- a/lib/galaxy/model/migrate/versions/0033_published_cols_for_histories_and_workflows.py +++ b/lib/galaxy/model/migrate/versions/0033_published_cols_for_histories_and_workflows.py @@ -27,8 +27,8 @@ def upgrade(migrate_engine): except Exception, e: print "Adding published column to history table failed: %s" % str( e ) log.debug( "Adding published column to history table failed: %s" % str( e ) ) - - + + if migrate_engine.name != 'sqlite': # Create index for published column in history table. try: @@ -37,7 +37,7 @@ def upgrade(migrate_engine): except: # Mysql doesn't have a named index, but alter should work History_table.c.published.alter( unique=False ) - + # Create published column in stored workflows table. StoredWorkflow_table = Table( "stored_workflow", metadata, autoload=True ) c = Column( "published", Boolean, index=True ) @@ -66,7 +66,7 @@ def upgrade(migrate_engine): except Exception, e: print "Adding importable column to page table failed: %s" % str( e ) log.debug( "Adding importable column to page table failed: %s" % str( e ) ) - + if migrate_engine.name != 'sqlite': # Create index for importable column in page table. try: @@ -87,7 +87,7 @@ def downgrade(migrate_engine): except Exception, e: print "Dropping column published from history table failed: %s" % str( e ) log.debug( "Dropping column published from history table failed: %s" % str( e ) ) - + # Drop published column from stored_workflow table. StoredWorkflow_table = Table( "stored_workflow", metadata, autoload=True ) try: @@ -95,7 +95,7 @@ def downgrade(migrate_engine): except Exception, e: print "Dropping column published from stored_workflow table failed: %s" % str( e ) log.debug( "Dropping column published from stored_workflow table failed: %s" % str( e ) ) - + # Drop importable column from page table. Page_table = Table( "page", metadata, autoload=True ) try: diff --git a/lib/galaxy/model/migrate/versions/0034_page_user_share_association.py b/lib/galaxy/model/migrate/versions/0034_page_user_share_association.py index 7c378318c20..b1cc1c9e443 100644 --- a/lib/galaxy/model/migrate/versions/0034_page_user_share_association.py +++ b/lib/galaxy/model/migrate/versions/0034_page_user_share_association.py @@ -17,7 +17,7 @@ PageUserShareAssociation_table = Table( "page_user_share_association", metadata, Column( "page_id", Integer, ForeignKey( "page.id" ), index=True ), Column( "user_id", Integer, ForeignKey( "galaxy_user.id" ), index=True ) ) - + def upgrade(migrate_engine): metadata.bind = migrate_engine print __doc__ @@ -29,11 +29,11 @@ def upgrade(migrate_engine): except Exception, e: print str(e) log.debug( "Creating page_user_share_association table failed: %s" % str( e ) ) - + def downgrade(migrate_engine): metadata.bind = migrate_engine metadata.reflect() - + # Drop workflow_tag_association table. try: PageUserShareAssociation_table.drop() diff --git a/lib/galaxy/model/migrate/versions/0035_item_annotations_and_workflow_step_tags.py b/lib/galaxy/model/migrate/versions/0035_item_annotations_and_workflow_step_tags.py index 5ef9d0d5b9b..bda5d0dd1f4 100644 --- a/lib/galaxy/model/migrate/versions/0035_item_annotations_and_workflow_step_tags.py +++ b/lib/galaxy/model/migrate/versions/0035_item_annotations_and_workflow_step_tags.py @@ -19,26 +19,26 @@ HistoryAnnotationAssociation_table = Table( "history_annotation_association", me Column( "history_id", Integer, ForeignKey( "history.id" ), index=True ), Column( "user_id", Integer, ForeignKey( "galaxy_user.id" ), index=True ), Column( "annotation", TEXT, index=True) ) - + HistoryDatasetAssociationAnnotationAssociation_table = Table( "history_dataset_association_annotation_association", metadata, Column( "id", Integer, primary_key=True ), Column( "history_dataset_association_id", Integer, ForeignKey( "history_dataset_association.id" ), index=True ), Column( "user_id", Integer, ForeignKey( "galaxy_user.id" ), index=True ), Column( "annotation", TEXT, index=True) ) - + StoredWorkflowAnnotationAssociation_table = Table( "stored_workflow_annotation_association", metadata, Column( "id", Integer, primary_key=True ), Column( "stored_workflow_id", Integer, ForeignKey( "stored_workflow.id" ), index=True ), Column( "user_id", Integer, ForeignKey( "galaxy_user.id" ), index=True ), Column( "annotation", TEXT, index=True) ) - + WorkflowStepAnnotationAssociation_table = Table( "workflow_step_annotation_association", metadata, Column( "id", Integer, primary_key=True ), Column( "workflow_step_id", Integer, ForeignKey( "workflow_step.id" ), index=True ), Column( "user_id", Integer, ForeignKey( "galaxy_user.id" ), index=True ), Column( "annotation", TEXT, index=True) ) -# Tagging tables. +# Tagging tables. WorkflowStepTagAssociation_table = Table( "workflow_step_tag_association", metadata, Column( "id", Integer, primary_key=True ), @@ -48,7 +48,7 @@ WorkflowStepTagAssociation_table = Table( "workflow_step_tag_association", metad Column( "user_tname", Unicode(255), index=True), Column( "value", Unicode(255), index=True), Column( "user_value", Unicode(255), index=True) ) - + def upgrade(migrate_engine): metadata.bind = migrate_engine print __doc__ @@ -60,39 +60,39 @@ def upgrade(migrate_engine): except Exception, e: print str(e) log.debug( "Creating history_annotation_association table failed: %s" % str( e ) ) - + # Create history_dataset_association_annotation_association table. try: HistoryDatasetAssociationAnnotationAssociation_table.create() except Exception, e: print str(e) - log.debug( "Creating history_dataset_association_annotation_association table failed: %s" % str( e ) ) - + log.debug( "Creating history_dataset_association_annotation_association table failed: %s" % str( e ) ) + # Create stored_workflow_annotation_association table. try: StoredWorkflowAnnotationAssociation_table.create() except Exception, e: print str(e) log.debug( "Creating stored_workflow_annotation_association table failed: %s" % str( e ) ) - + # Create workflow_step_annotation_association table. try: WorkflowStepAnnotationAssociation_table.create() except Exception, e: print str(e) log.debug( "Creating workflow_step_annotation_association table failed: %s" % str( e ) ) - + # Create workflow_step_tag_association table. try: WorkflowStepTagAssociation_table.create() except Exception, e: print str(e) log.debug( "Creating workflow_step_tag_association table failed: %s" % str( e ) ) - + def downgrade(migrate_engine): metadata.bind = migrate_engine metadata.reflect() - + # Drop history_annotation_association table. try: HistoryAnnotationAssociation_table.drop() @@ -105,7 +105,7 @@ def downgrade(migrate_engine): HistoryDatasetAssociationAnnotationAssociation_table.drop() except Exception, e: print str(e) - log.debug( "Dropping history_dataset_association_annotation_association table failed: %s" % str( e ) ) + log.debug( "Dropping history_dataset_association_annotation_association table failed: %s" % str( e ) ) # Drop stored_workflow_annotation_association table. try: @@ -127,4 +127,4 @@ def downgrade(migrate_engine): except Exception, e: print str(e) log.debug( "Dropping workflow_step_tag_association table failed: %s" % str( e ) ) - + diff --git a/lib/galaxy/model/migrate/versions/0036_add_deleted_column_to_library_template_assoc_tables.py b/lib/galaxy/model/migrate/versions/0036_add_deleted_column_to_library_template_assoc_tables.py index 17ff8d4b23f..a4aafd55ba0 100644 --- a/lib/galaxy/model/migrate/versions/0036_add_deleted_column_to_library_template_assoc_tables.py +++ b/lib/galaxy/model/migrate/versions/0036_add_deleted_column_to_library_template_assoc_tables.py @@ -22,7 +22,7 @@ def upgrade(migrate_engine): metadata.bind = migrate_engine print __doc__ metadata.reflect() - + LibraryInfoAssociation_table = Table( "library_info_association", metadata, autoload=True ) c = Column( "deleted", Boolean, index=True, default=False ) c.create( LibraryInfoAssociation_table, index_name='ix_library_info_association_deleted') @@ -32,7 +32,7 @@ def upgrade(migrate_engine): migrate_engine.execute( cmd ) except Exception, e: log.debug( "deleted to false in library_info_association failed: %s" % ( str( e ) ) ) - + LibraryFolderInfoAssociation_table = Table( "library_folder_info_association", metadata, autoload=True ) c = Column( "deleted", Boolean, index=True, default=False ) c.create( LibraryFolderInfoAssociation_table, index_name='ix_library_folder_info_association_deleted') diff --git a/lib/galaxy/model/migrate/versions/0037_samples_library.py b/lib/galaxy/model/migrate/versions/0037_samples_library.py index a100aa9197a..329837b197a 100644 --- a/lib/galaxy/model/migrate/versions/0037_samples_library.py +++ b/lib/galaxy/model/migrate/versions/0037_samples_library.py @@ -1,8 +1,8 @@ """ -This migration script removes the library_id & folder_id fields in the 'request' table and -adds the same to the 'sample' table. This also adds a 'datatx' column to request_type table +This migration script removes the library_id & folder_id fields in the 'request' table and +adds the same to the 'sample' table. This also adds a 'datatx' column to request_type table to store the sequencer login information. Finally, this adds a 'dataset_files' column to -the sample table. +the sample table. """ from sqlalchemy import * from sqlalchemy.orm import * @@ -42,7 +42,7 @@ def upgrade(migrate_engine): col.create( RequestType_table ) assert col is RequestType_table.c.datatx_info except Exception, e: - log.debug( "Adding column 'datatx_info' to request_type table failed: %s" % ( str( e ) ) ) + log.debug( "Adding column 'datatx_info' to request_type table failed: %s" % ( str( e ) ) ) # request table try: Request_table = Table( "request", metadata, autoload=True ) @@ -68,7 +68,7 @@ def upgrade(migrate_engine): try: RequestTemp_table.create() except Exception, e: - log.debug( "Creating request_temp table failed: %s" % str( e ) ) + log.debug( "Creating request_temp table failed: %s" % str( e ) ) # insert all the rows from the request table to the request_temp table cmd = \ "INSERT INTO request_temp " + \ @@ -81,7 +81,7 @@ def upgrade(migrate_engine): "request_type_id," + \ "user_id," + \ "deleted " + \ - "FROM request;" + "FROM request;" migrate_engine.execute( cmd ) # delete the 'request' table try: @@ -89,19 +89,19 @@ def upgrade(migrate_engine): except Exception, e: log.debug( "Dropping request table failed: %s" % str( e ) ) # rename table request_temp to request - cmd = "ALTER TABLE request_temp RENAME TO request" + cmd = "ALTER TABLE request_temp RENAME TO request" migrate_engine.execute( cmd ) else: # Delete the library_id column in 'request' table try: Request_table.c.library_id.drop() except Exception, e: - log.debug( "Deleting column 'library_id' to request table failed: %s" % ( str( e ) ) ) + log.debug( "Deleting column 'library_id' to request table failed: %s" % ( str( e ) ) ) # Delete the folder_id column in 'request' table try: Request_table.c.folder_id.drop() except Exception, e: - log.debug( "Deleting column 'folder_id' to request table failed: %s" % ( str( e ) ) ) + log.debug( "Deleting column 'folder_id' to request table failed: %s" % ( str( e ) ) ) # sample table try: Sample_table = Table( "sample", metadata, autoload=True ) diff --git a/lib/galaxy/model/migrate/versions/0038_add_inheritable_column_to_library_template_assoc_tables.py b/lib/galaxy/model/migrate/versions/0038_add_inheritable_column_to_library_template_assoc_tables.py index 4d93f8459d7..0c189ed970e 100644 --- a/lib/galaxy/model/migrate/versions/0038_add_inheritable_column_to_library_template_assoc_tables.py +++ b/lib/galaxy/model/migrate/versions/0038_add_inheritable_column_to_library_template_assoc_tables.py @@ -1,7 +1,7 @@ """ Migration script to add an inheritable column to the following tables: library_info_association, library_folder_info_association. -Also, in case of sqlite check if the previous migration script deleted the +Also, in case of sqlite check if the previous migration script deleted the request table and if so, restore the table. """ @@ -25,14 +25,14 @@ def get_false_value(migrate_engine): def upgrade(migrate_engine): metadata.bind = migrate_engine print __doc__ - + # - # In case of sqlite, check if the previous migration script deleted the + # In case of sqlite, check if the previous migration script deleted the # request table and if so, restore the table. # if migrate_engine.name == 'sqlite': if not migrate_engine.has_table('request'): - # load the tables referenced in foreign keys + # load the tables referenced in foreign keys metadata.reflect(only=['form_values', 'request_type', 'galaxy_user']) # create a temporary table Request_table = Table( 'request', metadata, @@ -48,10 +48,10 @@ def upgrade(migrate_engine): try: Request_table.create() except Exception, e: - log.debug( "Creating request table failed: %s" % str( e ) ) + log.debug( "Creating request table failed: %s" % str( e ) ) metadata.reflect() - + LibraryInfoAssociation_table = Table( "library_info_association", metadata, autoload=True ) c = Column( "inheritable", Boolean, index=True, default=False ) c.create( LibraryInfoAssociation_table, index_name='ix_library_info_association_inheritable') @@ -61,7 +61,7 @@ def upgrade(migrate_engine): migrate_engine.execute( cmd ) except Exception, e: log.debug( "Setting value of column inheritable to false in library_info_association failed: %s" % ( str( e ) ) ) - + LibraryFolderInfoAssociation_table = Table( "library_folder_info_association", metadata, autoload=True ) c = Column( "inheritable", Boolean, index=True, default=False ) c.create( LibraryFolderInfoAssociation_table, index_name='ix_library_folder_info_association_inheritable') diff --git a/lib/galaxy/model/migrate/versions/0039_add_synopsis_column_to_library_table.py b/lib/galaxy/model/migrate/versions/0039_add_synopsis_column_to_library_table.py index 9df8f6cef54..538fbc3e2c8 100644 --- a/lib/galaxy/model/migrate/versions/0039_add_synopsis_column_to_library_table.py +++ b/lib/galaxy/model/migrate/versions/0039_add_synopsis_column_to_library_table.py @@ -15,7 +15,7 @@ def upgrade(migrate_engine): metadata.bind = migrate_engine print __doc__ metadata.reflect() - + Library_table = Table( "library", metadata, autoload=True ) c = Column( "synopsis", TEXT ) c.create( Library_table ) diff --git a/lib/galaxy/model/migrate/versions/0040_page_annotations.py b/lib/galaxy/model/migrate/versions/0040_page_annotations.py index db717def49a..47b0396ab78 100644 --- a/lib/galaxy/model/migrate/versions/0040_page_annotations.py +++ b/lib/galaxy/model/migrate/versions/0040_page_annotations.py @@ -17,7 +17,7 @@ PageAnnotationAssociation_table = Table( "page_annotation_association", metadata Column( "page_id", Integer, ForeignKey( "page.id" ), index=True ), Column( "user_id", Integer, ForeignKey( "galaxy_user.id" ), index=True ), Column( "annotation", TEXT, index=True) ) - + def upgrade(migrate_engine): metadata.bind = migrate_engine print __doc__ @@ -29,15 +29,15 @@ def upgrade(migrate_engine): except Exception, e: print str(e) log.debug( "Creating page_annotation_association table failed: %s" % str( e ) ) - + def downgrade(migrate_engine): metadata.bind = migrate_engine metadata.reflect() - + # Drop page_annotation_association table. try: PageAnnotationAssociation_table.drop() except Exception, e: print str(e) log.debug( "Dropping page_annotation_association table failed: %s" % str( e ) ) - + diff --git a/lib/galaxy/model/migrate/versions/0041_workflow_invocation.py b/lib/galaxy/model/migrate/versions/0041_workflow_invocation.py index 228ad54ed97..856d93c3044 100644 --- a/lib/galaxy/model/migrate/versions/0041_workflow_invocation.py +++ b/lib/galaxy/model/migrate/versions/0041_workflow_invocation.py @@ -33,7 +33,7 @@ WorkflowInvocationStep_table = Table( "workflow_invocation_step", metadata, ) tables = [ WorkflowInvocation_table, WorkflowInvocationStep_table ] - + def upgrade(migrate_engine): metadata.bind = migrate_engine print __doc__ @@ -44,7 +44,7 @@ def upgrade(migrate_engine): table.create() except: log.warn( "Failed to create table '%s', ignoring (might result in wrong schema)" % table.name ) - + def downgrade(migrate_engine): metadata.bind = migrate_engine metadata.reflect() diff --git a/lib/galaxy/model/migrate/versions/0044_add_notify_column_to_request_table.py b/lib/galaxy/model/migrate/versions/0044_add_notify_column_to_request_table.py index 23a5e3850bc..26e8a086874 100644 --- a/lib/galaxy/model/migrate/versions/0044_add_notify_column_to_request_table.py +++ b/lib/galaxy/model/migrate/versions/0044_add_notify_column_to_request_table.py @@ -15,7 +15,7 @@ def upgrade(migrate_engine): metadata.bind = migrate_engine print __doc__ metadata.reflect() - + Request_table = Table( "request", metadata, autoload=True ) c = Column( "notify", Boolean, default=False ) c.create( Request_table ) diff --git a/lib/galaxy/model/migrate/versions/0050_drop_cloud_tables.py b/lib/galaxy/model/migrate/versions/0050_drop_cloud_tables.py index dd357af5395..467521356f8 100644 --- a/lib/galaxy/model/migrate/versions/0050_drop_cloud_tables.py +++ b/lib/galaxy/model/migrate/versions/0050_drop_cloud_tables.py @@ -17,8 +17,8 @@ def display_migration_details(): print "========================================" print "This script drops tables that were associated with the old Galaxy Cloud functionality." print "========================================" - -CloudImage_table = Table( "cloud_image", metadata, + +CloudImage_table = Table( "cloud_image", metadata, Column( "id", Integer, primary_key=True ), Column( "create_time", DateTime, default=now ), Column( "update_time", DateTime, default=now, onupdate=now ), @@ -30,7 +30,7 @@ CloudImage_table = Table( "cloud_image", metadata, Column( "deleted", Boolean, default=False ) ) """ UserConfiguredInstance (UCI) table """ -UCI_table = Table( "cloud_uci", metadata, +UCI_table = Table( "cloud_uci", metadata, Column( "id", Integer, primary_key=True ), Column( "create_time", DateTime, default=now ), Column( "update_time", DateTime, default=now, onupdate=now ), @@ -45,7 +45,7 @@ UCI_table = Table( "cloud_uci", metadata, Column( "launch_time", DateTime ), Column( "deleted", Boolean, default=False ) ) -CloudInstance_table = Table( "cloud_instance", metadata, +CloudInstance_table = Table( "cloud_instance", metadata, Column( "id", Integer, primary_key=True ), Column( "create_time", DateTime, default=now ), Column( "update_time", DateTime, default=now, onupdate=now ), @@ -64,7 +64,7 @@ CloudInstance_table = Table( "cloud_instance", metadata, Column( "security_group", TEXT ), Column( "availability_zone", TEXT ) ) -CloudStore_table = Table( "cloud_store", metadata, +CloudStore_table = Table( "cloud_store", metadata, Column( "id", Integer, primary_key=True ), Column( "create_time", DateTime, default=now ), Column( "update_time", DateTime, default=now, onupdate=now ), @@ -81,7 +81,7 @@ CloudStore_table = Table( "cloud_store", metadata, Column( "error", TEXT ), Column( "deleted", Boolean, default=False ) ) -CloudSnapshot_table = Table( "cloud_snapshot", metadata, +CloudSnapshot_table = Table( "cloud_snapshot", metadata, Column( "id", Integer, primary_key=True ), Column( "create_time", DateTime, default=now ), Column( "update_time", DateTime, default=now, onupdate=now ), @@ -94,7 +94,7 @@ CloudSnapshot_table = Table( "cloud_snapshot", metadata, Column( "error", TEXT ), Column( "deleted", Boolean, default=False ) ) -CloudUserCredentials_table = Table( "cloud_user_credentials", metadata, +CloudUserCredentials_table = Table( "cloud_user_credentials", metadata, Column( "id", Integer, primary_key=True ), Column( "create_time", DateTime, default=now ), Column( "update_time", DateTime, default=now, onupdate=now ), @@ -105,7 +105,7 @@ CloudUserCredentials_table = Table( "cloud_user_credentials", metadata, Column( "secret_key", TEXT ), Column( "deleted", Boolean, default=False ) ) -CloudProvider_table = Table( "cloud_provider", metadata, +CloudProvider_table = Table( "cloud_provider", metadata, Column( "id", Integer, primary_key=True ), Column( "create_time", DateTime, default=now ), Column( "update_time", DateTime, default=now, onupdate=now ), @@ -142,8 +142,8 @@ def upgrade(migrate_engine): CloudProvider_table.drop() except Exception, e: log.debug( "Dropping cloud tables failed: %s" % str( e ) ) - - + + def downgrade(migrate_engine): metadata.bind = migrate_engine pass diff --git a/lib/galaxy/model/migrate/versions/0052_sample_dataset_table.py b/lib/galaxy/model/migrate/versions/0052_sample_dataset_table.py index 5dffc8aec58..38d5dc8a469 100644 --- a/lib/galaxy/model/migrate/versions/0052_sample_dataset_table.py +++ b/lib/galaxy/model/migrate/versions/0052_sample_dataset_table.py @@ -1,5 +1,5 @@ """ -Migration script to add the sample_dataset table and remove the 'dataset_files' column +Migration script to add the sample_dataset table and remove the 'dataset_files' column from the 'sample' table """ @@ -28,7 +28,7 @@ def nextval( table, col='id' ): return "null" else: raise Exception( 'Unable to convert data for unknown database type: %s' % migrate_engine.name ) - + def localtimestamp(): if migrate_engine.name == 'postgres' or migrate_engine.name == 'mysql': return "LOCALTIMESTAMP" @@ -41,7 +41,7 @@ SampleDataset_table = Table('sample_dataset', metadata, Column( "id", Integer, primary_key=True ), Column( "create_time", DateTime, default=now ), Column( "update_time", DateTime, default=now, onupdate=now ), - Column( "sample_id", Integer, ForeignKey( "sample.id" ), index=True ), + Column( "sample_id", Integer, ForeignKey( "sample.id" ), index=True ), Column( "name", TrimmedString( 255 ), nullable=False ), Column( "file_path", TrimmedString( 255 ), nullable=False ), Column( "status", TrimmedString( 255 ), nullable=False ), @@ -56,7 +56,7 @@ def upgrade(migrate_engine): SampleDataset_table.create() except Exception, e: log.debug( "Creating sample_dataset table failed: %s" % str( e ) ) - + cmd = "SELECT id, dataset_files FROM sample" result = migrate_engine.execute( cmd ) for r in result: @@ -76,7 +76,7 @@ def upgrade(migrate_engine): "", df.get('size', '').replace('"', '').replace("'", "").replace(df.get('filepath', ''), '').strip() ) migrate_engine.execute( cmd ) - + # Delete the dataset_files column in the Sample table try: Sample_table = Table( "sample", metadata, autoload=True ) @@ -87,7 +87,7 @@ def upgrade(migrate_engine): try: Sample_table.c.dataset_files.drop() except Exception, e: - log.debug( "Deleting column 'dataset_files' from the 'sample' table failed: %s" % ( str( e ) ) ) + log.debug( "Deleting column 'dataset_files' from the 'sample' table failed: %s" % ( str( e ) ) ) def downgrade(migrate_engine): diff --git a/lib/galaxy/model/migrate/versions/0053_item_ratings.py b/lib/galaxy/model/migrate/versions/0053_item_ratings.py index 0352c1b69c6..5152530831c 100644 --- a/lib/galaxy/model/migrate/versions/0053_item_ratings.py +++ b/lib/galaxy/model/migrate/versions/0053_item_ratings.py @@ -19,31 +19,31 @@ HistoryRatingAssociation_table = Table( "history_rating_association", metadata, Column( "history_id", Integer, ForeignKey( "history.id" ), index=True ), Column( "user_id", Integer, ForeignKey( "galaxy_user.id" ), index=True ), Column( "rating", Integer, index=True) ) - + HistoryDatasetAssociationRatingAssociation_table = Table( "history_dataset_association_rating_association", metadata, Column( "id", Integer, primary_key=True ), Column( "history_dataset_association_id", Integer, ForeignKey( "history_dataset_association.id" ), index=True ), Column( "user_id", Integer, ForeignKey( "galaxy_user.id" ), index=True ), Column( "rating", Integer, index=True) ) - + StoredWorkflowRatingAssociation_table = Table( "stored_workflow_rating_association", metadata, Column( "id", Integer, primary_key=True ), Column( "stored_workflow_id", Integer, ForeignKey( "stored_workflow.id" ), index=True ), Column( "user_id", Integer, ForeignKey( "galaxy_user.id" ), index=True ), Column( "rating", Integer, index=True) ) - + PageRatingAssociation_table = Table( "page_rating_association", metadata, Column( "id", Integer, primary_key=True ), Column( "page_id", Integer, ForeignKey( "page.id" ), index=True ), Column( "user_id", Integer, ForeignKey( "galaxy_user.id" ), index=True ), Column( "rating", Integer, index=True) ) - + VisualizationRatingAssociation_table = Table( "visualization_rating_association", metadata, Column( "id", Integer, primary_key=True ), Column( "visualization_id", Integer, ForeignKey( "visualization.id" ), index=True ), Column( "user_id", Integer, ForeignKey( "galaxy_user.id" ), index=True ), Column( "rating", Integer, index=True) ) - + def upgrade(migrate_engine): metadata.bind = migrate_engine print __doc__ @@ -55,7 +55,7 @@ def upgrade(migrate_engine): except Exception, e: print str(e) log.debug( "Creating history_rating_association table failed: %s" % str( e ) ) - + # Create history_dataset_association_rating_association table. try: HistoryDatasetAssociationRatingAssociation_table.create() @@ -71,33 +71,33 @@ def upgrade(migrate_engine): log.debug( "Adding index 'ix_hda_rating_association_hda_id' to table 'history_dataset_association_rating_association' table failed: %s" % str( e ) ) else: print str(e) - log.debug( "Creating history_dataset_association_rating_association table failed: %s" % str( e ) ) - + log.debug( "Creating history_dataset_association_rating_association table failed: %s" % str( e ) ) + # Create stored_workflow_rating_association table. try: StoredWorkflowRatingAssociation_table.create() except Exception, e: print str(e) log.debug( "Creating stored_workflow_rating_association table failed: %s" % str( e ) ) - + # Create page_rating_association table. try: PageRatingAssociation_table.create() except Exception, e: print str(e) log.debug( "Creating page_rating_association table failed: %s" % str( e ) ) - + # Create visualization_rating_association table. try: VisualizationRatingAssociation_table.create() except Exception, e: print str(e) log.debug( "Creating visualization_rating_association table failed: %s" % str( e ) ) - + def downgrade(migrate_engine): metadata.bind = migrate_engine metadata.reflect() - + # Drop history_rating_association table. try: HistoryRatingAssociation_table.drop() @@ -110,7 +110,7 @@ def downgrade(migrate_engine): HistoryDatasetAssociationRatingAssociation_table.drop() except Exception, e: print str(e) - log.debug( "Dropping history_dataset_association_rating_association table failed: %s" % str( e ) ) + log.debug( "Dropping history_dataset_association_rating_association table failed: %s" % str( e ) ) # Drop stored_workflow_rating_association table. try: @@ -124,8 +124,8 @@ def downgrade(migrate_engine): PageRatingAssociation_table.drop() except Exception, e: print str(e) - log.debug( "Dropping page_rating_association table failed: %s" % str( e ) ) - + log.debug( "Dropping page_rating_association table failed: %s" % str( e ) ) + # Drop visualization_rating_association table. try: VisualizationRatingAssociation_table.drop() diff --git a/lib/galaxy/model/migrate/versions/0054_visualization_dbkey.py b/lib/galaxy/model/migrate/versions/0054_visualization_dbkey.py index 5cedc15bbd7..be6a6a1f72b 100644 --- a/lib/galaxy/model/migrate/versions/0054_visualization_dbkey.py +++ b/lib/galaxy/model/migrate/versions/0054_visualization_dbkey.py @@ -15,10 +15,10 @@ metadata = MetaData() def upgrade(migrate_engine): metadata.bind = migrate_engine - + print __doc__ metadata.reflect() - + Visualization_table = Table( "visualization", metadata, autoload=True ) Visualization_revision_table = Table( "visualization_revision", metadata, autoload=True ) @@ -43,7 +43,7 @@ def upgrade(migrate_engine): def downgrade(migrate_engine): metadata.bind = migrate_engine metadata.reflect() - + Visualization_table = Table( "visualization", metadata, autoload=True ) Visualization_revision_table = Table( "visualization_revision", metadata, autoload=True ) diff --git a/lib/galaxy/model/migrate/versions/0057_request_notify.py b/lib/galaxy/model/migrate/versions/0057_request_notify.py index 9edf9dee593..bb38ae22ae6 100644 --- a/lib/galaxy/model/migrate/versions/0057_request_notify.py +++ b/lib/galaxy/model/migrate/versions/0057_request_notify.py @@ -29,7 +29,7 @@ def upgrade(migrate_engine): except NoSuchTableError, e: Request_table = None log.debug( "Failed loading table 'request'" ) - + if Request_table is not None: # create the column again as JSONType @@ -38,7 +38,7 @@ def upgrade(migrate_engine): col.create( Request_table ) assert col is Request_table.c.notification except Exception, e: - log.debug( "Creating column 'notification' in the 'request' table failed: %s" % ( str( e ) ) ) + log.debug( "Creating column 'notification' in the 'request' table failed: %s" % ( str( e ) ) ) cmd = "SELECT id, user_id, notify FROM request" result = migrate_engine.execute( cmd ) @@ -48,7 +48,7 @@ def upgrade(migrate_engine): notify_new = dict(email=[], sample_states=[], body='', subject='') cmd = "update request set notification='%s' where id=%i" % (to_json_string(notify_new), id) migrate_engine.execute( cmd ) - + cmd = "SELECT id, notification FROM request" result = migrate_engine.execute( cmd ) for r in result: @@ -59,9 +59,9 @@ def upgrade(migrate_engine): try: Request_table.c.notify.drop() except Exception, e: - log.debug( "Deleting column 'notify' from the 'request' table failed: %s" % ( str( e ) ) ) - - + log.debug( "Deleting column 'notify' from the 'request' table failed: %s" % ( str( e ) ) ) + + def downgrade(migrate_engine): metadata.bind = migrate_engine diff --git a/lib/galaxy/model/migrate/versions/0058_history_import_export.py b/lib/galaxy/model/migrate/versions/0058_history_import_export.py index 842a0895642..9a1b693fba6 100644 --- a/lib/galaxy/model/migrate/versions/0058_history_import_export.py +++ b/lib/galaxy/model/migrate/versions/0058_history_import_export.py @@ -24,7 +24,7 @@ JobExportHistoryArchive_table = Table( "job_export_history_archive", metadata, Column( "datasets_attrs_filename", TEXT ), Column( "jobs_attrs_filename", TEXT ) ) - + def upgrade(migrate_engine): print __doc__ metadata.bind = migrate_engine @@ -36,11 +36,11 @@ def upgrade(migrate_engine): except Exception, e: print str(e) log.debug( "Creating job_export_history_archive table failed: %s" % str( e ) ) - + def downgrade(migrate_engine): metadata.bind = migrate_engine metadata.reflect() - + # Drop job_export_history_archive table. try: JobExportHistoryArchive_table.drop() diff --git a/lib/galaxy/model/migrate/versions/0059_sample_dataset_file_path.py b/lib/galaxy/model/migrate/versions/0059_sample_dataset_file_path.py index 5e46486a3de..3ffca0455eb 100644 --- a/lib/galaxy/model/migrate/versions/0059_sample_dataset_file_path.py +++ b/lib/galaxy/model/migrate/versions/0059_sample_dataset_file_path.py @@ -1,5 +1,5 @@ """ -Migration script to modify the 'file_path' field type in 'sample_dataset' table +Migration script to modify the 'file_path' field type in 'sample_dataset' table to 'TEXT' so that it can support large file paths exceeding 255 characters """ @@ -41,18 +41,18 @@ def upgrade(migrate_engine): try: SampleDataset_table.c.file_path.drop() except Exception, e: - log.debug( "Deleting column 'file_path' from the 'sample_dataset' table failed: %s" % ( str( e ) ) ) + log.debug( "Deleting column 'file_path' from the 'sample_dataset' table failed: %s" % ( str( e ) ) ) # create the column again try: col = Column( "file_path", TEXT ) col.create( SampleDataset_table ) assert col is SampleDataset_table.c.file_path except Exception, e: - log.debug( "Creating column 'file_path' in the 'sample_dataset' table failed: %s" % ( str( e ) ) ) + log.debug( "Creating column 'file_path' in the 'sample_dataset' table failed: %s" % ( str( e ) ) ) for id, file_path in filepath_dict.items(): cmd = "update sample_dataset set file_path='%s' where id=%i" % (file_path, id) - migrate_engine.execute( cmd ) + migrate_engine.execute( cmd ) def downgrade(migrate_engine): metadata.bind = migrate_engine diff --git a/lib/galaxy/model/migrate/versions/0060_history_archive_import.py b/lib/galaxy/model/migrate/versions/0060_history_archive_import.py index 4c850a3153c..46257f550f3 100644 --- a/lib/galaxy/model/migrate/versions/0060_history_archive_import.py +++ b/lib/galaxy/model/migrate/versions/0060_history_archive_import.py @@ -1,5 +1,5 @@ """ -Migration script to create column and table for importing histories from +Migration script to create column and table for importing histories from file archives. """ @@ -26,20 +26,20 @@ JobImportHistoryArchive_table = Table( "job_import_history_archive", metadata, Column( "history_id", Integer, ForeignKey( "history.id" ), index=True ), Column( "archive_dir", TEXT ) ) - + def upgrade(migrate_engine): metadata.bind = migrate_engine print __doc__ metadata.reflect() - + # Add column to history table and initialize. try: History_table = Table( "history", metadata, autoload=True ) importing_col.create( History_table, index_name="ix_history_importing") assert importing_col is History_table.c.importing - + # Initialize column to false. - if migrate_engine.name == 'mysql' or migrate_engine.name == 'sqlite': + if migrate_engine.name == 'mysql' or migrate_engine.name == 'sqlite': default_false = "0" elif migrate_engine.name in ['postgres', 'postgresql']: default_false = "false" @@ -47,17 +47,17 @@ def upgrade(migrate_engine): except Exception, e: print str(e) log.debug( "Adding column 'importing' to history table failed: %s" % str( e ) ) - + # Create job_import_history_archive table. try: JobImportHistoryArchive_table.create() except Exception, e: log.debug( "Creating job_import_history_archive table failed: %s" % str( e ) ) - + def downgrade(migrate_engine): metadata.bind = migrate_engine metadata.reflect() - + # Drop 'importing' column from history table. try: History_table = Table( "history", metadata, autoload=True ) @@ -65,7 +65,7 @@ def downgrade(migrate_engine): importing_col.drop() except Exception, e: log.debug( "Dropping column 'importing' from history table failed: %s" % ( str( e ) ) ) - + # Drop job_import_history_archive table. try: JobImportHistoryArchive_table.drop() diff --git a/lib/galaxy/model/migrate/versions/0061_tasks.py b/lib/galaxy/model/migrate/versions/0061_tasks.py index e7d5d398cd8..07c51234512 100644 --- a/lib/galaxy/model/migrate/versions/0061_tasks.py +++ b/lib/galaxy/model/migrate/versions/0061_tasks.py @@ -20,7 +20,7 @@ Task_table = Table( "task", metadata, Column( "execution_time", DateTime ), Column( "update_time", DateTime, default=now, onupdate=now ), Column( "state", String( 64 ), index=True ), - Column( "command_line", TEXT ), + Column( "command_line", TEXT ), Column( "param_filename", String( 1024 ) ), Column( "runner_name", String( 255 ) ), Column( "stdout", TEXT ), diff --git a/lib/galaxy/model/migrate/versions/0062_user_openid_table.py b/lib/galaxy/model/migrate/versions/0062_user_openid_table.py index aae4702e203..acc4551c3a3 100644 --- a/lib/galaxy/model/migrate/versions/0062_user_openid_table.py +++ b/lib/galaxy/model/migrate/versions/0062_user_openid_table.py @@ -31,7 +31,7 @@ def upgrade(migrate_engine): metadata.bind = migrate_engine print __doc__ metadata.reflect() - + # Create galaxy_user_openid table try: UserOpenID_table.create() @@ -48,11 +48,11 @@ def upgrade(migrate_engine): i.create() except Exception, e: log.debug( "Adding index '%s' failed: %s" % ( ix_name, str( e ) ) ) - + def downgrade(migrate_engine): metadata.bind = migrate_engine metadata.reflect() - + # Drop galaxy_user_openid table try: UserOpenID_table.drop() diff --git a/lib/galaxy/model/migrate/versions/0063_sequencer_table.py b/lib/galaxy/model/migrate/versions/0063_sequencer_table.py index 81875278bed..3270649160d 100644 --- a/lib/galaxy/model/migrate/versions/0063_sequencer_table.py +++ b/lib/galaxy/model/migrate/versions/0063_sequencer_table.py @@ -1,5 +1,5 @@ """ -Migration script to create a new 'sequencer' table +Migration script to create a new 'sequencer' table """ from sqlalchemy import * @@ -40,8 +40,8 @@ def upgrade(migrate_engine): Sequencer_table.create() except Exception, e: log.debug( "Creating 'sequencer' table failed: %s" % str( e ) ) - - + + def downgrade(migrate_engine): metadata.bind = migrate_engine metadata.reflect() @@ -55,5 +55,5 @@ def downgrade(migrate_engine): try: Sequencer_table.drop() except Exception, e: - log.debug( "Deleting 'sequencer' table failed: %s" % str( e ) ) + log.debug( "Deleting 'sequencer' table failed: %s" % str( e ) ) diff --git a/lib/galaxy/model/migrate/versions/0065_add_name_to_form_fields_and_values.py b/lib/galaxy/model/migrate/versions/0065_add_name_to_form_fields_and_values.py index d09f716e323..1da427152ca 100644 --- a/lib/galaxy/model/migrate/versions/0065_add_name_to_form_fields_and_values.py +++ b/lib/galaxy/model/migrate/versions/0065_add_name_to_form_fields_and_values.py @@ -1,6 +1,6 @@ """ -Migration script to add 'name' attribute to the JSON dict which describes -a form definition field and the form values in the database. In the 'form_values' +Migration script to add 'name' attribute to the JSON dict which describes +a form definition field and the form values in the database. In the 'form_values' table, the 'content' column is now a JSON dict instead of a list. """ @@ -87,7 +87,7 @@ def upgrade(migrate_engine): values_dict[ field_name ] = get_value(values_list, field_index ) cmd = "UPDATE form_values SET content='%s' WHERE id=%i" %( to_json_string( values_dict ), form_values_id ) migrate_engine.execute( cmd ) - + def downgrade(migrate_engine): metadata.bind = migrate_engine metadata.reflect() @@ -119,7 +119,7 @@ def downgrade(migrate_engine): for field_index, field in enumerate( fields_list ): field_name = field[ 'name' ] field_value = values_dict[ field_name ] - values_list.append( field_value ) + values_list.append( field_value ) cmd = "UPDATE form_values SET content='%s' WHERE id=%i" %( to_json_string( values_list ), form_values_id ) migrate_engine.execute( cmd ) # remove name attribute from the field column of the form_definition table diff --git a/lib/galaxy/model/migrate/versions/0066_deferred_job_and_transfer_job_tables.py b/lib/galaxy/model/migrate/versions/0066_deferred_job_and_transfer_job_tables.py index 8cb0a5590d7..51ee837711a 100644 --- a/lib/galaxy/model/migrate/versions/0066_deferred_job_and_transfer_job_tables.py +++ b/lib/galaxy/model/migrate/versions/0066_deferred_job_and_transfer_job_tables.py @@ -40,23 +40,23 @@ def upgrade(migrate_engine): metadata.bind = migrate_engine print __doc__ metadata.reflect() - + # Create deferred_job table try: DeferredJob_table.create() except Exception, e: log.error( "Creating deferred_job table failed: %s" % str( e ) ) - + # Create transfer_job table try: TransferJob_table.create() except Exception, e: log.error( "Creating transfer_job table failed: %s" % str( e ) ) - + def downgrade(migrate_engine): metadata.bind = migrate_engine metadata.reflect() - + # Drop deferred_job table try: DeferredJob_table.drop() diff --git a/lib/galaxy/model/migrate/versions/0067_populate_sequencer_table.py b/lib/galaxy/model/migrate/versions/0067_populate_sequencer_table.py index 962e3bca5c1..ff0d7513f12 100644 --- a/lib/galaxy/model/migrate/versions/0067_populate_sequencer_table.py +++ b/lib/galaxy/model/migrate/versions/0067_populate_sequencer_table.py @@ -2,7 +2,7 @@ Migration script to populate the 'sequencer' table and it is populated using unique entries in the 'datatx_info' column in the 'request_type' table. It also deletes the 'datatx_info' column in the 'request_type' table and adds a foreign key to the 'sequencer' table. The -actual contents of the datatx_info column are stored as form_values. +actual contents of the datatx_info column are stored as form_values. """ from sqlalchemy import * @@ -30,7 +30,7 @@ def nextval( table, col='id' ): return "null" else: raise Exception( 'Unable to convert data for unknown database type: %s' % migrate_engine.name ) - + def localtimestamp(): if migrate_engine.name == 'postgres' or migrate_engine.name == 'mysql': return "LOCALTIMESTAMP" @@ -46,7 +46,7 @@ def get_latest_id( table ): return row[0] else: raise Exception( 'Unable to get the latest id in the %s table.' % table ) - + def boolean( value ): if migrate_engine.name == 'postgres' or migrate_engine.name == 'mysql': return value @@ -59,9 +59,9 @@ def boolean( value ): def create_sequencer_form_definition(): ''' - Create a new form_definition containing 5 fields (host, username, password, + Create a new form_definition containing 5 fields (host, username, password, data_dir & rename_datasets) which described the existing datatx_info json - dict in the request_type table + dict in the request_type table ''' # create new form_definition_current in the db cmd = "INSERT INTO form_definition_current VALUES ( %s, %s, %s, %s, %s )" @@ -77,14 +77,14 @@ def create_sequencer_form_definition(): form_definition_name = 'Generic sequencer form' form_definition_desc = '' form_definition_fields = [] - fields = [ ( 'Host', 'TextField' ), + fields = [ ( 'Host', 'TextField' ), ( 'User name', 'TextField' ), ( 'Password', 'PasswordField' ), ( 'Data directory', 'TextField' ) ] for index, ( label, field_type ) in enumerate( fields ): form_definition_fields.append( { 'name': 'field_%i' % index, - 'label': label, - 'helptext': '', + 'label': label, + 'helptext': '', 'visible': True, 'required': False, 'type': field_type, @@ -92,8 +92,8 @@ def create_sequencer_form_definition(): 'layout': 'none', 'default': '' } ) form_definition_fields.append( { 'name': 'field_%i' % len( fields ), - 'label': 'Prepend the experiment name and sample name to the dataset name?', - 'helptext': 'Galaxy datasets are renamed by prepending the experiment name and sample name to the dataset name, ensuring dataset names remain unique in Galaxy even when multiple datasets have the same name on the sequencer.', + 'label': 'Prepend the experiment name and sample name to the dataset name?', + 'helptext': 'Galaxy datasets are renamed by prepending the experiment name and sample name to the dataset name, ensuring dataset names remain unique in Galaxy even when multiple datasets have the same name on the sequencer.', 'visible': True, 'required': False, 'type': 'SelectField', @@ -153,9 +153,9 @@ def add_sequencer( sequencer_index, sequencer_form_definition_id, sequencer_info 'field_2': sequencer_info.get( 'password', '' ), 'field_3': sequencer_info.get( 'data_dir', '' ), 'field_4': sequencer_info.get( 'rename_dataset', '' ) } ) - cmd = "INSERT INTO form_values VALUES ( %s, %s, %s, %s, '%s' )" % ( nextval( 'form_values' ), - localtimestamp(), - localtimestamp(), + cmd = "INSERT INTO form_values VALUES ( %s, %s, %s, %s, '%s' )" % ( nextval( 'form_values' ), + localtimestamp(), + localtimestamp(), sequencer_form_definition_id, values ) migrate_engine.execute(cmd) @@ -167,13 +167,13 @@ def add_sequencer( sequencer_index, sequencer_form_definition_id, sequencer_info result_datasets = dict() sequencer_type_id = 'simple_unknown_sequencer' cmd = "INSERT INTO sequencer VALUES ( %s, %s, %s, '%s', '%s', '%s', '%s', %s, %s, %s )" - cmd = cmd % ( nextval('sequencer'), - localtimestamp(), - localtimestamp(), - name, - desc, + cmd = cmd % ( nextval('sequencer'), + localtimestamp(), + localtimestamp(), + name, + desc, sequencer_type_id, - version, + version, sequencer_form_definition_id, sequencer_form_values_id, boolean( 'false' ) ) @@ -184,7 +184,7 @@ def update_sequencer_id_in_request_type( request_type_id, sequencer_id ): '''Update the foreign key to the sequencer table in the request_type table''' cmd = "UPDATE request_type SET sequencer_id=%i WHERE id=%i" %( sequencer_id, request_type_id ) migrate_engine.execute( cmd ) - + def upgrade(migrate_engine): metadata.bind = migrate_engine print __doc__ @@ -210,19 +210,19 @@ def upgrade(migrate_engine): col.create( RequestType_table ) assert col is RequestType_table.c.sequencer_id except Exception, e: - log.debug( "Creating column 'sequencer_id' in the 'request_type' table failed: %s" % ( str( e ) ) ) - # copy the sequencer information contained in the 'datatx_info' column + log.debug( "Creating column 'sequencer_id' in the 'request_type' table failed: %s" % ( str( e ) ) ) + # copy the sequencer information contained in the 'datatx_info' column # of the request_type table to the form values referenced in the sequencer table cmd = "SELECT id, name, datatx_info FROM request_type ORDER BY id ASC" result = migrate_engine.execute( cmd ) results_list = result.fetchall() # Proceed only if request_types exists if len( results_list ): - # In this migration script the all the contents of the datatx_info are stored as form_values - # with a pointer to the sequencer table. This way the sequencer information can be customized + # In this migration script the all the contents of the datatx_info are stored as form_values + # with a pointer to the sequencer table. This way the sequencer information can be customized # by the admin and is no longer restricted to host, username, password, data directory. - # For the existing request_types in the database, we add a new form_definition - # with these 4 fields. Then we populate the sequencer table with unique datatx_info + # For the existing request_types in the database, we add a new form_definition + # with these 4 fields. Then we populate the sequencer table with unique datatx_info # column from the existing request_types. sequencer_form_definition_id = create_sequencer_form_definition() sequencer_index = 1 @@ -244,14 +244,14 @@ def upgrade(migrate_engine): # now update the sequencer_id column in request_type table update_sequencer_id_in_request_type( request_type_id, sequencer_id ) sequencer_index = sequencer_index + 1 - + # Finally delete the 'datatx_info' column from the request_type table try: RequestType_table.c.datatx_info.drop() except Exception, e: - log.debug( "Deleting column 'datatx_info' in the 'request_type' table failed: %s" % ( str( e ) ) ) - - + log.debug( "Deleting column 'datatx_info' in the 'request_type' table failed: %s" % ( str( e ) ) ) + + def downgrade(migrate_engine): metadata.bind = migrate_engine metadata.reflect() @@ -267,8 +267,8 @@ def downgrade(migrate_engine): col.create( RequestType_table ) assert col is RequestType_table.c.datatx_info except Exception, e: - log.debug( "Creating column 'datatx_info' in the 'request_type' table failed: %s" % ( str( e ) ) ) - # restore the datatx_info column data in the request_type table with data from + log.debug( "Creating column 'datatx_info' in the 'request_type' table failed: %s" % ( str( e ) ) ) + # restore the datatx_info column data in the request_type table with data from # the sequencer and the form_values table cmd = "SELECT request_type.id, form_values.content "\ + " FROM request_type, sequencer, form_values "\ @@ -277,7 +277,7 @@ def downgrade(migrate_engine): result = migrate_engine.execute( cmd ) for row in result: request_type_id = row[0] - seq_values = from_json_string( str( row[1] ) ) + seq_values = from_json_string( str( row[1] ) ) # create the datatx_info json dict datatx_info = to_json_string( dict( host = seq_values.get( 'field_0', '' ), username = seq_values.get( 'field_1', '' ), @@ -291,22 +291,22 @@ def downgrade(migrate_engine): try: RequestType_table.c.sequencer_id.drop() except Exception, e: - log.debug( "Deleting column 'sequencer_id' in the 'request_type' table failed: %s" % ( str( e ) ) ) + log.debug( "Deleting column 'sequencer_id' in the 'request_type' table failed: %s" % ( str( e ) ) ) + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - diff --git a/lib/galaxy/model/migrate/versions/0069_rename_sequencer_form_type.py b/lib/galaxy/model/migrate/versions/0069_rename_sequencer_form_type.py index 7f2915f7d46..337b3cd1742 100644 --- a/lib/galaxy/model/migrate/versions/0069_rename_sequencer_form_type.py +++ b/lib/galaxy/model/migrate/versions/0069_rename_sequencer_form_type.py @@ -27,8 +27,8 @@ def upgrade(migrate_engine): cmd = "update form_definition set type='%s' where type='%s'" % ( new_form_type, current_form_type ) migrate_engine.execute( cmd ) - - + + def downgrade(migrate_engine): metadata.bind = migrate_engine metadata.reflect() diff --git a/lib/galaxy/model/migrate/versions/0074_add_purged_column_to_library_dataset_table.py b/lib/galaxy/model/migrate/versions/0074_add_purged_column_to_library_dataset_table.py index 17f5bbf1ebf..16bbb6f088f 100644 --- a/lib/galaxy/model/migrate/versions/0074_add_purged_column_to_library_dataset_table.py +++ b/lib/galaxy/model/migrate/versions/0074_add_purged_column_to_library_dataset_table.py @@ -51,7 +51,7 @@ def upgrade(migrate_engine): cmd = "SELECT * FROM library_dataset WHERE deleted = %s;" % boolean_true(migrate_engine) deleted_lds = migrate_engine.execute( cmd ).fetchall() for row in deleted_lds: - cmd = "SELECT * FROM library_dataset_dataset_association WHERE library_dataset_id = %d AND library_dataset_dataset_association.deleted = %s;" % ( int( row.id ), boolean_false(migrate_engine) ) + cmd = "SELECT * FROM library_dataset_dataset_association WHERE library_dataset_id = %d AND library_dataset_dataset_association.deleted = %s;" % ( int( row.id ), boolean_false(migrate_engine) ) active_lddas = migrate_engine.execute( cmd ).fetchall() if not active_lddas: print "Updating purged column to True for LibraryDataset id : ", int( row.id ) diff --git a/lib/galaxy/model/migrate/versions/0076_fix_form_values_data_corruption.py b/lib/galaxy/model/migrate/versions/0076_fix_form_values_data_corruption.py index 4134d0524ed..622781195eb 100644 --- a/lib/galaxy/model/migrate/versions/0076_fix_form_values_data_corruption.py +++ b/lib/galaxy/model/migrate/versions/0076_fix_form_values_data_corruption.py @@ -61,8 +61,8 @@ def upgrade(migrate_engine): field_name_key = '"%s": "' % field['name'] field_index = field_values_str.find( field_name_key ) if field_index == -1: - # if the field name is not present the field values dict then - # inform the admin that this form values cannot be fixed + # if the field name is not present the field values dict then + # inform the admin that this form values cannot be fixed print "The 'content' field of row 'id' %i does not have the field '%s' in the 'form_values' table and could not be fixed by this migration script." % ( int( field['id'] ), field['name'] ) else: # check if this is the last field diff --git a/lib/galaxy/model/migrate/versions/0077_create_tool_tag_association_table.py b/lib/galaxy/model/migrate/versions/0077_create_tool_tag_association_table.py index 38a36bbcad3..8b8fdbc0f83 100644 --- a/lib/galaxy/model/migrate/versions/0077_create_tool_tag_association_table.py +++ b/lib/galaxy/model/migrate/versions/0077_create_tool_tag_association_table.py @@ -32,17 +32,17 @@ def upgrade(migrate_engine): metadata.bind = migrate_engine print __doc__ metadata.reflect() - + # Create tool_tag_association table try: ToolTagAssociation_table.create() except Exception, e: log.error( "Creating tool_tag_association table failed: %s" % str( e ) ) - + def downgrade(migrate_engine): metadata.bind = migrate_engine metadata.reflect() - + # Drop tool_tag_association table try: ToolTagAssociation_table.drop() diff --git a/lib/galaxy/model/migrate/versions/0080_quota_tables.py b/lib/galaxy/model/migrate/versions/0080_quota_tables.py index c9097c053ee..dd83be5bd91 100644 --- a/lib/galaxy/model/migrate/versions/0080_quota_tables.py +++ b/lib/galaxy/model/migrate/versions/0080_quota_tables.py @@ -54,7 +54,7 @@ def upgrade(migrate_engine): metadata.bind = migrate_engine print __doc__ metadata.reflect() - + # Create quota table try: Quota_table.create() @@ -95,7 +95,7 @@ def upgrade(migrate_engine): def downgrade(migrate_engine): metadata.bind = migrate_engine metadata.reflect() - + # Drop default_quota_association table try: DefaultQuotaAssociation_table.drop() diff --git a/lib/galaxy/model/migrate/versions/0081_add_tool_version_to_hda_ldda.py b/lib/galaxy/model/migrate/versions/0081_add_tool_version_to_hda_ldda.py index 6efec998795..795cfa7a073 100644 --- a/lib/galaxy/model/migrate/versions/0081_add_tool_version_to_hda_ldda.py +++ b/lib/galaxy/model/migrate/versions/0081_add_tool_version_to_hda_ldda.py @@ -20,12 +20,12 @@ def upgrade(migrate_engine): c = Column( "tool_version", TEXT ) c.create( hda_table ) assert c is hda_table.c.tool_version - + ldda_table = Table( "library_dataset_dataset_association", metadata, autoload=True ) c = Column( "tool_version", TEXT ) c.create( ldda_table ) assert c is ldda_table.c.tool_version - + except Exception, e: print "Adding the tool_version column to the hda/ldda tables failed: ", str( e ) @@ -35,7 +35,7 @@ def downgrade(migrate_engine): try: hda_table = Table( "history_dataset_association", metadata, autoload=True ) hda_table.c.tool_version.drop() - + ldda_table = Table( "library_dataset_dataset_association", metadata, autoload=True ) ldda_table.c.tool_version.drop() except Exception, e: diff --git a/lib/galaxy/model/migrate/versions/0086_add_tool_shed_repository_table_columns.py b/lib/galaxy/model/migrate/versions/0086_add_tool_shed_repository_table_columns.py index 0a4a813e4af..828bacd910b 100644 --- a/lib/galaxy/model/migrate/versions/0086_add_tool_shed_repository_table_columns.py +++ b/lib/galaxy/model/migrate/versions/0086_add_tool_shed_repository_table_columns.py @@ -24,7 +24,7 @@ log.addHandler( handler ) metadata = MetaData() def get_default_false(migrate_engine): - if migrate_engine.name == 'mysql' or migrate_engine.name == 'sqlite': + if migrate_engine.name == 'mysql' or migrate_engine.name == 'sqlite': return "0" elif migrate_engine.name in ['postgresql', 'postgres']: return "false" diff --git a/lib/galaxy/model/migrate/versions/0087_tool_id_guid_map_table.py b/lib/galaxy/model/migrate/versions/0087_tool_id_guid_map_table.py index d14f0562e7a..caa3b4eb74c 100644 --- a/lib/galaxy/model/migrate/versions/0087_tool_id_guid_map_table.py +++ b/lib/galaxy/model/migrate/versions/0087_tool_id_guid_map_table.py @@ -42,7 +42,7 @@ def upgrade(migrate_engine): ToolIdGuidMap_table.create() except Exception, e: log.debug( "Creating tool_id_guid_map table failed: %s" % str( e ) ) - + def downgrade(migrate_engine): metadata.bind = migrate_engine metadata.reflect() diff --git a/lib/galaxy/model/migrate/versions/0091_add_tool_version_tables.py b/lib/galaxy/model/migrate/versions/0091_add_tool_version_tables.py index 5dced066ada..434955a32d2 100644 --- a/lib/galaxy/model/migrate/versions/0091_add_tool_version_tables.py +++ b/lib/galaxy/model/migrate/versions/0091_add_tool_version_tables.py @@ -33,7 +33,7 @@ def nextval( table, col='id' ): return "null" else: raise Exception( 'Unable to convert data for unknown database type: %s' % migrate_engine.name ) - + def localtimestamp(): if migrate_engine.name == 'postgres' or migrate_engine.name == 'mysql': return "LOCALTIMESTAMP" @@ -57,9 +57,9 @@ ToolVersionAssociation_table = Table( "tool_version_association", metadata, def upgrade(migrate_engine): metadata.bind = migrate_engine print __doc__ - + ToolIdGuidMap_table = Table( "tool_id_guid_map", metadata, autoload=True ) - + metadata.reflect() # Create the tables. try: @@ -92,7 +92,7 @@ def upgrade(migrate_engine): ToolIdGuidMap_table.drop() except Exception, e: log.debug( "Dropping tool_id_guid_map table failed: %s" % str( e ) ) - + def downgrade(migrate_engine): metadata.bind = migrate_engine diff --git a/lib/galaxy/model/migrate/versions/0092_add_migrate_tools_table.py b/lib/galaxy/model/migrate/versions/0092_add_migrate_tools_table.py index f107b620220..f323e2823fa 100644 --- a/lib/galaxy/model/migrate/versions/0092_add_migrate_tools_table.py +++ b/lib/galaxy/model/migrate/versions/0092_add_migrate_tools_table.py @@ -31,7 +31,7 @@ MigrateTools_table = Table( "migrate_tools", metadata, def upgrade(migrate_engine): metadata.bind = migrate_engine print __doc__ - + metadata.reflect() # Create the table. try: @@ -40,7 +40,7 @@ def upgrade(migrate_engine): migrate_engine.execute( cmd ) except Exception, e: log.debug( "Creating migrate_tools table failed: %s" % str( e ) ) - + def downgrade(migrate_engine): metadata.bind = migrate_engine metadata.reflect() diff --git a/lib/galaxy/model/migrate/versions/0093_add_job_params_col.py b/lib/galaxy/model/migrate/versions/0093_add_job_params_col.py index 58618d61f9f..20912f44323 100644 --- a/lib/galaxy/model/migrate/versions/0093_add_job_params_col.py +++ b/lib/galaxy/model/migrate/versions/0093_add_job_params_col.py @@ -26,21 +26,21 @@ def upgrade(migrate_engine): metadata.bind = migrate_engine print __doc__ metadata.reflect() - + # Add column to Job table. try: Job_table = Table( "job", metadata, autoload=True ) params_col.create( Job_table, index_name="ix_job_params") assert params_col is Job_table.c.params - + except Exception, e: print str(e) log.debug( "Adding column 'params' to job table failed: %s" % str( e ) ) - + def downgrade(migrate_engine): metadata.bind = migrate_engine metadata.reflect() - + # Drop column from Job table. try: Job_table = Table( "job", metadata, autoload=True ) diff --git a/lib/galaxy/model/migrate/versions/0094_add_job_handler_col.py b/lib/galaxy/model/migrate/versions/0094_add_job_handler_col.py index 15f3dee9013..f8ad97dadd6 100644 --- a/lib/galaxy/model/migrate/versions/0094_add_job_handler_col.py +++ b/lib/galaxy/model/migrate/versions/0094_add_job_handler_col.py @@ -26,21 +26,21 @@ def upgrade(migrate_engine): metadata.bind = migrate_engine print __doc__ metadata.reflect() - + # Add column to Job table. try: Job_table = Table( "job", metadata, autoload=True ) handler_col.create( Job_table, index_name="ix_job_handler" ) assert handler_col is Job_table.c.handler - + except Exception, e: print str(e) log.debug( "Adding column 'handler' to job table failed: %s" % str( e ) ) - + def downgrade(migrate_engine): metadata.bind = migrate_engine metadata.reflect() - + # Drop column from Job table. try: Job_table = Table( "job", metadata, autoload=True ) diff --git a/lib/galaxy/model/migrate/versions/0095_hda_subsets.py b/lib/galaxy/model/migrate/versions/0095_hda_subsets.py index 0dedcf2895a..21c7dd3b036 100644 --- a/lib/galaxy/model/migrate/versions/0095_hda_subsets.py +++ b/lib/galaxy/model/migrate/versions/0095_hda_subsets.py @@ -20,7 +20,7 @@ HistoryDatasetAssociationSubset_table = Table( "history_dataset_association_subs Column( "history_dataset_association_subset_id", Integer, ForeignKey( "history_dataset_association.id" ) ), Column( "location", Unicode(255), index=True) ) - + def upgrade(migrate_engine): metadata.bind = migrate_engine print __doc__ @@ -32,7 +32,7 @@ def upgrade(migrate_engine): except Exception, e: print str(e) log.debug( "Creating history_dataset_association_subset table failed: %s" % str( e ) ) - + # Manually create indexes because they are too long for MySQL databases. i1 = Index( "ix_hda_id", HistoryDatasetAssociationSubset_table.c.history_dataset_association_id ) i2 = Index( "ix_hda_subset_id", HistoryDatasetAssociationSubset_table.c.history_dataset_association_subset_id ) @@ -42,11 +42,11 @@ def upgrade(migrate_engine): except Exception, e: print str(e) log.debug( "Adding indices to table 'history_dataset_association_subset' table failed: %s" % str( e ) ) - + def downgrade(migrate_engine): metadata.bind = migrate_engine metadata.reflect() - + # Drop history_dataset_association_subset table. try: HistoryDatasetAssociationSubset_table.drop() diff --git a/lib/galaxy/model/migrate/versions/0096_openid_provider.py b/lib/galaxy/model/migrate/versions/0096_openid_provider.py index 727a17d2576..15c3d993a76 100644 --- a/lib/galaxy/model/migrate/versions/0096_openid_provider.py +++ b/lib/galaxy/model/migrate/versions/0096_openid_provider.py @@ -28,7 +28,7 @@ def upgrade(migrate_engine): except Exception, e: print "Adding provider column to galaxy_user_openid table failed: %s" % str( e ) log.debug( "Adding provider column to galaxy_user_openid table failed: %s" % str( e ) ) - + try: cmd = "DELETE FROM galaxy_user_openid WHERE openid='%s'" % ( BAD_IDENTIFIER ) migrate_engine.execute( cmd ) diff --git a/lib/galaxy/model/migrate/versions/0098_genome_index_tool_data_table.py b/lib/galaxy/model/migrate/versions/0098_genome_index_tool_data_table.py index c9c492e5b98..88096c51beb 100644 --- a/lib/galaxy/model/migrate/versions/0098_genome_index_tool_data_table.py +++ b/lib/galaxy/model/migrate/versions/0098_genome_index_tool_data_table.py @@ -35,11 +35,11 @@ GenomeIndexToolData_table = Table( "genome_index_tool_data", metadata, Column( "indexer", String( 64 ) ), Column( "user_id", Integer, ForeignKey( "galaxy_user.id" ), index=True ), ) - + def upgrade(migrate_engine): metadata.bind = migrate_engine print __doc__ - + metadata.reflect() try: GenomeIndexToolData_table.create() diff --git a/lib/galaxy/model/migrate/versions/0108_add_extended_metadata.py b/lib/galaxy/model/migrate/versions/0108_add_extended_metadata.py index 5bf071e2ef8..91a8bb84758 100644 --- a/lib/galaxy/model/migrate/versions/0108_add_extended_metadata.py +++ b/lib/galaxy/model/migrate/versions/0108_add_extended_metadata.py @@ -1,5 +1,5 @@ """ -Add the ExtendedMetadata and ExtendedMetadataIndex tables +Add the ExtendedMetadata and ExtendedMetadataIndex tables """ from sqlalchemy import * @@ -62,12 +62,12 @@ def downgrade(migrate_engine): except Exception, e: log.debug( "Dropping 'extended_metadata_index' table failed: %s" % ( str( e ) ) ) - try: + try: ExtendedMetadata_table.drop() except Exception, e: log.debug( "Dropping 'extended_metadata' table failed: %s" % ( str( e ) ) ) - - # Drop the LDDA table's extended metadata ID column. + + # Drop the LDDA table's extended metadata ID column. try: ldda_table = Table( "library_dataset_dataset_association", metadata, autoload=True ) extended_metadata_id = ldda_table.c.extended_metadata_id diff --git a/lib/galaxy/model/migrate/versions/0110_add_dataset_uuid.py b/lib/galaxy/model/migrate/versions/0110_add_dataset_uuid.py index fab8738c677..366419e1b55 100644 --- a/lib/galaxy/model/migrate/versions/0110_add_dataset_uuid.py +++ b/lib/galaxy/model/migrate/versions/0110_add_dataset_uuid.py @@ -23,7 +23,7 @@ def upgrade(migrate_engine): metadata = MetaData() metadata.bind = migrate_engine metadata.reflect() - + # Add the uuid colum to the dataset table try: dataset_table = Table( "dataset", metadata, autoload=True ) @@ -32,15 +32,15 @@ def upgrade(migrate_engine): except Exception, e: print str(e) log.error( "Adding column 'uuid' to dataset table failed: %s" % str( e ) ) - return + return def downgrade(migrate_engine): metadata = MetaData() metadata.bind = migrate_engine metadata.reflect() - - # Drop the dataset table's uuid column. + + # Drop the dataset table's uuid column. try: dataset_table = Table( "dataset", metadata, autoload=True ) dataset_uuid = dataset_table.c.uuid diff --git a/lib/galaxy/model/migrate/versions/0111_add_job_destinations.py b/lib/galaxy/model/migrate/versions/0111_add_job_destinations.py index c27db012ada..0a28694bc6c 100644 --- a/lib/galaxy/model/migrate/versions/0111_add_job_destinations.py +++ b/lib/galaxy/model/migrate/versions/0111_add_job_destinations.py @@ -41,7 +41,7 @@ def downgrade(migrate_engine): metadata.bind = migrate_engine metadata.reflect() Job_table = Table( "job", metadata, autoload=True ) - + try: Job_table.c.destination_params.drop() except Exception, e: From 7235cef8cb4675f867644ccb525f32450cf8a668 Mon Sep 17 00:00:00 2001 From: Dannon Baker Date: Thu, 2 May 2013 09:15:24 -0400 Subject: [PATCH 304/523] Remove assign_mapper usage in migrate0005 -- was succeeding previously due to a left-behind assign_mapper.pyc --- .../migrate/versions/0005_cleanup_datasets_fix.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/lib/galaxy/model/migrate/versions/0005_cleanup_datasets_fix.py b/lib/galaxy/model/migrate/versions/0005_cleanup_datasets_fix.py index 5b2272378f2..ad1b00d6c09 100644 --- a/lib/galaxy/model/migrate/versions/0005_cleanup_datasets_fix.py +++ b/lib/galaxy/model/migrate/versions/0005_cleanup_datasets_fix.py @@ -14,7 +14,8 @@ from sqlalchemy import * now = datetime.datetime.utcnow from sqlalchemy.orm import * -from galaxy.model.orm.ext.assignmapper import assign_mapper +from migrate import * +from migrate.changeset import * from galaxy.model.custom_types import * @@ -582,7 +583,7 @@ LibraryDataset.table = Table( "library_dataset", metadata, ##mappers -assign_mapper( context, Dataset, Dataset.table, +mapper( Dataset, Dataset.table, properties=dict( history_associations=relation( HistoryDatasetAssociation, @@ -599,7 +600,7 @@ assign_mapper( context, Dataset, Dataset.table, ) ) -assign_mapper( context, HistoryDatasetAssociation, HistoryDatasetAssociation.table, +mapper( HistoryDatasetAssociation, HistoryDatasetAssociation.table, properties=dict( dataset=relation( Dataset, @@ -622,7 +623,7 @@ assign_mapper( context, HistoryDatasetAssociation, HistoryDatasetAssociation.tab primaryjoin=( ( HistoryDatasetAssociation.table.c.parent_id == HistoryDatasetAssociation.table.c.id ) & ( HistoryDatasetAssociation.table.c.visible == True ) ) ) ) ) -assign_mapper( context, LibraryDatasetDatasetAssociation, LibraryDatasetDatasetAssociation.table, +mapper( LibraryDatasetDatasetAssociation, LibraryDatasetDatasetAssociation.table, properties=dict( dataset=relation( Dataset ), library_dataset = relation( LibraryDataset, @@ -644,7 +645,7 @@ assign_mapper( context, LibraryDatasetDatasetAssociation, LibraryDatasetDatasetA primaryjoin=( ( LibraryDatasetDatasetAssociation.table.c.parent_id == LibraryDatasetDatasetAssociation.table.c.id ) & ( LibraryDatasetDatasetAssociation.table.c.visible == True ) ) ) ) ) -assign_mapper( context, LibraryDataset, LibraryDataset.table, +mapper( LibraryDataset, LibraryDataset.table, properties=dict( library_dataset_dataset_association=relation( LibraryDatasetDatasetAssociation, primaryjoin=( LibraryDataset.table.c.library_dataset_dataset_association_id == LibraryDatasetDatasetAssociation.table.c.id ) ), expired_datasets = relation( LibraryDatasetDatasetAssociation, foreign_keys=[LibraryDataset.table.c.id,LibraryDataset.table.c.library_dataset_dataset_association_id ], primaryjoin=( ( LibraryDataset.table.c.id == LibraryDatasetDatasetAssociation.table.c.library_dataset_id ) & ( not_( LibraryDataset.table.c.library_dataset_dataset_association_id == LibraryDatasetDatasetAssociation.table.c.id ) ) ), viewonly=True, uselist=True ) From 8dd44babab58db3755f83e43c901e0d34334015a Mon Sep 17 00:00:00 2001 From: Jeremy Goecks Date: Thu, 2 May 2013 09:41:17 -0400 Subject: [PATCH 305/523] Trackster: whitespace fixes. --- static/scripts/viz/trackster/tracks.js | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/static/scripts/viz/trackster/tracks.js b/static/scripts/viz/trackster/tracks.js index 904131eb289..3bd034b2479 100644 --- a/static/scripts/viz/trackster/tracks.js +++ b/static/scripts/viz/trackster/tracks.js @@ -2722,6 +2722,7 @@ var TiledTrack = function(view, container, obj_dict) { } }; extend(TiledTrack.prototype, Drawable.prototype, Track.prototype, { + action_icons_def: Track.prototype.action_icons_def.concat( [ // Show more rows when all features are not slotted. { @@ -2737,6 +2738,7 @@ extend(TiledTrack.prototype, Drawable.prototype, Track.prototype, { hide: true } ] ), + /** * Returns a copy of the track. The copy uses the same data manager so that the tracks can share data. */ @@ -2752,6 +2754,7 @@ extend(TiledTrack.prototype, Drawable.prototype, Track.prototype, { new_track.enabled = this.enabled; return new_track; }, + /** * Set filters manager + HTML elements. */ @@ -2759,6 +2762,7 @@ extend(TiledTrack.prototype, Drawable.prototype, Track.prototype, { this.filters_manager = filters_manager; this.header_div.after(this.filters_manager.parent_div); }, + /** * Returns representation of object in a dictionary for easy saving. * Use from_dict to recreate object. @@ -2777,6 +2781,7 @@ extend(TiledTrack.prototype, Drawable.prototype, Track.prototype, { tool_state: (this.tool ? this.tool.state_dict() : {}) }; }, + /** * Change track's mode. */ @@ -2789,7 +2794,8 @@ extend(TiledTrack.prototype, Drawable.prototype, Track.prototype, { track.request_draw(); this.action_icons.mode_icon.attr("title", "Set display mode (now: " + track.mode + ")"); return track; - }, + }, + /** * Update track's buttons. */ @@ -2921,6 +2927,7 @@ extend(TiledTrack.prototype, Drawable.prototype, Track.prototype, { } }); }, + /** * Actions to be taken after draw has been completed. Draw is completed when all tiles have been * drawn/fetched and shown. @@ -3160,6 +3167,7 @@ extend(TiledTrack.prototype, Drawable.prototype, Track.prototype, { region_str = (region !== undefined ? region.toString() : "all"); return " - region=[" + region_str + "], parameters=[" + track.tool.get_param_values().join(", ") + "]"; }, + /** * Returns true if data is compatible with a given mode. Defaults to true because, for many tracks, * all data is compatible with all modes. @@ -3167,12 +3175,14 @@ extend(TiledTrack.prototype, Drawable.prototype, Track.prototype, { data_and_mode_compatible: function(data, mode) { return true; }, + /** * Returns true if data can be subsetted. Defaults to false to ensure data is fetched when needed. */ can_subset: function(data) { return false; }, + /** * Set up track to receive tool data. */ From b600848541c85a9a7c2b28bebf6baf33f71b5d7a Mon Sep 17 00:00:00 2001 From: Dannon Baker Date: Thu, 2 May 2013 10:01:35 -0400 Subject: [PATCH 306/523] Update toolshed mappings to use regular mapper, vs assign_mapper --- lib/galaxy/webapps/tool_shed/model/mapping.py | 39 +++++++++---------- 1 file changed, 19 insertions(+), 20 deletions(-) diff --git a/lib/galaxy/webapps/tool_shed/model/mapping.py b/lib/galaxy/webapps/tool_shed/model/mapping.py index b8b7563200b..9a4ab559c8f 100644 --- a/lib/galaxy/webapps/tool_shed/model/mapping.py +++ b/lib/galaxy/webapps/tool_shed/model/mapping.py @@ -10,7 +10,6 @@ import datetime from galaxy.webapps.tool_shed.model import * from galaxy.model.orm import * -from galaxy.model.orm.ext.assignmapper import * from galaxy.model.custom_types import * from galaxy.util.bunch import Bunch import galaxy.webapps.tool_shed.util.shed_statistics as shed_statistics @@ -200,27 +199,27 @@ Tag.table = Table( "tag", metadata, UniqueConstraint( "name" ) ) # With the tables defined we can define the mappers and setup the relationships between the model objects. -assign_mapper( context, User, User.table, +mapper( User, User.table, properties=dict( active_repositories=relation( Repository, primaryjoin=( ( Repository.table.c.user_id == User.table.c.id ) & ( not_( Repository.table.c.deleted ) ) ), order_by=( Repository.table.c.name ) ), galaxy_sessions=relation( GalaxySession, order_by=desc( GalaxySession.table.c.update_time ) ), api_keys=relation( APIKeys, backref="user", order_by=desc( APIKeys.table.c.create_time ) ) ) ) -assign_mapper( context, APIKeys, APIKeys.table, +mapper( APIKeys, APIKeys.table, properties = {} ) -assign_mapper( context, Group, Group.table, +mapper( Group, Group.table, properties=dict( users=relation( UserGroupAssociation ) ) ) -assign_mapper( context, Role, Role.table, +mapper( Role, Role.table, properties=dict( users=relation( UserRoleAssociation ), groups=relation( GroupRoleAssociation ) ) ) -assign_mapper( context, UserGroupAssociation, UserGroupAssociation.table, +mapper( UserGroupAssociation, UserGroupAssociation.table, properties=dict( user=relation( User, backref = "groups" ), group=relation( Group, backref = "members" ) ) ) -assign_mapper( context, UserRoleAssociation, UserRoleAssociation.table, +mapper( UserRoleAssociation, UserRoleAssociation.table, properties=dict( user=relation( User, backref="roles" ), non_private_roles=relation( User, @@ -228,28 +227,28 @@ assign_mapper( context, UserRoleAssociation, UserRoleAssociation.table, primaryjoin=( ( User.table.c.id == UserRoleAssociation.table.c.user_id ) & ( UserRoleAssociation.table.c.role_id == Role.table.c.id ) & not_( Role.table.c.name == User.table.c.email ) ) ), role=relation( Role ) ) ) -assign_mapper( context, GroupRoleAssociation, GroupRoleAssociation.table, +mapper( GroupRoleAssociation, GroupRoleAssociation.table, properties=dict( group=relation( Group, backref="roles" ), role=relation( Role ) ) ) -assign_mapper( context, GalaxySession, GalaxySession.table, - properties=dict( user=relation( User.mapper ) ) ) +mapper( GalaxySession, GalaxySession.table, + properties=dict( user=relation( User ) ) ) -assign_mapper( context, Tag, Tag.table, +mapper( Tag, Tag.table, properties=dict( children=relation(Tag, backref=backref( 'parent', remote_side=[ Tag.table.c.id ] ) ) ) ) -assign_mapper( context, Category, Category.table, +mapper( Category, Category.table, properties=dict( repositories=relation( RepositoryCategoryAssociation, secondary=Repository.table, primaryjoin=( Category.table.c.id == RepositoryCategoryAssociation.table.c.category_id ), secondaryjoin=( RepositoryCategoryAssociation.table.c.repository_id == Repository.table.c.id ) ) ) ) -assign_mapper( context, Repository, Repository.table, +mapper( Repository, Repository.table, properties = dict( categories=relation( RepositoryCategoryAssociation ), ratings=relation( RepositoryRatingAssociation, order_by=desc( RepositoryRatingAssociation.table.c.update_time ), backref="repositories" ), - user=relation( User.mapper ), + user=relation( User ), downloadable_revisions=relation( RepositoryMetadata, primaryjoin=( ( Repository.table.c.id == RepositoryMetadata.table.c.repository_id ) & ( RepositoryMetadata.table.c.downloadable == True ) ), order_by=desc( RepositoryMetadata.table.c.update_time ) ), @@ -262,13 +261,13 @@ assign_mapper( context, Repository, Repository.table, primaryjoin=( Repository.table.c.id == RepositoryReview.table.c.repository_id ), secondaryjoin=( RepositoryReview.table.c.user_id == User.table.c.id ) ) ) ) -assign_mapper( context, RepositoryMetadata, RepositoryMetadata.table, +mapper( RepositoryMetadata, RepositoryMetadata.table, properties=dict( repository=relation( Repository ), reviews=relation( RepositoryReview, foreign_keys=[ RepositoryMetadata.table.c.repository_id, RepositoryMetadata.table.c.changeset_revision ], primaryjoin=( ( RepositoryMetadata.table.c.repository_id == RepositoryReview.table.c.repository_id ) & ( RepositoryMetadata.table.c.changeset_revision == RepositoryReview.table.c.changeset_revision ) ) ) ) ) -assign_mapper( context, RepositoryReview, RepositoryReview.table, +mapper( RepositoryReview, RepositoryReview.table, properties=dict( repository=relation( Repository, primaryjoin=( RepositoryReview.table.c.repository_id == Repository.table.c.id ) ), # Take case when using the mapper below! It should be used only when a new review is being created for a repository change set revision. @@ -284,17 +283,17 @@ assign_mapper( context, RepositoryReview, RepositoryReview.table, private_component_reviews=relation( ComponentReview, primaryjoin=( ( RepositoryReview.table.c.id == ComponentReview.table.c.repository_review_id ) & ( ComponentReview.table.c.deleted == False ) & ( ComponentReview.table.c.private == True ) ) ) ) ) -assign_mapper( context, ComponentReview, ComponentReview.table, +mapper( ComponentReview, ComponentReview.table, properties=dict( repository_review=relation( RepositoryReview ), component=relation( Component, primaryjoin=( ComponentReview.table.c.component_id == Component.table.c.id ) ) ) ) -assign_mapper( context, Component, Component.table ) +mapper( Component, Component.table ) -assign_mapper( context, RepositoryRatingAssociation, RepositoryRatingAssociation.table, +mapper( RepositoryRatingAssociation, RepositoryRatingAssociation.table, properties=dict( repository=relation( Repository ), user=relation( User ) ) ) -assign_mapper( context, RepositoryCategoryAssociation, RepositoryCategoryAssociation.table, +mapper( RepositoryCategoryAssociation, RepositoryCategoryAssociation.table, properties=dict( category=relation( Category ), repository=relation( Repository ) ) ) From f78f5acbfbfc9e4d0aa074a620542a1518c23987 Mon Sep 17 00:00:00 2001 From: Jeremy Goecks Date: Thu, 2 May 2013 10:33:28 -0400 Subject: [PATCH 307/523] Trackster: remove nodraw parameter because it is no longer needed. --- static/scripts/viz/trackster/tracks.js | 38 ++++++++++++-------------- 1 file changed, 17 insertions(+), 21 deletions(-) diff --git a/static/scripts/viz/trackster/tracks.js b/static/scripts/viz/trackster/tracks.js index 3bd034b2479..eb06b388a32 100644 --- a/static/scripts/viz/trackster/tracks.js +++ b/static/scripts/viz/trackster/tracks.js @@ -892,7 +892,7 @@ var TracksterView = Backbone.View.extend({ ], saved_values: obj_dict.prefs, onchange: function() { - track.request_redraw(false, true); + track.request_redraw(true); } }); }, @@ -1266,8 +1266,7 @@ extend( TracksterView.prototype, DrawableCollection.prototype, { view.chrom_select.val(view.chrom); view.max_high = found.len-1; // -1 because we're using 0-based indexing. view.reset(); - view.request_redraw(true); - + for (var i = 0, len = view.drawables.length; i < len; i++) { var drawable = view.drawables[i]; if (drawable.init) { @@ -1392,8 +1391,7 @@ extend( TracksterView.prototype, DrawableCollection.prototype, { * Request that view redraw some or all tracks. If a track is not specificied, redraw all tracks. */ // FIXME: change method call so that track is first and additional parameters are optional. - // FIXME: is nodraw parameter needed? - request_redraw: function(nodraw, force, clear_after, a_track) { + request_redraw: function(force, clear_after, a_track) { var view = this, // Either redrawing a single drawable or all view's drawables. @@ -1428,7 +1426,7 @@ extend( TracksterView.prototype, DrawableCollection.prototype, { // Set up redraw if it has not been requested since last redraw. if (!this.requested_redraw) { - requestAnimationFrame(function() { view._redraw(nodraw); }); + requestAnimationFrame(function() { view._redraw(); }); this.requested_redraw = true; } }, @@ -1437,7 +1435,7 @@ extend( TracksterView.prototype, DrawableCollection.prototype, { * NOTE: this method should never be called directly; request_redraw() should be used so * that requestAnimationFrame can manage redrawing. */ - _redraw: function(nodraw) { + _redraw: function() { // TODO: move this code to function that does location setting. // Clear because requested redraw is being handled now. @@ -1482,21 +1480,19 @@ extend( TracksterView.prototype, DrawableCollection.prototype, { this.overview_highlight.css({ left: left_px, width: width_px }); } - if (!nodraw) { - var track, force, clear_after; - for (var i = 0, len = this.tracks_to_be_redrawn.length; i < len; i++) { - track = this.tracks_to_be_redrawn[i][0]; - force = this.tracks_to_be_redrawn[i][1]; - clear_after = this.tracks_to_be_redrawn[i][2]; - if (track) { - track._draw(force, clear_after); - } - } - this.tracks_to_be_redrawn = []; - for (i = 0, len = this.label_tracks.length; i < len; i++) { - this.label_tracks[i]._draw(); + var track, force, clear_after; + for (var i = 0, len = this.tracks_to_be_redrawn.length; i < len; i++) { + track = this.tracks_to_be_redrawn[i][0]; + force = this.tracks_to_be_redrawn[i][1]; + clear_after = this.tracks_to_be_redrawn[i][2]; + if (track) { + track._draw(force, clear_after); } } + this.tracks_to_be_redrawn = []; + for (i = 0, len = this.label_tracks.length; i < len; i++) { + this.label_tracks[i]._draw(); + } }, zoom_in: function (point, container) { if (this.max_high === 0 || this.high - this.low <= this.min_separation) { @@ -2848,7 +2844,7 @@ extend(TiledTrack.prototype, Drawable.prototype, Track.prototype, { * Request that track be drawn. */ request_draw: function(force, clear_after) { - this.view.request_redraw(false, force, clear_after, this); + this.view.request_redraw(force, clear_after, this); }, /** From 1c14b4a4a77c1eab8d9d32ad79ce9cb77c541fbb Mon Sep 17 00:00:00 2001 From: Dannon Baker Date: Thu, 2 May 2013 10:45:44 -0400 Subject: [PATCH 308/523] Fix toolshed functional tests to not use 'pool_size' with sqlite --- test/tool_shed/functional_tests.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/test/tool_shed/functional_tests.py b/test/tool_shed/functional_tests.py index 292476a7cbc..71f1cb721ae 100644 --- a/test/tool_shed/functional_tests.py +++ b/test/tool_shed/functional_tests.py @@ -184,11 +184,14 @@ def main(): kwargs[ 'object_store' ] = 'distributed' kwargs[ 'distributed_object_store_config_file' ] = 'distributed_object_store_conf.xml.sample' + + if not toolshed_database_connection.startswith( 'sqlite://' ): + kwargs[ 'database_engine_option_pool_size' ] = '10' + toolshedapp = ToolshedUniverseApplication( admin_users = 'test@bx.psu.edu', allow_user_creation = True, allow_user_deletion = True, database_connection = toolshed_database_connection, - database_engine_option_pool_size = '10', datatype_converters_config_file = 'datatype_converters_conf.xml.sample', file_path = shed_file_path, global_conf = global_conf, @@ -270,13 +273,13 @@ def main(): # ---- Build Galaxy Application -------------------------------------------------- galaxy_global_conf = { '__file__' : 'universe_wsgi.ini.sample' } if not galaxy_database_connection.startswith( 'sqlite://' ): + kwargs[ 'database_engine_option_pool_size' ] = '10' kwargs[ 'database_engine_option_max_overflow' ] = '20' galaxyapp = GalaxyUniverseApplication( allow_user_creation = True, allow_user_deletion = True, admin_users = 'test@bx.psu.edu', allow_library_path_paste = True, database_connection = galaxy_database_connection, - database_engine_option_pool_size = '10', datatype_converters_config_file = "datatype_converters_conf.xml.sample", enable_tool_shed_check = True, file_path = galaxy_file_path, From 0a19ca75fe140a0a89da4bc343ab17961327c1c0 Mon Sep 17 00:00:00 2001 From: Jeremy Goecks Date: Thu, 2 May 2013 10:58:19 -0400 Subject: [PATCH 309/523] Trackster: optionally clear tile cache when requesting a track redraw. --- static/scripts/viz/trackster/filters.js | 6 ++-- static/scripts/viz/trackster/tracks.js | 41 ++++++++++--------------- 2 files changed, 20 insertions(+), 27 deletions(-) diff --git a/static/scripts/viz/trackster/filters.js b/static/scripts/viz/trackster/filters.js index 0b4dfe5328c..437fee0ceea 100644 --- a/static/scripts/viz/trackster/filters.js +++ b/static/scripts/viz/trackster/filters.js @@ -178,7 +178,7 @@ var NumberFilter = function(obj_dict) { filter.manager.alpha_filter = null; filter.transparency_icon.removeClass("active"); } - filter.manager.track.request_draw(true, true); + filter.manager.track.request_draw(false, true, true); } ) .appendTo(display_controls_div).hide(); this.height_icon = create_action_icon("Use filter for data height", "arrow-resize-090", @@ -195,7 +195,7 @@ var NumberFilter = function(obj_dict) { filter.manager.height_filter = null; filter.height_icon.removeClass("active"); } - filter.manager.track.request_draw(true, true); + filter.manager.track.request_draw(false, true, true); } ) .appendTo(display_controls_div).hide(); filter.parent_div.hover( function() { @@ -265,7 +265,7 @@ extend(NumberFilter.prototype, { var self = this; setTimeout(function() { if (values[0] === self.low && values[1] === self.high) { - self.manager.track.request_draw(true, true); + self.manager.track.request_draw(false, true, true); } }, 25); diff --git a/static/scripts/viz/trackster/tracks.js b/static/scripts/viz/trackster/tracks.js index eb06b388a32..05bc4809008 100644 --- a/static/scripts/viz/trackster/tracks.js +++ b/static/scripts/viz/trackster/tracks.js @@ -845,9 +845,9 @@ extend(DrawableGroup.prototype, Drawable.prototype, DrawableCollection.prototype return obj_dict; }, - request_draw: function(clear_after, force) { + request_draw: function(clear_tile_cache, force, clear_after) { for (var i = 0; i < this.drawables.length; i++) { - this.drawables[i].request_draw(clear_after, force); + this.drawables[i].request_draw(clear_tile_cache, force, clear_after); } } }); @@ -1390,7 +1390,6 @@ extend( TracksterView.prototype, DrawableCollection.prototype, { /** * Request that view redraw some or all tracks. If a track is not specificied, redraw all tracks. */ - // FIXME: change method call so that track is first and additional parameters are optional. request_redraw: function(force, clear_after, a_track) { var view = this, @@ -2101,7 +2100,7 @@ var FeatureTrackTile = function(track, region, resolution, canvas, data, w_scale tile.stale = true; track.data_manager.get_more_data(tile_region, track.mode, tile.resolution, {}, track.data_manager.DEEP_DATA_REQ); $(".bs-tooltip").hide(); - track.request_draw(true); + track.request_draw(false, true); }).dblclick(function(e) { // Do not propogate as this would normally zoom in. e.stopPropagation(); @@ -2112,7 +2111,7 @@ var FeatureTrackTile = function(track, region, resolution, canvas, data, w_scale tile.stale = true; track.data_manager.get_more_data(tile_region, track.mode, tile.resolution, {}, track.data_manager.BROAD_DATA_REQ); $(".bs-tooltip").hide(); - track.request_draw(true); + track.request_draw(false, true); }).dblclick(function(e) { // Do not propogate as this would normally zoom in. e.stopPropagation(); @@ -2786,8 +2785,7 @@ extend(TiledTrack.prototype, Drawable.prototype, Track.prototype, { // TODO: is it necessary to store the mode in two places (.mode and track_config)? track.mode = new_mode; track.config.values['mode'] = new_mode; - track.tile_cache.clear(); - track.request_draw(); + track.request_draw(true); this.action_icons.mode_icon.attr("title", "Set display mode (now: " + track.mode + ")"); return track; }, @@ -2843,7 +2841,10 @@ extend(TiledTrack.prototype, Drawable.prototype, Track.prototype, { /** * Request that track be drawn. */ - request_draw: function(force, clear_after) { + request_draw: function(clear_tile_cache, force, clear_after) { + if (clear_tile_cache) { + this.tile_cache.clear(); + } this.view.request_redraw(force, clear_after, this); }, @@ -2962,8 +2963,7 @@ extend(TiledTrack.prototype, Drawable.prototype, Track.prototype, { $(".bs-tooltip").remove(); var new_val = parseFloat(new_val); track.prefs.histogram_max = (!isNaN(new_val) ? new_val : null); - track.tile_cache.clear(); - track.request_draw(); + track.request_draw(true); }, help_text: "Set max value; leave blank to use default" }).addClass('yaxislabel top').css("color", this.prefs.label_color); @@ -3670,8 +3670,7 @@ extend(LineTrack.prototype, Drawable.prototype, TiledTrack.prototype, { set_min_value: function(new_val) { this.prefs.min_value = new_val; $('#linetrack_' + this.dataset.id + '_minval').text(this.prefs.min_value); - this.tile_cache.clear(); - this.request_draw(); + this.request_draw(true); }, /** @@ -3680,8 +3679,7 @@ extend(LineTrack.prototype, Drawable.prototype, TiledTrack.prototype, { set_max_value: function(new_val) { this.prefs.max_value = new_val; $('#linetrack_' + this.dataset.id + '_maxval').text(this.prefs.max_value); - this.tile_cache.clear(); - this.request_draw(); + this.request_draw(true); }, predraw_init: function() { @@ -3811,16 +3809,14 @@ extend(DiagonalHeatmapTrack.prototype, Drawable.prototype, TiledTrack.prototype, */ set_min_value: function(new_val) { this.prefs.min_value = new_val; - this.tile_cache.clear(); - this.request_draw(); + this.request_draw(true); }, /** * Set track maximum value. */ set_max_value: function(new_val) { this.prefs.max_value = new_val; - this.tile_cache.clear(); - this.request_draw(); + this.request_draw(true); }, /** @@ -3874,9 +3870,8 @@ var FeatureTrack = function(view, container, obj_dict) { saved_values: obj_dict.prefs, onchange: function() { track.set_name(track.prefs.name); - track.tile_cache.clear(); track.set_painter_from_config(); - track.request_draw(); + track.request_draw(true); } }); this.prefs = this.config.values; @@ -4210,8 +4205,7 @@ var VariantTrack = function(view, container, obj_dict) { saved_values: obj_dict.prefs, onchange: function() { this.track.set_name(this.track.prefs.name); - this.track.tile_cache.clear(); - this.track.request_draw(); + this.track.request_draw(true); } }); this.prefs = this.config.values; @@ -4369,8 +4363,7 @@ var ReadTrack = function (view, container, obj_dict) { saved_values: obj_dict.prefs, onchange: function() { this.track.set_name(this.track.prefs.name); - this.track.tile_cache.clear(); - this.track.request_draw(); + this.track.request_draw(true); } }); this.prefs = this.config.values; From fbf3f7999fd11c5b1f986787515ff7635d63f468 Mon Sep 17 00:00:00 2001 From: Dannon Baker Date: Thu, 2 May 2013 11:52:23 -0400 Subject: [PATCH 310/523] Fix category queries in toolshed repository controller --- lib/galaxy/webapps/tool_shed/controllers/repository.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/galaxy/webapps/tool_shed/controllers/repository.py b/lib/galaxy/webapps/tool_shed/controllers/repository.py index 2c036743771..1eafcb8a8ff 100644 --- a/lib/galaxy/webapps/tool_shed/controllers/repository.py +++ b/lib/galaxy/webapps/tool_shed/controllers/repository.py @@ -871,7 +871,7 @@ class RepositoryController( BaseUIController, common_util.ItemRatings ): if category_ids: # Create category associations for category_id in category_ids: - category = trans.app.model.Category.get( trans.security.decode_id( category_id ) ) + category = trans.sa_session.query(model.Category).get( trans.security.decode_id( category_id ) ) rca = trans.app.model.RepositoryCategoryAssociation( repository, category ) trans.sa_session.add( rca ) flush_needed = True @@ -1913,7 +1913,7 @@ class RepositoryController( BaseUIController, common_util.ItemRatings ): if category_ids: # Create category associations for category_id in category_ids: - category = trans.app.model.Category.get( trans.security.decode_id( category_id ) ) + category = trans.sa_session.query(model.Category).get( trans.security.decode_id( category_id ) ) rca = trans.app.model.RepositoryCategoryAssociation( repository, category ) trans.sa_session.add( rca ) trans.sa_session.flush() From d4368980ed570585439bc3a9146da8a06e69e1f7 Mon Sep 17 00:00:00 2001 From: Greg Von Kuster Date: Thu, 2 May 2013 11:57:42 -0400 Subject: [PATCH 311/523] Fix for generating a repository dependency key when prior installation required is true. --- .../util/repository_dependency_util.py | 42 ++++++++++++++----- .../webapps/tool_shed/repository/common.mako | 1 + 2 files changed, 33 insertions(+), 10 deletions(-) diff --git a/lib/tool_shed/util/repository_dependency_util.py b/lib/tool_shed/util/repository_dependency_util.py index c199a383e4c..d17566ac50f 100644 --- a/lib/tool_shed/util/repository_dependency_util.py +++ b/lib/tool_shed/util/repository_dependency_util.py @@ -256,12 +256,34 @@ def generate_message_for_invalid_repository_dependencies( metadata_dict ): message = '%s ' % str( error ) return message -def get_key_for_repository_changeset_revision( toolshed_base_url, repository, repository_metadata ): - return container_util.generate_repository_dependencies_key_for_repository( toolshed_base_url=toolshed_base_url, - repository_name=repository.name, - repository_owner=repository.user.username, - changeset_revision=repository_metadata.changeset_revision, - prior_installation_required=False ) +def get_key_for_repository_changeset_revision( toolshed_base_url, repository, repository_metadata, all_repository_dependencies ): + prior_installation_required = get_prior_installation_required_for_key( toolshed_base_url, repository, repository_metadata, all_repository_dependencies ) + # Create a key with the value of prior_installation_required defaulted to False. + key = container_util.generate_repository_dependencies_key_for_repository( toolshed_base_url=toolshed_base_url, + repository_name=repository.name, + repository_owner=repository.user.username, + changeset_revision=repository_metadata.changeset_revision, + prior_installation_required=prior_installation_required ) + return key + +def get_prior_installation_required_for_key( toolshed_base_url, repository, repository_metadata, all_repository_dependencies ): + """ + If all_repository_dependencies contains a repository dependency tuple that is associated with the received repository, return the + value of the tuple's prior_installation_required component. + """ + rd_tuple = ( toolshed_base_url, repository.name, repository.user.username, repository_metadata.changeset_revision ) + for rd_key, rd_tups in all_repository_dependencies.items(): + if rd_key in [ 'root_key', 'description' ]: + continue + for rd_tup in rd_tups: + rd_toolshed, rd_name, rd_owner, rd_changeset_revision, rd_prior_installation_required = suc.parse_repository_dependency_tuple( rd_tup ) + if rd_toolshed == toolshed_base_url and \ + rd_name == repository.name and \ + rd_owner == repository.user.username and \ + rd_changeset_revision == repository_metadata.changeset_revision: + return rd_prior_installation_required + # Default prior_installation_required to False. + return False def get_repository_dependencies_for_changeset_revision( trans, repository, repository_metadata, toolshed_base_url, key_rd_dicts_to_be_processed=None, all_repository_dependencies=None, @@ -285,7 +307,7 @@ def get_repository_dependencies_for_changeset_revision( trans, repository, repos metadata = repository_metadata.metadata if metadata: if 'repository_dependencies' in metadata: - current_repository_key = get_key_for_repository_changeset_revision( toolshed_base_url, repository, repository_metadata ) + current_repository_key = get_key_for_repository_changeset_revision( toolshed_base_url, repository, repository_metadata, all_repository_dependencies ) repository_dependencies_dict = metadata[ 'repository_dependencies' ] if not all_repository_dependencies: all_repository_dependencies = initialize_all_repository_dependencies( current_repository_key, @@ -554,7 +576,7 @@ def merge_missing_repository_dependencies_to_installed_container( containers_dic # Change the folder label from 'Missing repository dependencies' to be 'Repository dependencies' for display. root_container = containers_dict[ 'missing_repository_dependencies' ] for sub_container in root_container.folders: - # There should only be 1 subfolder. + # There should only be 1 sub-folder. sub_container.label = 'Repository dependencies' containers_dict[ 'repository_dependencies' ] = root_container containers_dict[ 'missing_repository_dependencies' ] = None @@ -608,8 +630,8 @@ def populate_repository_dependency_objects_for_processing( trans, current_reposi def prune_invalid_repository_dependencies( repository_dependencies ): """ - Eliminate all invalid entries in the received repository_dependencies dictionary. An entry is invalid if if the value_list of the key/value pair is - empty. This occurs when an invalid combination of tool shed, name , owner, changeset_revision is used and a repository_metadata reocrd is not found. + Eliminate all invalid entries in the received repository_dependencies dictionary. An entry is invalid if the value_list of the key/value pair is + empty. This occurs when an invalid combination of tool shed, name , owner, changeset_revision is used and a repository_metadata record is not found. """ valid_repository_dependencies = {} description = repository_dependencies.get( 'description', None ) diff --git a/templates/webapps/tool_shed/repository/common.mako b/templates/webapps/tool_shed/repository/common.mako index c829239e948..f64bda8514c 100644 --- a/templates/webapps/tool_shed/repository/common.mako +++ b/templates/webapps/tool_shed/repository/common.mako @@ -980,6 +980,7 @@
    %endif %if tool_test_results_root_folder: +

    Automated tool test results
    From c2e9c572fe2cd174b1b388ad0dd0bc0590d6ba84 Mon Sep 17 00:00:00 2001 From: Dannon Baker Date: Thu, 2 May 2013 12:51:50 -0400 Subject: [PATCH 312/523] Fix (hack) for a suspected circular import preventing tool shed functional tests from running in some environments --- test/tool_shed/functional_tests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/tool_shed/functional_tests.py b/test/tool_shed/functional_tests.py index 71f1cb721ae..0d531082274 100644 --- a/test/tool_shed/functional_tests.py +++ b/test/tool_shed/functional_tests.py @@ -1,6 +1,6 @@ #!/usr/bin/env python -import os, sys, shutil, tempfile, re, string +import os, sys, shutil, tempfile, re, string, multiprocessing # Assume we are run from the galaxy root directory, add lib to the python path cwd = os.getcwd() From dbe33372be07b6a0e3a0a9c0c951f267cf24872c Mon Sep 17 00:00:00 2001 From: Jeremy Goecks Date: Thu, 2 May 2013 13:15:02 -0400 Subject: [PATCH 313/523] Trackster: implement generic functionality for adding and editing track min/max values. Also redraw by default on resize. --- static/scripts/viz/trackster/tracks.js | 152 ++++++++----------------- 1 file changed, 48 insertions(+), 104 deletions(-) diff --git a/static/scripts/viz/trackster/tracks.js b/static/scripts/viz/trackster/tracks.js index 05bc4809008..dbbfd50b810 100644 --- a/static/scripts/viz/trackster/tracks.js +++ b/static/scripts/viz/trackster/tracks.js @@ -2442,7 +2442,9 @@ extend(Track.prototype, Drawable.prototype, { /** * Action to take during resize. */ - on_resize: function() {}, + on_resize: function() { + this.request_draw(true); + }, /** * Add resizing handle to drawable's container_div. @@ -2925,6 +2927,36 @@ extend(TiledTrack.prototype, Drawable.prototype, Track.prototype, { }); }, + /** + * Add a maximum/minimum label to track. + */ + _add_yaxis_label: function(type, val, pref_name) { + var css_class = (type === 'max' ? 'top' : 'bottom'), + text = (type === 'max' ? 'max' : 'min'); + + var label = this.container_div.find(".yaxislabel." + css_class); + + if (label.length !== 0) { + // Label already exists, so update value. + label.text(val); + } + else { + // Add label. + var track = this; + label = $("
    ").text(val).make_text_editable({ + num_cols: 12, + on_finish: function(new_val) { + $(".bs-tooltip").remove(); + var new_val = parseFloat(new_val); + track.prefs[ pref_name ] = (!isNaN(new_val) ? new_val : null); + track.request_draw(true); + }, + help_text: "Set " + text + " value" + }).addClass('yaxislabel ' + css_class).css("color", this.prefs.label_color); + this.container_div.prepend(label); + } + }, + /** * Actions to be taken after draw has been completed. Draw is completed when all tiles have been * drawn/fetched and shown. @@ -2947,27 +2979,13 @@ extend(TiledTrack.prototype, Drawable.prototype, Track.prototype, { } // - // If using SummaryTree tiles, show max and make it editable. + // If using SummaryTree tiles, show max. // var track = this, first_tile = tiles[0]; if (first_tile instanceof SummaryTreeTile) { - // Remove old max. - this.container_div.find(".yaxislabel").remove(); - - // Add new max. - var max_val = (this.prefs.histogram_max ? this.prefs.histogram_max : first_tile.max_val), - max_label = $("
    ").text(max_val).make_text_editable({ - num_cols: 12, - on_finish: function(new_val) { - $(".bs-tooltip").remove(); - var new_val = parseFloat(new_val); - track.prefs.histogram_max = (!isNaN(new_val) ? new_val : null); - track.request_draw(true); - }, - help_text: "Set max value; leave blank to use default" - }).addClass('yaxislabel top').css("color", this.prefs.label_color); - this.container_div.prepend(max_label); + var max_val = (this.prefs.histogram_max ? this.prefs.histogram_max : first_tile.max_val); + this._add_yaxis_label('max', max_val, 'histogram_max'); } }, @@ -3496,7 +3514,7 @@ extend(CompositeTrack.prototype, TiledTrack.prototype, { // Replace track with group. var index = this.container.replace_drawable(this, group, true); - group.request_draw(); + group.request_draw(true); }, /** @@ -3647,8 +3665,7 @@ var LineTrack = function (view, container, obj_dict) { onchange: function() { track.set_name(track.prefs.name); track.vertical_range = track.prefs.max_value - track.prefs.min_value; - track.set_min_value(track.prefs.min_value); - track.set_max_value(track.prefs.max_value); + track.request_redraw(true); } }); @@ -3657,30 +3674,6 @@ var LineTrack = function (view, container, obj_dict) { this.vertical_range = this.config.values.max_value - this.config.values.min_value; }; extend(LineTrack.prototype, Drawable.prototype, TiledTrack.prototype, { - /** - * Action to take during resize. - */ - on_resize: function() { - this.request_draw(true); - }, - - /** - * Set track minimum value. - */ - set_min_value: function(new_val) { - this.prefs.min_value = new_val; - $('#linetrack_' + this.dataset.id + '_minval').text(this.prefs.min_value); - this.request_draw(true); - }, - - /** - * Set track maximum value. - */ - set_max_value: function(new_val) { - this.prefs.max_value = new_val; - $('#linetrack_' + this.dataset.id + '_maxval').text(this.prefs.max_value); - this.request_draw(true); - }, predraw_init: function() { var track = this; @@ -3701,45 +3694,9 @@ extend(LineTrack.prototype, Drawable.prototype, TiledTrack.prototype, { // Update the prefs track.prefs.min_value = min_value; track.prefs.max_value = max_value; - // Update the config - // FIXME: we should probably only save this when the user explicately sets it - // since we lose the ability to compute it on the fly (when changing - // chromosomes for example). - $('#track_' + track.dataset.id + '_minval').val(track.prefs.min_value); - $('#track_' + track.dataset.id + '_maxval').val(track.prefs.max_value); } track.vertical_range = track.prefs.max_value - track.prefs.min_value; track.total_frequency = data.total_frequency; - - // Draw y-axis labels if necessary - track.container_div.find(".yaxislabel").remove(); - - // Add min, max labels. - var - min_label = $("
    ").text(round(track.prefs.min_value, 3)).make_text_editable({ - num_cols: 6, - on_finish: function(new_val) { - $(".bs-tooltip").remove(); - var new_val = parseFloat(new_val); - if (!isNaN(new_val)) { - track.set_min_value(new_val); - } - }, - help_text: "Set min value" - }).addClass('yaxislabel bottom').attr("id", 'linetrack_' + track.dataset.id + '_minval') - .prependTo(track.container_div), - max_label = $("
    ").text(round(track.prefs.max_value, 3)).make_text_editable({ - num_cols: 6, - on_finish: function(new_val) { - $(".bs-tooltip").remove(); - var new_val = parseFloat(new_val); - if (!isNaN(new_val)) { - track.set_max_value(new_val); - } - }, - help_text: "Set max value" - }).addClass('yaxislabel top').attr("id", 'linetrack_' + track.dataset.id + '_maxval') - .prependTo(track.container_div); }); }, @@ -3763,6 +3720,15 @@ extend(LineTrack.prototype, Drawable.prototype, TiledTrack.prototype, { */ can_subset: function(entry) { return (entry.data[1][0] - entry.data[0][0] === 1); + }, + + /** + * Add min, max labels. + */ + postdraw_actions: function(tiles, width, w_scale, clear_after) { + // Add min, max labels. + this._add_yaxis_label('max', round(this.prefs.max_value, 3), 'max_value'); + this._add_yaxis_label('min', round(this.prefs.min_value, 3), 'min_value'); } }); @@ -3788,8 +3754,7 @@ var DiagonalHeatmapTrack = function (view, container, obj_dict) { onchange: function() { track.set_name(track.prefs.name); track.vertical_range = track.prefs.max_value - track.prefs.min_value; - track.set_min_value(track.prefs.min_value); - track.set_max_value(track.prefs.max_value); + this.request_redraw(true); } }); @@ -3798,27 +3763,6 @@ var DiagonalHeatmapTrack = function (view, container, obj_dict) { this.vertical_range = this.config.values.max_value - this.config.values.min_value; }; extend(DiagonalHeatmapTrack.prototype, Drawable.prototype, TiledTrack.prototype, { - /** - * Action to take during resize. - */ - on_resize: function() { - this.request_draw(true); - }, - /** - * Set track minimum value. - */ - set_min_value: function(new_val) { - this.prefs.min_value = new_val; - this.request_draw(true); - }, - /** - * Set track maximum value. - */ - set_max_value: function(new_val) { - this.prefs.max_value = new_val; - this.request_draw(true); - }, - /** * Draw tile. */ From c6ffdf9788bca3d832d0eec8bcf48d65470fa6a0 Mon Sep 17 00:00:00 2001 From: Dannon Baker Date: Thu, 2 May 2013 13:23:57 -0400 Subject: [PATCH 314/523] Model: galaxy.util.as_bool absolute import, fix from bad merge. --- lib/galaxy/model/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/galaxy/model/__init__.py b/lib/galaxy/model/__init__.py index 6fd86ab1dab..827f521567a 100644 --- a/lib/galaxy/model/__init__.py +++ b/lib/galaxy/model/__init__.py @@ -3467,7 +3467,7 @@ class ToolShedRepository( object ): prior_installation_required = False elif len( rd_tup ) == 5: tool_shed, name, owner, changeset_revision, prior_installation_required = rd_tup - prior_installation_required = util.asbool( str( prior_installation_required ) ) + prior_installation_required = galaxy.util.asbool( str( prior_installation_required ) ) if prior_installation_required: required_rd_tups_that_must_be_installed.append( ( tool_shed, name, owner, changeset_revision, prior_installation_required ) ) return required_rd_tups_that_must_be_installed From 14dd589b622295d6f1c3ed0b59e2c1d0e794e1c2 Mon Sep 17 00:00:00 2001 From: Dannon Baker Date: Thu, 2 May 2013 13:26:20 -0400 Subject: [PATCH 315/523] viz/genomes: Fix query.get() --- lib/galaxy/visualization/genomes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/galaxy/visualization/genomes.py b/lib/galaxy/visualization/genomes.py index 2bcdd38e11f..003bf5fedb3 100644 --- a/lib/galaxy/visualization/genomes.py +++ b/lib/galaxy/visualization/genomes.py @@ -342,7 +342,7 @@ class Genomes( object ): else: user_keys = from_json_string( dbkey_user.preferences['dbkeys'] ) dbkey_attributes = user_keys[ dbkey ] - fasta_dataset = trans.app.model.HistoryDatasetAssociation.get( dbkey_attributes[ 'fasta' ] ) + fasta_dataset = trans.sa_session.query( trans.app.model.HistoryDatasetAssociation ).get( dbkey_attributes[ 'fasta' ] ) msg = fasta_dataset.convert_dataset( trans, 'twobit' ) if msg: return msg From 8d7ef6bc21fd5aa917dd8c0e435259a87f6ed607 Mon Sep 17 00:00:00 2001 From: Dannon Baker Date: Thu, 2 May 2013 13:27:52 -0400 Subject: [PATCH 316/523] base admin controller: Fix query.get() --- lib/galaxy/web/base/controllers/admin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/galaxy/web/base/controllers/admin.py b/lib/galaxy/web/base/controllers/admin.py index 799e12521f7..b102e4e34fd 100644 --- a/lib/galaxy/web/base/controllers/admin.py +++ b/lib/galaxy/web/base/controllers/admin.py @@ -1026,7 +1026,7 @@ class Admin( object ): error_msg = "This job was stopped by an administrator: %s Contact support for additional help." \ % ( stop_msg, self.app.config.get("support_url", "http://wiki.galaxyproject.org/Support" ) ) if trans.app.config.track_jobs_in_database: - job = trans.app.model.Job.get( job_id ) + job = trans.sa_session.query( trans.app.model.Job ).get( job_id ) job.stderr = error_msg job.state = trans.app.model.Job.states.DELETED_NEW trans.sa_session.add( job ) From 55f9c6821a95a0c708bc07f196f69a8acfd870a5 Mon Sep 17 00:00:00 2001 From: Dannon Baker Date: Thu, 2 May 2013 13:29:02 -0400 Subject: [PATCH 317/523] data manager controller: Fix query.get() --- lib/galaxy/webapps/galaxy/controllers/data_manager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/galaxy/webapps/galaxy/controllers/data_manager.py b/lib/galaxy/webapps/galaxy/controllers/data_manager.py index 729305fad58..18d4ad9442d 100644 --- a/lib/galaxy/webapps/galaxy/controllers/data_manager.py +++ b/lib/galaxy/webapps/galaxy/controllers/data_manager.py @@ -45,7 +45,7 @@ class DataManager( BaseUIController ): job_id = kwd.get( 'id', None ) try: job_id = trans.security.decode_id( job_id ) - job = trans.app.model.Job.get( job_id ) + job = trans.sa_session.query( trans.app.model.Job ).get( job_id ) except Exception, e: job = None log.error( "Bad job id (%s) passed to view_job: %s" % ( job_id, e ) ) From eca1e5d7e78564fbea585339d2b322618d61a29f Mon Sep 17 00:00:00 2001 From: Dannon Baker Date: Thu, 2 May 2013 13:30:47 -0400 Subject: [PATCH 318/523] root controller: Fix query.get() --- lib/galaxy/webapps/galaxy/controllers/root.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/galaxy/webapps/galaxy/controllers/root.py b/lib/galaxy/webapps/galaxy/controllers/root.py index c4e9346cea2..31f9a43d9a1 100644 --- a/lib/galaxy/webapps/galaxy/controllers/root.py +++ b/lib/galaxy/webapps/galaxy/controllers/root.py @@ -199,7 +199,7 @@ class RootController( BaseUIController, UsesHistoryMixin, UsesHistoryDatasetAsso raise Exception( "No dataset with hid '%d'" % hid ) else: try: - data = self.app.model.HistoryDatasetAssociation.get( id ) + data = trans.sa_session.query( self.app.model.HistoryDatasetAssociation ).get( id ) except: return "Dataset id '%s' is invalid" % str( id ) if data: @@ -466,7 +466,6 @@ class RootController( BaseUIController, UsesHistoryMixin, UsesHistoryDatasetAsso old_data = trans.sa_session.query( self.app.model.HistoryDatasetAssociation ).get( id ) new_data = old_data.copy() ## new_data.parent = None - ## history = trans.app.model.History.get( old_data.history_id ) history = trans.get_history() history.add_dataset(new_data) trans.sa_session.add( new_data ) From 63923ebaf7ff5df4cb2c3495f5ec034f4a7a0d26 Mon Sep 17 00:00:00 2001 From: Dannon Baker Date: Thu, 2 May 2013 13:32:26 -0400 Subject: [PATCH 319/523] library common: Fix query.get() --- lib/galaxy/webapps/galaxy/controllers/library_common.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/galaxy/webapps/galaxy/controllers/library_common.py b/lib/galaxy/webapps/galaxy/controllers/library_common.py index 12f49c346c9..3951e94b7cb 100644 --- a/lib/galaxy/webapps/galaxy/controllers/library_common.py +++ b/lib/galaxy/webapps/galaxy/controllers/library_common.py @@ -2697,7 +2697,7 @@ def lucene_search( trans, cntrller, search_term, search_url, **kwd ): response = urllib2.urlopen( full_url ) ldda_ids = util.json.from_json_string( response.read() )[ "ids" ] response.close() - lddas = [ trans.app.model.LibraryDatasetDatasetAssociation.get( ldda_id ) for ldda_id in ldda_ids ] + lddas = [ trans.sa_session.query( trans.app.model.LibraryDatasetDatasetAssociation ).get( ldda_id ) for ldda_id in ldda_ids ] return status, message, get_sorted_accessible_library_items( trans, cntrller, lddas, 'name' ) def whoosh_search( trans, cntrller, search_term, **kwd ): """Return display of results from a full-text whoosh search of data libraries.""" @@ -2722,7 +2722,7 @@ def whoosh_search( trans, cntrller, search_term, **kwd ): ldda_ids = [ result[ 'id' ] for result in results ] lddas = [] for ldda_id in ldda_ids: - ldda = trans.app.model.LibraryDatasetDatasetAssociation.get( ldda_id ) + ldda = trans.sa_session.query( trans.app.model.LibraryDatasetDatasetAssociation ).get( ldda_id ) if ldda: lddas.append( ldda ) lddas = get_sorted_accessible_library_items( trans, cntrller, lddas, 'name' ) From c149e2b3fa921af1cf5922c9a9afcd8c799948fa Mon Sep 17 00:00:00 2001 From: Dannon Baker Date: Thu, 2 May 2013 13:34:06 -0400 Subject: [PATCH 320/523] visualization controller: Fix query.get() --- lib/galaxy/webapps/galaxy/controllers/visualization.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/galaxy/webapps/galaxy/controllers/visualization.py b/lib/galaxy/webapps/galaxy/controllers/visualization.py index 658b6294760..365714f389e 100644 --- a/lib/galaxy/webapps/galaxy/controllers/visualization.py +++ b/lib/galaxy/webapps/galaxy/controllers/visualization.py @@ -122,7 +122,7 @@ class HistoryDatasetsSelectionGrid( grids.Grid ): Current item for grid is the history being queried. This is a bit of hack since current_item typically means the current item in the grid. """ - return model.History.get( trans.security.decode_id( kwargs[ 'f-history' ] ) ) + return trans.sa_session.query( model.History ).get( trans.security.decode_id( kwargs[ 'f-history' ] ) ) def build_initial_query( self, trans, **kwargs ): return trans.sa_session.query( self.model_class ).join( model.History.table ).join( model.Dataset.table ) def apply_query_filter( self, trans, query, **kwargs ): From a9a7ed66777fd760b80946a1287cfcf882e2234a Mon Sep 17 00:00:00 2001 From: Dannon Baker Date: Thu, 2 May 2013 13:48:56 -0400 Subject: [PATCH 321/523] Fix all but the metadata.py assign_mapper-> object.get() query issue -- should be the last one --- lib/galaxy/datatypes/metadata.py | 1 + lib/galaxy/tools/actions/__init__.py | 2 +- lib/galaxy/webapps/galaxy/controllers/user.py | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/galaxy/datatypes/metadata.py b/lib/galaxy/datatypes/metadata.py index 57c70c00084..907104e4109 100644 --- a/lib/galaxy/datatypes/metadata.py +++ b/lib/galaxy/datatypes/metadata.py @@ -446,6 +446,7 @@ class FileParameter( MetadataParameter ): try: # FIXME: this query requires a monkey patch in assignmapper.py since # MetadataParameters do not have a handle to the sqlalchemy session + # DBTODO this is problematic now. return galaxy.model.MetadataFile.get( value ) except: #value was not a valid id diff --git a/lib/galaxy/tools/actions/__init__.py b/lib/galaxy/tools/actions/__init__.py index b61b8a304f5..636f17311a9 100644 --- a/lib/galaxy/tools/actions/__init__.py +++ b/lib/galaxy/tools/actions/__init__.py @@ -209,7 +209,7 @@ class DefaultToolAction( object ): # Custom build. custom_build_dict = from_json_string( trans.user.preferences[ 'dbkeys' ] )[ input_dbkey ] if 'fasta' in custom_build_dict: - build_fasta_dataset = trans.app.model.HistoryDatasetAssociation.get( custom_build_dict[ 'fasta' ] ) + build_fasta_dataset = trans.sa_session.query( trans.app.model.HistoryDatasetAssociation ).get( custom_build_dict[ 'fasta' ] ) chrom_info = build_fasta_dataset.get_converted_dataset( trans, 'len' ).file_name if not chrom_info: diff --git a/lib/galaxy/webapps/galaxy/controllers/user.py b/lib/galaxy/webapps/galaxy/controllers/user.py index e1a4ee8ba9a..2c47513f790 100644 --- a/lib/galaxy/webapps/galaxy/controllers/user.py +++ b/lib/galaxy/webapps/galaxy/controllers/user.py @@ -1329,7 +1329,7 @@ class User( BaseUIController, UsesFormDefinitionsMixin ): continue # Get len file. - fasta_dataset = trans.app.model.HistoryDatasetAssociation.get( attributes[ 'fasta' ] ) + fasta_dataset = trans.sa_session.query( trans.app.model.HistoryDatasetAssociation ).get( attributes[ 'fasta' ] ) len_dataset = fasta_dataset.get_converted_dataset( trans, "len" ) # HACK: need to request dataset again b/c get_converted_dataset() # doesn't return dataset (as it probably should). From f2ac6eefc31eb96eb1e6744b546fe3e65ac01697 Mon Sep 17 00:00:00 2001 From: Dannon Baker Date: Thu, 2 May 2013 13:55:34 -0400 Subject: [PATCH 322/523] Remove commented out debugging cruft in model/mapping.py --- lib/galaxy/model/mapping.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/lib/galaxy/model/mapping.py b/lib/galaxy/model/mapping.py index ace80877cef..fc922a27c39 100644 --- a/lib/galaxy/model/mapping.py +++ b/lib/galaxy/model/mapping.py @@ -2043,10 +2043,7 @@ def init( file_path, url, engine_options={}, create_tables=False, database_query m_obs = inspect.getmembers(model, inspect.isclass) m_obs = dict([m for m in m_obs if m[1].__module__ == 'galaxy.model']) result = Bunch( **m_obs ) - #result_old = Bunch( **globals() ) - #import pdb; pdb.set_trace() result.engine = engine - # model.flush() has been removed. result.session = Session # For backward compatibility with "model.context.current" result.context = Session From c27337351f9e7cb11af2d2031c89307ad3aae4d4 Mon Sep 17 00:00:00 2001 From: Dannon Baker Date: Thu, 2 May 2013 14:30:25 -0400 Subject: [PATCH 323/523] Model whitespace cleanup. --- lib/galaxy/model/mapping.py | 356 ++++++++++++++++++------------------ 1 file changed, 178 insertions(+), 178 deletions(-) diff --git a/lib/galaxy/model/mapping.py b/lib/galaxy/model/mapping.py index fc922a27c39..1e4594e1ed3 100644 --- a/lib/galaxy/model/mapping.py +++ b/lib/galaxy/model/mapping.py @@ -36,7 +36,7 @@ dialect_to_egg = { } # NOTE REGARDING TIMESTAMPS: -# It is currently difficult to have the timestamps calculated by the +# It is currently difficult to have the timestamps calculated by the # database in a portable way, so we're doing it in the client. This # also saves us from needing to postfetch on postgres. HOWEVER: it # relies on the client's clock being set correctly, so if clustering @@ -63,7 +63,7 @@ model.UserAddress.table = Table( "user_address", metadata, Column( "create_time", DateTime, default=now ), Column( "update_time", DateTime, default=now, onupdate=now ), Column( "user_id", Integer, ForeignKey( "galaxy_user.id" ), index=True ), - Column( "desc", TrimmedString( 255 )), + Column( "desc", TrimmedString( 255 )), Column( "name", TrimmedString( 255 ), nullable=False), Column( "institution", TrimmedString( 255 )), Column( "address", TrimmedString( 255 ), nullable=False), @@ -106,7 +106,7 @@ model.HistoryUserShareAssociation.table = Table( "history_user_share_association Column( "user_id", Integer, ForeignKey( "galaxy_user.id" ), index=True ) ) -model.HistoryDatasetAssociation.table = Table( "history_dataset_association", metadata, +model.HistoryDatasetAssociation.table = Table( "history_dataset_association", metadata, Column( "id", Integer, primary_key=True ), Column( "history_id", Integer, ForeignKey( "history.id" ), index=True ), Column( "dataset_id", Integer, ForeignKey( "dataset.id" ), index=True ), @@ -129,7 +129,7 @@ model.HistoryDatasetAssociation.table = Table( "history_dataset_association", me Column( "purged", Boolean, index=True, default=False ), Column( "visible", Boolean ) ) -model.Dataset.table = Table( "dataset", metadata, +model.Dataset.table = Table( "dataset", metadata, Column( "id", Integer, primary_key=True ), Column( "create_time", DateTime, default=now ), Column( "update_time", DateTime, index=True, default=now, onupdate=now ), @@ -151,7 +151,7 @@ model.HistoryDatasetAssociationDisplayAtAuthorization.table = Table( "history_da Column( "history_dataset_association_id", Integer, ForeignKey( "history_dataset_association.id" ), index=True ), Column( "user_id", Integer, ForeignKey( "galaxy_user.id" ), index=True ), Column( "site", TrimmedString( 255 ) ) ) - + model.HistoryDatasetAssociationSubset.table = Table( "history_dataset_association_subset", metadata, Column( "id", Integer, primary_key=True ), Column( "history_dataset_association_id", Integer, ForeignKey( "history_dataset_association.id" ), index=True ), @@ -184,7 +184,7 @@ model.Group.table = Table( "galaxy_group", metadata, Column( "name", String( 255 ), index=True, unique=True ), Column( "deleted", Boolean, index=True, default=False ) ) -model.UserGroupAssociation.table = Table( "user_group_association", metadata, +model.UserGroupAssociation.table = Table( "user_group_association", metadata, Column( "id", Integer, primary_key=True ), Column( "user_id", Integer, ForeignKey( "galaxy_user.id" ), index=True ), Column( "group_id", Integer, ForeignKey( "galaxy_group.id" ), index=True ), @@ -297,7 +297,7 @@ model.DefaultHistoryPermissions.table = Table( "default_history_permissions", me Column( "action", TEXT ), Column( "role_id", Integer, ForeignKey( "role.id" ), index=True ) ) -model.LibraryDataset.table = Table( "library_dataset", metadata, +model.LibraryDataset.table = Table( "library_dataset", metadata, Column( "id", Integer, primary_key=True ), Column( "library_dataset_dataset_association_id", Integer, ForeignKey( "library_dataset_dataset_association.id", use_alter=True, name="library_dataset_dataset_association_id_fk" ), nullable=True, index=True ),#current version of dataset, if null, there is not a current version selected Column( "folder_id", Integer, ForeignKey( "library_folder.id" ), index=True ), @@ -309,7 +309,7 @@ model.LibraryDataset.table = Table( "library_dataset", metadata, Column( "deleted", Boolean, index=True, default=False ), Column( "purged", Boolean, index=True, default=False ) ) -model.LibraryDatasetDatasetAssociation.table = Table( "library_dataset_dataset_association", metadata, +model.LibraryDatasetDatasetAssociation.table = Table( "library_dataset_dataset_association", metadata, Column( "id", Integer, primary_key=True ), Column( "library_dataset_id", Integer, ForeignKey( "library_dataset.id" ), index=True ), Column( "dataset_id", Integer, ForeignKey( "dataset.id" ), index=True ), @@ -337,7 +337,7 @@ model.LibraryDatasetDatasetAssociation.table = Table( "library_dataset_dataset_a model.ExtendedMetadata.table = Table("extended_metadata", metadata, - Column( "id", Integer, primary_key=True ), + Column( "id", Integer, primary_key=True ), Column( "data", JSONType ) ) model.ExtendedMetadataIndex.table = Table("extended_metadata_index", metadata, @@ -467,7 +467,7 @@ model.Job.table = Table( "job", metadata, Column( "tool_version", TEXT, default="1.0.0" ), Column( "state", String( 64 ), index=True ), Column( "info", TrimmedString( 255 ) ), - Column( "command_line", TEXT ), + Column( "command_line", TEXT ), Column( "param_filename", String( 1024 ) ), Column( "runner_name", String( 255 ) ), Column( "stdout", TEXT ), @@ -477,32 +477,32 @@ model.Job.table = Table( "job", metadata, Column( "session_id", Integer, ForeignKey( "galaxy_session.id" ), index=True, nullable=True ), Column( "user_id", Integer, ForeignKey( "galaxy_user.id" ), index=True, nullable=True ), Column( "job_runner_name", String( 255 ) ), - Column( "job_runner_external_id", String( 255 ) ), + Column( "job_runner_external_id", String( 255 ) ), Column( "destination_id", String( 255 ), nullable=True ), - Column( "destination_params", JSONType, nullable=True ), + Column( "destination_params", JSONType, nullable=True ), Column( "object_store_id", TrimmedString( 255 ), index=True ), Column( "imported", Boolean, default=False, index=True ), Column( "params", TrimmedString(255), index=True ), Column( "handler", TrimmedString( 255 ), index=True ) ) - + model.JobParameter.table = Table( "job_parameter", metadata, Column( "id", Integer, primary_key=True ), Column( "job_id", Integer, ForeignKey( "job.id" ), index=True ), Column( "name", String(255) ), Column( "value", TEXT ) ) - + model.JobToInputDatasetAssociation.table = Table( "job_to_input_dataset", metadata, Column( "id", Integer, primary_key=True ), Column( "job_id", Integer, ForeignKey( "job.id" ), index=True ), Column( "dataset_id", Integer, ForeignKey( "history_dataset_association.id" ), index=True ), Column( "name", String(255) ) ) - + model.JobToOutputDatasetAssociation.table = Table( "job_to_output_dataset", metadata, Column( "id", Integer, primary_key=True ), Column( "job_id", Integer, ForeignKey( "job.id" ), index=True ), Column( "dataset_id", Integer, ForeignKey( "history_dataset_association.id" ), index=True ), Column( "name", String(255) ) ) - + model.JobToInputLibraryDatasetAssociation.table = Table( "job_to_input_library_dataset", metadata, Column( "id", Integer, primary_key=True ), Column( "job_id", Integer, ForeignKey( "job.id" ), index=True ), @@ -514,7 +514,7 @@ model.JobToOutputLibraryDatasetAssociation.table = Table( "job_to_output_library Column( "job_id", Integer, ForeignKey( "job.id" ), index=True ), Column( "ldda_id", Integer, ForeignKey( "library_dataset_dataset_association.id" ), index=True ), Column( "name", String(255) ) ) - + model.JobExternalOutputMetadata.table = Table( "job_external_output_metadata", metadata, Column( "id", Integer, primary_key=True ), Column( "job_id", Integer, ForeignKey( "job.id" ), index=True ), @@ -526,7 +526,7 @@ model.JobExternalOutputMetadata.table = Table( "job_external_output_metadata", m Column( "filename_kwds", String( 255 ) ), Column( "filename_override_metadata", String( 255 ) ), Column( "job_runner_external_pid", String( 255 ) ) ) - + model.JobExportHistoryArchive.table = Table( "job_export_history_archive", metadata, Column( "id", Integer, primary_key=True ), Column( "job_id", Integer, ForeignKey( "job.id" ), index=True ), @@ -537,7 +537,7 @@ model.JobExportHistoryArchive.table = Table( "job_export_history_archive", metad Column( "datasets_attrs_filename", TEXT ), Column( "jobs_attrs_filename", TEXT ) ) - + model.JobImportHistoryArchive.table = Table( "job_import_history_archive", metadata, Column( "id", Integer, primary_key=True ), Column( "job_id", Integer, ForeignKey( "job.id" ), index=True ), @@ -557,14 +557,14 @@ model.GenomeIndexToolData.table = Table( "genome_index_tool_data", metadata, Column( "indexer", String( 64 ) ), Column( "user_id", Integer, ForeignKey( "galaxy_user.id" ), index=True ), ) - + model.Task.table = Table( "task", metadata, Column( "id", Integer, primary_key=True ), Column( "create_time", DateTime, default=now ), Column( "execution_time", DateTime ), Column( "update_time", DateTime, default=now, onupdate=now ), Column( "state", String( 64 ), index=True ), - Column( "command_line", TEXT ), + Column( "command_line", TEXT ), Column( "param_filename", String( 1024 ) ), Column( "runner_name", String( 255 ) ), Column( "stdout", TEXT ), @@ -609,7 +609,7 @@ model.TransferJob.table = Table( "transfer_job", metadata, Column( "socket", Integer ), Column( "params", JSONType ) ) -model.Event.table = Table( "event", metadata, +model.Event.table = Table( "event", metadata, Column( "id", Integer, primary_key=True ), Column( "create_time", DateTime, default=now ), Column( "update_time", DateTime, default=now, onupdate=now ), @@ -718,10 +718,10 @@ model.StoredWorkflowUserShareAssociation.table = Table( "stored_workflow_user_sh model.StoredWorkflowMenuEntry.table = Table( "stored_workflow_menu_entry", metadata, Column( "id", Integer, primary_key=True ), Column( "stored_workflow_id", Integer, ForeignKey( "stored_workflow.id" ), index=True ), - Column( "user_id", Integer, ForeignKey( "galaxy_user.id" ), index=True ), + Column( "user_id", Integer, ForeignKey( "galaxy_user.id" ), index=True ), Column( "order_index", Integer ) ) -model.MetadataFile.table = Table( "metadata_file", metadata, +model.MetadataFile.table = Table( "metadata_file", metadata, Column( "id", Integer, primary_key=True ), Column( "name", TEXT ), Column( "hda_id", Integer, ForeignKey( "history_dataset_association.id" ), index=True, nullable=True ), @@ -746,8 +746,8 @@ model.FormDefinition.table = Table('form_definition', metadata, Column( "name", TrimmedString( 255 ), nullable=False ), Column( "desc", TEXT ), Column( "form_definition_current_id", - Integer, - ForeignKey( "form_definition_current.id", name='for_def_form_def_current_id_fk', use_alter=True ), + Integer, + ForeignKey( "form_definition_current.id", name='for_def_form_def_current_id_fk', use_alter=True ), index=True ), Column( "fields", JSONType() ), Column( "type", TrimmedString( 255 ), index=True ), @@ -811,7 +811,7 @@ model.RequestEvent.table = Table('request_event', metadata, Column( "id", Integer, primary_key=True), Column( "create_time", DateTime, default=now ), Column( "update_time", DateTime, default=now, onupdate=now ), - Column( "request_id", Integer, ForeignKey( "request.id" ), index=True ), + Column( "request_id", Integer, ForeignKey( "request.id" ), index=True ), Column( "state", TrimmedString( 255 ), index=True ), Column( "comment", TEXT ) ) @@ -842,21 +842,21 @@ model.SampleEvent.table = Table('sample_event', metadata, Column( "id", Integer, primary_key=True ), Column( "create_time", DateTime, default=now ), Column( "update_time", DateTime, default=now, onupdate=now ), - Column( "sample_id", Integer, ForeignKey( "sample.id" ), index=True ), - Column( "sample_state_id", Integer, ForeignKey( "sample_state.id" ), index=True ), + Column( "sample_id", Integer, ForeignKey( "sample.id" ), index=True ), + Column( "sample_state_id", Integer, ForeignKey( "sample_state.id" ), index=True ), Column( "comment", TEXT ) ) model.SampleDataset.table = Table('sample_dataset', metadata, Column( "id", Integer, primary_key=True ), Column( "create_time", DateTime, default=now ), Column( "update_time", DateTime, default=now, onupdate=now ), - Column( "sample_id", Integer, ForeignKey( "sample.id" ), index=True ), + Column( "sample_id", Integer, ForeignKey( "sample.id" ), index=True ), Column( "name", TrimmedString( 255 ), nullable=False ), Column( "file_path", TEXT ), Column( "status", TrimmedString( 255 ), nullable=False ), Column( "error_msg", TEXT ), Column( "size", TrimmedString( 255 ) ), - Column( "external_service_id", Integer, ForeignKey( "external_service.id" ), index=True ) ) + Column( "external_service_id", Integer, ForeignKey( "external_service.id" ), index=True ) ) model.Run.table = Table( 'run', metadata, Column( "id", Integer, primary_key=True ), @@ -886,9 +886,9 @@ model.Page.table = Table( "page", metadata, ForeignKey( "page_revision.id", use_alter=True, name='page_latest_revision_id_fk' ), index=True ), Column( "title", TEXT ), Column( "slug", TEXT, unique=True, index=True ), - Column( "importable", Boolean, index=True, default=False ), - Column( "published", Boolean, index=True, default=False ), - Column( "deleted", Boolean, index=True, default=False ), + Column( "importable", Boolean, index=True, default=False ), + Column( "published", Boolean, index=True, default=False ), + Column( "deleted", Boolean, index=True, default=False ), ) model.PageRevision.table = Table( "page_revision", metadata, @@ -931,13 +931,13 @@ model.VisualizationRevision.table = Table( "visualization_revision", metadata, Column( "dbkey", TEXT, index=True ), Column( "config", JSONType ) ) - + model.VisualizationUserShareAssociation.table = Table( "visualization_user_share_association", metadata, Column( "id", Integer, primary_key=True ), Column( "visualization_id", Integer, ForeignKey( "visualization.id" ), index=True ), Column( "user_id", Integer, ForeignKey( "galaxy_user.id" ), index=True ) ) - + #Data Manager tables model.DataManagerHistoryAssociation.table = Table( "data_manager_history_association", metadata, Column( "id", Integer, primary_key=True), @@ -961,7 +961,7 @@ model.Tag.table = Table( "tag", metadata, Column( "id", Integer, primary_key=True ), Column( "type", Integer ), Column( "parent_id", Integer, ForeignKey( "tag.id" ) ), - Column( "name", TrimmedString(255) ), + Column( "name", TrimmedString(255) ), UniqueConstraint( "name" ) ) model.HistoryTagAssociation.table = Table( "history_tag_association", metadata, @@ -972,7 +972,7 @@ model.HistoryTagAssociation.table = Table( "history_tag_association", metadata, Column( "user_tname", TrimmedString(255), index=True), Column( "value", TrimmedString(255), index=True), Column( "user_value", TrimmedString(255), index=True) ) - + model.DatasetTagAssociation.table = Table( "dataset_tag_association", metadata, Column( "id", Integer, primary_key=True ), Column( "dataset_id", Integer, ForeignKey( "dataset.id" ), index=True ), @@ -990,7 +990,7 @@ model.HistoryDatasetAssociationTagAssociation.table = Table( "history_dataset_as Column( "user_tname", TrimmedString(255), index=True), Column( "value", TrimmedString(255), index=True), Column( "user_value", TrimmedString(255), index=True) ) - + model.StoredWorkflowTagAssociation.table = Table( "stored_workflow_tag_association", metadata, Column( "id", Integer, primary_key=True ), Column( "stored_workflow_id", Integer, ForeignKey( "stored_workflow.id" ), index=True ), @@ -1008,7 +1008,7 @@ model.PageTagAssociation.table = Table( "page_tag_association", metadata, Column( "user_tname", TrimmedString(255), index=True), Column( "value", TrimmedString(255), index=True), Column( "user_value", TrimmedString(255), index=True) ) - + model.WorkflowStepTagAssociation.table = Table( "workflow_step_tag_association", metadata, Column( "id", Integer, primary_key=True ), Column( "workflow_step_id", Integer, ForeignKey( "workflow_step.id" ), index=True ), @@ -1017,7 +1017,7 @@ model.WorkflowStepTagAssociation.table = Table( "workflow_step_tag_association", Column( "user_tname", Unicode(255), index=True), Column( "value", Unicode(255), index=True), Column( "user_value", Unicode(255), index=True) ) - + model.VisualizationTagAssociation.table = Table( "visualization_tag_association", metadata, Column( "id", Integer, primary_key=True ), Column( "visualization_id", Integer, ForeignKey( "visualization.id" ), index=True ), @@ -1026,7 +1026,7 @@ model.VisualizationTagAssociation.table = Table( "visualization_tag_association" Column( "user_tname", TrimmedString(255), index=True), Column( "value", TrimmedString(255), index=True), Column( "user_value", TrimmedString(255), index=True) ) - + model.ToolTagAssociation.table = Table( "tool_tag_association", metadata, Column( "id", Integer, primary_key=True ), Column( "tool_id", TrimmedString(255), index=True ), @@ -1035,7 +1035,7 @@ model.ToolTagAssociation.table = Table( "tool_tag_association", metadata, Column( "user_tname", TrimmedString(255), index=True), Column( "value", TrimmedString(255), index=True), Column( "user_value", TrimmedString(255), index=True) ) - + # Annotation tables. model.HistoryAnnotationAssociation.table = Table( "history_annotation_association", metadata, @@ -1061,58 +1061,58 @@ model.WorkflowStepAnnotationAssociation.table = Table( "workflow_step_annotation Column( "workflow_step_id", Integer, ForeignKey( "workflow_step.id" ), index=True ), Column( "user_id", Integer, ForeignKey( "galaxy_user.id" ), index=True ), Column( "annotation", TEXT, index=True) ) - + model.PageAnnotationAssociation.table = Table( "page_annotation_association", metadata, Column( "id", Integer, primary_key=True ), Column( "page_id", Integer, ForeignKey( "page.id" ), index=True ), Column( "user_id", Integer, ForeignKey( "galaxy_user.id" ), index=True ), Column( "annotation", TEXT, index=True) ) - + model.VisualizationAnnotationAssociation.table = Table( "visualization_annotation_association", metadata, Column( "id", Integer, primary_key=True ), Column( "visualization_id", Integer, ForeignKey( "visualization.id" ), index=True ), Column( "user_id", Integer, ForeignKey( "galaxy_user.id" ), index=True ), Column( "annotation", TEXT, index=True) ) - + # Ratings tables. model.HistoryRatingAssociation.table = Table( "history_rating_association", metadata, Column( "id", Integer, primary_key=True ), Column( "history_id", Integer, ForeignKey( "history.id" ), index=True ), Column( "user_id", Integer, ForeignKey( "galaxy_user.id" ), index=True ), Column( "rating", Integer, index=True) ) - + model.HistoryDatasetAssociationRatingAssociation.table = Table( "history_dataset_association_rating_association", metadata, Column( "id", Integer, primary_key=True ), Column( "history_dataset_association_id", Integer, ForeignKey( "history_dataset_association.id" ), index=True ), Column( "user_id", Integer, ForeignKey( "galaxy_user.id" ), index=True ), Column( "rating", Integer, index=True) ) - + model.StoredWorkflowRatingAssociation.table = Table( "stored_workflow_rating_association", metadata, Column( "id", Integer, primary_key=True ), Column( "stored_workflow_id", Integer, ForeignKey( "stored_workflow.id" ), index=True ), Column( "user_id", Integer, ForeignKey( "galaxy_user.id" ), index=True ), Column( "rating", Integer, index=True) ) - + model.PageRatingAssociation.table = Table( "page_rating_association", metadata, Column( "id", Integer, primary_key=True ), Column( "page_id", Integer, ForeignKey( "page.id" ), index=True ), Column( "user_id", Integer, ForeignKey( "galaxy_user.id" ), index=True ), Column( "rating", Integer, index=True) ) - + model.VisualizationRatingAssociation.table = Table( "visualization_rating_association", metadata, Column( "id", Integer, primary_key=True ), Column( "visualization_id", Integer, ForeignKey( "visualization.id" ), index=True ), Column( "user_id", Integer, ForeignKey( "galaxy_user.id" ), index=True ), Column( "rating", Integer, index=True) ) - + # User tables. - + model.UserPreference.table = Table( "user_preference", metadata, Column( "id", Integer, primary_key=True ), Column( "user_id", Integer, ForeignKey( "galaxy_user.id" ), index=True ), Column( "name", Unicode( 255 ), index=True), Column( "value", Unicode( 1024 ) ) ) - + model.UserAction.table = Table( "user_action", metadata, Column( "id", Integer, primary_key=True ), Column( "create_time", DateTime, default=now ), @@ -1128,12 +1128,12 @@ model.APIKeys.table = Table( "api_keys", metadata, Column( "user_id", Integer, ForeignKey( "galaxy_user.id" ), index=True ), Column( "key", TrimmedString( 32 ), index=True, unique=True ) ) -# With the tables defined we can define the mappers and setup the +# With the tables defined we can define the mappers and setup the # relationships between the model objects. mapper( model.Sample, model.Sample.table, - properties=dict( + properties=dict( events=relation( model.SampleEvent, backref="sample", order_by=desc( model.SampleEvent.table.c.update_time ) ), datasets=relation( model.SampleDataset, backref="sample", @@ -1143,7 +1143,7 @@ mapper( model.Sample, model.Sample.table, request=relation( model.Request, primaryjoin=( model.Sample.table.c.request_id == model.Request.table.c.id ) ), folder=relation( model.LibraryFolder, - primaryjoin=( model.Sample.table.c.folder_id == model.LibraryFolder.table.c.id ) ), + primaryjoin=( model.Sample.table.c.folder_id == model.LibraryFolder.table.c.id ) ), library=relation( model.Library, primaryjoin=( model.Sample.table.c.library_id == model.Library.table.c.id ) ), history=relation( model.History, @@ -1182,11 +1182,11 @@ mapper( model.ExternalService, model.ExternalService.table, model.FormDefinition.table.c.id ) ), form_values=relation( model.FormValues, primaryjoin=( model.ExternalService.table.c.form_values_id == - model.FormValues.table.c.id ) ) + model.FormValues.table.c.id ) ) ) ) -mapper( model.RequestType, model.RequestType.table, - properties=dict( states=relation( model.SampleState, +mapper( model.RequestType, model.RequestType.table, + properties=dict( states=relation( model.SampleState, backref="request_type", primaryjoin=( model.RequestType.table.c.id == model.SampleState.table.c.request_type_id ), order_by=asc( model.SampleState.table.c.update_time ) ), @@ -1198,7 +1198,7 @@ mapper( model.RequestType, model.RequestType.table, mapper( model.RequestTypeExternalServiceAssociation, model.RequestTypeExternalServiceAssociation.table, properties=dict( - request_type=relation( model.RequestType, + request_type=relation( model.RequestType, primaryjoin=( ( model.RequestTypeExternalServiceAssociation.table.c.request_type_id == model.RequestType.table.c.id ) ), backref="external_service_associations" ), external_service=relation( model.ExternalService, primaryjoin=( model.RequestTypeExternalServiceAssociation.table.c.external_service_id @@ -1226,19 +1226,19 @@ mapper( model.FormDefinitionCurrent, model.FormDefinitionCurrent.table, primaryjoin=( model.FormDefinitionCurrent.table.c.latest_form_id == model.FormDefinition.table.c.id ) ) ) ) -mapper( model.SampleEvent, model.SampleEvent.table, +mapper( model.SampleEvent, model.SampleEvent.table, properties=dict( state=relation( model.SampleState, primaryjoin=( model.SampleEvent.table.c.sample_state_id == model.SampleState.table.c.id ) ), ) ) - + mapper( model.SampleState, model.SampleState.table, properties=None ) mapper( model.SampleDataset, model.SampleDataset.table, properties=dict( external_service=relation( model.ExternalService, primaryjoin=( model.SampleDataset.table.c.external_service_id == model.ExternalService.table.c.id ) ) - ) + ) ) @@ -1252,20 +1252,20 @@ mapper( model.RequestTypeRunAssociation, model.RequestTypeRunAssociation.table, mapper( model.Run, model.Run.table, properties=dict( template=relation( model.FormDefinition, - primaryjoin=( model.Run.table.c.form_definition_id == model.FormDefinition.table.c.id ) ), + primaryjoin=( model.Run.table.c.form_definition_id == model.FormDefinition.table.c.id ) ), info=relation( model.FormValues, primaryjoin=( model.Run.table.c.form_values_id == model.FormValues.table.c.id ) ) ) ) mapper( model.UserAddress, model.UserAddress.table, - properties=dict( + properties=dict( user=relation( model.User, primaryjoin=( model.UserAddress.table.c.user_id == model.User.table.c.id ), backref='addresses', - order_by=desc(model.UserAddress.table.c.update_time)), + order_by=desc(model.UserAddress.table.c.update_time)), ) ) mapper( model.UserOpenID, model.UserOpenID.table, - properties=dict( + properties=dict( session=relation( model.GalaxySession, primaryjoin=( model.UserOpenID.table.c.session_id == model.GalaxySession.table.c.id ), backref='openids', @@ -1279,38 +1279,38 @@ mapper( model.UserOpenID, model.UserOpenID.table, mapper( model.ValidationError, model.ValidationError.table ) mapper( model.HistoryDatasetAssociation, model.HistoryDatasetAssociation.table, - properties=dict( - dataset=relation( - model.Dataset, + properties=dict( + dataset=relation( + model.Dataset, primaryjoin=( model.Dataset.table.c.id == model.HistoryDatasetAssociation.table.c.dataset_id ), lazy=False ), # .history defined in History mapper - copied_from_history_dataset_association=relation( - model.HistoryDatasetAssociation, + copied_from_history_dataset_association=relation( + model.HistoryDatasetAssociation, primaryjoin=( model.HistoryDatasetAssociation.table.c.copied_from_history_dataset_association_id == model.HistoryDatasetAssociation.table.c.id ), remote_side=[model.HistoryDatasetAssociation.table.c.id], uselist=False ), - copied_to_history_dataset_associations=relation( - model.HistoryDatasetAssociation, + copied_to_history_dataset_associations=relation( + model.HistoryDatasetAssociation, primaryjoin=( model.HistoryDatasetAssociation.table.c.copied_from_history_dataset_association_id == model.HistoryDatasetAssociation.table.c.id ) ), - copied_from_library_dataset_dataset_association=relation( - model.LibraryDatasetDatasetAssociation, + copied_from_library_dataset_dataset_association=relation( + model.LibraryDatasetDatasetAssociation, primaryjoin=( model.HistoryDatasetAssociation.table.c.copied_from_library_dataset_dataset_association_id == model.LibraryDatasetDatasetAssociation.table.c.id ), uselist=False ), - copied_to_library_dataset_dataset_associations=relation( - model.LibraryDatasetDatasetAssociation, + copied_to_library_dataset_dataset_associations=relation( + model.LibraryDatasetDatasetAssociation, primaryjoin=( model.HistoryDatasetAssociation.table.c.copied_from_library_dataset_dataset_association_id == model.LibraryDatasetDatasetAssociation.table.c.id ) ), - implicitly_converted_datasets=relation( - model.ImplicitlyConvertedDatasetAssociation, + implicitly_converted_datasets=relation( + model.ImplicitlyConvertedDatasetAssociation, primaryjoin=( model.ImplicitlyConvertedDatasetAssociation.table.c.hda_parent_id == model.HistoryDatasetAssociation.table.c.id ) ), - implicitly_converted_parent_datasets=relation( - model.ImplicitlyConvertedDatasetAssociation, + implicitly_converted_parent_datasets=relation( + model.ImplicitlyConvertedDatasetAssociation, primaryjoin=( model.ImplicitlyConvertedDatasetAssociation.table.c.hda_id == model.HistoryDatasetAssociation.table.c.id ) ), - children=relation( - model.HistoryDatasetAssociation, + children=relation( + model.HistoryDatasetAssociation, primaryjoin=( model.HistoryDatasetAssociation.table.c.parent_id == model.HistoryDatasetAssociation.table.c.id ), backref=backref( "parent", primaryjoin=( model.HistoryDatasetAssociation.table.c.parent_id == model.HistoryDatasetAssociation.table.c.id ), remote_side=[model.HistoryDatasetAssociation.table.c.id], uselist=False ) ), - visible_children=relation( - model.HistoryDatasetAssociation, + visible_children=relation( + model.HistoryDatasetAssociation, primaryjoin=( ( model.HistoryDatasetAssociation.table.c.parent_id == model.HistoryDatasetAssociation.table.c.id ) & ( model.HistoryDatasetAssociation.table.c.visible == True ) ) ), tags=relation( model.HistoryDatasetAssociationTagAssociation, order_by=model.HistoryDatasetAssociationTagAssociation.table.c.id, backref='history_tag_associations' ), annotations=relation( model.HistoryDatasetAssociationAnnotationAssociation, order_by=model.HistoryDatasetAssociationAnnotationAssociation.table.c.id, backref="hdas" ), @@ -1318,21 +1318,21 @@ mapper( model.HistoryDatasetAssociation, model.HistoryDatasetAssociation.table, ) mapper( model.Dataset, model.Dataset.table, - properties=dict( - history_associations=relation( - model.HistoryDatasetAssociation, + properties=dict( + history_associations=relation( + model.HistoryDatasetAssociation, primaryjoin=( model.Dataset.table.c.id == model.HistoryDatasetAssociation.table.c.dataset_id ) ), - active_history_associations=relation( - model.HistoryDatasetAssociation, + active_history_associations=relation( + model.HistoryDatasetAssociation, primaryjoin=( ( model.Dataset.table.c.id == model.HistoryDatasetAssociation.table.c.dataset_id ) & ( model.HistoryDatasetAssociation.table.c.deleted == False ) & ( model.HistoryDatasetAssociation.table.c.purged == False ) ) ), purged_history_associations=relation( model.HistoryDatasetAssociation, primaryjoin=( ( model.Dataset.table.c.id == model.HistoryDatasetAssociation.table.c.dataset_id ) & ( model.HistoryDatasetAssociation.table.c.purged == True ) ) ), - library_associations=relation( - model.LibraryDatasetDatasetAssociation, + library_associations=relation( + model.LibraryDatasetDatasetAssociation, primaryjoin=( model.Dataset.table.c.id == model.LibraryDatasetDatasetAssociation.table.c.dataset_id ) ), - active_library_associations=relation( - model.LibraryDatasetDatasetAssociation, + active_library_associations=relation( + model.LibraryDatasetDatasetAssociation, primaryjoin=( ( model.Dataset.table.c.id == model.LibraryDatasetDatasetAssociation.table.c.dataset_id ) & ( model.LibraryDatasetDatasetAssociation.table.c.deleted == False ) ) ), tags=relation(model.DatasetTagAssociation, order_by=model.DatasetTagAssociation.table.c.id, backref='datasets') ) ) @@ -1340,7 +1340,7 @@ mapper( model.Dataset, model.Dataset.table, mapper( model.HistoryDatasetAssociationDisplayAtAuthorization, model.HistoryDatasetAssociationDisplayAtAuthorization.table, properties=dict( history_dataset_association = relation( model.HistoryDatasetAssociation ), user = relation( model.User ) ) ) - + mapper( model.HistoryDatasetAssociationSubset, model.HistoryDatasetAssociationSubset.table, properties=dict( hda = relation( model.HistoryDatasetAssociation, primaryjoin=( model.HistoryDatasetAssociationSubset.table.c.history_dataset_association_id == model.HistoryDatasetAssociation.table.c.id ) ), @@ -1348,18 +1348,18 @@ mapper( model.HistoryDatasetAssociationSubset, model.HistoryDatasetAssociationSu primaryjoin=( model.HistoryDatasetAssociationSubset.table.c.history_dataset_association_subset_id == model.HistoryDatasetAssociation.table.c.id ) ) ) ) -mapper( model.ImplicitlyConvertedDatasetAssociation, model.ImplicitlyConvertedDatasetAssociation.table, - properties=dict( parent_hda=relation( - model.HistoryDatasetAssociation, +mapper( model.ImplicitlyConvertedDatasetAssociation, model.ImplicitlyConvertedDatasetAssociation.table, + properties=dict( parent_hda=relation( + model.HistoryDatasetAssociation, primaryjoin=( model.ImplicitlyConvertedDatasetAssociation.table.c.hda_parent_id == model.HistoryDatasetAssociation.table.c.id ) ), - parent_ldda=relation( - model.LibraryDatasetDatasetAssociation, + parent_ldda=relation( + model.LibraryDatasetDatasetAssociation, primaryjoin=( model.ImplicitlyConvertedDatasetAssociation.table.c.ldda_parent_id == model.LibraryDatasetDatasetAssociation.table.c.id ) ), - dataset_ldda=relation( - model.LibraryDatasetDatasetAssociation, + dataset_ldda=relation( + model.LibraryDatasetDatasetAssociation, primaryjoin=( model.ImplicitlyConvertedDatasetAssociation.table.c.ldda_id == model.LibraryDatasetDatasetAssociation.table.c.id ) ), - dataset=relation( - model.HistoryDatasetAssociation, + dataset=relation( + model.HistoryDatasetAssociation, primaryjoin=( model.ImplicitlyConvertedDatasetAssociation.table.c.hda_id == model.HistoryDatasetAssociation.table.c.id ) ) ) ) mapper( model.History, model.History.table, @@ -1370,10 +1370,10 @@ mapper( model.History, model.History.table, order_by=asc( model.HistoryDatasetAssociation.table.c.hid ), viewonly=True ), tags=relation( model.HistoryTagAssociation, order_by=model.HistoryTagAssociation.table.c.id, backref="histories" ), annotations=relation( model.HistoryAnnotationAssociation, order_by=model.HistoryAnnotationAssociation.table.c.id, backref="histories" ), - ratings=relation( model.HistoryRatingAssociation, order_by=model.HistoryRatingAssociation.table.c.id, backref="histories" ) ) + ratings=relation( model.HistoryRatingAssociation, order_by=model.HistoryRatingAssociation.table.c.id, backref="histories" ) ) ) -# Set up proxy so that +# Set up proxy so that # History.users_shared_with # returns a list of users that history is shared with. model.History.users_shared_with_dot_users = association_proxy( 'users_shared_with', 'user' ) @@ -1383,9 +1383,9 @@ mapper( model.HistoryUserShareAssociation, model.HistoryUserShareAssociation.tab history=relation( model.History, backref='users_shared_with' ) ) ) -mapper( model.User, model.User.table, +mapper( model.User, model.User.table, properties=dict( histories=relation( model.History, backref="user", - order_by=desc(model.History.table.c.update_time) ), + order_by=desc(model.History.table.c.update_time) ), active_histories=relation( model.History, primaryjoin=( ( model.History.table.c.user_id == model.User.table.c.id ) & ( not_( model.History.table.c.deleted ) ) ), order_by=desc( model.History.table.c.update_time ) ), galaxy_sessions=relation( model.GalaxySession, order_by=desc( model.GalaxySession.table.c.update_time ) ), stored_workflow_menu_entries=relation( model.StoredWorkflowMenuEntry, backref="user", @@ -1398,7 +1398,7 @@ mapper( model.User, model.User.table, primaryjoin=( model.User.table.c.form_values_id == model.FormValues.table.c.id ) ), api_keys=relation( model.APIKeys, backref="user", order_by=desc( model.APIKeys.table.c.create_time ) ), ) ) - + # Set up proxy so that this syntax is possible: # .preferences[pref_name] = pref_value model.User.preferences = association_proxy('_preferences', 'value', creator=model.UserPreference) @@ -1428,7 +1428,7 @@ mapper( model.Role, model.Role.table, mapper( model.UserRoleAssociation, model.UserRoleAssociation.table, properties=dict( user=relation( model.User, backref="roles" ), - non_private_roles=relation( model.User, + non_private_roles=relation( model.User, backref="non_private_roles", primaryjoin=( ( model.User.table.c.id == model.UserRoleAssociation.table.c.user_id ) & ( model.UserRoleAssociation.table.c.role_id == model.Role.table.c.id ) & not_( model.Role.table.c.name == model.User.table.c.email ) ) ), role=relation( model.Role ) @@ -1500,7 +1500,7 @@ mapper( model.Library, model.Library.table, mapper( model.ExtendedMetadata, model.ExtendedMetadata.table, properties=dict( - children=relation( + children=relation( model.ExtendedMetadataIndex, primaryjoin=( model.ExtendedMetadataIndex.table.c.extended_metadata_id == model.ExtendedMetadata.table.c.id ), backref=backref("parent", @@ -1514,7 +1514,7 @@ mapper( model.ExtendedMetadataIndex, model.ExtendedMetadataIndex.table, properties=dict( extended_metadata=relation( model.ExtendedMetadata, - primaryjoin=( ( model.ExtendedMetadataIndex.table.c.extended_metadata_id == model.ExtendedMetadata.table.c.id ) ) + primaryjoin=( ( model.ExtendedMetadataIndex.table.c.extended_metadata_id == model.ExtendedMetadata.table.c.id ) ) ) ) ) @@ -1524,7 +1524,7 @@ mapper( model.LibraryInfoAssociation, model.LibraryInfoAssociation.table, properties=dict( library=relation( model.Library, primaryjoin=( ( model.LibraryInfoAssociation.table.c.library_id == model.Library.table.c.id ) & ( not_( model.LibraryInfoAssociation.table.c.deleted ) ) ), backref="info_association" ), template=relation( model.FormDefinition, - primaryjoin=( model.LibraryInfoAssociation.table.c.form_definition_id == model.FormDefinition.table.c.id ) ), + primaryjoin=( model.LibraryInfoAssociation.table.c.form_definition_id == model.FormDefinition.table.c.id ) ), info=relation( model.FormValues, primaryjoin=( model.LibraryInfoAssociation.table.c.form_values_id == model.FormValues.table.c.id ) ) ) ) @@ -1557,53 +1557,53 @@ mapper( model.LibraryFolderInfoAssociation, model.LibraryFolderInfoAssociation.t properties=dict( folder=relation( model.LibraryFolder, primaryjoin=( ( model.LibraryFolderInfoAssociation.table.c.library_folder_id == model.LibraryFolder.table.c.id ) & ( not_( model.LibraryFolderInfoAssociation.table.c.deleted ) ) ), backref="info_association" ), template=relation( model.FormDefinition, - primaryjoin=( model.LibraryFolderInfoAssociation.table.c.form_definition_id == model.FormDefinition.table.c.id ) ), + primaryjoin=( model.LibraryFolderInfoAssociation.table.c.form_definition_id == model.FormDefinition.table.c.id ) ), info=relation( model.FormValues, primaryjoin=( model.LibraryFolderInfoAssociation.table.c.form_values_id == model.FormValues.table.c.id ) ) ) ) mapper( model.LibraryDataset, model.LibraryDataset.table, - properties=dict( + properties=dict( folder=relation( model.LibraryFolder ), library_dataset_dataset_association=relation( model.LibraryDatasetDatasetAssociation, primaryjoin=( model.LibraryDataset.table.c.library_dataset_dataset_association_id == model.LibraryDatasetDatasetAssociation.table.c.id ) ), expired_datasets = relation( model.LibraryDatasetDatasetAssociation, foreign_keys=[model.LibraryDataset.table.c.id,model.LibraryDataset.table.c.library_dataset_dataset_association_id ], primaryjoin=( ( model.LibraryDataset.table.c.id == model.LibraryDatasetDatasetAssociation.table.c.library_dataset_id ) & ( not_( model.LibraryDataset.table.c.library_dataset_dataset_association_id == model.LibraryDatasetDatasetAssociation.table.c.id ) ) ), viewonly=True, uselist=True ) ) ) mapper( model.LibraryDatasetDatasetAssociation, model.LibraryDatasetDatasetAssociation.table, - properties=dict( + properties=dict( dataset=relation( model.Dataset ), library_dataset = relation( model.LibraryDataset, primaryjoin=( model.LibraryDatasetDatasetAssociation.table.c.library_dataset_id == model.LibraryDataset.table.c.id ) ), #user=relation( model.User.mapper ), user=relation( model.User ), - copied_from_library_dataset_dataset_association=relation( - model.LibraryDatasetDatasetAssociation, + copied_from_library_dataset_dataset_association=relation( + model.LibraryDatasetDatasetAssociation, primaryjoin=( model.LibraryDatasetDatasetAssociation.table.c.copied_from_library_dataset_dataset_association_id == model.LibraryDatasetDatasetAssociation.table.c.id ), remote_side=[model.LibraryDatasetDatasetAssociation.table.c.id], uselist=False ), - copied_to_library_dataset_dataset_associations=relation( - model.LibraryDatasetDatasetAssociation, + copied_to_library_dataset_dataset_associations=relation( + model.LibraryDatasetDatasetAssociation, primaryjoin=( model.LibraryDatasetDatasetAssociation.table.c.copied_from_library_dataset_dataset_association_id == model.LibraryDatasetDatasetAssociation.table.c.id ) ), - copied_from_history_dataset_association=relation( - model.HistoryDatasetAssociation, + copied_from_history_dataset_association=relation( + model.HistoryDatasetAssociation, primaryjoin=( model.LibraryDatasetDatasetAssociation.table.c.copied_from_history_dataset_association_id == model.HistoryDatasetAssociation.table.c.id ), uselist=False ), - copied_to_history_dataset_associations=relation( - model.HistoryDatasetAssociation, + copied_to_history_dataset_associations=relation( + model.HistoryDatasetAssociation, primaryjoin=( model.HistoryDatasetAssociation.table.c.copied_from_library_dataset_dataset_association_id == model.LibraryDatasetDatasetAssociation.table.c.id ) ), - implicitly_converted_datasets=relation( - model.ImplicitlyConvertedDatasetAssociation, + implicitly_converted_datasets=relation( + model.ImplicitlyConvertedDatasetAssociation, primaryjoin=( model.ImplicitlyConvertedDatasetAssociation.table.c.ldda_parent_id == model.LibraryDatasetDatasetAssociation.table.c.id ) ), - children=relation( - model.LibraryDatasetDatasetAssociation, + children=relation( + model.LibraryDatasetDatasetAssociation, primaryjoin=( model.LibraryDatasetDatasetAssociation.table.c.parent_id == model.LibraryDatasetDatasetAssociation.table.c.id ), backref=backref( "parent", primaryjoin=( model.LibraryDatasetDatasetAssociation.table.c.parent_id == model.LibraryDatasetDatasetAssociation.table.c.id ), remote_side=[model.LibraryDatasetDatasetAssociation.table.c.id] ) ), - visible_children=relation( - model.LibraryDatasetDatasetAssociation, + visible_children=relation( + model.LibraryDatasetDatasetAssociation, primaryjoin=( ( model.LibraryDatasetDatasetAssociation.table.c.parent_id == model.LibraryDatasetDatasetAssociation.table.c.id ) & ( model.LibraryDatasetDatasetAssociation.table.c.visible == True ) ) ), extended_metadata=relation( model.ExtendedMetadata, - primaryjoin=( ( model.LibraryDatasetDatasetAssociation.table.c.extended_metadata_id == model.ExtendedMetadata.table.c.id ) ) + primaryjoin=( ( model.LibraryDatasetDatasetAssociation.table.c.extended_metadata_id == model.ExtendedMetadata.table.c.id ) ) ) )) @@ -1611,7 +1611,7 @@ mapper( model.LibraryDatasetDatasetInfoAssociation, model.LibraryDatasetDatasetI properties=dict( library_dataset_dataset_association=relation( model.LibraryDatasetDatasetAssociation, primaryjoin=( ( model.LibraryDatasetDatasetInfoAssociation.table.c.library_dataset_dataset_association_id == model.LibraryDatasetDatasetAssociation.table.c.id ) & ( not_( model.LibraryDatasetDatasetInfoAssociation.table.c.deleted ) ) ), backref="info_association" ), template=relation( model.FormDefinition, - primaryjoin=( model.LibraryDatasetDatasetInfoAssociation.table.c.form_definition_id == model.FormDefinition.table.c.id ) ), + primaryjoin=( model.LibraryDatasetDatasetInfoAssociation.table.c.form_definition_id == model.FormDefinition.table.c.id ) ), info=relation( model.FormValues, primaryjoin=( model.LibraryDatasetDatasetInfoAssociation.table.c.form_values_id == model.FormValues.table.c.id ) ) ) ) @@ -1631,15 +1631,15 @@ mapper( model.JobToOutputLibraryDatasetAssociation, model.JobToOutputLibraryData mapper( model.JobParameter, model.JobParameter.table ) mapper( model.JobExternalOutputMetadata, model.JobExternalOutputMetadata.table, - properties=dict( job = relation( model.Job ), + properties=dict( job = relation( model.Job ), history_dataset_association = relation( model.HistoryDatasetAssociation, lazy = False ), library_dataset_dataset_association = relation( model.LibraryDatasetDatasetAssociation, lazy = False ) ) ) - + mapper( model.JobExportHistoryArchive, model.JobExportHistoryArchive.table, properties=dict( job = relation( model.Job ), history = relation( model.History ), dataset = relation( model.Dataset ) ) ) - + mapper( model.JobImportHistoryArchive, model.JobImportHistoryArchive.table, properties=dict( job = relation( model.Job ), history = relation( model.History ) ) ) @@ -1654,10 +1654,10 @@ mapper(model.PostJobAction, model.PostJobAction.table, properties=dict(workflow_step = relation( model.WorkflowStep, backref='post_job_actions', primaryjoin=(model.WorkflowStep.table.c.id == model.PostJobAction.table.c.workflow_step_id)))) mapper( model.PostJobActionAssociation, model.PostJobActionAssociation.table, - properties=dict( job = relation( model.Job ), + properties=dict( job = relation( model.Job ), post_job_action = relation( model.PostJobAction) ) ) -mapper( model.Job, model.Job.table, +mapper( model.Job, model.Job.table, #properties=dict( user=relation( model.User.mapper ), properties=dict( user=relation( model.User ), galaxy_session=relation( model.GalaxySession ), @@ -1669,18 +1669,18 @@ mapper( model.Job, model.Job.table, post_job_actions=relation( model.PostJobActionAssociation, lazy=False ), input_library_datasets=relation( model.JobToInputLibraryDatasetAssociation ), output_library_datasets=relation( model.JobToOutputLibraryDatasetAssociation ), - external_output_metadata = relation( model.JobExternalOutputMetadata, lazy = False ), + external_output_metadata = relation( model.JobExternalOutputMetadata, lazy = False ), tasks = relation(model.Task) ) ) mapper( model.Task, model.Task.table, properties=dict( job = relation( model.Job ) ) ) - -mapper( model.DeferredJob, model.DeferredJob.table, + +mapper( model.DeferredJob, model.DeferredJob.table, properties = {} ) -mapper( model.TransferJob, model.TransferJob.table, +mapper( model.TransferJob, model.TransferJob.table, properties = {} ) - + mapper( model.Event, model.Event.table, properties=dict( history=relation( model.History ), galaxy_session=relation( model.GalaxySession ), @@ -1694,7 +1694,7 @@ mapper( model.GalaxySession, model.GalaxySession.table, user=relation( model.User ) ) ) mapper( model.GalaxySessionToHistoryAssociation, model.GalaxySessionToHistoryAssociation.table, - properties=dict( galaxy_session=relation( model.GalaxySession ), + properties=dict( galaxy_session=relation( model.GalaxySession ), history=relation( model.History ) ) ) mapper( model.Workflow, model.Workflow.table, @@ -1709,7 +1709,7 @@ mapper( model.Workflow, model.Workflow.table, mapper( model.WorkflowStep, model.WorkflowStep.table, properties=dict( - tags=relation(model.WorkflowStepTagAssociation, order_by=model.WorkflowStepTagAssociation.table.c.id, backref="workflow_steps"), + tags=relation(model.WorkflowStepTagAssociation, order_by=model.WorkflowStepTagAssociation.table.c.id, backref="workflow_steps"), annotations=relation( model.WorkflowStepAnnotationAssociation, order_by=model.WorkflowStepAnnotationAssociation.table.c.id, backref="workflow_steps" ) ) ) @@ -1742,7 +1742,7 @@ mapper( model.StoredWorkflow, model.StoredWorkflow.table, ratings=relation( model.StoredWorkflowRatingAssociation, order_by=model.StoredWorkflowRatingAssociation.table.c.id, backref="stored_workflows" ) ) ) -# Set up proxy so that +# Set up proxy so that # StoredWorkflow.users_shared_with # returns a list of users that workflow is shared with. model.StoredWorkflow.users_shared_with_dot_users = association_proxy( 'users_shared_with', 'user' ) @@ -1780,9 +1780,9 @@ mapper( model.Page, model.Page.table, lazy=False ), tags=relation(model.PageTagAssociation, order_by=model.PageTagAssociation.table.c.id, backref="pages"), annotations=relation( model.PageAnnotationAssociation, order_by=model.PageAnnotationAssociation.table.c.id, backref="pages" ), - ratings=relation( model.PageRatingAssociation, order_by=model.PageRatingAssociation.table.c.id, backref="pages" ) + ratings=relation( model.PageRatingAssociation, order_by=model.PageRatingAssociation.table.c.id, backref="pages" ) ) ) - + mapper( model.ToolShedRepository, model.ToolShedRepository.table, properties=dict( tool_versions=relation( model.ToolVersion, primaryjoin=( model.ToolShedRepository.table.c.id == model.ToolVersion.table.c.tool_shed_repository_id ), @@ -1796,7 +1796,7 @@ mapper( model.ToolShedRepository, model.ToolShedRepository.table, mapper( model.RepositoryRepositoryDependencyAssociation, model.RepositoryRepositoryDependencyAssociation.table, properties=dict( repository=relation( model.ToolShedRepository, - primaryjoin=( model.RepositoryRepositoryDependencyAssociation.table.c.tool_shed_repository_id == model.ToolShedRepository.table.c.id ) ), + primaryjoin=( model.RepositoryRepositoryDependencyAssociation.table.c.tool_shed_repository_id == model.ToolShedRepository.table.c.id ) ), repository_dependency=relation( model.RepositoryDependency, primaryjoin=( model.RepositoryRepositoryDependencyAssociation.table.c.repository_dependency_id == model.RepositoryDependency.table.c.id ) ) ) ) @@ -1807,7 +1807,7 @@ mapper( model.RepositoryDependency, model.RepositoryDependency.table, mapper( model.ToolDependency, model.ToolDependency.table ) mapper( model.ToolVersion, model.ToolVersion.table, - properties=dict( + properties=dict( parent_tool_association=relation( model.ToolVersionAssociation, primaryjoin=( model.ToolVersion.table.c.id == model.ToolVersionAssociation.table.c.tool_id ) ), child_tool_association=relation( model.ToolVersionAssociation, @@ -1817,11 +1817,11 @@ mapper( model.ToolVersion, model.ToolVersion.table, mapper( model.ToolVersionAssociation, model.ToolVersionAssociation.table ) -# Set up proxy so that +# Set up proxy so that # Page.users_shared_with # returns a list of users that page is shared with. model.Page.users_shared_with_dot_users = association_proxy( 'users_shared_with', 'user' ) - + mapper( model.PageUserShareAssociation, model.PageUserShareAssociation.table, properties=dict( user=relation( model.User, backref='pages_shared_by_others' ), page=relation( model.Page, backref='users_shared_with' ) @@ -1839,23 +1839,23 @@ mapper( model.Visualization, model.Visualization.table, lazy=False ), tags=relation( model.VisualizationTagAssociation, order_by=model.VisualizationTagAssociation.table.c.id, backref="visualizations" ), annotations=relation( model.VisualizationAnnotationAssociation, order_by=model.VisualizationAnnotationAssociation.table.c.id, backref="visualizations" ), - ratings=relation( model.VisualizationRatingAssociation, order_by=model.VisualizationRatingAssociation.table.c.id, backref="visualizations" ) + ratings=relation( model.VisualizationRatingAssociation, order_by=model.VisualizationRatingAssociation.table.c.id, backref="visualizations" ) ) ) - -# Set up proxy so that + +# Set up proxy so that # Visualization.users_shared_with # returns a list of users that visualization is shared with. model.Visualization.users_shared_with_dot_users = association_proxy( 'users_shared_with', 'user' ) - + mapper( model.VisualizationUserShareAssociation, model.VisualizationUserShareAssociation.table, properties=dict( user=relation( model.User, backref='visualizations_shared_by_others' ), visualization=relation( model.Visualization, backref='users_shared_with' ) ) ) - + # Tag tables. mapper( model.Tag, model.Tag.table, - properties=dict( children=relation(model.Tag, backref=backref( 'parent', remote_side=[model.Tag.table.c.id] ) ) + properties=dict( children=relation(model.Tag, backref=backref( 'parent', remote_side=[model.Tag.table.c.id] ) ) ) ) mapper( model.HistoryTagAssociation, model.HistoryTagAssociation.table, @@ -1873,33 +1873,33 @@ mapper( model.HistoryDatasetAssociationTagAssociation, model.HistoryDatasetAssoc mapper( model.PageTagAssociation, model.PageTagAssociation.table, properties=dict( tag=relation(model.Tag, backref="tagged_pages"), user=relation( model.User ) ) ) - + mapper( model.StoredWorkflowTagAssociation, model.StoredWorkflowTagAssociation.table, properties=dict( tag=relation(model.Tag, backref="tagged_workflows"), user=relation( model.User ) ) ) - + mapper( model.WorkflowStepTagAssociation, model.WorkflowStepTagAssociation.table, properties=dict( tag=relation(model.Tag, backref="tagged_workflow_steps"), user=relation( model.User ) ) ) - + mapper( model.VisualizationTagAssociation, model.VisualizationTagAssociation.table, properties=dict( tag=relation(model.Tag, backref="tagged_visualizations"), user=relation( model.User ) ) ) - + mapper( model.ToolTagAssociation, model.ToolTagAssociation.table, properties=dict( tag=relation(model.Tag, backref="tagged_tools"), user=relation( model.User ) ) ) - + # Annotation tables. - + mapper( model.HistoryAnnotationAssociation, model.HistoryAnnotationAssociation.table, properties=dict( history=relation( model.History ), user=relation( model.User ) ) ) - + mapper( model.HistoryDatasetAssociationAnnotationAssociation, model.HistoryDatasetAssociationAnnotationAssociation.table, properties=dict( hda=relation( model.HistoryDatasetAssociation ), user=relation( model.User ) ) ) - + mapper( model.StoredWorkflowAnnotationAssociation, model.StoredWorkflowAnnotationAssociation.table, properties=dict( stored_workflow=relation( model.StoredWorkflow ), user=relation( model.User ) ) ) @@ -1907,25 +1907,25 @@ mapper( model.StoredWorkflowAnnotationAssociation, model.StoredWorkflowAnnotatio mapper( model.WorkflowStepAnnotationAssociation, model.WorkflowStepAnnotationAssociation.table, properties=dict( workflow_step=relation( model.WorkflowStep ), user=relation( model.User ) ) ) - + mapper( model.PageAnnotationAssociation, model.PageAnnotationAssociation.table, properties=dict( page=relation( model.Page ), user=relation( model.User ) ) ) - + mapper( model.VisualizationAnnotationAssociation, model.VisualizationAnnotationAssociation.table, properties=dict( visualization=relation( model.Visualization ), user=relation( model.User ) ) ) - + # Rating tables. mapper( model.HistoryRatingAssociation, model.HistoryRatingAssociation.table, properties=dict( history=relation( model.History ), user=relation( model.User ) ) ) - + mapper( model.HistoryDatasetAssociationRatingAssociation, model.HistoryDatasetAssociationRatingAssociation.table, properties=dict( hda=relation( model.HistoryDatasetAssociation ), user=relation( model.User ) ) ) - + mapper( model.StoredWorkflowRatingAssociation, model.StoredWorkflowRatingAssociation.table, properties=dict( stored_workflow=relation( model.StoredWorkflow ), user=relation( model.User ) ) ) @@ -1950,19 +1950,19 @@ mapper( model.DataManagerJobAssociation, model.DataManagerJobAssociation.table, ) # User tables. - -mapper( model.UserPreference, model.UserPreference.table, + +mapper( model.UserPreference, model.UserPreference.table, properties = {} ) - -mapper( model.UserAction, model.UserAction.table, + +mapper( model.UserAction, model.UserAction.table, #properties = dict( user=relation( model.User.mapper ) ) properties = dict( user=relation( model.User ) ) ) -mapper( model.APIKeys, model.APIKeys.table, +mapper( model.APIKeys, model.APIKeys.table, properties = {} ) - + #model.HistoryDatasetAssociation.mapper.add_property( "creating_job_associations", relation( model.JobToOutputDatasetAssociation ) ) #model.LibraryDatasetDatasetAssociation.mapper.add_property( "creating_job_associations", relation( model.JobToOutputLibraryDatasetAssociation ) ) @@ -2051,7 +2051,7 @@ def init( file_path, url, engine_options={}, create_tables=False, database_query #load local galaxy security policy result.security_agent = GalaxyRBACAgent( result ) return result - + def get_suite(): """Get unittest suite for this module""" import unittest, mapping_tests From d420857e866e9cf1011008044e55cd13a03e0775 Mon Sep 17 00:00:00 2001 From: Jeremy Goecks Date: Thu, 2 May 2013 15:14:50 -0400 Subject: [PATCH 324/523] Trackster: add editable min, max labels to tracks. --- static/scripts/viz/trackster/tracks.js | 79 ++++++++++++++++---------- 1 file changed, 50 insertions(+), 29 deletions(-) diff --git a/static/scripts/viz/trackster/tracks.js b/static/scripts/viz/trackster/tracks.js index dbbfd50b810..eceb290a1eb 100644 --- a/static/scripts/viz/trackster/tracks.js +++ b/static/scripts/viz/trackster/tracks.js @@ -2853,7 +2853,10 @@ extend(TiledTrack.prototype, Drawable.prototype, Track.prototype, { /** * Actions to be taken before drawing. */ - before_draw: function() {}, + before_draw: function() { + // Clear because this is set when drawing. + this.max_height_px = 0; + }, /** * Draw track. It is possible to force a redraw rather than use cached tiles and/or clear old @@ -2930,11 +2933,15 @@ extend(TiledTrack.prototype, Drawable.prototype, Track.prototype, { /** * Add a maximum/minimum label to track. */ - _add_yaxis_label: function(type, val, pref_name) { - var css_class = (type === 'max' ? 'top' : 'bottom'), - text = (type === 'max' ? 'max' : 'min'); - - var label = this.container_div.find(".yaxislabel." + css_class); + _add_yaxis_label: function(type, val, pref_name, on_change) { + var track = this, + css_class = (type === 'max' ? 'top' : 'bottom'), + text = (type === 'max' ? 'max' : 'min'), + // Default action for on_change is to redraw track. + on_change = on_change || function() { + track.request_draw(true); + }, + label = this.container_div.find(".yaxislabel." + css_class); if (label.length !== 0) { // Label already exists, so update value. @@ -2942,14 +2949,13 @@ extend(TiledTrack.prototype, Drawable.prototype, Track.prototype, { } else { // Add label. - var track = this; label = $("
    ").text(val).make_text_editable({ num_cols: 12, on_finish: function(new_val) { $(".bs-tooltip").remove(); var new_val = parseFloat(new_val); track.prefs[ pref_name ] = (!isNaN(new_val) ? new_val : null); - track.request_draw(true); + on_change(); }, help_text: "Set " + text + " value" }).addClass('yaxislabel ' + css_class).css("color", this.prefs.label_color); @@ -3337,6 +3343,9 @@ extend(CompositeTrack.prototype, TiledTrack.prototype, { unpack_drawables: DrawableCollection.prototype.unpack_drawables, + /** + * Change mode for all tracks. + */ change_mode: function(new_mode) { TiledTrack.prototype.change_mode.call(this, new_mode); for (var i = 0; i < this.drawables.length; i++) { @@ -3426,8 +3435,6 @@ extend(CompositeTrack.prototype, TiledTrack.prototype, { // Set up and draw tile. extend(tile_data, kwargs[ 'more_tile_data' ] ); - this.tile_predraw_init(); - var canvas = track.view.canvas_manager.new_canvas(), tile_low = region.get('start'), tile_high = region.get('end'), @@ -3518,9 +3525,11 @@ extend(CompositeTrack.prototype, TiledTrack.prototype, { }, /** - * Actions taken before drawing a tile. + * Actions taken before drawing. */ - tile_predraw_init: function() { + before_draw: function() { + TiledTrack.prototype.before_draw.call(this); + // // Set min, max for LineTracks to be largest min, max. // @@ -3541,6 +3550,8 @@ extend(CompositeTrack.prototype, TiledTrack.prototype, { } } } + this.prefs.min_value = min; + this.prefs.max_value = max; // Set all tracks to smallest min, biggest max. for (var i = 0; i < this.drawables.length; i++) { @@ -3550,6 +3561,18 @@ extend(CompositeTrack.prototype, TiledTrack.prototype, { } }, + /** + * Update minimum, maximum for component tracks. + */ + update_all_min_max: function() { + var track = this; + _.each(this.drawables, function(d) { + d.prefs.min_value = track.prefs.min_value; + d.prefs.max_value = track.prefs.max_value; + }); + this.request_draw(true); + }, + /** * Actions to be taken after draw has been completed. Draw is completed when all tiles have been * drawn/fetched and shown. @@ -3572,7 +3595,15 @@ extend(CompositeTrack.prototype, TiledTrack.prototype, { this.draw_helper(true, tile.region, tile.resolution, tile.html_elt.parent(), w_scale, { height: max_height } ); tile.html_elt.remove(); } - } + } + + // Wrap function so that it can be called without object reference. + var track = this, + t = function() { track.update_all_min_max(); }; + + // Add min, max labels. + this._add_yaxis_label('min', this.drawables[0].prefs.min_value, 'min_value', t); + this._add_yaxis_label('max', this.drawables[0].prefs.max_value, 'max_value', t); } }); @@ -3700,6 +3731,12 @@ extend(LineTrack.prototype, Drawable.prototype, TiledTrack.prototype, { }); }, + /** + * Actions to be taken before drawing. + */ + // FIXME: can the default behavior be used; right now it breaks during resize. + before_draw: function() {}, + /** * Draw LineTrack tile. */ @@ -3842,14 +3879,6 @@ extend(FeatureTrack.prototype, Drawable.prototype, TiledTrack.prototype, { } }, - /** - * Actions to be taken before drawing. - */ - before_draw: function() { - // Clear because this is set when drawing. - this.max_height_px = 0; - }, - /** * Actions to be taken after draw has been completed. Draw is completed when all tiles have been * drawn/fetched and shown. @@ -4210,14 +4239,6 @@ extend(VariantTrack.prototype, Drawable.prototype, TiledTrack.prototype, { } }, - /** - * Actions to be taken before drawing. - */ - before_draw: function() { - // Clear because this is set when drawing. - this.max_height_px = 0; - }, - /** * Additional initialization required before drawing track for the first time. */ From 2e1700f6a7c6203c68815705417ca04a6708b9cf Mon Sep 17 00:00:00 2001 From: Greg Von Kuster Date: Thu, 2 May 2013 15:20:02 -0400 Subject: [PATCH 325/523] Attempt to standardize the main page title for repository pages in the tool shed by displaying the repository name. --- .../webapps/tool_shed/repository/browse_repository.mako | 2 +- .../tool_shed/repository/preview_tools_in_changeset.mako | 2 +- templates/webapps/tool_shed/repository/rate_repository.mako | 2 +- templates/webapps/tool_shed/repository/upload.mako | 6 +++--- templates/webapps/tool_shed/repository/view_changelog.mako | 2 +- templates/webapps/tool_shed/repository/view_changeset.mako | 2 +- templates/webapps/tool_shed/repository/view_repository.mako | 2 +- .../webapps/tool_shed/repository/view_tool_metadata.mako | 2 +- 8 files changed, 10 insertions(+), 10 deletions(-) diff --git a/templates/webapps/tool_shed/repository/browse_repository.mako b/templates/webapps/tool_shed/repository/browse_repository.mako index 38a503f8b07..ba6a022409a 100644 --- a/templates/webapps/tool_shed/repository/browse_repository.mako +++ b/templates/webapps/tool_shed/repository/browse_repository.mako @@ -39,7 +39,7 @@ ${render_tool_shed_repository_actions( repository )} %if can_browse_contents:
    -
    Browse ${repository.name | h} revision ${repository.tip( trans.app ) | h} (repository tip)
    +
    Repository '${repository.name | h}' revision ${repository.tip( trans.app ) | h} (repository tip)
    %if can_download:
    diff --git a/templates/webapps/tool_shed/repository/preview_tools_in_changeset.mako b/templates/webapps/tool_shed/repository/preview_tools_in_changeset.mako index 5c7ae8784b1..d0d9d3d2921 100644 --- a/templates/webapps/tool_shed/repository/preview_tools_in_changeset.mako +++ b/templates/webapps/tool_shed/repository/preview_tools_in_changeset.mako @@ -31,7 +31,7 @@ ${render_galaxy_repository_actions( repository=repository )} %endif
    -
    Repository ${repository.name | h}
    +
    Repository '${repository.name | h}'
    %if len( changeset_revision_select_field.options ) > 1:
    diff --git a/templates/webapps/tool_shed/repository/rate_repository.mako b/templates/webapps/tool_shed/repository/rate_repository.mako index 799f9811c9a..77c48bc90a0 100644 --- a/templates/webapps/tool_shed/repository/rate_repository.mako +++ b/templates/webapps/tool_shed/repository/rate_repository.mako @@ -64,7 +64,7 @@ ${render_tool_shed_repository_actions( repository, repo=None, metadata=None, cha

    -
    Rate and Review
    +
    Repository '${repository.name | h}'
    diff --git a/templates/webapps/tool_shed/repository/upload.mako b/templates/webapps/tool_shed/repository/upload.mako index c70f85c6b82..31451e3f16e 100644 --- a/templates/webapps/tool_shed/repository/upload.mako +++ b/templates/webapps/tool_shed/repository/upload.mako @@ -41,12 +41,12 @@ ${render_tool_shed_repository_actions( repository=repository)}
    -
    Upload a single file or a tarball
    +
    Repository '${repository.name | h}'
    - Uploading may take a while, depending upon the size of the file. Wait until a message is displayed in your - browser after clicking the Upload button below. + Upload a single file or tarball. Uploading may take a while, depending upon the size of the file. + Wait until a message is displayed in your browser after clicking the Upload button below.
    diff --git a/templates/webapps/tool_shed/repository/view_changelog.mako b/templates/webapps/tool_shed/repository/view_changelog.mako index 02b0bd4c594..f8b8cda2934 100644 --- a/templates/webapps/tool_shed/repository/view_changelog.mako +++ b/templates/webapps/tool_shed/repository/view_changelog.mako @@ -38,7 +38,7 @@ ${render_tool_shed_repository_actions( repository=repository )} %if can_download:
    -
    ${repository.name | h}
    +
    Repository '${repository.name | h}'
    diff --git a/templates/webapps/tool_shed/repository/view_changeset.mako b/templates/webapps/tool_shed/repository/view_changeset.mako index be95ae8cb3f..fe65564d577 100644 --- a/templates/webapps/tool_shed/repository/view_changeset.mako +++ b/templates/webapps/tool_shed/repository/view_changeset.mako @@ -37,7 +37,7 @@ ${render_tool_shed_repository_actions( repository=repository )} %if can_download:
    -
    ${repository.name | h}
    +
    Repository '${repository.name | h}'
    diff --git a/templates/webapps/tool_shed/repository/view_repository.mako b/templates/webapps/tool_shed/repository/view_repository.mako index 872b884b3d0..c415fd1161b 100644 --- a/templates/webapps/tool_shed/repository/view_repository.mako +++ b/templates/webapps/tool_shed/repository/view_repository.mako @@ -80,7 +80,7 @@

    %endif

    -
    Repository '${repository.name}'
    +
    Repository '${repository.name | h}'
    diff --git a/templates/webapps/tool_shed/repository/view_tool_metadata.mako b/templates/webapps/tool_shed/repository/view_tool_metadata.mako index 729aae74cd5..27cb0173909 100644 --- a/templates/webapps/tool_shed/repository/view_tool_metadata.mako +++ b/templates/webapps/tool_shed/repository/view_tool_metadata.mako @@ -47,7 +47,7 @@

    %if can_download:

    -
    Repository '${repository.name}'
    +
    Repository '${repository.name | h}'
    From a165a1b0de246e9f3628e812790d62b380133b26 Mon Sep 17 00:00:00 2001 From: Greg Von Kuster Date: Thu, 2 May 2013 15:44:05 -0400 Subject: [PATCH 326/523] Re-add the ability to preview and install repositories from the Valid repositories grid in the tool shed. --- lib/tool_shed/grids/repository_grids.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/tool_shed/grids/repository_grids.py b/lib/tool_shed/grids/repository_grids.py index 35f1a1fdfbd..b96b754078b 100644 --- a/lib/tool_shed/grids/repository_grids.py +++ b/lib/tool_shed/grids/repository_grids.py @@ -1424,7 +1424,7 @@ class ValidRepositoryGrid( RepositoryGrid ): columns = [ RepositoryGrid.NameColumn( "Name", key="name", - attach_popup=False ), + attach_popup=True ), RepositoryGrid.DescriptionColumn( "Synopsis", key="description", attach_popup=False ), From 167a3346656db7e78b640344e412eced60698c68 Mon Sep 17 00:00:00 2001 From: Daniel Blankenberg Date: Thu, 2 May 2013 15:46:06 -0400 Subject: [PATCH 327/523] Display error to user when genomespace file browser tool is used with no imported datasets. --- tools/genomespace/genomespace_file_browser.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tools/genomespace/genomespace_file_browser.py b/tools/genomespace/genomespace_file_browser.py index bc6e35c8f39..002b72d90b9 100644 --- a/tools/genomespace/genomespace_file_browser.py +++ b/tools/genomespace/genomespace_file_browser.py @@ -113,6 +113,10 @@ def download_from_genomespace_file_browser( json_parameter_file, genomespace_sit if name.startswith( file_url_prefix ): name = name[len( file_url_prefix ):] file_numbers.append( int( name ) ) + if not file_numbers: + if output_filename: + open( output_filename, 'wb' ) #erase contents of file + raise Exception( "You must select at least one file to import into Galaxy." ) file_numbers.sort() used_filenames = [] for file_num in file_numbers: From 7f25395ee1e7d4bde4b8dcebdedd2f2d537908f8 Mon Sep 17 00:00:00 2001 From: Nate Coraor Date: Thu, 2 May 2013 15:50:00 -0400 Subject: [PATCH 328/523] Delete/stop jobs when a history is deleted. --- lib/galaxy/webapps/galaxy/controllers/history.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lib/galaxy/webapps/galaxy/controllers/history.py b/lib/galaxy/webapps/galaxy/controllers/history.py index ed4648313c6..26b330bc2f9 100644 --- a/lib/galaxy/webapps/galaxy/controllers/history.py +++ b/lib/galaxy/webapps/galaxy/controllers/history.py @@ -562,6 +562,12 @@ class HistoryController( BaseUIController, SharableMixin, UsesAnnotations, UsesI history.purged = True self.sa_session.add( history ) self.sa_session.flush() + for hda in history.datasets: + job = hda.creating_job_associations[0].job + if job.history_id == history.id and job.state in [ trans.app.model.Job.states.QUEUED, trans.app.model.Job.states.RUNNING, trans.app.model.Job.states.NEW ]: + # No need to check other outputs since the job's parent history is this history + job.mark_deleted( trans.app.config.track_jobs_in_database ) + trans.app.job_manager.job_stop_queue.put( job.id ) # Regardless of whether it was previously deleted, we make a new history active trans.new_history() return trans.show_ok_message( "History deleted, a new history is active", refresh_frames=['history'] ) From d42a9772c6bd8eeb246a34d6ab97a47a3a86d4e0 Mon Sep 17 00:00:00 2001 From: Greg Von Kuster Date: Thu, 2 May 2013 16:44:49 -0400 Subject: [PATCH 329/523] Fix tool shed functional test I broke. --- .../tool_shed/functional/test_0000_basic_repository_features.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/tool_shed/functional/test_0000_basic_repository_features.py b/test/tool_shed/functional/test_0000_basic_repository_features.py index 4db9adf3dbb..3b269ceca6a 100644 --- a/test/tool_shed/functional/test_0000_basic_repository_features.py +++ b/test/tool_shed/functional/test_0000_basic_repository_features.py @@ -99,7 +99,7 @@ class TestBasicRepositoryFeatures( ShedTwillTestCase ): tool_metadata_strings_displayed=tool_metadata_strings_displayed, tool_page_strings_displayed=tool_page_strings_displayed ) self.check_repository_metadata( repository, tip_only=False ) - self.browse_repository( repository, strings_displayed=[ 'Browse %s revision' % repository.name, '(repository tip)' ] ) + self.browse_repository( repository, strings_displayed=[ "Repository '%s' revision" % repository.name, '(repository tip)' ] ) self.display_repository_clone_page( common.test_user_1_name, repository_name, strings_displayed=[ 'Uploaded filtering 1.1.0', latest_changeset_revision ] ) From c56b10c08208b58bc396790d2509e72e93fe9340 Mon Sep 17 00:00:00 2001 From: Daniel Blankenberg Date: Fri, 3 May 2013 10:38:16 -0400 Subject: [PATCH 330/523] Do not allow deleted datasets to be displayed. --- lib/galaxy/webapps/galaxy/controllers/dataset.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/galaxy/webapps/galaxy/controllers/dataset.py b/lib/galaxy/webapps/galaxy/controllers/dataset.py index f1d944e4ed4..2da4d77cc40 100644 --- a/lib/galaxy/webapps/galaxy/controllers/dataset.py +++ b/lib/galaxy/webapps/galaxy/controllers/dataset.py @@ -290,6 +290,8 @@ class DatasetInterface( BaseUIController, UsesAnnotations, UsesHistoryMixin, Use raise paste.httpexceptions.HTTPRequestRangeNotSatisfiable( "Invalid reference dataset id: %s." % str( hda_id ) ) if not self._can_access_dataset( trans, data ): return trans.show_error_message( "You are not allowed to access this dataset" ) + if data.deleted: + return trans.show_error_message( "The dataset you are attempting to view has been deleted." ) if data.state == trans.model.Dataset.states.UPLOAD: return trans.show_error_message( "Please wait until this dataset finishes uploading before attempting to view it." ) return data From 76a267c3ff0b20a90a16307cb879085402e9ab80 Mon Sep 17 00:00:00 2001 From: Jeremy Goecks Date: Fri, 3 May 2013 10:52:11 -0400 Subject: [PATCH 331/523] Trackster: only query feature tracks for feature locations. --- static/scripts/viz/trackster/tracks.js | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/static/scripts/viz/trackster/tracks.js b/static/scripts/viz/trackster/tracks.js index eceb290a1eb..05bece4d0d5 100644 --- a/static/scripts/viz/trackster/tracks.js +++ b/static/scripts/viz/trackster/tracks.js @@ -438,6 +438,7 @@ var DrawableCollection = function(view, container, obj_dict) { this.obj_type = obj_dict.obj_type; this.drawables = []; }; + extend(DrawableCollection.prototype, Drawable.prototype, { /** * Unpack and add drawables to the collection. @@ -560,6 +561,26 @@ extend(DrawableCollection.prototype, Drawable.prototype, { */ get_drawables: function() { return this.drawables; + }, + + /** + * Returns all tracks in collection. + */ + get_tracks: function(track_type) { + // Initialize queue with copy of drawables array. + var queue = this.drawables.slice(0), + tracks = [], + drawable; + while (queue.length !== 0) { + drawable = queue.shift(); + if (drawable instanceof track_type) { + tracks.push(drawable); + } + else if (drawable.drawables) { + queue = queue.concat(drawable.drawables); + } + } + return tracks; } }); @@ -967,8 +988,8 @@ var TracksterView = Backbone.View.extend({ source: function(request, response) { // Using current text, query each track and create list of all matching features. var all_features = [], - feature_search_deferreds = $.map(view.get_drawables(), function(drawable) { - return drawable.data_manager.search_features(request.term).success(function(dataset_features) { + feature_search_deferreds = $.map(view.get_tracks(FeatureTrack), function(t) { + return t.data_manager.search_features(request.term).success(function(dataset_features) { all_features = all_features.concat(dataset_features); }); }); From 7eb3abeb6e41af0ac5d7fe4387c85053b16830a5 Mon Sep 17 00:00:00 2001 From: Greg Von Kuster Date: Fri, 3 May 2013 11:10:45 -0400 Subject: [PATCH 332/523] Fixes for rendering the Repository Actions menu in the tool shed. --- .../common/repository_actions_menu.mako | 107 ++++++++++++++---- .../repository/manage_repository.mako | 55 +++++++-- 2 files changed, 132 insertions(+), 30 deletions(-) diff --git a/templates/webapps/tool_shed/common/repository_actions_menu.mako b/templates/webapps/tool_shed/common/repository_actions_menu.mako index 6001bcb4bce..e8b5853e03d 100644 --- a/templates/webapps/tool_shed/common/repository_actions_menu.mako +++ b/templates/webapps/tool_shed/common/repository_actions_menu.mako @@ -5,19 +5,55 @@ from tool_shed.util.review_util import can_browse_repository_reviews, changeset_revision_reviewed_by_user, get_review_by_repository_id_changeset_revision_user_id from tool_shed.util.shed_util_common import changeset_is_malicious - has_metadata = repository.metadata_revisions - has_readme = metadata and 'readme' in metadata + if repository.metadata_revisions: + has_metadata = True + else: + has_metadata = False + is_admin = trans.user_is_admin() - is_deprecated = repository.deprecated - is_new = repository.is_new( trans.app ) - is_malicious = changeset_is_malicious( trans, trans.security.encode_id( repository.id ), repository.tip( trans.app ) ) + + if repository.deprecated: + is_deprecated = True + else: + is_deprecated = False + + if repository.is_new( trans.app ): + is_new = True + else: + is_new = False + + if changeset_is_malicious( trans, trans.security.encode_id( repository.id ), repository.tip( trans.app ) ): + is_malicious = True + else: + is_malicious = False can_browse_contents = not is_new - can_browse_reviews = can_browse_repository_reviews( trans, repository ) - can_contact_owner = trans.user and trans.user != repository.user - can_deprecate = not is_new and trans.user and ( is_admin or repository.user == trans.user ) and not is_deprecated - can_push = not is_deprecated and trans.app.security_agent.can_push( trans.app, trans.user, repository ) - can_download = not is_deprecated and not is_new and ( not is_malicious or can_push ) + + if can_browse_repository_reviews( trans, repository ): + can_browse_reviews = True + else: + can_browse_reviews = False + + if trans.user and trans.user != repository.user: + can_contact_owner = True + else: + can_contact_owner = False + + if not is_new and trans.user and ( is_admin or repository.user == trans.user ) and not is_deprecated: + can_deprecate = True + else: + can_deprecate = False + + if not is_deprecated and trans.app.security_agent.can_push( trans.app, trans.user, repository ): + can_push = True + else: + can_push = False + + if not is_deprecated and not is_new and ( not is_malicious or can_push ): + can_download = True + else: + can_download = False + if not is_deprecated: if repo and len( repo ) > 0: can_reset_all_metadata = True @@ -25,16 +61,30 @@ can_reset_all_metadata = False else: can_reset_all_metadata = False - can_upload = can_push and not is_deprecated - can_rate = not is_new and not is_deprecated and trans.user and repository.user != trans.user - if changeset_revision: - can_review_repository = has_metadata and not is_deprecated and trans.app.security_agent.user_can_review_repositories( trans.user ) - reviewed_by_user = changeset_revision_reviewed_by_user( trans, trans.user, repository, changeset_revision ) + + if can_push and not is_deprecated: + can_upload = True + else: + can_upload = False + + if not is_new and not is_deprecated and trans.user and repository.user != trans.user: + can_rate = True + else: + can_rate = False + + if changeset_revision is not None: + if has_metadata and not is_deprecated and trans.app.security_agent.user_can_review_repositories( trans.user ): + can_review_repository = True + else: + can_review_repository = False + if changeset_revision_reviewed_by_user( trans, trans.user, repository, changeset_revision ): + reviewed_by_user = True + else: + reviewed_by_user = False else: can_review_repository = False reviewed_by_user = False - # Determine if the current changeset revision has been reviewed by the current user. if reviewed_by_user: review = get_review_by_repository_id_changeset_revision_user_id( trans=trans, repository_id=trans.security.encode_id( repository.id ), @@ -44,17 +94,29 @@ else: review_id = None - can_set_metadata = not is_new and not is_deprecated - if changeset_revision: + if not is_new and not is_deprecated: + can_set_metadata = True + else: + can_set_metadata = False + + if changeset_revision is not None: if changeset_revision == repository.tip( trans.app ): changeset_revision_is_repository_tip = True else: changeset_revision_is_repository_tip = False else: changeset_revision_is_repository_tip = False - can_set_malicious = metadata and can_set_metadata and is_admin and changeset_revision_is_repository_tip - can_undeprecate = trans.user and ( is_admin or repository.user == trans.user ) and is_deprecated - can_manage = is_admin or repository.user == trans.user + + if trans.user and ( is_admin or repository.user == trans.user ) and is_deprecated: + can_undeprecate = True + else: + can_undeprecate = False + + if is_admin or repository.user == trans.user: + can_manage = True + else: + can_manage = False + can_view_change_log = not is_new if can_push: @@ -69,6 +131,9 @@ %if can_upload: Upload files to repository %endif + %if can_undeprecate: + Mark repository as not deprecated + %endif %else:
  • Repository Actions
  • diff --git a/templates/webapps/tool_shed/repository/manage_repository.mako b/templates/webapps/tool_shed/repository/manage_repository.mako index 636b5c2e1f6..3f972d4804e 100644 --- a/templates/webapps/tool_shed/repository/manage_repository.mako +++ b/templates/webapps/tool_shed/repository/manage_repository.mako @@ -6,19 +6,56 @@ <% from galaxy.web.framework.helpers import time_ago + from tool_shed.util.shed_util_common import changeset_is_malicious + + if repository.metadata_revisions: + has_metadata = True + else: + has_metadata = False - has_metadata = repository.metadata_revisions - has_readme = metadata and 'readme' in metadata is_admin = trans.user_is_admin() is_new = repository.is_new( trans.app ) - is_deprecated = repository.deprecated - can_push = not is_deprecated and trans.app.security_agent.can_push( trans.app, trans.user, repository ) - can_download = not is_deprecated and not is_new and ( not is_malicious or can_push ) - can_review_repository = has_metadata and not is_deprecated and trans.app.security_agent.user_can_review_repositories( trans.user ) - can_set_metadata = not is_new and not is_deprecated - changeset_revision_is_repository_tip = changeset_revision == repository.tip( trans.app ) - can_set_malicious = metadata and can_set_metadata and is_admin and changeset_revision_is_repository_tip + if repository.deprecated: + is_deprecated = True + else: + is_deprecated = False + + if changeset_is_malicious( trans, trans.security.encode_id( repository.id ), repository.tip( trans.app ) ): + is_malicious = True + else: + is_malicious = False + + if not is_deprecated and trans.app.security_agent.can_push( trans.app, trans.user, repository ): + can_push = True + else: + can_push = False + + if not is_deprecated and not is_new and ( not is_malicious or can_push ): + can_download = True + else: + can_download = False + + if has_metadata and not is_deprecated and trans.app.security_agent.user_can_review_repositories( trans.user ): + can_review_repository = True + else: + can_review_repository = False + + if not is_new and not is_deprecated: + can_set_metadata = True + else: + can_set_metadata = False + + if changeset_revision == repository.tip( trans.app ): + changeset_revision_is_repository_tip = True + else: + changeset_revision_is_repository_tip = False + + if metadata and can_set_metadata and is_admin and changeset_revision_is_repository_tip: + can_set_malicious = True + else: + can_set_malicious = False + can_view_change_log = not is_new if changeset_revision_is_repository_tip: From 4db8ba6c294f03eb02375f79d2d83c6f3e5e6f02 Mon Sep 17 00:00:00 2001 From: Greg Von Kuster Date: Fri, 3 May 2013 11:26:12 -0400 Subject: [PATCH 333/523] Column cleanup in the repositories grid in the tool shed. --- lib/galaxy/webapps/tool_shed/controllers/repository.py | 4 +++- lib/tool_shed/grids/repository_grids.py | 6 +----- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/lib/galaxy/webapps/tool_shed/controllers/repository.py b/lib/galaxy/webapps/tool_shed/controllers/repository.py index 6633bcb892e..c925a1bf800 100644 --- a/lib/galaxy/webapps/tool_shed/controllers/repository.py +++ b/lib/galaxy/webapps/tool_shed/controllers/repository.py @@ -95,7 +95,9 @@ class RepositoryController( BaseUIController, common_util.ItemRatings ): kwd[ 'id' ] = trans.security.encode_id( repository.id ) except: pass - return self.browse_repositories( trans, **kwd ) + return trans.response.send_redirect( web.url_for( controller='repository', + action='browse_repositories', + **kwd ) ) if 'operation' in kwd: operation = kwd[ 'operation' ].lower() if operation in [ "repositories_by_category", "repositories_by_user" ]: diff --git a/lib/tool_shed/grids/repository_grids.py b/lib/tool_shed/grids/repository_grids.py index b96b754078b..abde6758cd4 100644 --- a/lib/tool_shed/grids/repository_grids.py +++ b/lib/tool_shed/grids/repository_grids.py @@ -211,11 +211,7 @@ class RepositoryGrid( grids.Grid ): key="description", attach_popup=False ), MetadataRevisionColumn( "Metadata Revisions" ), - TipRevisionColumn( "Tip Revision" ), - CategoryColumn( "Category", - model_class=model.Category, - key="Category.name", - attach_popup=False ), + ToolsFunctionallyCorrectColumn( "Tools Verified" ), UserColumn( "Owner", model_class=model.User, link=( lambda item: dict( operation="repositories_by_user", id=item.id ) ), From fb55aad3634d47776e177608003d8a9633dda40d Mon Sep 17 00:00:00 2001 From: Dannon Baker Date: Fri, 3 May 2013 12:26:40 -0400 Subject: [PATCH 334/523] Require decorator egg in migrate/check to prevent sqlalchemy-migrate from dying in certain environments --- lib/galaxy/model/migrate/check.py | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/galaxy/model/migrate/check.py b/lib/galaxy/model/migrate/check.py index 3554ffe3550..8d0ac96e330 100644 --- a/lib/galaxy/model/migrate/check.py +++ b/lib/galaxy/model/migrate/check.py @@ -4,6 +4,7 @@ import logging from galaxy import eggs eggs.require( "SQLAlchemy" ) +eggs.require( "decorator" ) #Required by sqlalchemy-migrate eggs.require( "Tempita " ) #Required by sqlalchemy-migrate eggs.require( "sqlalchemy-migrate" ) From e436468f28d784144e94a011461f85bb8470acda Mon Sep 17 00:00:00 2001 From: Dannon Baker Date: Fri, 3 May 2013 12:31:20 -0400 Subject: [PATCH 335/523] Require decorator egg wherever we have sqlalchemy-migrate. Should be able to resolve dependencies and do this automatically --- lib/galaxy/webapps/tool_shed/model/migrate/check.py | 1 + lib/tool_shed/galaxy_install/migrate/check.py | 1 + scripts/db_shell.py | 1 + scripts/manage_tools.py | 1 + 4 files changed, 4 insertions(+) diff --git a/lib/galaxy/webapps/tool_shed/model/migrate/check.py b/lib/galaxy/webapps/tool_shed/model/migrate/check.py index 9ffe5468f06..c17ab097da6 100644 --- a/lib/galaxy/webapps/tool_shed/model/migrate/check.py +++ b/lib/galaxy/webapps/tool_shed/model/migrate/check.py @@ -3,6 +3,7 @@ import sys, os.path, logging from galaxy import eggs import pkg_resources +pkg_resources.require( "decorator" ) pkg_resources.require( "sqlalchemy-migrate" ) pkg_resources.require( "Tempita" ) diff --git a/lib/tool_shed/galaxy_install/migrate/check.py b/lib/tool_shed/galaxy_install/migrate/check.py index cad697d8532..c632788a5ff 100644 --- a/lib/tool_shed/galaxy_install/migrate/check.py +++ b/lib/tool_shed/galaxy_install/migrate/check.py @@ -1,6 +1,7 @@ import sys, os, logging, subprocess from galaxy import eggs import pkg_resources +pkg_resources.require( "decorator" ) pkg_resources.require( "sqlalchemy-migrate" ) from migrate.versioning import repository, schema diff --git a/scripts/db_shell.py b/scripts/db_shell.py index 8f0f5554bc8..b2825672c70 100644 --- a/scripts/db_shell.py +++ b/scripts/db_shell.py @@ -19,6 +19,7 @@ sys.path = new_path from galaxy import eggs import pkg_resources +pkg_resources.require( "decorator" ) pkg_resources.require( "sqlalchemy-migrate" ) pkg_resources.require( "SQLAlchemy" ) diff --git a/scripts/manage_tools.py b/scripts/manage_tools.py index 917c82d43c4..975b4a68072 100644 --- a/scripts/manage_tools.py +++ b/scripts/manage_tools.py @@ -7,6 +7,7 @@ sys.path = new_path from galaxy import eggs import pkg_resources +pkg_resources.require( "decorator" ) pkg_resources.require( "sqlalchemy-migrate" ) from migrate.versioning.shell import main From cd23ac701469bd6b656bb6c213b83ea28151dec1 Mon Sep 17 00:00:00 2001 From: Dannon Baker Date: Fri, 3 May 2013 12:55:55 -0400 Subject: [PATCH 336/523] Make sure dependent eggs are loaded for sqlalchemy-migrate, regardless of entry point. This is a hack, and dependency management should/will eventually handle it. --- lib/galaxy/webapps/tool_shed/model/migrate/check.py | 1 + lib/tool_shed/galaxy_install/migrate/check.py | 2 ++ scripts/db_shell.py | 1 + scripts/manage_tools.py | 2 ++ 4 files changed, 6 insertions(+) diff --git a/lib/galaxy/webapps/tool_shed/model/migrate/check.py b/lib/galaxy/webapps/tool_shed/model/migrate/check.py index c17ab097da6..caa72c0b9a7 100644 --- a/lib/galaxy/webapps/tool_shed/model/migrate/check.py +++ b/lib/galaxy/webapps/tool_shed/model/migrate/check.py @@ -3,6 +3,7 @@ import sys, os.path, logging from galaxy import eggs import pkg_resources +pkg_resources.require( "SQLAlchemy" ) pkg_resources.require( "decorator" ) pkg_resources.require( "sqlalchemy-migrate" ) pkg_resources.require( "Tempita" ) diff --git a/lib/tool_shed/galaxy_install/migrate/check.py b/lib/tool_shed/galaxy_install/migrate/check.py index c632788a5ff..b84931d9ba3 100644 --- a/lib/tool_shed/galaxy_install/migrate/check.py +++ b/lib/tool_shed/galaxy_install/migrate/check.py @@ -1,7 +1,9 @@ import sys, os, logging, subprocess from galaxy import eggs import pkg_resources +pkg_resources.require( "SQLAlchemy" ) pkg_resources.require( "decorator" ) +pkg_resources.require( "Tempita " ) pkg_resources.require( "sqlalchemy-migrate" ) from migrate.versioning import repository, schema diff --git a/scripts/db_shell.py b/scripts/db_shell.py index b2825672c70..a8022a90fcf 100644 --- a/scripts/db_shell.py +++ b/scripts/db_shell.py @@ -20,6 +20,7 @@ from galaxy import eggs import pkg_resources pkg_resources.require( "decorator" ) +pkg_resources.require( "Tempita" ) pkg_resources.require( "sqlalchemy-migrate" ) pkg_resources.require( "SQLAlchemy" ) diff --git a/scripts/manage_tools.py b/scripts/manage_tools.py index 975b4a68072..f807ea91e00 100644 --- a/scripts/manage_tools.py +++ b/scripts/manage_tools.py @@ -7,7 +7,9 @@ sys.path = new_path from galaxy import eggs import pkg_resources +pkg_resources.require( "SQLAlchemy" ) pkg_resources.require( "decorator" ) +pkg_resources.require( "Tempita " ) pkg_resources.require( "sqlalchemy-migrate" ) from migrate.versioning.shell import main From fe8f1c32f07223552379360d065e1b9b57f00fb4 Mon Sep 17 00:00:00 2001 From: Dave Bouvier Date: Fri, 3 May 2013 15:25:20 -0400 Subject: [PATCH 337/523] Explicitly uninstall all installed repositories after each set of tests has finished running. This prevents certain edge cases from interfering with subsequent tests. --- .../functional/test_install_repositories.py | 4 +- .../functional_tests.py | 89 ++++++++++++++----- 2 files changed, 71 insertions(+), 22 deletions(-) diff --git a/test/install_and_test_tool_shed_repositories/functional/test_install_repositories.py b/test/install_and_test_tool_shed_repositories/functional/test_install_repositories.py index b7c710a9d53..eb2ef530282 100644 --- a/test/install_and_test_tool_shed_repositories/functional/test_install_repositories.py +++ b/test/install_and_test_tool_shed_repositories/functional/test_install_repositories.py @@ -3,8 +3,10 @@ import install_and_test_tool_shed_repositories.base.test_db_util as test_db_util from install_and_test_tool_shed_repositories.base.twilltestcase import InstallTestRepository log = logging.getLogger(__name__) + class InstallTestRepositories( InstallTestRepository ): """Abstract test case that installs and uninstalls a predefined list of repositories.""" + def do_installation( self, repository_info_dict ): self.logout() self.login( email='test@bx.psu.edu', username='test' ) @@ -73,7 +75,7 @@ def generate_uninstall_method( repository_dict=None ): return test_install_repository test_method = make_uninstall_method( repository_dict ) test_method.__doc__ = "Uninstall the repository %s." % repository_dict[ 'name' ] - namespace[ 'install_repository_%s' % repository_dict[ 'name' ] ] = test_method + namespace[ 'uninstall_repository_%s' % repository_dict[ 'name' ] ] = test_method # The new.classobj function returns a new class object, with name name, derived # from baseclasses (which should be a tuple of classes) and with namespace dict. new_class_obj = new.classobj( name, baseclasses, namespace ) diff --git a/test/install_and_test_tool_shed_repositories/functional_tests.py b/test/install_and_test_tool_shed_repositories/functional_tests.py index d12c0491954..c8c750a0d56 100644 --- a/test/install_and_test_tool_shed_repositories/functional_tests.py +++ b/test/install_and_test_tool_shed_repositories/functional_tests.py @@ -179,19 +179,26 @@ class ReportResults( Plugin ): return passed_tests return [] -def execute_uninstall_method( repository_dict ): - # Delete any configured tool functional tests from the test_toolbox.__dict__, otherwise nose will find them - # and try to re-run the tests after uninstalling the repository, which will cause false failure reports, - # since the test data has been deleted from disk by now. - tests_to_delete = [] - for key in test_toolbox.__dict__: - if key.startswith( 'TestForTool_' ): - log.info( 'Tool test found in test_toolbox, deleting: %s' % key ) - tests_to_delete.append( key ) - for key in tests_to_delete: - del test_toolbox.__dict__[ key ] - # Generate a test method to uninstall this repository through the embedded Galaxy application's web interface. - test_install_repositories.generate_uninstall_method( repository_dict ) +def execute_uninstall_method( app ): + # Clean out any generated tests. + remove_generated_tests( app ) + sa_session = app.model.context.current + repositories_to_uninstall = sa_session.query( app.model.ToolShedRepository ).all() + for repository in repositories_to_uninstall: + if repository.status == app.model.ToolShedRepository.states.UNINSTALLED: + continue + if repository.status not in [ app.model.ToolShedRepository.installation_status.UNINSTALLED, + app.model.ToolShedRepository.installation_status.ERROR, + app.model.ToolShedRepository.installation_status.INSTALLED ]: + repository.status = app.model.ToolShedRepository.installation_status.ERROR + sa_session.add( repository ) + sa_session.flush() + name = str( repository.name ) + owner = str( repository.owner ) + changeset_revision = str( repository.installed_changeset_revision ) + repository_dict = dict( name=name, owner=owner, changeset_revision=changeset_revision ) + # Generate a test method to uninstall this repository through the embedded Galaxy application's web interface. + test_install_repositories.generate_uninstall_method( repository_dict ) # Set up nose to run the generated uninstall method as a functional test. test_config = nose.config.Config( env=os.environ, plugins=nose.plugins.manager.DefaultPluginManager() ) test_config.configure( sys.argv ) @@ -309,6 +316,25 @@ def register_test_result( url, metadata_id, test_results_dict, passed_tests=Fals else: return update( tool_shed_api_key, '%s' % ( url_join( galaxy_tool_shed_url, 'api', 'repository_revisions', metadata_id ) ), params, return_formatted=False ) +def remove_generated_tests( app ): + # Delete any configured tool functional tests from the test_toolbox.__dict__, otherwise nose will find them + # and try to re-run the tests after uninstalling the repository, which will cause false failure reports, + # since the test data has been deleted from disk by now. + tests_to_delete = [] + global test_toolbox + for key in test_toolbox.__dict__: + if key.startswith( 'TestForTool_' ): + log.info( 'Tool test found in test_toolbox, deleting: %s' % key ) + # We can't delete this test just yet, we're still iterating over __dict__. + tests_to_delete.append( key ) + tool_id = key.replace( 'TestForTool_', '' ) + if tool_id in app.toolbox.tools_by_id: + # But we can remove the relevant tool from app.toolbox.tools_by_id. + del app.toolbox.tools_by_id[ tool_id ] + for key in tests_to_delete: + # Now delete the tests found in the previous loop. + del test_toolbox.__dict__[ key ] + def run_tests( test_config ): loader = nose.loader.TestLoader( config=test_config ) test_config.plugins.addPlugin( ReportResults() ) @@ -417,12 +443,12 @@ def main(): global_conf = { '__file__' : 'universe_wsgi.ini.sample' } if not database_connection.startswith( 'sqlite://' ): kwargs[ 'database_engine_option_max_overflow' ] = '20' + kwargs[ 'database_engine_option_pool_size' ] = '10' app = UniverseApplication( admin_users = 'test@bx.psu.edu', allow_user_creation = True, allow_user_deletion = True, allow_library_path_paste = True, database_connection = database_connection, - database_engine_option_pool_size = '10', datatype_converters_config_file = "datatype_converters_conf.xml.sample", file_path = galaxy_file_path, global_conf = global_conf, @@ -546,7 +572,14 @@ def main(): # it will record the result of the tests, and if any failed, the traceback and captured output of the tool that was run. # After all tests have completed, the repository is uninstalled, so that the previous test cases don't interfere with # the next repository's functional tests. + keep_skipping = True for repository_info_dict in detailed_repository_list: + if repository_info_dict[ 'name' ] == 'simsearch' and repository_info_dict[ 'changeset_revision' ] == '430f55978191': + keep_skipping = False + if keep_skipping: + continue + if repository_info_dict[ 'name' ] == 'sortmerna': + continue """ Each repository_info_dict looks something like: { @@ -575,10 +608,13 @@ def main(): metadata_revision_id = repository_info_dict.get( 'id', None ) # Add the URL for the tool shed we're installing from, so the automated installation methods go to the right place. repository_info_dict[ 'tool_shed_url' ] = galaxy_tool_shed_url - log.info( "Installing and testing revision %s of repository id %s..." % ( str( changeset_revision ), str( repository_id ) ) ) # Get the name and owner out of the repository info dict. - name = repository_info_dict[ 'name' ] - owner = repository_info_dict[ 'owner' ] + name = str( repository_info_dict[ 'name' ] ) + owner = str( repository_info_dict[ 'owner' ] ) + log.info( "Installing and testing revision %s of repository id %s (%s/%s)..." % \ + ( str( changeset_revision ), str( repository_id ), owner, name ) ) + # Explicitly clear tests from twill's test environment. + remove_generated_tests( app ) # Use the repository information dict to generate an install method that will install the repository into the embedded # Galaxy application, with tool dependencies and repository dependencies, if any. test_install_repositories.generate_install_method( repository_info_dict ) @@ -661,7 +697,7 @@ def main(): register_test_result( galaxy_tool_shed_url, metadata_revision_id, repository_status, passed_tests=False ) # Run the cleanup method. This removes tool functional test methods from the test_toolbox module and uninstalls the # repository using Twill. - execute_uninstall_method( repository_info_dict ) + execute_uninstall_method( app ) # Set the test_toolbox.toolbox module-level variable to the new app.toolbox. test_toolbox.toolbox = app.toolbox repositories_failed.append( dict( name=name, owner=owner, changeset_revision=changeset_revision ) ) @@ -675,6 +711,8 @@ def main(): os.environ[ 'GALAXY_TOOL_SHED_TEST_FILE' ] = galaxy_shed_tools_dict os.environ[ 'GALAXY_TEST_HOST' ] = galaxy_test_host os.environ[ 'GALAXY_TEST_PORT' ] = galaxy_test_port + # Explicitly clear tests from twill's test environment. + remove_generated_tests( app ) # Set the module-level variable 'toolbox', so that test.functional.test_toolbox will generate the appropriate test methods. test_toolbox.toolbox = app.toolbox # Generate the test methods for this installed repository. We need to pass in True here, or it will look @@ -751,14 +789,23 @@ def main(): register_test_result( galaxy_tool_shed_url, metadata_revision_id, repository_status, passed_tests=False ) log.debug( 'Revision %s of repository %s installed successfully, but did not pass functional tests.' % \ ( changeset_revision, name ) ) - # Run the cleanup method. This removes tool functional test methods from the test_toolbox module and uninstalls the + # Run the uninstall method. This removes tool functional test methods from the test_toolbox module and uninstalls the # repository using Twill. - success = execute_uninstall_method( repository_info_dict ) + log.debug( 'Uninstalling changeset revision %s of repository %s' % \ + ( repository_info_dict[ 'changeset_revision' ], repository_info_dict[ 'name' ] ) ) + success = execute_uninstall_method( app ) + if not success: + log.error( 'Repository %s failed to uninstall.' ) # Set the test_toolbox.toolbox module-level variable to the new app.toolbox. test_toolbox.toolbox = app.toolbox else: + # Even if the repository failed to install, execute the uninstall method, in case a dependency did succeed. + log.debug( 'Uninstalling repository %s' % repository_info_dict[ 'name' ] ) + success = execute_uninstall_method( app ) + if not success: + log.error( 'Repository %s failed to uninstall.' ) repositories_failed_install.append( dict( name=name, owner=owner, changeset_revision=changeset_revision ) ) - log.debug( 'Repository %s failed to install correctly.' % name ) + log.debug( 'Repository %s failed to install correctly.' % repository_info_dict[ 'name' ] ) except: log.exception( "Failure running tests" ) From f51719aaa976b076f6bc2898f2be574ac9dd62ee Mon Sep 17 00:00:00 2001 From: Dannon Baker Date: Fri, 3 May 2013 15:46:53 -0400 Subject: [PATCH 338/523] Fix install_repository_tools script to appropriately handle a trailing slash in the URL. --- scripts/api/install_repository_tools.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/api/install_repository_tools.py b/scripts/api/install_repository_tools.py index 67e91f5a5ea..508b8236ce1 100644 --- a/scripts/api/install_repository_tools.py +++ b/scripts/api/install_repository_tools.py @@ -34,7 +34,7 @@ def main( options ): data[ 'install_repository_dependencies' ] = options.install_repository_dependencies if options.install_tool_dependencies: data[ 'install_tool_dependencies' ] = options.install_tool_dependencies - submit( options.api, '%s%s' % ( options.local_url, '/api/tool_shed_repositories/new/install_repository_revision' ), data ) + submit( options.api, '%s%s' % ( options.local_url.strip('/'), '/api/tool_shed_repositories/new/install_repository_revision' ), data ) if __name__ == '__main__': parser = argparse.ArgumentParser( description='Installation of tool shed repositories via the Galaxy API.' ) From 9cf536f537fc1991553ef5c157bcc418024f9f59 Mon Sep 17 00:00:00 2001 From: Dave Bouvier Date: Fri, 3 May 2013 15:55:56 -0400 Subject: [PATCH 339/523] Rename install_repository_tools to install_tool_shed_repositories. Use shed_util_common.url_join to generate the API url. --- scripts/api/install_tool_shed_repositories.py | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 scripts/api/install_tool_shed_repositories.py diff --git a/scripts/api/install_tool_shed_repositories.py b/scripts/api/install_tool_shed_repositories.py new file mode 100644 index 00000000000..508b8236ce1 --- /dev/null +++ b/scripts/api/install_tool_shed_repositories.py @@ -0,0 +1,52 @@ +#!/usr/bin/env python +""" +Install a specified repository revision from a specified tool shed into Galaxy. This example demonstrates installation of a repository that contains +valid tools, loading them into a section of the Galaxy tool panel or creating a new tool panel section. +You can choose if tool dependencies or repository dependencies should be installed, use --repository-deps or --tool-deps. + +This example requires a tool panel config file (e.g., tool_conf.xml, shed_tool_conf.xml, etc) to contain a tool panel section like the following: + +
    +
    + +Here is a working example of how to use this script to install a repository from the test tool shed. +./install_repository_tools.py --api --local --url http://testtoolshed.g2.bx.psu.edu --name gregs_filter --owner greg --revision f28d5018f9cb --tool-deps +""" + +import os +import sys +import argparse +sys.path.insert( 0, os.path.dirname( __file__ ) ) +from common import submit + +def main( options ): + """Collect all user data and install the tools via the Galaxy API.""" + data = {} + data[ 'tool_shed_url' ] = options.tool_shed_url + data[ 'name' ] = options.name + data[ 'owner' ] = options.owner + data[ 'changeset_revision' ] = options.changeset_revision + if options.tool_panel_section_id: + data[ 'tool_panel_section_id' ] = options.tool_panel_section_id + elif options.new_tool_panel_section_label: + data[ 'new_tool_panel_section_label' ] = options.new_tool_panel_section_label + if options.install_repository_dependencies: + data[ 'install_repository_dependencies' ] = options.install_repository_dependencies + if options.install_tool_dependencies: + data[ 'install_tool_dependencies' ] = options.install_tool_dependencies + submit( options.api, '%s%s' % ( options.local_url.strip('/'), '/api/tool_shed_repositories/new/install_repository_revision' ), data ) + +if __name__ == '__main__': + parser = argparse.ArgumentParser( description='Installation of tool shed repositories via the Galaxy API.' ) + parser.add_argument( "-u", "--url", dest="tool_shed_url", required=True, help="Tool Shed URL" ) + parser.add_argument( "-a", "--api", dest="api", required=True, help="API Key" ) + parser.add_argument( "-l", "--local", dest="local_url", required=True, help="URL of the galaxy instance." ) + parser.add_argument( "-n", "--name", required=True, help="Repository name." ) + parser.add_argument( "-o", "--owner", required=True, help="Repository owner." ) + parser.add_argument( "-r", "--revision", dest="changeset_revision", required=True, help="Repository owner." ) + parser.add_argument( "--panel-section-id", dest="tool_panel_section_id", help="Tool panel section id if you want to add your repository to an existing tool section." ) + parser.add_argument( "--panel-section-name", dest="new_tool_panel_section_label", help="New tool panel section label. If specified a new tool section will be created." ) + parser.add_argument( "--repository-deps", dest="install_repository_dependencies", action="store_true", default=False, help="Install repository dependencies. [False]") + parser.add_argument( "--tool-deps", dest="install_tool_dependencies", action="store_true", default=False, help="Install tool dependencies. [False]" ) + options = parser.parse_args() + main( options ) From 9fb2031b04b5240f23ced5c4072c78c4109a48c3 Mon Sep 17 00:00:00 2001 From: Daniel Blankenberg Date: Fri, 3 May 2013 16:02:39 -0400 Subject: [PATCH 340/523] Provide user to tools as __user__. --- lib/galaxy/jobs/__init__.py | 3 +++ lib/galaxy/tools/__init__.py | 3 +++ 2 files changed, 6 insertions(+) diff --git a/lib/galaxy/jobs/__init__.py b/lib/galaxy/jobs/__init__.py index d7edef3b41f..608aef1f3bb 100644 --- a/lib/galaxy/jobs/__init__.py +++ b/lib/galaxy/jobs/__init__.py @@ -678,13 +678,16 @@ class JobWrapper( object ): # These can be passed on the command line if wanted as $__user_*__ if job.history and job.history.user: + user = job.history.user user_id = '%d' % job.history.user.id user_email = str(job.history.user.email) user_name = str(job.history.user.username) else: + user = None user_id = 'Anonymous' user_email = 'Anonymous' user_name = 'Anonymous' + incoming['__user__'] = user incoming['__user_id__'] = incoming['userId'] = user_id incoming['__user_email__'] = incoming['userEmail'] = user_email incoming['__user_name__'] = user_name diff --git a/lib/galaxy/tools/__init__.py b/lib/galaxy/tools/__init__.py index 9e02cdfdb6c..a5bc3afa035 100755 --- a/lib/galaxy/tools/__init__.py +++ b/lib/galaxy/tools/__init__.py @@ -2546,6 +2546,7 @@ class Tool( object ): param_dict['__root_dir__'] = param_dict['GALAXY_ROOT_DIR'] = os.path.abspath( self.app.config.root ) param_dict['__datatypes_config__'] = param_dict['GALAXY_DATATYPES_CONF_FILE'] = self.app.datatypes_registry.integrated_datatypes_configs param_dict['__admin_users__'] = self.app.config.admin_users + param_dict['__user__'] = RawObjectWrapper( param_dict.get( '__user__', None ) ) # Return the dictionary of parameters return param_dict def build_param_file( self, param_dict, directory=None ): @@ -3243,6 +3244,8 @@ class RawObjectWrapper( ToolParameterValueWrapper ): """ def __init__( self, obj ): self.obj = obj + def __nonzero__( self ): + return bool( self.obj ) #FIXME: would it be safe/backwards compatible to rename .obj to .value, so that we can just inherit this method? def __str__( self ): return "%s:%s" % (self.obj.__module__, self.obj.__class__.__name__) def __getattr__( self, key ): From 14cec88abc91cc4fef20c2c0ebb7e83d0d6c79c3 Mon Sep 17 00:00:00 2001 From: Daniel Blankenberg Date: Fri, 3 May 2013 16:02:40 -0400 Subject: [PATCH 341/523] Update GenomeSpace tools to use __user__ as needed. --- tools/genomespace/genomespace_exporter.xml | 9 ++++----- tools/genomespace/genomespace_importer.xml | 7 +++---- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/tools/genomespace/genomespace_exporter.xml b/tools/genomespace/genomespace_exporter.xml index 3a36de94efa..8f89895db5c 100644 --- a/tools/genomespace/genomespace_exporter.xml +++ b/tools/genomespace/genomespace_exporter.xml @@ -3,10 +3,9 @@ - send data to GenomeSpace genomespace_exporter.py --genomespace_site "prod" - #assert $__user_id__ != 'Anonymous', Exception( 'You must be logged in to use this tool.' ) - #set $user = $__app__.model.User.get( $__user_id__ ) - #set $username = $user.preferences.get( 'genomespace_username', None ) - #set $token = $user.preferences.get( 'genomespace_token', None ) + #assert $__user__, Exception( 'You must be logged in to use this tool.' ) + #set $username = $__user__.preferences.get( 'genomespace_username', None ) + #set $token = $__user__.preferences.get( 'genomespace_token', None ) #assert None not in ( $username, $token ), Exception( 'You must associate a GenomeSpace OpenID with your account and log in with it.' ) #import binascii --username "${username}" @@ -36,7 +35,7 @@ - + diff --git a/tools/genomespace/genomespace_importer.xml b/tools/genomespace/genomespace_importer.xml index db6e126726b..fc0a73b6384 100644 --- a/tools/genomespace/genomespace_importer.xml +++ b/tools/genomespace/genomespace_importer.xml @@ -3,10 +3,9 @@ - receive data from GenomeSpace genomespace_importer.py --genomespace_site "prod" - #assert $__user_id__ != 'Anonymous', Exception( 'You must be logged in to use this tool.' ) - #set $user = $__app__.model.User.get( $__user_id__ ) - #set $username = $user.preferences.get( 'genomespace_username', None ) - #set $token = $user.preferences.get( 'genomespace_token', None ) + #assert $__user__, Exception( 'You must be logged in to use this tool.' ) + #set $username = $__user__.preferences.get( 'genomespace_username', None ) + #set $token = $__user__.preferences.get( 'genomespace_token', None ) #assert None not in ( $username, $token ), Exception( 'You must associate a GenomeSpace OpenID with your account and log in with it.' ) --username "${username}" --token "${token}" From 1ec7371480a3f323b14fe8c861e219b0b84ef653 Mon Sep 17 00:00:00 2001 From: Jeremy Goecks Date: Fri, 3 May 2013 18:02:57 -0400 Subject: [PATCH 342/523] When deleting a history and marking associated jobs deleted, first check that there is a job to mark. --- lib/galaxy/webapps/galaxy/controllers/history.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/lib/galaxy/webapps/galaxy/controllers/history.py b/lib/galaxy/webapps/galaxy/controllers/history.py index 26b330bc2f9..387fc74ced7 100644 --- a/lib/galaxy/webapps/galaxy/controllers/history.py +++ b/lib/galaxy/webapps/galaxy/controllers/history.py @@ -563,11 +563,14 @@ class HistoryController( BaseUIController, SharableMixin, UsesAnnotations, UsesI self.sa_session.add( history ) self.sa_session.flush() for hda in history.datasets: - job = hda.creating_job_associations[0].job - if job.history_id == history.id and job.state in [ trans.app.model.Job.states.QUEUED, trans.app.model.Job.states.RUNNING, trans.app.model.Job.states.NEW ]: - # No need to check other outputs since the job's parent history is this history - job.mark_deleted( trans.app.config.track_jobs_in_database ) - trans.app.job_manager.job_stop_queue.put( job.id ) + # Not all datasets have jobs associated with them (e.g., datasets imported from libraries). + if hda.creating_job_associations: + # HDA has associated job, so try marking it deleted. + job = hda.creating_job_associations[0].job + if job.history_id == history.id and job.state in [ trans.app.model.Job.states.QUEUED, trans.app.model.Job.states.RUNNING, trans.app.model.Job.states.NEW ]: + # No need to check other outputs since the job's parent history is this history + job.mark_deleted( trans.app.config.track_jobs_in_database ) + trans.app.job_manager.job_stop_queue.put( job.id ) # Regardless of whether it was previously deleted, we make a new history active trans.new_history() return trans.show_ok_message( "History deleted, a new history is active", refresh_frames=['history'] ) From f94a1ae935ad034bce4e921d8812420e1cb4037a Mon Sep 17 00:00:00 2001 From: Dave Bouvier Date: Sat, 4 May 2013 09:07:50 -0400 Subject: [PATCH 343/523] Remove debug filtering. --- .../functional_tests.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/test/install_and_test_tool_shed_repositories/functional_tests.py b/test/install_and_test_tool_shed_repositories/functional_tests.py index c8c750a0d56..7f39a9035fa 100644 --- a/test/install_and_test_tool_shed_repositories/functional_tests.py +++ b/test/install_and_test_tool_shed_repositories/functional_tests.py @@ -572,14 +572,7 @@ def main(): # it will record the result of the tests, and if any failed, the traceback and captured output of the tool that was run. # After all tests have completed, the repository is uninstalled, so that the previous test cases don't interfere with # the next repository's functional tests. - keep_skipping = True for repository_info_dict in detailed_repository_list: - if repository_info_dict[ 'name' ] == 'simsearch' and repository_info_dict[ 'changeset_revision' ] == '430f55978191': - keep_skipping = False - if keep_skipping: - continue - if repository_info_dict[ 'name' ] == 'sortmerna': - continue """ Each repository_info_dict looks something like: { From 314668b422cb7265ac51161ec2d2df92f33d1c56 Mon Sep 17 00:00:00 2001 From: Dannon Baker Date: Sun, 5 May 2013 11:05:50 -0400 Subject: [PATCH 344/523] Temporarily disable database_connection in metadata.py until a better solution is found --- lib/galaxy/datatypes/metadata.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/galaxy/datatypes/metadata.py b/lib/galaxy/datatypes/metadata.py index 907104e4109..6e851ec9f12 100644 --- a/lib/galaxy/datatypes/metadata.py +++ b/lib/galaxy/datatypes/metadata.py @@ -28,7 +28,7 @@ log = logging.getLogger(__name__) STATEMENTS = "__galaxy_statements__" #this is the name of the property in a Datatype class where new metadata spec element Statements are stored -DATABASE_CONNECTION_AVAILABLE = True #When False, certain metadata parameter types (see FileParameter) will behave differently +DATABASE_CONNECTION_AVAILABLE = False #When False, certain metadata parameter types (see FileParameter) will behave differently class Statement( object ): """ From 3127fdfd5c38968585f742bab513087a4d7f03f8 Mon Sep 17 00:00:00 2001 From: Dannon Baker Date: Sun, 5 May 2013 11:54:28 -0400 Subject: [PATCH 345/523] Workflow controller: remove unused variables. --- .../webapps/galaxy/controllers/workflow.py | 26 ++++++++++--------- 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/lib/galaxy/webapps/galaxy/controllers/workflow.py b/lib/galaxy/webapps/galaxy/controllers/workflow.py index 913f16cf587..ffe1d6985ea 100644 --- a/lib/galaxy/webapps/galaxy/controllers/workflow.py +++ b/lib/galaxy/webapps/galaxy/controllers/workflow.py @@ -134,7 +134,7 @@ class WorkflowController( BaseUIController, SharableMixin, UsesStoredWorkflowMix @web.require_login( "use Galaxy workflows" ) def list_grid( self, trans, **kwargs ): """ List user's stored workflows. """ - status = message = None + # status = message = None if 'operation' in kwargs: operation = kwargs['operation'].lower() if operation == "rename": @@ -462,7 +462,7 @@ class WorkflowController( BaseUIController, SharableMixin, UsesStoredWorkflowMix return trans.show_error_message( "The specified workflow does not exist." ) # Rate workflow. - stored_rating = self.rate_item( trans.sa_session, trans.get_user(), stored, rating ) + self.rate_item( trans.sa_session, trans.get_user(), stored, rating ) return self.get_ave_item_rating_data( trans.sa_session, stored ) @@ -507,8 +507,6 @@ class WorkflowController( BaseUIController, SharableMixin, UsesStoredWorkflowMix @web.require_login( "use Galaxy workflows" ) def gen_image( self, trans, id ): stored = self.get_stored_workflow( trans, id, check_ownership=True ) - session = trans.sa_session - trans.response.set_content_type("image/svg+xml") return self._workflow_to_svg_canvas( trans, stored ).standalone_xml() @@ -942,7 +940,6 @@ class WorkflowController( BaseUIController, SharableMixin, UsesStoredWorkflowMix """ # Load encoded workflow from database - user = trans.get_user() id = trans.security.decode_id( id ) trans.workflow_building_mode = True stored = trans.sa_session.query( model.StoredWorkflow ).get( id ) @@ -1034,6 +1031,7 @@ class WorkflowController( BaseUIController, SharableMixin, UsesStoredWorkflowMix trans.response.headers["Content-Disposition"] = 'attachment; filename="Galaxy-Workflow-%s.ga"' % ( sname ) trans.response.set_content_type( 'application/galaxy-archive' ) return stored_dict + @web.expose def import_workflow( self, trans, cntrller='workflow', **kwd ): """ @@ -1097,7 +1095,7 @@ class WorkflowController( BaseUIController, SharableMixin, UsesStoredWorkflowMix else: uploaded_file = file_data.file uploaded_file_name = uploaded_file.name - uploaded_file_filename = file_data.filename + # uploaded_file_filename = file_data.filename if os.path.getsize( os.path.abspath( uploaded_file_name ) ) > 0: # We're reading the file as text so we can re-use the existing code below. # This may not be ideal... @@ -1198,6 +1196,7 @@ class WorkflowController( BaseUIController, SharableMixin, UsesStoredWorkflowMix status=status, use_panels=True, myexperiment_target_url=myexperiment_target_url ) + @web.json def get_datatypes( self, trans ): ext_to_class_name = dict() @@ -1335,7 +1334,8 @@ class WorkflowController( BaseUIController, SharableMixin, UsesStoredWorkflowMix # Build the state for each step errors = {} has_upgrade_messages = False - has_errors = False + # has_errors is never used + # has_errors = False saved_history = None if history_id is not None: saved_history = trans.get_history(); @@ -1498,7 +1498,8 @@ class WorkflowController( BaseUIController, SharableMixin, UsesStoredWorkflowMix step.state = step.module.state # Error dict if step.tool_errors: - has_errors = True + # has_errors is never used. + # has_errors = True errors[step.id] = step.tool_errors else: ## Non-tool specific stuff? @@ -1553,7 +1554,8 @@ class WorkflowController( BaseUIController, SharableMixin, UsesStoredWorkflowMix # Build the state for each step errors = {} has_upgrade_messages = False - has_errors = False + # has_errors is never used + # has_errors = False if kwargs: # If kwargs were provided, the states for each step should have # been POSTed @@ -1591,7 +1593,8 @@ class WorkflowController( BaseUIController, SharableMixin, UsesStoredWorkflowMix step.state = step.module.state # Error dict if step.tool_errors: - has_errors = True + # has_errors is never used + # has_errors = True errors[step.id] = step.tool_errors else: ## Non-tool specific stuff? @@ -1825,7 +1828,7 @@ class WorkflowController( BaseUIController, SharableMixin, UsesStoredWorkflowMix # Unpack and add post-job actions. post_job_actions = step_dict.get( 'post_job_actions', {} ) for name, pja_dict in post_job_actions.items(): - pja = model.PostJobAction( pja_dict[ 'action_type' ], + model.PostJobAction( pja_dict[ 'action_type' ], step, pja_dict[ 'output_name' ], pja_dict[ 'action_arguments' ] ) # Second pass to deal with connections between steps @@ -2064,7 +2067,6 @@ def cleanup_param_values( inputs, values ): but track the associations. """ associations = [] - names_to_clean = [] # dbkey is pushed in by the framework if 'dbkey' in values: del values['dbkey'] From 54dca297614979b99f9a98fc782d5c02fca4b261 Mon Sep 17 00:00:00 2001 From: Dannon Baker Date: Sun, 5 May 2013 12:12:19 -0400 Subject: [PATCH 346/523] Workflow controller - trim trailing whitespace, add spacing between classes/methods. --- .../webapps/galaxy/controllers/workflow.py | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/lib/galaxy/webapps/galaxy/controllers/workflow.py b/lib/galaxy/webapps/galaxy/controllers/workflow.py index ffe1d6985ea..20fad7beba5 100644 --- a/lib/galaxy/webapps/galaxy/controllers/workflow.py +++ b/lib/galaxy/webapps/galaxy/controllers/workflow.py @@ -37,8 +37,8 @@ from galaxy.web.framework.helpers import to_unicode from galaxy.workflow.modules import module_factory, ToolModule - class StoredWorkflowListGrid( grids.Grid ): + class StepsColumn( grids.GridColumn ): def get_value(self, trans, grid, workflow): return len( workflow.latest_workflow.steps ) @@ -73,6 +73,7 @@ class StoredWorkflowListGrid( grids.Grid ): def apply_query_filter( self, trans, query, **kwargs ): return query.filter_by( user=trans.user, deleted=False ) + class StoredWorkflowAllPublishedGrid( grids.Grid ): title = "Published Workflows" model_class = model.StoredWorkflow @@ -94,13 +95,16 @@ class StoredWorkflowAllPublishedGrid( grids.Grid ): key="free-text-search", visible=False, filterable="standard" ) ) operations = [] + def build_initial_query( self, trans, **kwargs ): # Join so that searching stored_workflow.user makes sense. return trans.sa_session.query( self.model_class ).join( model.User.table ) + def apply_query_filter( self, trans, query, **kwargs ): # A public workflow is published, has a slug, and is not deleted. return query.filter( self.model_class.published==True ).filter( self.model_class.slug != None ).filter( self.model_class.deleted == False ) + # Simple SGML parser to get all content in a single tag. class SingleTagContentsParser( sgmllib.SGMLParser ): @@ -119,6 +123,7 @@ class SingleTagContentsParser( sgmllib.SGMLParser ): if self.cur_tag == self.target_tag: self.tag_content += text + class WorkflowController( BaseUIController, SharableMixin, UsesStoredWorkflowMixin, UsesAnnotations, UsesItemRatings ): stored_list_grid = StoredWorkflowListGrid() published_list_grid = StoredWorkflowAllPublishedGrid() @@ -847,7 +852,7 @@ class WorkflowController( BaseUIController, SharableMixin, UsesStoredWorkflowMix for input_name, conns in step.temp_input_connections.iteritems(): if conns: conn_dicts = conns if isinstance(conns,list) else [conns] - for conn_dict in conn_dicts: + for conn_dict in conn_dicts: conn = model.WorkflowStepConnection() conn.input_step = step conn.input_name = input_name @@ -883,6 +888,7 @@ class WorkflowController( BaseUIController, SharableMixin, UsesStoredWorkflowMix """ stored = self.get_stored_workflow( trans, id, check_ownership=False, check_accessible=True ) return trans.fill_template( "/workflow/export.mako", item=stored, use_panels=True ) + @web.expose @web.require_login( "use workflows" ) def import_from_myexp( self, trans, myexp_id, **kwd ): @@ -932,6 +938,7 @@ class WorkflowController( BaseUIController, SharableMixin, UsesStoredWorkflowMix return trans.show_warn_message( "Imported, but this workflow contains cycles. %s" % workflow_list_str ) else: return trans.show_message( "Workflow '%s' imported. %s" % (workflow.name, workflow_list_str) ) + @web.expose @web.require_login( "use workflows" ) def export_to_myexp( self, trans, id, myexp_username, myexp_password ): @@ -1767,7 +1774,7 @@ class WorkflowController( BaseUIController, SharableMixin, UsesStoredWorkflowMix # tools. This should be removed at some point. Mirrored # hack in _workflow_from_dict should never be removed so # existing workflow exports continue to function. - for input_name, input_conn in dict(input_conn_dict).iteritems(): + for input_name, input_conn in dict(input_conn_dict).iteritems(): if len(input_conn) == 1: input_conn_dict[input_name] = input_conn[0] step_dict['input_connections'] = input_conn_dict @@ -1776,6 +1783,7 @@ class WorkflowController( BaseUIController, SharableMixin, UsesStoredWorkflowMix # Add to return value data['steps'][step.order_index] = step_dict return data + def _workflow_from_dict( self, trans, data, source=None, add_to_menu=False ): """ Creates a workflow from a dict. Created workflow is stored in the database and returned. @@ -1869,16 +1877,13 @@ class WorkflowController( BaseUIController, SharableMixin, UsesStoredWorkflowMix return stored, missing_tool_tups def _workflow_to_svg_canvas( self, trans, stored ): - workflow = stored.latest_workflow data = [] - canvas = svgfig.canvas(style="stroke:black; fill:none; stroke-width:1px; stroke-linejoin:round; text-anchor:left") text = svgfig.SVG("g") connectors = svgfig.SVG("g") boxes = svgfig.SVG("g") svgfig.Text.defaults["font-size"] = "10px" - in_pos = {} out_pos = {} margin = 5 @@ -2021,6 +2026,7 @@ def order_workflow_steps_with_levels( steps ): except CycleError: return None + class FakeJob( object ): """ Fake job object for datasets that have no creating_job_associations, From 50609bda21999e0a4a5c7d10dafbb5e29e17d83f Mon Sep 17 00:00:00 2001 From: James Taylor Date: Sun, 5 May 2013 12:17:33 -0400 Subject: [PATCH 347/523] Password security: use PBKDF2 scheme with sha256 for any new/changed passwords. Scheme is encoded in the database field to allow other schemes to be used in the future. If no recognized prefix is seen, sha1 is assumed. --- lib/galaxy/model/__init__.py | 5 +- lib/galaxy/model/mapping.py | 2 +- .../0115_longer_user_password_field.py | 25 ++++++ lib/galaxy/security/passwords.py | 83 +++++++++++++++++++ 4 files changed, 112 insertions(+), 3 deletions(-) create mode 100644 lib/galaxy/model/migrate/versions/0115_longer_user_password_field.py create mode 100644 lib/galaxy/security/passwords.py diff --git a/lib/galaxy/model/__init__.py b/lib/galaxy/model/__init__.py index 827f521567a..4527299eb35 100644 --- a/lib/galaxy/model/__init__.py +++ b/lib/galaxy/model/__init__.py @@ -21,6 +21,7 @@ import time import galaxy.datatypes import galaxy.datatypes.registry +import galaxy.security.passwords from galaxy.datatypes.metadata import MetadataCollection from galaxy.model.item_attrs import APIItem, UsesAnnotations from galaxy.security import get_permitted_actions @@ -86,13 +87,13 @@ class User( object, APIItem ): """ Set user password to the digest of `cleartext`. """ - self.password = new_secure_hash( text_type=cleartext ) + self.password = galaxy.security.passwords.hash_password( cleartext ) def check_password( self, cleartext ): """ Check if `cleartext` matches user password when hashed. """ - return self.password == new_secure_hash( text_type=cleartext ) + return galaxy.security.passwords.check_password( cleartext, self.password ) def all_roles( self ): """ diff --git a/lib/galaxy/model/mapping.py b/lib/galaxy/model/mapping.py index 1e4594e1ed3..35d983e9eb2 100644 --- a/lib/galaxy/model/mapping.py +++ b/lib/galaxy/model/mapping.py @@ -51,7 +51,7 @@ model.User.table = Table( "galaxy_user", metadata, Column( "update_time", DateTime, default=now, onupdate=now ), Column( "email", TrimmedString( 255 ), index=True, nullable=False ), Column( "username", TrimmedString( 255 ), index=True, unique=True ), - Column( "password", TrimmedString( 40 ), nullable=False ), + Column( "password", TrimmedString( 255 ), nullable=False ), Column( "external", Boolean, default=False ), Column( "form_values_id", Integer, ForeignKey( "form_values.id" ), index=True ), Column( "deleted", Boolean, index=True, default=False ), diff --git a/lib/galaxy/model/migrate/versions/0115_longer_user_password_field.py b/lib/galaxy/model/migrate/versions/0115_longer_user_password_field.py new file mode 100644 index 00000000000..9d868398804 --- /dev/null +++ b/lib/galaxy/model/migrate/versions/0115_longer_user_password_field.py @@ -0,0 +1,25 @@ +""" +Expand the length of the password fields in the galaxy_user table to allow for other hasing schemes +""" + +from sqlalchemy import * +from migrate import * + +import logging +log = logging.getLogger( __name__ ) + +def upgrade( migrate_engine ): + meta = MetaData( bind=migrate_engine ) + user = Table( 'galaxy_user', meta, autoload=True ) + try: + user.c.password.alter(type=String(255)) + except: + log.exception( "Altering password column failed" ) + +def downgrade(migrate_engine): + meta = MetaData( bind=migrate_engine ) + user = Table( 'galaxy_user', meta, autoload=True ) + try: + user.c.password.alter(type=String(40)) + except: + log.exception( "Altering password column failed" ) \ No newline at end of file diff --git a/lib/galaxy/security/passwords.py b/lib/galaxy/security/passwords.py new file mode 100644 index 00000000000..4390b52a446 --- /dev/null +++ b/lib/galaxy/security/passwords.py @@ -0,0 +1,83 @@ +import hmac +import hashlib +from struct import Struct +from operator import xor +from itertools import izip, starmap +from os import urandom +from base64 import b64encode + +SALT_LENGTH = 12 +KEY_LENGTH = 24 +HASH_FUNCTION = 'sha256' +COST_FACTOR = 10000 + +def hash_password( password ): + """ + Hash a password, currently will use the PBKDF2 scheme. + """ + return hash_password_PBKDF2( password ) + +def check_password( guess, hashed ): + """ + Check a hashed password. Supports either PBKDF2 if the hash is + prefixed with that string, or sha1 otherwise. + """ + if hashed.startswith( "PBKDF2" ): + if check_password_PBKDF2( guess, hashed ): + return True + else: + # Passwords were originally encoded with sha1 and hexed + if hashlib.sha1( guess ).hexdigest() == hashed: + return True + # Password does not match + return False + +def hash_password_PBKDF2( password ): + # Generate a random salt + salt = b64encode( urandom( SALT_LENGTH ) ) + # Apply the pbkdf2 encoding + hashed = pbkdf2_bin( bytes( password ), salt, COST_FACTOR, KEY_LENGTH, getattr( hashlib, HASH_FUNCTION ) ) + # Format + return 'PBKDF2${0}${1}${2}${3}'.format( HASH_FUNCTION, COST_FACTOR, salt, b64encode( hashed ) ) + +def check_password_PBKDF2( guess, hashed ): + # Split the database representation to extract cost_factor and salt + name, hash_function, cost_factor, salt, encoded_original = hashed.split( '$', 5 ) + # Hash the guess using the same parameters + hashed_guess = pbkdf2_bin( bytes( guess ), salt, int( cost_factor ), KEY_LENGTH, getattr( hashlib, hash_function ) ) + encoded_guess = b64encode( hashed_guess ) + return safe_str_cmp( encoded_original, encoded_guess ) + +## Taken from https://github.com/mitsuhiko/python-pbkdf2/blob/master/pbkdf2.py +## (c) Copyright 2011 by Armin Ronacher, BSD LICENSE + +_pack_int = Struct('>I').pack + +def pbkdf2_bin( data, salt, iterations=1000, keylen=24, hashfunc=None ): + """Returns a binary digest for the PBKDF2 hash algorithm of `data` + with the given `salt`. It iterates `iterations` time and produces a + key of `keylen` bytes. By default SHA-1 is used as hash function, + a different hashlib `hashfunc` can be provided. + """ + hashfunc = hashfunc or hashlib.sha1 + mac = hmac.new(data, None, hashfunc) + def _pseudorandom(x, mac=mac): + h = mac.copy() + h.update(x) + return map(ord, h.digest()) + buf = [] + for block in xrange(1, -(-keylen // mac.digest_size) + 1): + rv = u = _pseudorandom(salt + _pack_int(block)) + for i in xrange(iterations - 1): + u = _pseudorandom(''.join(map(chr, u))) + rv = starmap(xor, izip(rv, u)) + buf.extend(rv) + return ''.join(map(chr, buf))[:keylen] + +def safe_str_cmp(a, b): + if len(a) != len(b): + return False + rv = 0 + for x, y in izip(a, b): + rv |= ord(x) ^ ord(y) + return rv == 0 \ No newline at end of file From 243b265ccec9034c5f656ae3e1481fe5055652f1 Mon Sep 17 00:00:00 2001 From: Dannon Baker Date: Sun, 5 May 2013 13:57:21 -0400 Subject: [PATCH 348/523] Add docstring comment for workflow controller 'imp' --- lib/galaxy/webapps/galaxy/controllers/workflow.py | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/galaxy/webapps/galaxy/controllers/workflow.py b/lib/galaxy/webapps/galaxy/controllers/workflow.py index 20fad7beba5..cb674354424 100644 --- a/lib/galaxy/webapps/galaxy/controllers/workflow.py +++ b/lib/galaxy/webapps/galaxy/controllers/workflow.py @@ -356,6 +356,7 @@ class WorkflowController( BaseUIController, SharableMixin, UsesStoredWorkflowMix @web.expose @web.require_login( "to import a workflow", use_panels=True ) def imp( self, trans, id, **kwargs ): + """Imports a workflow shared by other users.""" # Set referer message. referer = trans.request.referer if referer is not "": From ae36520d56892084fc7b5e20a1d5cee966067c42 Mon Sep 17 00:00:00 2001 From: Dannon Baker Date: Mon, 6 May 2013 12:50:06 -0400 Subject: [PATCH 349/523] Metadata setting: Always set externally. --- lib/galaxy/config.py | 1 - lib/galaxy/datatypes/metadata.py | 22 +++++-------------- lib/galaxy/datatypes/registry.py | 6 +---- lib/galaxy/jobs/__init__.py | 7 ++---- lib/galaxy/jobs/deferred/data_transfer.py | 14 +++++------- lib/galaxy/jobs/runners/__init__.py | 2 +- lib/galaxy/jobs/runners/local.py | 4 ++-- lib/galaxy/jobs/runners/lwr.py | 2 +- lib/galaxy/jobs/runners/tasks.py | 2 +- lib/galaxy/tools/imp_exp/__init__.py | 13 ++++------- lib/galaxy/webapps/galaxy/api/tools.py | 12 +++------- .../webapps/galaxy/controllers/dataset.py | 14 ++++-------- lib/galaxy/webapps/galaxy/controllers/user.py | 3 --- scripts/extract_dataset_part.py | 1 - scripts/functional_tests.py | 1 - scripts/set_metadata.py | 1 - 16 files changed, 29 insertions(+), 76 deletions(-) diff --git a/lib/galaxy/config.py b/lib/galaxy/config.py index 6e4cc731b9b..a8ff07f1ff8 100644 --- a/lib/galaxy/config.py +++ b/lib/galaxy/config.py @@ -86,7 +86,6 @@ class Configuration( object ): self.galaxy_data_manager_data_path = kwargs.get( 'galaxy_data_manager_data_path', self.tool_data_path ) self.tool_secret = kwargs.get( "tool_secret", "" ) self.id_secret = kwargs.get( "id_secret", "USING THE DEFAULT IS NOT SECURE!" ) - self.set_metadata_externally = string_as_bool( kwargs.get( "set_metadata_externally", "False" ) ) self.retry_metadata_internally = string_as_bool( kwargs.get( "retry_metadata_internally", "True" ) ) self.use_remote_user = string_as_bool( kwargs.get( "use_remote_user", "False" ) ) self.remote_user_maildomain = kwargs.get( "remote_user_maildomain", None ) diff --git a/lib/galaxy/datatypes/metadata.py b/lib/galaxy/datatypes/metadata.py index 6e851ec9f12..e8b1f8d737d 100644 --- a/lib/galaxy/datatypes/metadata.py +++ b/lib/galaxy/datatypes/metadata.py @@ -28,8 +28,6 @@ log = logging.getLogger(__name__) STATEMENTS = "__galaxy_statements__" #this is the name of the property in a Datatype class where new metadata spec element Statements are stored -DATABASE_CONNECTION_AVAILABLE = False #When False, certain metadata parameter types (see FileParameter) will behave differently - class Statement( object ): """ This class inserts its target into a list in the surrounding @@ -442,19 +440,9 @@ class FileParameter( MetadataParameter ): return None if isinstance( value, galaxy.model.MetadataFile ) or isinstance( value, MetadataTempFile ): return value - if DATABASE_CONNECTION_AVAILABLE: - try: - # FIXME: this query requires a monkey patch in assignmapper.py since - # MetadataParameters do not have a handle to the sqlalchemy session - # DBTODO this is problematic now. - return galaxy.model.MetadataFile.get( value ) - except: - #value was not a valid id - return None - else: - mf = galaxy.model.MetadataFile() - mf.id = value #we assume this is a valid id, since we cannot check it - return mf + mf = galaxy.model.MetadataFile() + mf.id = value #we assume this is a valid id, since we cannot check it + return mf def make_copy( self, value, target_context, source_context ): value = self.wrap( value ) @@ -499,13 +487,13 @@ class FileParameter( MetadataParameter ): return value def new_file( self, dataset = None, **kwds ): - if DATABASE_CONNECTION_AVAILABLE: + if object_session( dataset ): mf = galaxy.model.MetadataFile( name = self.spec.name, dataset = dataset, **kwds ) object_session( dataset ).add( mf ) object_session( dataset ).flush() #flush to assign id return mf else: - #we need to make a tmp file that is accessable to the head node, + #we need to make a tmp file that is accessable to the head node, #we will be copying its contents into the MetadataFile objects filename after restoring from JSON #we do not include 'dataset' in the kwds passed, as from_JSON_value() will handle this for us return MetadataTempFile( **kwds ) diff --git a/lib/galaxy/datatypes/registry.py b/lib/galaxy/datatypes/registry.py index 7008cfcd6b8..b268a214fce 100644 --- a/lib/galaxy/datatypes/registry.py +++ b/lib/galaxy/datatypes/registry.py @@ -397,7 +397,7 @@ class Registry( object ): except KeyError: builder = data.Text() return builder - def change_datatype(self, data, ext, set_meta = True ): + def change_datatype(self, data, ext): data.extension = ext # call init_meta and copy metadata from itself. The datatype # being converted *to* will handle any metadata copying and @@ -405,10 +405,6 @@ class Registry( object ): if data.has_data(): data.set_size() data.init_meta( copy_from=data ) - if set_meta: - #metadata is being set internally - data.set_meta( overwrite = False ) - data.set_peek() return data def old_change_datatype(self, data, ext): """Creates and returns a new datatype based on an existing data and an extension""" diff --git a/lib/galaxy/jobs/__init__.py b/lib/galaxy/jobs/__init__.py index 608aef1f3bb..f48cb2023a5 100644 --- a/lib/galaxy/jobs/__init__.py +++ b/lib/galaxy/jobs/__init__.py @@ -953,9 +953,7 @@ class JobWrapper( object ): #either use the metadata from originating output dataset, or call set_meta on the copies #it would be quicker to just copy the metadata from the originating output dataset, #but somewhat trickier (need to recurse up the copied_from tree), for now we'll call set_meta() - if not self.app.config.set_metadata_externally or \ - ( not self.external_output_metadata.external_metadata_set_successfully( dataset, self.sa_session ) \ - and self.app.config.retry_metadata_internally ): + if ( not self.external_output_metadata.external_metadata_set_successfully( dataset, self.sa_session ) and self.app.config.retry_metadata_internally ): dataset.datatype.set_meta( dataset, overwrite = False ) #call datatype.set_meta directly for the initial set_meta call during dataset creation elif not self.external_output_metadata.external_metadata_set_successfully( dataset, self.sa_session ) and job.states.ERROR != final_job_state: dataset._state = model.Dataset.states.FAILED_METADATA @@ -1226,8 +1224,7 @@ class JobWrapper( object ): try: for fname in self.extra_filenames: os.remove( fname ) - if self.app.config.set_metadata_externally: - self.external_output_metadata.cleanup_external_metadata( self.sa_session ) + self.external_output_metadata.cleanup_external_metadata( self.sa_session ) galaxy.tools.imp_exp.JobExportHistoryArchiveWrapper( self.job_id ).cleanup_after_job( self.sa_session ) galaxy.tools.imp_exp.JobImportHistoryArchiveWrapper( self.app, self.job_id ).cleanup_after_job() galaxy.tools.genome_index.GenomeIndexToolWrapper( self.job_id ).postprocessing( self.sa_session, self.app ) diff --git a/lib/galaxy/jobs/deferred/data_transfer.py b/lib/galaxy/jobs/deferred/data_transfer.py index 6e9dcee3787..803cdc590b1 100644 --- a/lib/galaxy/jobs/deferred/data_transfer.py +++ b/lib/galaxy/jobs/deferred/data_transfer.py @@ -136,15 +136,11 @@ class DataTransfer( object ): if name not in [ 'name', 'info', 'dbkey', 'base_name' ]: if spec.get( 'default' ): setattr( ldda.metadata, name, spec.unwrap( spec.get( 'default' ) ) ) - if self.app.config.set_metadata_externally: - self.app.datatypes_registry.set_external_metadata_tool.tool_action.execute( self.app.datatypes_registry.set_external_metadata_tool, - FakeTrans( self.app, - history=sample.history, - user=sample.request.user ), - incoming = { 'input1':ldda } ) - else: - ldda.set_meta() - ldda.datatype.after_setting_metadata( ldda ) + self.app.datatypes_registry.set_external_metadata_tool.tool_action.execute( self.app.datatypes_registry.set_external_metadata_tool, + FakeTrans( self.app, + history=sample.history, + user=sample.request.user ), + incoming = { 'input1':ldda } ) ldda.state = ldda.states.OK # TODO: not sure if this flush is necessary self.sa_session.add( ldda ) diff --git a/lib/galaxy/jobs/runners/__init__.py b/lib/galaxy/jobs/runners/__init__.py index 7730c8cb42c..3c9fc928098 100644 --- a/lib/galaxy/jobs/runners/__init__.py +++ b/lib/galaxy/jobs/runners/__init__.py @@ -174,7 +174,7 @@ class BaseJobRunner( object ): # Append metadata setting commands, we don't want to overwrite metadata # that was copied over in init_meta(), as per established behavior - if include_metadata and self.app.config.set_metadata_externally: + if include_metadata: commands += "; cd %s; " % os.path.abspath( os.getcwd() ) commands += job_wrapper.setup_external_metadata( exec_dir = os.path.abspath( os.getcwd() ), diff --git a/lib/galaxy/jobs/runners/local.py b/lib/galaxy/jobs/runners/local.py index dfab9c8c881..b02def96335 100644 --- a/lib/galaxy/jobs/runners/local.py +++ b/lib/galaxy/jobs/runners/local.py @@ -83,14 +83,14 @@ class LocalJobRunner( BaseJobRunner ): stdout_file.close() stderr_file.close() log.debug('execution finished: %s' % command_line) - except Exception, exc: + except Exception: job_wrapper.fail( "failure running job", exception=True ) log.exception("failure running job %d" % job_wrapper.job_id) return #run the metadata setting script here #this is terminate-able when output dataset/job is deleted #so that long running set_meta()s can be canceled without having to reboot the server - if job_wrapper.get_state() not in [ model.Job.states.ERROR, model.Job.states.DELETED ] and self.app.config.set_metadata_externally and job_wrapper.output_paths: + if job_wrapper.get_state() not in [ model.Job.states.ERROR, model.Job.states.DELETED ] and job_wrapper.output_paths: external_metadata_script = job_wrapper.setup_external_metadata( output_fnames = job_wrapper.get_output_fnames(), set_extension = True, tmp_dir = job_wrapper.working_directory, diff --git a/lib/galaxy/jobs/runners/lwr.py b/lib/galaxy/jobs/runners/lwr.py index 9ecdea62602..daa367dd032 100644 --- a/lib/galaxy/jobs/runners/lwr.py +++ b/lib/galaxy/jobs/runners/lwr.py @@ -143,7 +143,7 @@ class LwrJobRunner( AsynchronousJobRunner ): #run the metadata setting script here #this is terminate-able when output dataset/job is deleted #so that long running set_meta()s can be canceled without having to reboot the server - if job_wrapper.get_state() not in [ model.Job.states.ERROR, model.Job.states.DELETED ] and self.app.config.set_metadata_externally and job_wrapper.output_paths: + if job_wrapper.get_state() not in [ model.Job.states.ERROR, model.Job.states.DELETED ] and job_wrapper.output_paths: external_metadata_script = job_wrapper.setup_external_metadata( output_fnames = job_wrapper.get_output_fnames(), set_extension = True, kwds = { 'overwrite' : False } ) #we don't want to overwrite metadata that was copied over in init_meta(), as per established behavior diff --git a/lib/galaxy/jobs/runners/tasks.py b/lib/galaxy/jobs/runners/tasks.py index 7245cad29cf..8887a716c86 100644 --- a/lib/galaxy/jobs/runners/tasks.py +++ b/lib/galaxy/jobs/runners/tasks.py @@ -123,7 +123,7 @@ class TaskedJobRunner( BaseJobRunner ): #run the metadata setting script here #this is terminate-able when output dataset/job is deleted #so that long running set_meta()s can be canceled without having to reboot the server - if job_wrapper.get_state() not in [ model.Job.states.ERROR, model.Job.states.DELETED ] and self.app.config.set_metadata_externally and job_wrapper.output_paths: + if job_wrapper.get_state() not in [ model.Job.states.ERROR, model.Job.states.DELETED ] and job_wrapper.output_paths: external_metadata_script = job_wrapper.setup_external_metadata( output_fnames = job_wrapper.get_output_fnames(), set_extension = True, kwds = { 'overwrite' : False } ) #we don't want to overwrite metadata that was copied over in init_meta(), as per established behavior diff --git a/lib/galaxy/tools/imp_exp/__init__.py b/lib/galaxy/tools/imp_exp/__init__.py index 72783044e57..45b3f3a72f5 100644 --- a/lib/galaxy/tools/imp_exp/__init__.py +++ b/lib/galaxy/tools/imp_exp/__init__.py @@ -189,15 +189,10 @@ class JobImportHistoryArchiveWrapper( object, UsesHistoryMixin, UsesAnnotations # Although metadata is set above, need to set metadata to recover BAI for BAMs. if hda.extension == 'bam': - if self.app.config.set_metadata_externally: - self.app.datatypes_registry.set_external_metadata_tool.tool_action.execute_via_app( - self.app.datatypes_registry.set_external_metadata_tool, self.app, jiha.job.session_id, - new_history.id, jiha.job.user, incoming={ 'input1': hda }, overwrite=False - ) - else: - message = 'Attributes updated' - hda.set_meta() - hda.datatype.after_setting_metadata( hda ) + self.app.datatypes_registry.set_external_metadata_tool.tool_action.execute_via_app( + self.app.datatypes_registry.set_external_metadata_tool, self.app, jiha.job.session_id, + new_history.id, jiha.job.user, incoming={ 'input1': hda }, overwrite=False + ) # # Create jobs. diff --git a/lib/galaxy/webapps/galaxy/api/tools.py b/lib/galaxy/webapps/galaxy/api/tools.py index 3bde1891a43..3559fc2784b 100644 --- a/lib/galaxy/webapps/galaxy/api/tools.py +++ b/lib/galaxy/webapps/galaxy/api/tools.py @@ -365,15 +365,9 @@ class ToolsController( BaseAPIController, UsesVisualizationMixin ): # Set metadata. # TODO: set meta internally if dataset is small enough? - if trans.app.config.set_metadata_externally: - trans.app.datatypes_registry.set_external_metadata_tool.tool_action.execute( trans.app.datatypes_registry.set_external_metadata_tool, - trans, incoming = { 'input1':new_dataset }, - overwrite=False, job_params={ "source" : "trackster" } ) - else: - message = 'Attributes updated' - new_dataset.set_meta() - new_dataset.datatype.after_setting_metadata( new_dataset ) - + trans.app.datatypes_registry.set_external_metadata_tool.tool_action.execute( trans.app.datatypes_registry.set_external_metadata_tool, + trans, incoming = { 'input1':new_dataset }, + overwrite=False, job_params={ "source" : "trackster" } ) # Add HDA subset association. subset_association = trans.app.model.HistoryDatasetAssociationSubset( hda=input_dataset, subset=new_dataset, location=regions_str ) trans.sa_session.add( subset_association ) diff --git a/lib/galaxy/webapps/galaxy/controllers/dataset.py b/lib/galaxy/webapps/galaxy/controllers/dataset.py index 2da4d77cc40..fb20536148c 100644 --- a/lib/galaxy/webapps/galaxy/controllers/dataset.py +++ b/lib/galaxy/webapps/galaxy/controllers/dataset.py @@ -379,10 +379,9 @@ class DatasetInterface( BaseUIController, UsesAnnotations, UsesHistoryMixin, Use message = "This dataset is currently being used as input or output. You cannot change datatype until the jobs have completed or you have canceled them." error = True else: - trans.app.datatypes_registry.change_datatype( data, params.datatype, set_meta = not trans.app.config.set_metadata_externally ) + trans.app.datatypes_registry.change_datatype( data, params.datatype ) trans.sa_session.flush() - if trans.app.config.set_metadata_externally: - trans.app.datatypes_registry.set_external_metadata_tool.tool_action.execute( trans.app.datatypes_registry.set_external_metadata_tool, trans, incoming = { 'input1':data }, overwrite = False ) #overwrite is False as per existing behavior + trans.app.datatypes_registry.set_external_metadata_tool.tool_action.execute( trans.app.datatypes_registry.set_external_metadata_tool, trans, incoming = { 'input1':data }, overwrite = False ) #overwrite is False as per existing behavior message = "Changed the type of dataset '%s' to %s" % ( to_unicode( data.name ), params.datatype ) refresh_frames=['history'] else: @@ -436,13 +435,8 @@ class DatasetInterface( BaseUIController, UsesAnnotations, UsesHistoryMixin, Use if name not in [ 'name', 'info', 'dbkey', 'base_name' ]: if spec.get( 'default' ): setattr( data.metadata, name, spec.unwrap( spec.get( 'default' ) ) ) - if trans.app.config.set_metadata_externally: - message = 'Attributes have been queued to be updated' - trans.app.datatypes_registry.set_external_metadata_tool.tool_action.execute( trans.app.datatypes_registry.set_external_metadata_tool, trans, incoming = { 'input1':data } ) - else: - message = 'Attributes updated' - data.set_meta() - data.datatype.after_setting_metadata( data ) + message = 'Attributes have been queued to be updated' + trans.app.datatypes_registry.set_external_metadata_tool.tool_action.execute( trans.app.datatypes_registry.set_external_metadata_tool, trans, incoming = { 'input1':data } ) trans.sa_session.flush() refresh_frames=['history'] elif params.convert_data: diff --git a/lib/galaxy/webapps/galaxy/controllers/user.py b/lib/galaxy/webapps/galaxy/controllers/user.py index 2c47513f790..f8129e2d36b 100644 --- a/lib/galaxy/webapps/galaxy/controllers/user.py +++ b/lib/galaxy/webapps/galaxy/controllers/user.py @@ -1339,9 +1339,6 @@ class User( BaseUIController, UsesFormDefinitionsMixin ): continue # Get chrom count file. - # NOTE: this conversion doesn't work well with set_metadata_externally=False - # because the conversion occurs before metadata can be set; the - # dataset is marked as deleted and a subsequent conversion is run. chrom_count_dataset = len_dataset.get_converted_dataset( trans, "linecount" ) if not chrom_count_dataset or chrom_count_dataset.state != trans.app.model.Job.states.OK: # No valid linecount dataset. diff --git a/scripts/extract_dataset_part.py b/scripts/extract_dataset_part.py index a87f5e82973..862cb8f40cb 100644 --- a/scripts/extract_dataset_part.py +++ b/scripts/extract_dataset_part.py @@ -24,7 +24,6 @@ import simplejson # This junk is here to prevent loading errors import galaxy.model.mapping #need to load this before we unpickle, in order to setup properties assigned by the mappers galaxy.model.Job() #this looks REAL stupid, but it is REQUIRED in order for SA to insert parameters into the classes defined by the mappers --> it appears that instantiating ANY mapper'ed class would suffice here -galaxy.datatypes.metadata.DATABASE_CONNECTION_AVAILABLE = False #Let metadata know that there is no database connection, and to just assume object ids are valid def __main__(): """ diff --git a/scripts/functional_tests.py b/scripts/functional_tests.py index 16e7da4672d..4b17930663d 100644 --- a/scripts/functional_tests.py +++ b/scripts/functional_tests.py @@ -185,7 +185,6 @@ def main(): cluster_files_directory = cluster_files_directory, job_working_directory = job_working_directory, outputs_to_working_directory = 'True', - set_metadata_externally = 'True', static_enabled = 'False', debug = 'False', track_jobs_in_database = 'True', diff --git a/scripts/set_metadata.py b/scripts/set_metadata.py index ea1099182a9..3921729b5e4 100644 --- a/scripts/set_metadata.py +++ b/scripts/set_metadata.py @@ -28,7 +28,6 @@ pkg_resources.require("simplejson") import simplejson import galaxy.model.mapping #need to load this before we unpickle, in order to setup properties assigned by the mappers galaxy.model.Job() #this looks REAL stupid, but it is REQUIRED in order for SA to insert parameters into the classes defined by the mappers --> it appears that instantiating ANY mapper'ed class would suffice here -galaxy.datatypes.metadata.DATABASE_CONNECTION_AVAILABLE = False #Let metadata know that there is no database connection, and to just assume object ids are valid from galaxy.util import stringify_dictionary_keys from galaxy.util.json import from_json_string from sqlalchemy.orm import clear_mappers From 68065e123800a5a2e700d636db9354702ac42fe5 Mon Sep 17 00:00:00 2001 From: Dannon Baker Date: Mon, 6 May 2013 13:07:31 -0400 Subject: [PATCH 350/523] Remove set_metadata_externally from the universe sample. --- universe_wsgi.ini.sample | 6 ------ 1 file changed, 6 deletions(-) diff --git a/universe_wsgi.ini.sample b/universe_wsgi.ini.sample index d474d526c5f..8e1668e6340 100644 --- a/universe_wsgi.ini.sample +++ b/universe_wsgi.ini.sample @@ -625,12 +625,6 @@ use_interactive = True # running more than one Galaxy server using the same database. #enable_job_recovery = True -# Setting metadata on job outputs to in a separate process (or if using a -# cluster, on the cluster). Thanks to Python's Global Interpreter Lock and the -# hefty expense that setting metadata incurs, your Galaxy process may become -# unresponsive when this operation occurs internally. -#set_metadata_externally = False - # Although it is fairly reliable, setting metadata can occasionally fail. In # these instances, you can choose to retry setting it internally or leave it in # a failed state (since retrying internally may cause the Galaxy process to be From afbf065eb318645be23892af0914c443e64ec2cc Mon Sep 17 00:00:00 2001 From: Jeremy Goecks Date: Mon, 6 May 2013 14:40:44 -0400 Subject: [PATCH 351/523] Tophat2 wrapper update: (a) remove unneeded wrapper script; and (b) use stdio tags to capture and show output; and (c) update functional tests for most recent version of Tophat. --- tools/ngs_rna/tophat2_wrapper.py | 270 ------------------------------ tools/ngs_rna/tophat2_wrapper.xml | 101 ++++++----- 2 files changed, 58 insertions(+), 313 deletions(-) delete mode 100644 tools/ngs_rna/tophat2_wrapper.py diff --git a/tools/ngs_rna/tophat2_wrapper.py b/tools/ngs_rna/tophat2_wrapper.py deleted file mode 100644 index 4daf7cc8d42..00000000000 --- a/tools/ngs_rna/tophat2_wrapper.py +++ /dev/null @@ -1,270 +0,0 @@ -#!/usr/bin/env python - -import optparse, os, shutil, subprocess, sys, tempfile, fileinput - -def stop_err( msg ): - sys.stderr.write( "%s\n" % msg ) - sys.exit() - -def __main__(): - #Parse Command Line - parser = optparse.OptionParser() - parser.add_option( '-p', '--num-threads', dest='num_threads', help='Use this many threads to align reads. The default is 1.' ) - parser.add_option( '-C', '--color-space', dest='color_space', action='store_true', help='This indicates color-space data' ) - parser.add_option( '-J', '--junctions-output', dest='junctions_output_file', help='Junctions output file; formate is BED.' ) - parser.add_option( '-H', '--hits-output', dest='accepted_hits_output_file', help='Accepted hits output file; formate is BAM.' ) - parser.add_option( '', '--own-file', dest='own_file', help='' ) - parser.add_option( '-D', '--indexes-path', dest='index_path', help='Indexes directory; location of .ebwt and .fa files.' ) - parser.add_option( '-r', '--mate-inner-dist', dest='mate_inner_dist', help='This is the expected (mean) inner distance between mate pairs. \ - For, example, for paired end runs with fragments selected at 300bp, \ - where each end is 50bp, you should set -r to be 200. There is no default, \ - and this parameter is required for paired end runs.') - parser.add_option( '', '--mate-std-dev', dest='mate_std_dev', help='Standard deviation of distribution on inner distances between male pairs.' ) - parser.add_option( '', '--read-mismatches', dest='read_mismatches' ) - parser.add_option( '', '--bowtie-n', action="store_true", dest='bowtie_n' ) - parser.add_option( '', '--no-discordant', action="store_true", dest='report_concordant_pairs_only' ) - parser.add_option( '-a', '--min-anchor-length', dest='min_anchor_length', - help='The "anchor length". TopHat will report junctions spanned by reads with at least this many bases on each side of the junction.' ) - parser.add_option( '-m', '--splice-mismatches', dest='splice_mismatches', help='The maximum number of mismatches that can appear in the anchor region of a spliced alignment.' ) - parser.add_option( '-i', '--min-intron-length', dest='min_intron_length', - help='The minimum intron length. TopHat will ignore donor/acceptor pairs closer than this many bases apart.' ) - parser.add_option( '-I', '--max-intron-length', dest='max_intron_length', - help='The maximum intron length. When searching for junctions ab initio, TopHat will ignore donor/acceptor pairs farther than this many bases apart, except when such a pair is supported by a split segment alignment of a long read.' ) - parser.add_option( '-g', '--max_multihits', dest='max_multihits', help='Maximum number of alignments to be allowed' ) - parser.add_option( '', '--seg-mismatches', dest='seg_mismatches', help='Number of mismatches allowed in each segment alignment for reads mapped independently' ) - parser.add_option( '', '--seg-length', dest='seg_length', help='Minimum length of read segments' ) - parser.add_option( '', '--library-type', dest='library_type', help='TopHat will treat the reads as strand specific. Every read alignment will have an XS attribute tag. Consider supplying library type options below to select the correct RNA-seq protocol.' ) - parser.add_option( '', '--allow-indels', action="store_true", help='Allow indel search. Indel search is disabled by default.(Not used since version 1.3.0)' ) - parser.add_option( '', '--max-insertion-length', dest='max_insertion_length', help='The maximum insertion length. The default is 3.' ) - parser.add_option( '', '--max-deletion-length', dest='max_deletion_length', help='The maximum deletion length. The default is 3.' ) - - # Options for supplying own junctions - parser.add_option( '-G', '--GTF', dest='gene_model_annotations', help='Supply TopHat with a list of gene model annotations. \ - TopHat will use the exon records in this file to build \ - a set of known splice junctions for each gene, and will \ - attempt to align reads to these junctions even if they \ - would not normally be covered by the initial mapping.') - parser.add_option( '-j', '--raw-juncs', dest='raw_juncs', help='Supply TopHat with a list of raw junctions. Junctions are \ - specified one per line, in a tab-delimited format. Records \ - look like: <+/-> left and right are \ - zero-based coordinates, and specify the last character of the \ - left sequenced to be spliced to the first character of the right \ - sequence, inclusive.') - parser.add_option( '', '--no-novel-juncs', action="store_true", dest='no_novel_juncs', help="Only look for junctions indicated in the \ - supplied GFF file. (ignored without -G)") - parser.add_option( '', '--no-novel-indels', action="store_true", dest='no_novel_indels', help="Skip indel search. Indel search is enabled by default.") - # Types of search. - parser.add_option( '', '--microexon-search', action="store_true", dest='microexon_search', help='With this option, the pipeline will attempt to find alignments incident to microexons. Works only for reads 50bp or longer.') - parser.add_option( '', '--coverage-search', action="store_true", dest='coverage_search', help='Enables the coverage based search for junctions. Use when coverage search is disabled by default (such as for reads 75bp or longer), for maximum sensitivity.') - parser.add_option( '', '--no-coverage-search', action="store_false", dest='coverage_search' ) - parser.add_option( '', '--min-segment-intron', dest='min_segment_intron', help='Minimum intron length that may be found during split-segment search' ) - parser.add_option( '', '--max-segment-intron', dest='max_segment_intron', help='Maximum intron length that may be found during split-segment search' ) - parser.add_option( '', '--min-coverage-intron', dest='min_coverage_intron', help='Minimum intron length that may be found during coverage search' ) - parser.add_option( '', '--max-coverage-intron', dest='max_coverage_intron', help='Maximum intron length that may be found during coverage search' ) - - # Bowtie2 options. - parser.add_option( '', '--b2-very-fast', action='store_true', dest='b2_very_fast') - parser.add_option( '', '--b2-fast', action='store_true', dest='b2_fast') - parser.add_option( '', '--b2-very-sensitive', action='store_true', dest='b2_very_sensitive') - parser.add_option( '', '--b2-sensitive', action='store_true', dest='b2_sensitive') - - # Fusion search options. - parser.add_option( '', '--fusion-search', action='store_true', dest='fusion_search' ) - parser.add_option( '', '--fusion-anchor-length', dest='fusion_anchor_length' ) - parser.add_option( '', '--fusion-min-dist', dest='fusion_min_dist' ) - parser.add_option( '', '--fusion-read-mismatches', dest='fusion_read_mismatches' ) - parser.add_option( '', '--fusion-multireads', dest='fusion_multireads' ) - parser.add_option( '', '--fusion-multipairs', dest='fusion_multipairs' ) - parser.add_option( '', '--fusion-ignore-chromosomes', dest='fusion_ignore_chromosomes' ) - - # Wrapper options. - parser.add_option( '-1', '--input1', dest='input1', help='The (forward or single-end) reads file in Sanger FASTQ format' ) - parser.add_option( '-2', '--input2', dest='input2', help='The reverse reads file in Sanger FASTQ format' ) - parser.add_option( '', '--single-paired', dest='single_paired', help='' ) - parser.add_option( '', '--settings', dest='settings', help='' ) - - # Read group options. - parser.add_option( '', '--rgid', dest='rgid', help='Read group identifier' ) - parser.add_option( '', '--rglb', dest='rglb', help='Library name' ) - parser.add_option( '', '--rgpl', dest='rgpl', help='Platform/technology used to produce the reads' ) - parser.add_option( '', '--rgsm', dest='rgsm', help='Sample' ) - - (options, args) = parser.parse_args() - - # Color or base space - space = '' - if options.color_space: - space = '-C' - - # Creat bowtie index if necessary. - tmp_index_dir = tempfile.mkdtemp() - if options.own_file: - index_path = os.path.join( tmp_index_dir, '.'.join( os.path.split( options.own_file )[1].split( '.' )[:-1] ) ) - try: - os.link( options.own_file, index_path + '.fa' ) - except: - # Tophat prefers (but doesn't require) fasta file to be in same directory, with .fa extension - pass - cmd_index = 'bowtie2-build %s -f %s %s' % ( space, options.own_file, index_path ) - try: - tmp = tempfile.NamedTemporaryFile( dir=tmp_index_dir ).name - tmp_stderr = open( tmp, 'wb' ) - proc = subprocess.Popen( args=cmd_index, shell=True, cwd=tmp_index_dir, stderr=tmp_stderr.fileno() ) - returncode = proc.wait() - tmp_stderr.close() - # get stderr, allowing for case where it's very large - tmp_stderr = open( tmp, 'rb' ) - stderr = '' - buffsize = 1048576 - try: - while True: - stderr += tmp_stderr.read( buffsize ) - if not stderr or len( stderr ) % buffsize != 0: - break - except OverflowError: - pass - tmp_stderr.close() - if returncode != 0: - raise Exception, stderr - except Exception, e: - if os.path.exists( tmp_index_dir ): - shutil.rmtree( tmp_index_dir ) - stop_err( 'Error indexing reference sequence\n' + str( e ) ) - else: - index_path = options.index_path - - # Build tophat command. - cmd = 'tophat2 --keep-fasta-order %s %s %s' - reads = options.input1 - if options.input2: - reads += ' ' + options.input2 - opts = '-p %s %s' % ( options.num_threads, space ) - if options.single_paired == 'paired': - opts += ' -r %s' % options.mate_inner_dist - if options.report_concordant_pairs_only: - opts += ' --no-discordant' - # Read group options. - if options.rgid: - if not options.rglb or not options.rgpl or not options.rgsm: - stop_err( 'If you want to specify read groups, you must include the ID, LB, PL, and SM tags.' ) - opts += ' --rg-id %s' % options.rgid - opts += ' --rg-library %s' % options.rglb - opts += ' --rg-platform %s' % options.rgpl - opts += ' --rg-sample %s' % options.rgsm - - if options.settings == 'preSet': - cmd = cmd % ( opts, index_path, reads ) - else: - try: - if int( options.min_anchor_length ) >= 3: - opts += ' -a %s' % options.min_anchor_length - else: - raise Exception, 'Minimum anchor length must be 3 or greater' - opts += ' -m %s' % options.splice_mismatches - opts += ' -i %s' % options.min_intron_length - opts += ' -I %s' % options.max_intron_length - opts += ' -g %s' % options.max_multihits - # Custom junctions options. - if options.gene_model_annotations: - opts += ' -G %s' % options.gene_model_annotations - if options.raw_juncs: - opts += ' -j %s' % options.raw_juncs - if options.no_novel_juncs: - opts += ' --no-novel-juncs' - if options.library_type: - opts += ' --library-type %s' % options.library_type - if options.no_novel_indels: - opts += ' --no-novel-indels' - else: - if options.max_insertion_length: - opts += ' --max-insertion-length %i' % int( options.max_insertion_length ) - if options.max_deletion_length: - opts += ' --max-deletion-length %i' % int( options.max_deletion_length ) - # Max options do not work for Tophat v1.2.0, despite documentation to the contrary. (Fixed in version 1.3.1) - # need to warn user of this fact - #sys.stdout.write( "Max insertion length and max deletion length options don't work in Tophat v1.2.0\n" ) - - if options.read_mismatches: - opts += ' --read-mismatches %i' % int( options.read_mismatches ) - if options.bowtie_n: - opts += ' --bowtie-n' - - # Search type options. - if options.coverage_search: - opts += ' --coverage-search --min-coverage-intron %s --max-coverage-intron %s' % ( options.min_coverage_intron, options.max_coverage_intron ) - else: - opts += ' --no-coverage-search' - if options.microexon_search: - opts += ' --microexon-search' - if options.single_paired == 'paired' and options.mate_std_dev: - opts += ' --mate-std-dev %s' % options.mate_std_dev - if options.seg_mismatches: - opts += ' --segment-mismatches %d' % int( options.seg_mismatches ) - if options.seg_length: - opts += ' --segment-length %d' % int( options.seg_length ) - if options.min_segment_intron: - opts += ' --min-segment-intron %d' % int( options.min_segment_intron ) - if options.max_segment_intron: - opts += ' --max-segment-intron %d' % int( options.max_segment_intron ) - - # Fusion search options. - if options.fusion_search: - opts += ' --fusion-search --fusion-anchor-length %i --fusion-min-dist %i --fusion-read-mismatches %i --fusion-multireads %i --fusion-multipairs %i' % \ - ( int( options.fusion_anchor_length ), int( options.fusion_min_dist ), - int( options.fusion_read_mismatches ), int( options.fusion_multireads ), - int( options.fusion_multipairs ) ) - if options.fusion_ignore_chromosomes: - opts += ' --fusion-ignore-chromosomes %s' % options.fusion_ignore_chromosomes - - # Bowtie2 options. - if options.b2_very_fast: - opts += ' --b2-very-fast' - if options.b2_fast: - opts += ' --b2-fast' - if options.b2_sensitive: - opts += ' --b2-sensitive' - if options.b2_very_sensitive: - opts += ' --b2-very-sensitive' - - cmd = cmd % ( opts, index_path, reads ) - - except Exception, e: - # Clean up temp dirs - if os.path.exists( tmp_index_dir ): - shutil.rmtree( tmp_index_dir ) - stop_err( 'Something is wrong with the alignment parameters and the alignment could not be run\n' + str( e ) ) - print cmd - - # Run - try: - tmp_out = tempfile.NamedTemporaryFile().name - tmp_stdout = open( tmp_out, 'wb' ) - tmp_err = tempfile.NamedTemporaryFile().name - tmp_stderr = open( tmp_err, 'wb' ) - proc = subprocess.Popen( args=cmd, shell=True, cwd=".", stdout=tmp_stdout, stderr=tmp_stderr ) - returncode = proc.wait() - tmp_stderr.close() - # get stderr, allowing for case where it's very large - tmp_stderr = open( tmp_err, 'rb' ) - stderr = '' - buffsize = 1048576 - try: - while True: - stderr += tmp_stderr.read( buffsize ) - if not stderr or len( stderr ) % buffsize != 0: - break - except OverflowError: - pass - tmp_stdout.close() - tmp_stderr.close() - if returncode != 0: - raise Exception, stderr - - except Exception, e: - stop_err( 'Error in tophat:\n' + str( e ) ) - - # Clean up temp dirs - if os.path.exists( tmp_index_dir ): - shutil.rmtree( tmp_index_dir ) - -if __name__=="__main__": __main__() diff --git a/tools/ngs_rna/tophat2_wrapper.xml b/tools/ngs_rna/tophat2_wrapper.xml index fb0f1d19bca..2040e114586 100644 --- a/tools/ngs_rna/tophat2_wrapper.xml +++ b/tools/ngs_rna/tophat2_wrapper.xml @@ -7,49 +7,38 @@ bowtie2 tophat2 - - tophat2_wrapper.py + + + ## + ## Set path to index, building the reference if necessary. + ## + + #set index_path = '' + #if $refGenomeSource.genomeSource == "history": + bowtie2-build "$refGenomeSource.ownFile" genome ; ln -s "$refGenomeSource.ownFile" genome.fa ; + #set index_path = 'genome' + #else: + #set index_path = $refGenomeSource.index.fields.path + #end if + + ## + ## Run tophat. + ## + + tophat2 ## Change this to accommodate the number of threads you have available. - --num-threads="4" - - ## Provide outputs. - --junctions-output=$junctions - --hits-output=$accepted_hits - - ## Handle reference file. - #if $refGenomeSource.genomeSource == "history": - --own-file=$refGenomeSource.ownFile - #else: - --indexes-path="${refGenomeSource.index.fields.path}" - #end if - - ## Are reads single-end or paired? - --single-paired=$singlePaired.sPaired - - ## First input file always required. - --input1=$input1 - - ## Second input only if input is paired-end. - ## Also set parameters specific to paired data. - #if $singlePaired.sPaired == "paired" - --input2=$singlePaired.input2 - -r $singlePaired.mate_inner_distance - --mate-std-dev=$singlePaired.mate_std_dev - - #if str($singlePaired.report_discordant_pairs) == "No": - --no-discordant - #end if - #end if + --num-threads 4 ## Set params. - --settings=$params.settingsType #if $params.settingsType == "full": --read-mismatches $params.read_mismatches #if str($params.bowtie_n) == "Yes": --bowtie-n #end if + --read-edit-dist $params.read_edit_dist + --read-realign-edit-dist $params.read_realign_edit_dist -a $params.anchor_length -m $params.splice_mismatches -i $params.min_intron_length @@ -57,9 +46,9 @@ -g $params.max_multihits --min-segment-intron $params.min_segment_intron --max-segment-intron $params.max_segment_intron - --seg-mismatches=$params.seg_mismatches - --seg-length=$params.seg_length - --library-type=$params.library_type + --segment-mismatches $params.seg_mismatches + --segment-length $params.seg_length + --library-type $params.library_type ## Indel search. #if $params.indel_search.allow_indel_search == "Yes": @@ -78,7 +67,6 @@ #if $params.own_junctions.raw_juncs.use_juncs == "Yes": -j $params.own_junctions.raw_juncs.raw_juncs #end if - ## TODO: No idea why a string cast is necessary, but it is: #if str($params.own_junctions.no_novel_juncs) == "Yes": --no-novel-juncs #end if @@ -91,7 +79,7 @@ #else: --no-coverage-search #end if - ## TODO: No idea why the type conversion is necessary, but it seems to be. + #if str($params.microexon_search) == "Yes": --microexon-search #end if @@ -116,12 +104,27 @@ ## Read group information. #if $readGroup.specReadGroup == "yes" - --rgid="$readGroup.rgid" - --rglb="$readGroup.rglb" - --rgpl="$readGroup.rgpl" - --rgsm="$readGroup.rgsm" + --rg-id "$readGroup.rgid" + --rg-library "$readGroup.rglb" + --rg-platform "$readGroup.rgpl" + --rg-sample "$readGroup.rgsm" + #end if + + ## Set index path, inputs and parameters specific to paired data. + #if $singlePaired.sPaired == "paired" + -r $singlePaired.mate_inner_distance + --mate-std-dev=$singlePaired.mate_std_dev + + #if str($singlePaired.report_discordant_pairs) == "No": + --no-discordant + #end if + + ${index_path} $singlePaired.input1 $singlePaired.input2 + #else + ${index_path} $singlePaired.input1 #end if + @@ -157,6 +160,10 @@ + + + + @@ -260,6 +267,11 @@ + + + + + (params['settingsType'] == 'full' and params['fusion_search']['do_search'] == 'Yes') @@ -277,6 +289,7 @@ + tophat_macros.xml @@ -312,7 +325,7 @@ - + @@ -369,6 +382,7 @@ + @@ -418,6 +432,7 @@ + From 0c1c31c1ce021c363c4651ffa9b7abba42babe4c Mon Sep 17 00:00:00 2001 From: Dave Bouvier Date: Mon, 6 May 2013 14:53:46 -0400 Subject: [PATCH 352/523] Limit when tests are explicitly removed from the tests dict. --- .../install_and_test_tool_shed_repositories/functional_tests.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/test/install_and_test_tool_shed_repositories/functional_tests.py b/test/install_and_test_tool_shed_repositories/functional_tests.py index 7f39a9035fa..d6a4d811529 100644 --- a/test/install_and_test_tool_shed_repositories/functional_tests.py +++ b/test/install_and_test_tool_shed_repositories/functional_tests.py @@ -704,8 +704,6 @@ def main(): os.environ[ 'GALAXY_TOOL_SHED_TEST_FILE' ] = galaxy_shed_tools_dict os.environ[ 'GALAXY_TEST_HOST' ] = galaxy_test_host os.environ[ 'GALAXY_TEST_PORT' ] = galaxy_test_port - # Explicitly clear tests from twill's test environment. - remove_generated_tests( app ) # Set the module-level variable 'toolbox', so that test.functional.test_toolbox will generate the appropriate test methods. test_toolbox.toolbox = app.toolbox # Generate the test methods for this installed repository. We need to pass in True here, or it will look From 395360b0793a95894907e97f7654e464fc800553 Mon Sep 17 00:00:00 2001 From: Dannon Baker Date: Mon, 6 May 2013 15:49:37 -0400 Subject: [PATCH 353/523] Update worfklow_run to use the same styles as the tool pane 'Execute' button. --- templates/webapps/galaxy/workflow/run.mako | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/webapps/galaxy/workflow/run.mako b/templates/webapps/galaxy/workflow/run.mako index 630aad357d2..dad194f51fd 100644 --- a/templates/webapps/galaxy/workflow/run.mako +++ b/templates/webapps/galaxy/workflow/run.mako @@ -504,6 +504,6 @@ if wf_parms: named:

    %endif - + %endif From 5e13e15723301886c91bc30f73111be02a063434 Mon Sep 17 00:00:00 2001 From: Dave Bouvier Date: Mon, 6 May 2013 16:57:57 -0400 Subject: [PATCH 354/523] Fix mismatch between tool id and test id when deleting tests/uninstalling tested repositories. --- .../functional_tests.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/test/install_and_test_tool_shed_repositories/functional_tests.py b/test/install_and_test_tool_shed_repositories/functional_tests.py index d6a4d811529..8931b8101c6 100644 --- a/test/install_and_test_tool_shed_repositories/functional_tests.py +++ b/test/install_and_test_tool_shed_repositories/functional_tests.py @@ -196,6 +196,7 @@ def execute_uninstall_method( app ): name = str( repository.name ) owner = str( repository.owner ) changeset_revision = str( repository.installed_changeset_revision ) + log.debug( 'Changeset revision %s of repository %s queued for uninstallation.' % ( changeset_revision, name ) ) repository_dict = dict( name=name, owner=owner, changeset_revision=changeset_revision ) # Generate a test method to uninstall this repository through the embedded Galaxy application's web interface. test_install_repositories.generate_uninstall_method( repository_dict ) @@ -321,6 +322,7 @@ def remove_generated_tests( app ): # and try to re-run the tests after uninstalling the repository, which will cause false failure reports, # since the test data has been deleted from disk by now. tests_to_delete = [] + tools_to_delete = [] global test_toolbox for key in test_toolbox.__dict__: if key.startswith( 'TestForTool_' ): @@ -328,12 +330,14 @@ def remove_generated_tests( app ): # We can't delete this test just yet, we're still iterating over __dict__. tests_to_delete.append( key ) tool_id = key.replace( 'TestForTool_', '' ) - if tool_id in app.toolbox.tools_by_id: - # But we can remove the relevant tool from app.toolbox.tools_by_id. - del app.toolbox.tools_by_id[ tool_id ] + for tool in app.toolbox.tools_by_id: + if tool.replace( '_', ' ' ) == tool_id.replace( '_', ' ' ): + tools_to_delete.append( tool ) for key in tests_to_delete: # Now delete the tests found in the previous loop. del test_toolbox.__dict__[ key ] + for tool in tools_to_delete: + del app.toolbox.tools_by_id[ tool ] def run_tests( test_config ): loader = nose.loader.TestLoader( config=test_config ) From c77ec36e9bdf0c2e48a5b5d27a6dc842970a2263 Mon Sep 17 00:00:00 2001 From: Dave Bouvier Date: Tue, 7 May 2013 11:22:38 -0400 Subject: [PATCH 355/523] Enable dynamic configuration of message box content and visibility in the wsgi .ini file. Display the configured brand, if any, in the element. --- lib/galaxy/config.py | 4 ++++ lib/galaxy/webapps/tool_shed/config.py | 4 ++++ templates/base/base_panels.mako | 19 +++++++++---------- tool_shed_wsgi.ini.sample | 5 +++++ universe_wsgi.ini.sample | 5 +++++ 5 files changed, 27 insertions(+), 10 deletions(-) diff --git a/lib/galaxy/config.py b/lib/galaxy/config.py index a8ff07f1ff8..3e02987b0f2 100644 --- a/lib/galaxy/config.py +++ b/lib/galaxy/config.py @@ -154,6 +154,10 @@ class Configuration( object ): self.ucsc_display_sites = kwargs.get( 'ucsc_display_sites', "main,test,archaea,ucla" ).lower().split(",") self.gbrowse_display_sites = kwargs.get( 'gbrowse_display_sites', "modencode,sgd_yeast,tair,wormbase,wormbase_ws120,wormbase_ws140,wormbase_ws170,wormbase_ws180,wormbase_ws190,wormbase_ws200,wormbase_ws204,wormbase_ws210,wormbase_ws220,wormbase_ws225" ).lower().split(",") self.brand = kwargs.get( 'brand', None ) + # Configuration for the message box directly below the masthead. + self.message_box_visible = kwargs.get( 'message_box_visible', False ) + self.message_box_content = kwargs.get( 'message_box_content', None ) + self.message_box_class = kwargs.get( 'message_box_class', 'info' ) self.support_url = kwargs.get( 'support_url', 'http://wiki.g2.bx.psu.edu/Support' ) self.wiki_url = kwargs.get( 'wiki_url', 'http://g2.trac.bx.psu.edu/' ) self.blog_url = kwargs.get( 'blog_url', None ) diff --git a/lib/galaxy/webapps/tool_shed/config.py b/lib/galaxy/webapps/tool_shed/config.py index 739596f71ef..4f1a51387ba 100644 --- a/lib/galaxy/webapps/tool_shed/config.py +++ b/lib/galaxy/webapps/tool_shed/config.py @@ -79,6 +79,10 @@ class Configuration( object ): self.nginx_upload_path = kwargs.get( 'nginx_upload_path', False ) self.log_actions = string_as_bool( kwargs.get( 'log_actions', 'False' ) ) self.brand = kwargs.get( 'brand', None ) + # Configuration for the message box directly below the masthead. + self.message_box_visible = kwargs.get( 'message_box_visible', False ) + self.message_box_content = kwargs.get( 'message_box_content', None ) + self.message_box_class = kwargs.get( 'message_box_class', 'info' ) self.support_url = kwargs.get( 'support_url', 'http://wiki.g2.bx.psu.edu/Support' ) self.wiki_url = kwargs.get( 'wiki_url', 'http://wiki.g2.bx.psu.edu/FrontPage' ) self.blog_url = kwargs.get( 'blog_url', None ) diff --git a/templates/base/base_panels.mako b/templates/base/base_panels.mako index e1bde686ff1..0722438540c 100644 --- a/templates/base/base_panels.mako +++ b/templates/base/base_panels.mako @@ -3,9 +3,8 @@ <% self.has_left_panel = hasattr( self, 'left_panel' ) self.has_right_panel = hasattr( self, 'right_panel' ) - self.message_box_visible=False + self.message_box_visible = app.config.message_box_visible self.overlay_visible=False - self.message_box_class="" self.active_view=None self.body_class="" self.require_javascript=False @@ -245,16 +244,16 @@ </div> </%def> -## Messagebox -<%def name="message_box_content()"> -</%def> - ## Document <html> <!--base_panels.mako--> ${self.init()} <head> - <title>${self.title()} + %if app.config.brand: + ${self.title()} / ${app.config.brand} + %else: + ${self.title()} + %endif ## For mobile browsers, don't scale up @@ -284,9 +283,9 @@ ${self.masthead()}
    -
    - %if self.message_box_visible: - ${self.message_box_content()} +
    + %if self.message_box_visible and app.config.message_box_content: + ${app.config.message_box_content} %endif
    ${self.overlay(visible=self.overlay_visible)} diff --git a/tool_shed_wsgi.ini.sample b/tool_shed_wsgi.ini.sample index 263a4b04265..3c211ef338a 100644 --- a/tool_shed_wsgi.ini.sample +++ b/tool_shed_wsgi.ini.sample @@ -88,6 +88,11 @@ sendmail_path = /usr/sbin/sendmail # Use the new iframe / javascript based layout use_new_layout = true +# Show a message box under the masthead. +#message_box_visible = False +#message_box_content = None +#message_box_class = info + # Serving static files (needed if running standalone) static_enabled = True static_cache_time = 360 diff --git a/universe_wsgi.ini.sample b/universe_wsgi.ini.sample index 8e1668e6340..65585890f00 100644 --- a/universe_wsgi.ini.sample +++ b/universe_wsgi.ini.sample @@ -264,6 +264,11 @@ nglims_config_file = tool-data/nglims.yaml # -- UI Localization +# Show a message box under the masthead. +#message_box_visible = False +#message_box_content = None +#message_box_class = info + # Append "/{brand}" to the "Galaxy" text in the masthead. #brand = None From e3365b0da8df2bfaee3a5a5deb30eb917dfa6685 Mon Sep 17 00:00:00 2001 From: Dave Bouvier Date: Tue, 7 May 2013 14:24:50 -0400 Subject: [PATCH 356/523] Add missing import and instantiation of security helper. --- lib/tool_shed/galaxy_install/migrate/common.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/tool_shed/galaxy_install/migrate/common.py b/lib/tool_shed/galaxy_install/migrate/common.py index a6d2535f23f..2a583dadfd7 100644 --- a/lib/tool_shed/galaxy_install/migrate/common.py +++ b/lib/tool_shed/galaxy_install/migrate/common.py @@ -3,6 +3,7 @@ import galaxy.config import galaxy.datatypes.registry from galaxy import tools from galaxy.tools.data import * +from galaxy.web import security import galaxy.model.mapping import galaxy.tools.search from galaxy.objectstore import build_object_store_from_config @@ -32,6 +33,8 @@ class MigrateToolsApplication( object ): self.config.database_connection = "sqlite:///%s?isolation_level=IMMEDIATE" % self.config.database self.config.update_integrated_tool_panel = True self.object_store = build_object_store_from_config( self.config ) + # Security helper + self.security = security.SecurityHelper( id_secret=self.config.id_secret ) # Setup the database engine and ORM self.model = galaxy.model.mapping.init( self.config.file_path, self.config.database_connection, From 7a3daf850017dc4510708b9cf7aae2005f5f4de1 Mon Sep 17 00:00:00 2001 From: Daniel Blankenberg Date: Tue, 7 May 2013 16:46:47 -0400 Subject: [PATCH 357/523] Fix for refreshing current history when copying datasets. --- lib/galaxy/webapps/galaxy/controllers/dataset.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/galaxy/webapps/galaxy/controllers/dataset.py b/lib/galaxy/webapps/galaxy/controllers/dataset.py index fb20536148c..a4a0029fcb1 100644 --- a/lib/galaxy/webapps/galaxy/controllers/dataset.py +++ b/lib/galaxy/webapps/galaxy/controllers/dataset.py @@ -1034,8 +1034,9 @@ class DatasetInterface( BaseUIController, UsesAnnotations, UsesHistoryMixin, Use user = trans.get_user() if source_history is not None: history = self.get_history(trans, source_history) + current_history = trans.get_history() else: - history = trans.get_history() + history = current_history = trans.get_history() refresh_frames = [] if source_dataset_ids: if not isinstance( source_dataset_ids, list ): @@ -1083,7 +1084,7 @@ class DatasetInterface( BaseUIController, UsesAnnotations, UsesHistoryMixin, Use else: for hist in target_histories: hist.add_dataset( hda.copy( copy_children = True ) ) - if history in target_histories: + if current_history in target_histories: refresh_frames = ['history'] trans.sa_session.flush() hist_names_str = ", ".join( ['%s' % @@ -1100,7 +1101,7 @@ class DatasetInterface( BaseUIController, UsesAnnotations, UsesHistoryMixin, Use target_histories = user.active_histories return trans.fill_template( "/dataset/copy_view.mako", source_history = history, - current_history = trans.get_history(), + current_history = current_history, source_dataset_ids = source_dataset_ids, target_history_id = target_history_id, target_history_ids = target_history_ids, From fb0cd7b30470afe8f371816972ac2811764edeb7 Mon Sep 17 00:00:00 2001 From: Daniel Blankenberg Date: Wed, 8 May 2013 10:45:12 -0400 Subject: [PATCH 358/523] Pack scripts --- static/scripts/packed/mvc/data.js | 2 +- static/scripts/packed/viz/trackster/filters.js | 2 +- static/scripts/packed/viz/trackster/painters.js | 2 +- static/scripts/packed/viz/trackster/tracks.js | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/static/scripts/packed/mvc/data.js b/static/scripts/packed/mvc/data.js index 260c196ad4b..eee349975c1 100644 --- a/static/scripts/packed/mvc/data.js +++ b/static/scripts/packed/mvc/data.js @@ -1 +1 @@ -define(["libs/backbone/backbone-relational"],function(){var d=Backbone.RelationalModel.extend({});var e=Backbone.RelationalModel.extend({defaults:{id:"",type:"",name:"",hda_ldda:"hda",metadata:null},initialize:function(){var i=new d();_.each(_.keys(this.attributes),function(j){if(j.indexOf("metadata_")===0){var l=j.split("metadata_")[1];i.set(l,this.attributes[j]);delete this.attributes[j]}},this);this.set("metadata",i)},get_metadata:function(i){return this.attributes.metadata.get(i)},urlRoot:galaxy_paths.get("datasets_url")});var c=e.extend({defaults:_.extend({},e.prototype.defaults,{chunk_url:null,first_data_chunk:null,chunk_index:-1,at_eof:false}),initialize:function(i){e.prototype.initialize.call(this);this.attributes.chunk_index=(this.attributes.first_data_chunk?1:0)},get_next_chunk:function(){if(this.attributes.at_eof){return null}var i=this,j=$.Deferred();$.getJSON(this.attributes.chunk_url,{chunk:i.attributes.chunk_index++}).success(function(k){var l;if(k.ck_data!==""){l=k}else{i.attributes.at_eof=true;l=null}j.resolve(l)});return j}});var g=Backbone.Collection.extend({model:e});var f=Backbone.View.extend({initialize:function(i){(new b(i)).render()},render:function(){var m=$("").attr({id:"content_table",cellpadding:0});this.$el.append(m);var i=this.model.get_metadata("column_names");if(i){m.append("")}var k=this.model.get("first_data_chunk");if(k){this._renderChunk(k)}var j=this,n=_.find(this.$el.parents(),function(o){return $(o).css("overflow")==="auto"}),l=false;if(!n){n=window}n=$(n);n.scroll(function(){if(!l&&(j.$el.height()-n.scrollTop()-n.height()<=0)){l=true;$.when(j.model.get_next_chunk()).then(function(o){if(o){j._renderChunk(o);l=false}})}});$("#loading_indicator").ajaxStart(function(){$(this).show()}).ajaxStop(function(){$(this).hide()})},_renderCell:function(k,i,l){var j=this.model.get_metadata("column_types");if(l!==undefined){return $(""),k=this.model.get_metadata("columns");if(j.length===k){_.each(j,function(n,m){l.append(this._renderCell(n,m))},this)}else{if(j.length>k){_.each(j.slice(0,k-1),function(n,m){l.append(this._renderCell(n,m))},this);l.append(this._renderCell(j.slice(k-1).join("\t"),k-1))}else{if(k>5&&j.length===k-1){_.each(j,function(n,m){l.append(this._renderCell(n,m))},this);l.append($("
    "+i.join("")+"
    ").attr("colspan",l).addClass("stringalign").text(k)}else{if(j[i]==="str"||j==="list"){return $("").addClass("stringalign").text(k)}else{return $("").text(k)}}},_renderRow:function(i){var j=i.split("\t"),l=$("
    "))}else{l.append(this._renderCell(i,0,k))}}}return l},_renderChunk:function(i){var j=this.$el.find("table");_.each(i.ck_data.split("\n"),function(k,l){j.append(this._renderRow(k))},this)}});var b=Backbone.View.extend({col:{chrom:null,start:null,end:null},url_viz:null,dataset_id:null,genome_build:null,initialize:function(i){var j=i.model.attributes.metadata.attributes;if(typeof j.chromCol==="undefined"||typeof j.startCol==="undefined"||typeof j.endCol==="undefined"){console.log("TabularButtonTrackster : Metadata for column identification is missing.")}else{this.col.chrom=j.chromCol-1;this.col.start=j.startCol-1;this.col.end=j.endCol-1}if(this.col.chrom===null){return}if(typeof i.model.attributes.id==="undefined"){console.log("TabularButtonTrackster : Dataset identification is missing.")}else{this.dataset_id=i.model.attributes.id}if(typeof i.model.attributes.url_viz==="undefined"){console.log("TabularButtonTrackster : Url for visualization controller is missing.")}else{this.url_viz=i.model.attributes.url_viz}if(typeof i.model.attributes.genome_build!=="undefined"){this.genome_build=i.model.attributes.genome_build}},events:{"mouseover tr":"btn_viz_show",mouseleave:"btn_viz_hide"},btn_viz_show:function(m){if(this.col.chrom===null){return}var q=$(m.target).parent();var n=q.children().eq(this.col.chrom).html();var i=q.children().eq(this.col.start).html();var k=q.children().eq(this.col.end).html();if(n!==""&&i!==""&&k!==""){var p={dataset_id:this.dataset_id,gene_region:n+":"+i+"-"+k};var l=q.offset();var j=l.left-10;var o=l.top;$("#btn_viz").css({position:"fixed",top:o+"px",left:j+"px"});$("#btn_viz").off("click");$("#btn_viz").click(this.create_trackster_action(this.url_viz,p,this.genome_build));$("#btn_viz").show()}},btn_viz_hide:function(){$("#btn_viz").hide()},create_trackster_action:function(i,k,j){return function(){var l={};if(j){l["f-dbkey"]=j}$.ajax({url:i+"/list_tracks?"+$.param(l),dataType:"html",error:function(){alert(("Could not add this dataset to browser")+".")},success:function(m){var n=window.parent;n.show_modal(("View Data in a New or Saved Visualization"),"",{Cancel:function(){n.hide_modal()},"View in saved visualization":function(){n.show_modal(("Add Data to Saved Visualization"),m,{Cancel:function(){n.hide_modal()},"Add to visualization":function(){$(n.document).find("input[name=id]:checked").each(function(){var o=$(this).val();k.id=o;n.location=i+"/trackster?"+$.param(k)})}})},"View in new visualization":function(){n.location=i+"/trackster?"+$.param(k)}})}});return false}},render:function(){var i=new IconButtonView({model:new IconButton({title:"Visualize",icon_class:"chart_curve",id:"btn_viz"})});this.$el.append(i.render().$el);$("#btn_viz").hide()}});var a=function(l,j,m,i){var k=new j({model:new l(m)});k.render();if(i){i.append(k.$el)}return k};var h=function(k,i){var j=$("
    ").appendTo(i);return new f({el:j,model:new c(k)}).render()};return{Dataset:e,TabularDataset:c,DatasetCollection:g,TabularDatasetChunkedView:f,createTabularDatasetChunkedView:h}}); \ No newline at end of file +define(["libs/backbone/backbone-relational"],function(){var d=Backbone.RelationalModel.extend({});var e=Backbone.RelationalModel.extend({defaults:{id:"",type:"",name:"",hda_ldda:"hda",metadata:null},initialize:function(){this._set_metadata();this.on("change",this._set_metadata,this)},_set_metadata:function(){var i=new d();_.each(_.keys(this.attributes),function(j){if(j.indexOf("metadata_")===0){var l=j.split("metadata_")[1];i.set(l,this.attributes[j]);delete this.attributes[j]}},this);this.set("metadata",i,{silent:true})},get_metadata:function(i){return this.attributes.metadata.get(i)},urlRoot:galaxy_paths.get("datasets_url")});var c=e.extend({defaults:_.extend({},e.prototype.defaults,{chunk_url:null,first_data_chunk:null,chunk_index:-1,at_eof:false}),initialize:function(i){e.prototype.initialize.call(this);this.attributes.chunk_index=(this.attributes.first_data_chunk?1:0)},get_next_chunk:function(){if(this.attributes.at_eof){return null}var i=this,j=$.Deferred();$.getJSON(this.attributes.chunk_url,{chunk:i.attributes.chunk_index++}).success(function(k){var l;if(k.ck_data!==""){l=k}else{i.attributes.at_eof=true;l=null}j.resolve(l)});return j}});var g=Backbone.Collection.extend({model:e});var f=Backbone.View.extend({initialize:function(i){(new b(i)).render()},render:function(){var m=$("").attr({id:"content_table",cellpadding:0});this.$el.append(m);var i=this.model.get_metadata("column_names");if(i){m.append("")}var k=this.model.get("first_data_chunk");if(k){this._renderChunk(k)}var j=this,n=_.find(this.$el.parents(),function(o){return $(o).css("overflow")==="auto"}),l=false;if(!n){n=window}n=$(n);n.scroll(function(){if(!l&&(j.$el.height()-n.scrollTop()-n.height()<=0)){l=true;$.when(j.model.get_next_chunk()).then(function(o){if(o){j._renderChunk(o);l=false}})}});$("#loading_indicator").ajaxStart(function(){$(this).show()}).ajaxStop(function(){$(this).hide()})},_renderCell:function(k,i,l){var j=this.model.get_metadata("column_types");if(l!==undefined){return $(""),k=this.model.get_metadata("columns");if(j.length===k){_.each(j,function(n,m){l.append(this._renderCell(n,m))},this)}else{if(j.length>k){_.each(j.slice(0,k-1),function(n,m){l.append(this._renderCell(n,m))},this);l.append(this._renderCell(j.slice(k-1).join("\t"),k-1))}else{if(k>5&&j.length===k-1){_.each(j,function(n,m){l.append(this._renderCell(n,m))},this);l.append($("
    "+i.join("")+"
    ").attr("colspan",l).addClass("stringalign").text(k)}else{if(j[i]==="str"||j==="list"){return $("").addClass("stringalign").text(k)}else{return $("").text(k)}}},_renderRow:function(i){var j=i.split("\t"),l=$("
    "))}else{l.append(this._renderCell(i,0,k))}}}return l},_renderChunk:function(i){var j=this.$el.find("table");_.each(i.ck_data.split("\n"),function(k,l){j.append(this._renderRow(k))},this)}});var b=Backbone.View.extend({col:{chrom:null,start:null,end:null},url_viz:null,dataset_id:null,genome_build:null,initialize:function(i){var j=i.model.attributes.metadata.attributes;if(typeof j.chromCol==="undefined"||typeof j.startCol==="undefined"||typeof j.endCol==="undefined"){console.log("TabularButtonTrackster : Metadata for column identification is missing.")}else{this.col.chrom=j.chromCol-1;this.col.start=j.startCol-1;this.col.end=j.endCol-1}if(this.col.chrom===null){return}if(typeof i.model.attributes.id==="undefined"){console.log("TabularButtonTrackster : Dataset identification is missing.")}else{this.dataset_id=i.model.attributes.id}if(typeof i.model.attributes.url_viz==="undefined"){console.log("TabularButtonTrackster : Url for visualization controller is missing.")}else{this.url_viz=i.model.attributes.url_viz}if(typeof i.model.attributes.genome_build!=="undefined"){this.genome_build=i.model.attributes.genome_build}},events:{"mouseover tr":"btn_viz_show",mouseleave:"btn_viz_hide"},btn_viz_show:function(m){if(this.col.chrom===null){return}var q=$(m.target).parent();var n=q.children().eq(this.col.chrom).html();var i=q.children().eq(this.col.start).html();var k=q.children().eq(this.col.end).html();if(n!==""&&i!==""&&k!==""){var p={dataset_id:this.dataset_id,gene_region:n+":"+i+"-"+k};var l=q.offset();var j=l.left-10;var o=l.top;$("#btn_viz").css({position:"fixed",top:o+"px",left:j+"px"});$("#btn_viz").off("click");$("#btn_viz").click(this.create_trackster_action(this.url_viz,p,this.genome_build));$("#btn_viz").show()}},btn_viz_hide:function(){$("#btn_viz").hide()},create_trackster_action:function(i,k,j){return function(){var l={};if(j){l["f-dbkey"]=j}$.ajax({url:i+"/list_tracks?"+$.param(l),dataType:"html",error:function(){alert(("Could not add this dataset to browser")+".")},success:function(m){var n=window.parent;n.show_modal(("View Data in a New or Saved Visualization"),"",{Cancel:function(){n.hide_modal()},"View in saved visualization":function(){n.show_modal(("Add Data to Saved Visualization"),m,{Cancel:function(){n.hide_modal()},"Add to visualization":function(){$(n.document).find("input[name=id]:checked").each(function(){var o=$(this).val();k.id=o;n.location=i+"/trackster?"+$.param(k)})}})},"View in new visualization":function(){n.location=i+"/trackster?"+$.param(k)}})}});return false}},render:function(){var i=new IconButtonView({model:new IconButton({title:"Visualize",icon_class:"chart_curve",id:"btn_viz"})});this.$el.append(i.render().$el);$("#btn_viz").hide()}});var a=function(l,j,m,i){var k=new j({model:new l(m)});k.render();if(i){i.append(k.$el)}return k};var h=function(k,i){var j=$("
    ").appendTo(i);return new f({el:j,model:new c(k)}).render()};return{Dataset:e,TabularDataset:c,DatasetCollection:g,TabularDatasetChunkedView:f,createTabularDatasetChunkedView:h}}); \ No newline at end of file diff --git a/static/scripts/packed/viz/trackster/filters.js b/static/scripts/packed/viz/trackster/filters.js index f36ecf33564..f21639ffc27 100644 --- a/static/scripts/packed/viz/trackster/filters.js +++ b/static/scripts/packed/viz/trackster/filters.js @@ -1 +1 @@ -define(["libs/underscore"],function(c){var f=c.extend;var a=function(g){this.manager=null;this.name=g.name;this.index=g.index;this.tool_id=g.tool_id;this.tool_exp_name=g.tool_exp_name};f(a.prototype,{to_dict:function(){return{name:this.name,index:this.index,tool_id:this.tool_id,tool_exp_name:this.tool_exp_name}}});var d=function(i,h,g){return $("").attr("href","javascript:void(0);").attr("title",i).addClass("icon-button").addClass(h).tooltip().click(g)};var e=function(o){a.call(this,o);this.low=("low" in o?o.low:-Number.MAX_VALUE);this.high=("high" in o?o.high:Number.MAX_VALUE);this.min=("min" in o?o.min:Number.MAX_VALUE);this.max=("max" in o?o.max:-Number.MAX_VALUE);this.container=null;this.slider=null;this.slider_label=null;var k=function(p,q,r){p.click(function(){var w=q.text(),u=parseFloat(r.slider("option","max")),t=(u<=1?4:u<=1000000?u.toString().length:6),v=false,s=$(this).parents(".slider-row");s.addClass("input");if(r.slider("option","values")){t=2*t+1;v=true}q.text("");$("").attr("size",t).attr("maxlength",t).attr("value",w).appendTo(q).focus().select().click(function(x){x.stopPropagation()}).blur(function(){$(this).remove();q.text(w);s.removeClass("input")}).keyup(function(B){if(B.keyCode===27){$(this).trigger("blur")}else{if(B.keyCode===13){var z=r.slider("option","min"),x=r.slider("option","max"),A=function(C){return(isNaN(C)||C>x||C").addClass("filter-row slider-row");var g=$("
    ").addClass("elt-label").appendTo(h.parent_div),m=$("").addClass("slider-name").text(h.name+" ").appendTo(g),i=$("").text(this.low+"-"+this.high),j=$("").addClass("slider-value").appendTo(g).append("[").append(i).append("]");h.values_span=i;var l=$("
    ").addClass("slider").appendTo(h.parent_div);h.control_element=$("
    ").attr("id",h.name+"-filter-control").appendTo(l);h.control_element.slider({range:true,min:this.min,max:this.max,step:this.get_slider_step(this.min,this.max),values:[this.low,this.high],slide:function(p,q){h.slide(p,q)},change:function(p,q){h.control_element.slider("option","slide").call(h.control_element,p,q)}});h.slider=h.control_element;h.slider_label=i;k(j,i,h.control_element);var n=$("
    ").addClass("display-controls").appendTo(h.parent_div);this.transparency_icon=d("Use filter for data transparency","layer-transparent",function(){if(h.manager.alpha_filter!==h){h.manager.alpha_filter=h;h.manager.parent_div.find(".layer-transparent").removeClass("active").hide();h.transparency_icon.addClass("active").show()}else{h.manager.alpha_filter=null;h.transparency_icon.removeClass("active")}h.manager.track.request_draw(true,true)}).appendTo(n).hide();this.height_icon=d("Use filter for data height","arrow-resize-090",function(){if(h.manager.height_filter!==h){h.manager.height_filter=h;h.manager.parent_div.find(".arrow-resize-090").removeClass("active").hide();h.height_icon.addClass("active").show()}else{h.manager.height_filter=null;h.height_icon.removeClass("active")}h.manager.track.request_draw(true,true)}).appendTo(n).hide();h.parent_div.hover(function(){h.transparency_icon.show();h.height_icon.show()},function(){if(h.manager.alpha_filter!==h){h.transparency_icon.hide()}if(h.manager.height_filter!==h){h.height_icon.hide()}});$("
    ").appendTo(h.parent_div)};f(e.prototype,{to_dict:function(){var g=a.prototype.to_dict.call(this);return f(g,{type:"number",min:this.min,max:this.max,low:this.low,high:this.high})},copy:function(){return new e({name:this.name,index:this.index,tool_id:this.tool_id,tool_exp_name:this.tool_exp_name})},get_slider_step:function(i,g){var h=g-i;return(h<=2?0.01:1)},slide:function(i,j){var h=j.values;this.values_span.text(h[0]+"-"+h[1]);this.low=h[0];this.high=h[1];var g=this;setTimeout(function(){if(h[0]===g.low&&h[1]===g.high){g.manager.track.request_draw(true,true)}},25)},applies_to:function(g){if(g.length>this.index){return true}return false},_keep_val:function(g){return(isNaN(g)||(g>=this.low&&g<=this.high))},keep:function(h){if(!this.applies_to(h)){return true}var k=this;var l=h[this.index];if(l instanceof Array){var j=true;for(var g=0;gthis.max){this.max=Math.ceil(l);g=true}}return g},update_ui_elt:function(){if(this.ming){this.slider.slider("option","min",this.min);this.slider.slider("option","max",this.max);this.slider.slider("option","step",this.get_slider_step(this.min,this.max));this.slider.slider("option","values",[this.min,this.max])}}});var b=function(j,p){this.track=j;this.alpha_filter=null;this.height_filter=null;this.filters=[];this.parent_div=$("
    ").addClass("filters").hide();this.parent_div.bind("drag",function(i){i.stopPropagation()}).click(function(i){i.stopPropagation()}).bind("dblclick",function(i){i.stopPropagation()}).bind("keydown",function(i){i.stopPropagation()});if(p&&"filters" in p){var g=("alpha_filter" in p?p.alpha_filter:null),k=("height_filter" in p?p.height_filter:null),m=p.filters,h;for(var n=0;n").addClass("param-row").appendTo(this.parent_div);var o=$("").attr("value","Run on complete dataset").appendTo(q);var l=this;o.click(function(){l.run_on_dataset()})}};f(b.prototype,{show:function(){this.parent_div.show()},hide:function(){this.parent_div.hide()},toggle:function(){this.parent_div.toggle()},visible:function(){return this.parent_div.is(":visible")},to_dict:function(){var k={},j=[],h;for(var g=0;g= "+o.low}if(o.max!==o.high){g=n(m,o.tool_id,[]);g[g.length]=o.tool_exp_name+" <= "+o.high}}}var h=[];for(var k in m){h[h.length]=[k,m[k]]}(function j(u,r){var p=r[0],q=p[0],t=p[1],s="("+t.join(") and (")+")",i={cond:s,input:u,target_dataset_id:u,tool_id:q};r=r.slice(1);$.getJSON(run_tool_url,i,function(v){if(v.error){show_modal("Filter Dataset","Error running tool "+q,{Close:hide_modal})}else{if(r.length===0){show_modal("Filtering Dataset","Filter(s) are running on the complete dataset. Outputs are in dataset's history.",{Close:hide_modal})}else{j(v.dataset_id,r)}}})})(this.track.dataset_id,h)}});return{FiltersManager:b,NumberFilter:e}}); \ No newline at end of file +define(["libs/underscore"],function(c){var f=c.extend;var a=function(g){this.manager=null;this.name=g.name;this.index=g.index;this.tool_id=g.tool_id;this.tool_exp_name=g.tool_exp_name};f(a.prototype,{to_dict:function(){return{name:this.name,index:this.index,tool_id:this.tool_id,tool_exp_name:this.tool_exp_name}}});var d=function(i,h,g){return $("").attr("href","javascript:void(0);").attr("title",i).addClass("icon-button").addClass(h).tooltip().click(g)};var e=function(o){a.call(this,o);this.low=("low" in o?o.low:-Number.MAX_VALUE);this.high=("high" in o?o.high:Number.MAX_VALUE);this.min=("min" in o?o.min:Number.MAX_VALUE);this.max=("max" in o?o.max:-Number.MAX_VALUE);this.container=null;this.slider=null;this.slider_label=null;var k=function(p,q,r){p.click(function(){var w=q.text(),u=parseFloat(r.slider("option","max")),t=(u<=1?4:u<=1000000?u.toString().length:6),v=false,s=$(this).parents(".slider-row");s.addClass("input");if(r.slider("option","values")){t=2*t+1;v=true}q.text("");$("").attr("size",t).attr("maxlength",t).attr("value",w).appendTo(q).focus().select().click(function(x){x.stopPropagation()}).blur(function(){$(this).remove();q.text(w);s.removeClass("input")}).keyup(function(B){if(B.keyCode===27){$(this).trigger("blur")}else{if(B.keyCode===13){var z=r.slider("option","min"),x=r.slider("option","max"),A=function(C){return(isNaN(C)||C>x||C").addClass("filter-row slider-row");var g=$("
    ").addClass("elt-label").appendTo(h.parent_div),m=$("").addClass("slider-name").text(h.name+" ").appendTo(g),i=$("").text(this.low+"-"+this.high),j=$("").addClass("slider-value").appendTo(g).append("[").append(i).append("]");h.values_span=i;var l=$("
    ").addClass("slider").appendTo(h.parent_div);h.control_element=$("
    ").attr("id",h.name+"-filter-control").appendTo(l);h.control_element.slider({range:true,min:this.min,max:this.max,step:this.get_slider_step(this.min,this.max),values:[this.low,this.high],slide:function(p,q){h.slide(p,q)},change:function(p,q){h.control_element.slider("option","slide").call(h.control_element,p,q)}});h.slider=h.control_element;h.slider_label=i;k(j,i,h.control_element);var n=$("
    ").addClass("display-controls").appendTo(h.parent_div);this.transparency_icon=d("Use filter for data transparency","layer-transparent",function(){if(h.manager.alpha_filter!==h){h.manager.alpha_filter=h;h.manager.parent_div.find(".layer-transparent").removeClass("active").hide();h.transparency_icon.addClass("active").show()}else{h.manager.alpha_filter=null;h.transparency_icon.removeClass("active")}h.manager.track.request_draw(false,true,true)}).appendTo(n).hide();this.height_icon=d("Use filter for data height","arrow-resize-090",function(){if(h.manager.height_filter!==h){h.manager.height_filter=h;h.manager.parent_div.find(".arrow-resize-090").removeClass("active").hide();h.height_icon.addClass("active").show()}else{h.manager.height_filter=null;h.height_icon.removeClass("active")}h.manager.track.request_draw(false,true,true)}).appendTo(n).hide();h.parent_div.hover(function(){h.transparency_icon.show();h.height_icon.show()},function(){if(h.manager.alpha_filter!==h){h.transparency_icon.hide()}if(h.manager.height_filter!==h){h.height_icon.hide()}});$("
    ").appendTo(h.parent_div)};f(e.prototype,{to_dict:function(){var g=a.prototype.to_dict.call(this);return f(g,{type:"number",min:this.min,max:this.max,low:this.low,high:this.high})},copy:function(){return new e({name:this.name,index:this.index,tool_id:this.tool_id,tool_exp_name:this.tool_exp_name})},get_slider_step:function(i,g){var h=g-i;return(h<=2?0.01:1)},slide:function(i,j){var h=j.values;this.values_span.text(h[0]+"-"+h[1]);this.low=h[0];this.high=h[1];var g=this;setTimeout(function(){if(h[0]===g.low&&h[1]===g.high){g.manager.track.request_draw(false,true,true)}},25)},applies_to:function(g){if(g.length>this.index){return true}return false},_keep_val:function(g){return(isNaN(g)||(g>=this.low&&g<=this.high))},keep:function(h){if(!this.applies_to(h)){return true}var k=this;var l=h[this.index];if(l instanceof Array){var j=true;for(var g=0;gthis.max){this.max=Math.ceil(l);g=true}}return g},update_ui_elt:function(){if(this.ming){this.slider.slider("option","min",this.min);this.slider.slider("option","max",this.max);this.slider.slider("option","step",this.get_slider_step(this.min,this.max));this.slider.slider("option","values",[this.min,this.max])}}});var b=function(j,p){this.track=j;this.alpha_filter=null;this.height_filter=null;this.filters=[];this.parent_div=$("
    ").addClass("filters").hide();this.parent_div.bind("drag",function(i){i.stopPropagation()}).click(function(i){i.stopPropagation()}).bind("dblclick",function(i){i.stopPropagation()}).bind("keydown",function(i){i.stopPropagation()});if(p&&"filters" in p){var g=("alpha_filter" in p?p.alpha_filter:null),k=("height_filter" in p?p.height_filter:null),m=p.filters,h;for(var n=0;n").addClass("param-row").appendTo(this.parent_div);var o=$("").attr("value","Run on complete dataset").appendTo(q);var l=this;o.click(function(){l.run_on_dataset()})}};f(b.prototype,{show:function(){this.parent_div.show()},hide:function(){this.parent_div.hide()},toggle:function(){this.parent_div.toggle()},visible:function(){return this.parent_div.is(":visible")},to_dict:function(){var k={},j=[],h;for(var g=0;g= "+o.low}if(o.max!==o.high){g=n(m,o.tool_id,[]);g[g.length]=o.tool_exp_name+" <= "+o.high}}}var h=[];for(var k in m){h[h.length]=[k,m[k]]}(function j(u,r){var p=r[0],q=p[0],t=p[1],s="("+t.join(") and (")+")",i={cond:s,input:u,target_dataset_id:u,tool_id:q};r=r.slice(1);$.getJSON(run_tool_url,i,function(v){if(v.error){show_modal("Filter Dataset","Error running tool "+q,{Close:hide_modal})}else{if(r.length===0){show_modal("Filtering Dataset","Filter(s) are running on the complete dataset. Outputs are in dataset's history.",{Close:hide_modal})}else{j(v.dataset_id,r)}}})})(this.track.dataset_id,h)}});return{FiltersManager:b,NumberFilter:e}}); \ No newline at end of file diff --git a/static/scripts/packed/viz/trackster/painters.js b/static/scripts/packed/viz/trackster/painters.js index 70122616796..83d43c82a15 100644 --- a/static/scripts/packed/viz/trackster/painters.js +++ b/static/scripts/packed/viz/trackster/painters.js @@ -1 +1 @@ -define(["libs/underscore"],function(_){var extend=_.extend;var BEFORE=1001,CONTAINS=1002,OVERLAP_START=1003,OVERLAP_END=1004,CONTAINED_BY=1005,AFTER=1006;var compute_overlap=function(first_region,second_region){var first_start=first_region[0],first_end=first_region[1],second_start=second_region[0],second_end=second_region[1],overlap;if(first_startsecond_end){overlap=AFTER}else{if(first_end<=second_end){overlap=CONTAINED_BY}else{overlap=OVERLAP_END}}}return overlap};var is_overlap=function(first_region,second_region){var overlap=compute_overlap(first_region,second_region);return(overlap!==BEFORE&&overlap!==AFTER)};var dashedLine=function(ctx,x1,y1,x2,y2,dashLen){if(dashLen===undefined){dashLen=4}var dX=x2-x1;var dY=y2-y1;var dashes=Math.floor(Math.sqrt(dX*dX+dY*dY)/dashLen);var dashX=dX/dashes;var dashY=dY/dashes;var q;for(q=0;q1){delta_x_px=Math.ceil((data[1][0]-data[0][0])*w_scale)}else{delta_x_px=10}var pref_color=parseInt(this.prefs.color.slice(1),16),pref_r=(pref_color&16711680)>>16,pref_g=(pref_color&65280)>>8,pref_b=pref_color&255;for(var i=0,len=data.length;imax_value){top_overflow=true;y=max_value}}if(mode==="Histogram"){y=Math.round(y/vertical_range*height_px);ctx.fillRect(x_scaled,y_zero,delta_x_px,-y)}else{if(mode==="Intensity"){var saturation=(y-min_value)/vertical_range,new_r=Math.round(pref_r+(255-pref_r)*(1-saturation)),new_g=Math.round(pref_g+(255-pref_g)*(1-saturation)),new_b=Math.round(pref_b+(255-pref_b)*(1-saturation));ctx.fillStyle="rgb("+new_r+","+new_g+","+new_b+")";ctx.fillRect(x_scaled,0,delta_x_px,height_px)}else{y=Math.round(height_px-(y-min_value)/vertical_range*height_px);if(in_path){ctx.lineTo(x_scaled,y)}else{in_path=true;if(mode==="Filled"){ctx.moveTo(x_scaled,height_px);ctx.lineTo(x_scaled,y)}else{ctx.moveTo(x_scaled,y)}}}}ctx.fillStyle=this.prefs.overflow_color;if(top_overflow||bot_overflow){var overflow_x;if(mode==="Histogram"||mode==="Intensity"){overflow_x=delta_x_px}else{x_scaled-=2;overflow_x=4}if(top_overflow){ctx.fillRect(x_scaled,0,overflow_x,3)}if(bot_overflow){ctx.fillRect(x_scaled,height_px-3,overflow_x,3)}}ctx.fillStyle=this.prefs.color}if(mode==="Filled"){if(in_path){ctx.lineTo(x_scaled,y_zero);ctx.lineTo(0,y_zero)}ctx.fill()}else{ctx.stroke()}ctx.restore()};var FeaturePositionMapper=function(slot_height){this.feature_positions={};this.slot_height=slot_height;this.translation=0;this.y_translation=0};FeaturePositionMapper.prototype.map_feature_data=function(feature_data,slot,x_start,x_end){if(!this.feature_positions[slot]){this.feature_positions[slot]=[]}this.feature_positions[slot].push({data:feature_data,x_start:x_start,x_end:x_end})};FeaturePositionMapper.prototype.get_feature_data=function(x,y){var slot=Math.floor((y-this.y_translation)/this.slot_height),feature_dict;if(!this.feature_positions[slot]){return null}x+=this.translation;for(var i=0;i=feature_dict.x_start&&x<=feature_dict.x_end){return feature_dict.data}}};var FeaturePainter=function(data,view_start,view_end,prefs,mode,alpha_scaler,height_scaler){Painter.call(this,data,view_start,view_end,prefs,mode);this.alpha_scaler=(alpha_scaler?alpha_scaler:new Scaler());this.height_scaler=(height_scaler?height_scaler:new Scaler())};FeaturePainter.prototype.default_prefs={block_color:"#FFF",connector_color:"#FFF"};extend(FeaturePainter.prototype,{get_required_height:function(rows_required,width){var required_height=this.get_row_height(),y_scale=required_height,mode=this.mode;if(mode==="no_detail"||mode==="Squish"||mode==="Pack"){required_height=rows_required*y_scale}return required_height+this.get_top_padding(width)+this.get_bottom_padding(width)},get_top_padding:function(width){return 0},get_bottom_padding:function(width){return Math.max(Math.round(this.get_row_height()/2),5)},draw:function(ctx,width,height,w_scale,slots){var data=this.data,view_start=this.view_start,view_end=this.view_end;ctx.save();ctx.fillStyle=this.prefs.block_color;ctx.textAlign="right";var y_scale=this.get_row_height(),feature_mapper=new FeaturePositionMapper(y_scale),x_draw_coords;for(var i=0,len=data.length;iview_start)&&(this.mode==="Dense"||slot!==null)){x_draw_coords=this.draw_element(ctx,this.mode,feature,slot,view_start,view_end,w_scale,y_scale,width);feature_mapper.map_feature_data(feature,slot,x_draw_coords[0],x_draw_coords[1])}}ctx.restore();feature_mapper.y_translation=this.get_top_padding(width);return feature_mapper},draw_element:function(ctx,mode,feature,slot,tile_low,tile_high,w_scale,y_scale,width){console.log("WARNING: Unimplemented function.");return[0,0]}});var DENSE_TRACK_HEIGHT=10,NO_DETAIL_TRACK_HEIGHT=3,SQUISH_TRACK_HEIGHT=5,PACK_TRACK_HEIGHT=10,NO_DETAIL_FEATURE_HEIGHT=1,DENSE_FEATURE_HEIGHT=9,SQUISH_FEATURE_HEIGHT=3,PACK_FEATURE_HEIGHT=9,LABEL_SPACING=2,CONNECTOR_COLOR="#ccc";var LinkedFeaturePainter=function(data,view_start,view_end,prefs,mode,alpha_scaler,height_scaler){FeaturePainter.call(this,data,view_start,view_end,prefs,mode,alpha_scaler,height_scaler);this.draw_background_connector=true;this.draw_individual_connectors=false};extend(LinkedFeaturePainter.prototype,FeaturePainter.prototype,{get_row_height:function(){var mode=this.mode,height;if(mode==="Dense"){height=DENSE_TRACK_HEIGHT}else{if(mode==="no_detail"){height=NO_DETAIL_TRACK_HEIGHT}else{if(mode==="Squish"){height=SQUISH_TRACK_HEIGHT}else{height=PACK_TRACK_HEIGHT}}}return height},draw_element:function(ctx,mode,feature,slot,tile_low,tile_high,w_scale,y_scale,width){var feature_uid=feature[0],feature_start=feature[1],feature_end=feature[2],feature_name=feature[3],feature_strand=feature[4],f_start=Math.floor(Math.max(0,(feature_start-tile_low-0.5)*w_scale)),f_end=Math.ceil(Math.min(width,Math.max(0,(feature_end-tile_low-0.5)*w_scale))),draw_start=f_start,draw_end=f_end,y_center=(mode==="Dense"?0:(0+slot))*y_scale+this.get_top_padding(width),thickness,y_start,thick_start=null,thick_end=null,block_color=(!feature_strand||feature_strand==="+"||feature_strand==="."?this.prefs.block_color:this.prefs.reverse_strand_color);label_color=this.prefs.label_color;ctx.globalAlpha=this.alpha_scaler.gen_val(feature);if(mode==="Dense"){slot=1}if(mode==="no_detail"){ctx.fillStyle=block_color;ctx.fillRect(f_start,y_center+5,f_end-f_start,NO_DETAIL_FEATURE_HEIGHT)}else{var feature_ts=feature[5],feature_te=feature[6],feature_blocks=feature[7],full_height=true;if(feature_ts&&feature_te){thick_start=Math.floor(Math.max(0,(feature_ts-tile_low)*w_scale));thick_end=Math.ceil(Math.min(width,Math.max(0,(feature_te-tile_low)*w_scale)))}var thin_height,thick_height;if(mode==="Squish"){thin_height=1;thick_height=SQUISH_FEATURE_HEIGHT;full_height=false}else{if(mode==="Dense"){thin_height=5;thick_height=DENSE_FEATURE_HEIGHT}else{thin_height=5;thick_height=PACK_FEATURE_HEIGHT}}if(!feature_blocks){ctx.fillStyle=block_color;ctx.fillRect(f_start,y_center+1,f_end-f_start,thick_height);if(feature_strand&&full_height){if(feature_strand==="+"){ctx.fillStyle=ctx.canvas.manager.get_pattern("right_strand_inv")}else{if(feature_strand==="-"){ctx.fillStyle=ctx.canvas.manager.get_pattern("left_strand_inv")}}ctx.fillRect(f_start,y_center+1,f_end-f_start,thick_height)}}else{var cur_y_center,cur_height;if(mode==="Squish"||mode==="Dense"){cur_y_center=y_center+Math.floor(SQUISH_FEATURE_HEIGHT/2)+1;cur_height=1}else{if(feature_strand){cur_y_center=y_center;cur_height=thick_height}else{cur_y_center+=(SQUISH_FEATURE_HEIGHT/2)+1;cur_height=1}}if(this.draw_background_connector){if(mode==="Squish"||mode==="Dense"){ctx.fillStyle=CONNECTOR_COLOR}else{if(feature_strand){if(feature_strand==="+"){ctx.fillStyle=ctx.canvas.manager.get_pattern("right_strand")}else{if(feature_strand==="-"){ctx.fillStyle=ctx.canvas.manager.get_pattern("left_strand")}}}else{ctx.fillStyle=CONNECTOR_COLOR}}ctx.fillRect(f_start,cur_y_center,f_end-f_start,cur_height)}var start_and_height;for(var k=0,k_len=feature_blocks.length;kblock_end){continue}ctx.fillStyle=block_color;ctx.fillRect(block_start,y_center+(thick_height-thin_height)/2+1,block_end-block_start,thin_height);if(thick_start!==undefined&&feature_te>feature_ts&&!(block_start>thick_end||block_endtile_low){ctx.fillStyle=label_color;if(tile_low===0&&f_start-ctx.measureText(feature_name).width<0){ctx.textAlign="left";ctx.fillText(feature_name,f_end+LABEL_SPACING,y_center+8);draw_end+=ctx.measureText(feature_name).width+LABEL_SPACING}else{ctx.textAlign="right";ctx.fillText(feature_name,f_start-LABEL_SPACING,y_center+8);draw_start-=ctx.measureText(feature_name).width+LABEL_SPACING}}}ctx.globalAlpha=1;return[draw_start,draw_end]}});var ReadPainter=function(data,view_start,view_end,prefs,mode,alpha_scaler,height_scaler,ref_seq,base_color_fn){FeaturePainter.call(this,data,view_start,view_end,prefs,mode,alpha_scaler,height_scaler);this.ref_seq=(ref_seq?ref_seq.data:null);this.base_color_fn=base_color_fn};extend(ReadPainter.prototype,FeaturePainter.prototype,{get_row_height:function(){var height,mode=this.mode;if(mode==="Dense"){height=DENSE_TRACK_HEIGHT}else{if(mode==="Squish"){height=SQUISH_TRACK_HEIGHT}else{height=PACK_TRACK_HEIGHT;if(this.prefs.show_insertions){height*=2}}}return height},_parse_cigar:function(cigar_str){var cigar_ops="MIDNSHP=X";var blocks=[[0,0]],cur_block=blocks[0],base_pos=0,parsed_cigar=_.map(cigar_str.match(/[0-9]+[MIDNSHP=X]/g),function(op){var op_len=parseInt(op.slice(0,-1),10),op_char=op.slice(-1);if(op_char==="N"){if(cur_block[1]!==0){cur_block=[base_pos+op_len,base_pos+op_len];blocks.push(cur_block)}}else{if("ISHP".indexOf(op_char)===-1){cur_block[1]+=op_len;base_pos+=op_len}}return[cigar_ops.indexOf(op_char),op_len]});return{blocks:blocks,cigar:parsed_cigar}},draw_read:function(ctx,mode,w_scale,y_center,tile_low,tile_high,feature_start,cigar,strand,read_seq){ctx.textAlign="center";var tile_region=[tile_low,tile_high],base_offset=0,seq_offset=0,gap=Math.round(w_scale/2),char_width_px=ctx.canvas.manager.char_width_px,block_color=(strand==="+"?this.prefs.block_color:this.prefs.reverse_strand_color),pack_mode=(mode==="Pack");var draw_last=[];if(!cigar){cigar=[[0,read_seq.length]]}for(var cig_id=0,len=cigar.length;cig_id=tile_low&&seq_start+c<=tile_high){ref_char=(this.ref_seq?this.ref_seq[seq_start-tile_low+c]:null);read_char=seq[c];if((ref_char&&(!this.prefs.show_differences||(read_char.toLowerCase!=="n"&&(ref_char.toLowerCase()!==read_char.toLowerCase()))))||(!ref_char&&!this.prefs.show_differences)){var c_start=Math.floor(Math.max(0,(seq_start+c-tile_low)*w_scale));ctx.fillStyle=this.base_color_fn(seq[c]);if(pack_mode&&w_scale>char_width_px){ctx.fillText(seq[c],c_start,y_center+9)}else{if(w_scale>0.05){ctx.fillRect(c_start-gap,y_center+(pack_mode?1:4),Math.max(1,Math.round(w_scale)),(pack_mode?PACK_FEATURE_HEIGHT:SQUISH_FEATURE_HEIGHT))}}}}}}seq_offset+=cig_len;base_offset+=cig_len;break;case"N":ctx.fillStyle=CONNECTOR_COLOR;ctx.fillRect(s_start,y_center+5,s_end-s_start,1);base_offset+=cig_len;break;case"D":ctx.fillStyle="black";ctx.fillRect(s_start,y_center+4,s_end-s_start,3);base_offset+=cig_len;break;case"P":break;case"I":var insert_x_coord=s_start-gap;if(is_overlap([seq_start,seq_start+cig_len],tile_region)){var seq=read_seq.slice(seq_offset,seq_offset+cig_len);if(this.prefs.show_insertions){var x_center=s_start-(s_end-s_start)/2;if((mode==="Pack"||this.mode==="Auto")&&read_seq!==undefined&&w_scale>char_width_px){ctx.fillStyle="yellow";ctx.fillRect(x_center-gap,y_center-9,s_end-s_start,9);draw_last[draw_last.length]={type:"triangle",data:[insert_x_coord,y_center+4,5]};ctx.fillStyle=CONNECTOR_COLOR;switch(compute_overlap([seq_start,seq_start+cig_len],tile_region)){case (OVERLAP_START):seq=seq.slice(tile_low-seq_start);break;case (OVERLAP_END):seq=seq.slice(0,seq_start-tile_high);break;case (CONTAINED_BY):break;case (CONTAINS):seq=seq.slice(tile_low-seq_start,seq_start-tile_high);break}for(var c=0,str_len=seq.length;cchar_width_px){draw_last.push({type:"text",data:[seq.length,insert_x_coord,y_center+9]})}else{}}}seq_offset+=cig_len;break}}ctx.fillStyle="yellow";var item,type,data;for(var i=0;i=tile_low&&feature[4][0]<=tile_high&&feature[4][2]){this.draw_read(ctx,mode,w_scale,y_center,tile_low,tile_high,feature[4][0],feature[4][2],feature[4][3],feature[4][4])}else{connector=false}if(feature[5][1]>=tile_low&&feature[5][0]<=tile_high&&feature[5][2]){this.draw_read(ctx,mode,w_scale,y_center,tile_low,tile_high,feature[5][0],feature[5][2],feature[5][3],feature[5][4])}else{connector=false}if(connector&&b2_start>b1_end){ctx.fillStyle=CONNECTOR_COLOR;dashedLine(ctx,b1_end,y_center+5,b2_start,y_center+5)}}else{this.draw_read(ctx,mode,w_scale,y_center,tile_low,tile_high,feature_start,feature[4],feature[5],feature[6])}if(mode==="Pack"&&feature_start>=tile_low&&feature_name!=="."){ctx.fillStyle=this.prefs.label_color;if(tile_low===0&&f_start-ctx.measureText(feature_name).width<0){ctx.textAlign="left";ctx.fillText(feature_name,f_end+LABEL_SPACING,y_center+8)}else{ctx.textAlign="right";ctx.fillText(feature_name,f_start-LABEL_SPACING,y_center+8)}}return[0,0]}});var RefBasedReadPainter=function(data,view_start,view_end,prefs,mode,alpha_scaler,height_scaler,ref_seq,base_color_fn){ReadPainter.call(this,data,view_start,view_end,prefs,mode,alpha_scaler,height_scaler,ref_seq,base_color_fn)};extend(RefBasedReadPainter.prototype,ReadPainter.prototype,FeaturePainter,{draw_read:function(ctx,mode,w_scale,y_center,tile_low,tile_high,feature_start,cigar,strand,read_seq){ctx.textAlign="center";var tile_region=[tile_low,tile_high],base_offset=0,seq_offset=0,gap=Math.round(w_scale/2),char_width_px=ctx.canvas.manager.char_width_px,block_color=(strand==="+"?this.prefs.block_color:this.prefs.reverse_strand_color),pack_mode=(mode==="Pack"),drawing_blocks=[];var draw_last=[];var t=this._parse_cigar(cigar);cigar=t.cigar;drawing_blocks=t.blocks;for(var i=0;ichar_width_px){ctx.fillText(cur_seq[c],c_start,y_center+9)}else{if(w_scale>0.05){ctx.fillRect(c_start-gap,y_center+(pack_mode?1:4),Math.max(1,Math.round(w_scale)),(pack_mode?PACK_FEATURE_HEIGHT:SQUISH_FEATURE_HEIGHT))}}}}}if(cig_op==="X"){seq_offset+=cig_len}base_offset+=cig_len;break;case"N":ctx.fillStyle=CONNECTOR_COLOR;ctx.fillRect(s_start,y_center+5,s_end-s_start,1);base_offset+=cig_len;break;case"D":ctx.fillStyle="black";ctx.fillRect(s_start,y_center+4,s_end-s_start,3);base_offset+=cig_len;break;case"I":var insert_x_coord=s_start-gap;if(is_overlap([seq_start,seq_start+cig_len],tile_region)){var seq=read_seq.slice(seq_offset,seq_offset+cig_len);if(this.prefs.show_insertions){var x_center=s_start-(s_end-s_start)/2;if((mode==="Pack"||this.mode==="Auto")&&read_seq!==undefined&&w_scale>char_width_px){ctx.fillStyle="yellow";ctx.fillRect(x_center-gap,y_center-9,s_end-s_start,9);draw_last[draw_last.length]={type:"triangle",data:[insert_x_coord,y_center+4,5]};ctx.fillStyle=CONNECTOR_COLOR;switch(compute_overlap([seq_start,seq_start+cig_len],tile_region)){case (OVERLAP_START):seq=seq.slice(tile_low-seq_start);break;case (OVERLAP_END):seq=seq.slice(0,seq_start-tile_high);break;case (CONTAINED_BY):break;case (CONTAINS):seq=seq.slice(tile_low-seq_start,seq_start-tile_high);break}for(var c=0,str_len=seq.length;cchar_width_px){draw_last.push({type:"text",data:[seq.length,insert_x_coord,y_center+9]})}else{}}}seq_offset+=cig_len;break}}ctx.fillStyle="yellow";var item,type,data;for(var i=0;i0){ctx.beginPath();ctx.arc(x_center,y_center,block2_start-x_center,Math.PI,0);ctx.stroke()}}});var Color=function(rgb,a){if(Array.isArray(rgb)){this.rgb=rgb}else{if(rgb.length==6){this.rgb=rgb.match(/.{2}/g).map(function(c){return parseInt(c,16)})}else{if(rgb.length==7){this.rgb=rgb.substring(1,7).match(/.{2}/g).map(function(c){return parseInt(c,16)})}else{this.rgb=rgb.split("").map(function(c){return parseInt(c+c,16)})}}}this.alpha=typeof(a)==="number"?a:1};Color.prototype={eval:function(){return this},toCSS:function(){if(this.alpha<1){return"rgba("+this.rgb.map(function(c){return Math.round(c)}).concat(this.alpha).join(", ")+")"}else{return"#"+this.rgb.map(function(i){i=Math.round(i);i=(i>255?255:(i<0?0:i)).toString(16);return i.length===1?"0"+i:i}).join("")}},toHSL:function(){var r=this.rgb[0]/255,g=this.rgb[1]/255,b=this.rgb[2]/255,a=this.alpha;var max=Math.max(r,g,b),min=Math.min(r,g,b);var h,s,l=(max+min)/2,d=max-min;if(max===min){h=s=0}else{s=l>0.5?d/(2-max-min):d/(max+min);switch(max){case r:h=(g-b)/d+(g255?255:(i<0?0:i)).toString(16);return i.length===1?"0"+i:i}).join("")},mix:function(color2,weight){color1=this;var p=weight;var w=p*2-1;var a=color1.toHSL().a-color2.toHSL().a;var w1=(((w*a==-1)?w:(w+a)/(1+w*a))+1)/2;var w2=1-w1;var rgb=[color1.rgb[0]*w1+color2.rgb[0]*w2,color1.rgb[1]*w1+color2.rgb[1]*w2,color1.rgb[2]*w1+color2.rgb[2]*w2];var alpha=color1.alpha*p+color2.alpha*(1-p);return new Color(rgb,alpha)}};var LinearRamp=function(start_color,end_color,start_value,end_value){this.start_color=new Color(start_color);this.end_color=new Color(end_color);this.start_value=start_value;this.end_value=end_value;this.value_range=end_value-start_value};LinearRamp.prototype.map_value=function(value){value=Math.max(value,this.start_value);value=Math.min(value,this.end_value);value=(value-this.start_value)/this.value_range;return this.start_color.mix(this.end_color,1-value).toCSS()};var SplitRamp=function(start_color,middle_color,end_color,start_value,end_value){this.positive_ramp=new LinearRamp(middle_color,end_color,0,end_value);this.negative_ramp=new LinearRamp(middle_color,start_color,0,-start_value);this.start_value=start_value;this.end_value=end_value};SplitRamp.prototype.map_value=function(value){value=Math.max(value,this.start_value);value=Math.min(value,this.end_value);if(value>=0){return this.positive_ramp.map_value(value)}else{return this.negative_ramp.map_value(-value)}};var DiagonalHeatmapPainter=function(data,view_start,view_end,prefs,mode){Painter.call(this,data,view_start,view_end,prefs,mode);var i,len;if(this.prefs.min_value===undefined){var min_value=Infinity;for(i=0,len=this.data.length;isecond_end){overlap=AFTER}else{if(first_end<=second_end){overlap=CONTAINED_BY}else{overlap=OVERLAP_END}}}return overlap};var is_overlap=function(first_region,second_region){var overlap=compute_overlap(first_region,second_region);return(overlap!==BEFORE&&overlap!==AFTER)};var dashedLine=function(ctx,x1,y1,x2,y2,dashLen){if(dashLen===undefined){dashLen=4}var dX=x2-x1;var dY=y2-y1;var dashes=Math.floor(Math.sqrt(dX*dX+dY*dY)/dashLen);var dashX=dX/dashes;var dashY=dY/dashes;var q;for(q=0;q1){delta_x_px=Math.ceil((data[1][0]-data[0][0])*w_scale)}else{delta_x_px=10}var pref_color=parseInt(this.prefs.color.slice(1),16),pref_r=(pref_color&16711680)>>16,pref_g=(pref_color&65280)>>8,pref_b=pref_color&255;for(var i=0,len=data.length;imax_value){top_overflow=true;y=max_value}}if(mode==="Histogram"){y=Math.round(y/vertical_range*height_px);ctx.fillRect(x_scaled,y_zero,delta_x_px,-y)}else{if(mode==="Intensity"){var saturation=(y-min_value)/vertical_range,new_r=Math.round(pref_r+(255-pref_r)*(1-saturation)),new_g=Math.round(pref_g+(255-pref_g)*(1-saturation)),new_b=Math.round(pref_b+(255-pref_b)*(1-saturation));ctx.fillStyle="rgb("+new_r+","+new_g+","+new_b+")";ctx.fillRect(x_scaled,0,delta_x_px,height_px)}else{y=Math.round(height_px-(y-min_value)/vertical_range*height_px);if(in_path){ctx.lineTo(x_scaled,y)}else{in_path=true;if(mode==="Filled"){ctx.moveTo(x_scaled,height_px);ctx.lineTo(x_scaled,y)}else{ctx.moveTo(x_scaled,y)}}}}ctx.fillStyle=this.prefs.overflow_color;if(top_overflow||bot_overflow){var overflow_x;if(mode==="Histogram"||mode==="Intensity"){overflow_x=delta_x_px}else{x_scaled-=2;overflow_x=4}if(top_overflow){ctx.fillRect(x_scaled,0,overflow_x,3)}if(bot_overflow){ctx.fillRect(x_scaled,height_px-3,overflow_x,3)}}ctx.fillStyle=this.prefs.color}if(mode==="Filled"){if(in_path){ctx.lineTo(x_scaled,y_zero);ctx.lineTo(0,y_zero)}ctx.fill()}else{ctx.stroke()}ctx.restore()};var FeaturePositionMapper=function(slot_height){this.feature_positions={};this.slot_height=slot_height;this.translation=0;this.y_translation=0};FeaturePositionMapper.prototype.map_feature_data=function(feature_data,slot,x_start,x_end){if(!this.feature_positions[slot]){this.feature_positions[slot]=[]}this.feature_positions[slot].push({data:feature_data,x_start:x_start,x_end:x_end})};FeaturePositionMapper.prototype.get_feature_data=function(x,y){var slot=Math.floor((y-this.y_translation)/this.slot_height),feature_dict;if(!this.feature_positions[slot]){return null}x+=this.translation;for(var i=0;i=feature_dict.x_start&&x<=feature_dict.x_end){return feature_dict.data}}};var FeaturePainter=function(data,view_start,view_end,prefs,mode,alpha_scaler,height_scaler){Painter.call(this,data,view_start,view_end,prefs,mode);this.alpha_scaler=(alpha_scaler?alpha_scaler:new Scaler());this.height_scaler=(height_scaler?height_scaler:new Scaler())};FeaturePainter.prototype.default_prefs={block_color:"#FFF",connector_color:"#FFF"};extend(FeaturePainter.prototype,{get_required_height:function(rows_required,width){var required_height=this.get_row_height(),y_scale=required_height,mode=this.mode;if(mode==="no_detail"||mode==="Squish"||mode==="Pack"){required_height=rows_required*y_scale}return required_height+this.get_top_padding(width)+this.get_bottom_padding(width)},get_top_padding:function(width){return 0},get_bottom_padding:function(width){return Math.max(Math.round(this.get_row_height()/2),5)},draw:function(ctx,width,height,w_scale,slots){var data=this.data,view_start=this.view_start,view_end=this.view_end;ctx.save();ctx.fillStyle=this.prefs.block_color;ctx.textAlign="right";var y_scale=this.get_row_height(),feature_mapper=new FeaturePositionMapper(y_scale),x_draw_coords;for(var i=0,len=data.length;iview_start)&&(this.mode==="Dense"||slot!==null)){x_draw_coords=this.draw_element(ctx,this.mode,feature,slot,view_start,view_end,w_scale,y_scale,width);feature_mapper.map_feature_data(feature,slot,x_draw_coords[0],x_draw_coords[1])}}ctx.restore();feature_mapper.y_translation=this.get_top_padding(width);return feature_mapper},draw_element:function(ctx,mode,feature,slot,tile_low,tile_high,w_scale,y_scale,width){console.log("WARNING: Unimplemented function.");return[0,0]}});var DENSE_TRACK_HEIGHT=10,NO_DETAIL_TRACK_HEIGHT=3,SQUISH_TRACK_HEIGHT=5,PACK_TRACK_HEIGHT=10,NO_DETAIL_FEATURE_HEIGHT=1,DENSE_FEATURE_HEIGHT=9,SQUISH_FEATURE_HEIGHT=3,PACK_FEATURE_HEIGHT=9,LABEL_SPACING=2,CONNECTOR_COLOR="#ccc";var LinkedFeaturePainter=function(data,view_start,view_end,prefs,mode,alpha_scaler,height_scaler){FeaturePainter.call(this,data,view_start,view_end,prefs,mode,alpha_scaler,height_scaler);this.draw_background_connector=true;this.draw_individual_connectors=false};extend(LinkedFeaturePainter.prototype,FeaturePainter.prototype,{get_row_height:function(){var mode=this.mode,height;if(mode==="Dense"){height=DENSE_TRACK_HEIGHT}else{if(mode==="no_detail"){height=NO_DETAIL_TRACK_HEIGHT}else{if(mode==="Squish"){height=SQUISH_TRACK_HEIGHT}else{height=PACK_TRACK_HEIGHT}}}return height},draw_element:function(ctx,mode,feature,slot,tile_low,tile_high,w_scale,y_scale,width){var feature_uid=feature[0],feature_start=feature[1],feature_end=feature[2],feature_name=feature[3],feature_strand=feature[4],f_start=Math.floor(Math.max(0,(feature_start-tile_low-0.5)*w_scale)),f_end=Math.ceil(Math.min(width,Math.max(0,(feature_end-tile_low-0.5)*w_scale))),draw_start=f_start,draw_end=f_end,y_center=(mode==="Dense"?0:(0+slot))*y_scale+this.get_top_padding(width),thickness,y_start,thick_start=null,thick_end=null,block_color=(!feature_strand||feature_strand==="+"||feature_strand==="."?this.prefs.block_color:this.prefs.reverse_strand_color);label_color=this.prefs.label_color;ctx.globalAlpha=this.alpha_scaler.gen_val(feature);if(mode==="Dense"){slot=1}if(mode==="no_detail"){ctx.fillStyle=block_color;ctx.fillRect(f_start,y_center+5,f_end-f_start,NO_DETAIL_FEATURE_HEIGHT)}else{var feature_ts=feature[5],feature_te=feature[6],feature_blocks=feature[7],full_height=true;if(feature_ts&&feature_te){thick_start=Math.floor(Math.max(0,(feature_ts-tile_low)*w_scale));thick_end=Math.ceil(Math.min(width,Math.max(0,(feature_te-tile_low)*w_scale)))}var thin_height,thick_height;if(mode==="Squish"){thin_height=1;thick_height=SQUISH_FEATURE_HEIGHT;full_height=false}else{if(mode==="Dense"){thin_height=5;thick_height=DENSE_FEATURE_HEIGHT}else{thin_height=5;thick_height=PACK_FEATURE_HEIGHT}}if(!feature_blocks){ctx.fillStyle=block_color;ctx.fillRect(f_start,y_center+1,f_end-f_start,thick_height);if(feature_strand&&full_height){if(feature_strand==="+"){ctx.fillStyle=ctx.canvas.manager.get_pattern("right_strand_inv")}else{if(feature_strand==="-"){ctx.fillStyle=ctx.canvas.manager.get_pattern("left_strand_inv")}}ctx.fillRect(f_start,y_center+1,f_end-f_start,thick_height)}}else{var cur_y_center,cur_height;if(mode==="Squish"||mode==="Dense"){cur_y_center=y_center+Math.floor(SQUISH_FEATURE_HEIGHT/2)+1;cur_height=1}else{if(feature_strand){cur_y_center=y_center;cur_height=thick_height}else{cur_y_center+=(SQUISH_FEATURE_HEIGHT/2)+1;cur_height=1}}if(this.draw_background_connector){if(mode==="Squish"||mode==="Dense"){ctx.fillStyle=CONNECTOR_COLOR}else{if(feature_strand){if(feature_strand==="+"){ctx.fillStyle=ctx.canvas.manager.get_pattern("right_strand")}else{if(feature_strand==="-"){ctx.fillStyle=ctx.canvas.manager.get_pattern("left_strand")}}}else{ctx.fillStyle=CONNECTOR_COLOR}}ctx.fillRect(f_start,cur_y_center,f_end-f_start,cur_height)}var start_and_height;for(var k=0,k_len=feature_blocks.length;kblock_end){continue}ctx.fillStyle=block_color;ctx.fillRect(block_start,y_center+(thick_height-thin_height)/2+1,block_end-block_start,thin_height);if(thick_start!==undefined&&feature_te>feature_ts&&!(block_start>thick_end||block_endtile_low){ctx.fillStyle=label_color;if(tile_low===0&&f_start-ctx.measureText(feature_name).width<0){ctx.textAlign="left";ctx.fillText(feature_name,f_end+LABEL_SPACING,y_center+8);draw_end+=ctx.measureText(feature_name).width+LABEL_SPACING}else{ctx.textAlign="right";ctx.fillText(feature_name,f_start-LABEL_SPACING,y_center+8);draw_start-=ctx.measureText(feature_name).width+LABEL_SPACING}}}ctx.globalAlpha=1;return[draw_start,draw_end]}});var ReadPainter=function(data,view_start,view_end,prefs,mode,alpha_scaler,height_scaler,ref_seq,base_color_fn){FeaturePainter.call(this,data,view_start,view_end,prefs,mode,alpha_scaler,height_scaler);this.ref_seq=(ref_seq?ref_seq.data:null);this.base_color_fn=base_color_fn};extend(ReadPainter.prototype,FeaturePainter.prototype,{get_row_height:function(){var height,mode=this.mode;if(mode==="Dense"){height=DENSE_TRACK_HEIGHT}else{if(mode==="Squish"){height=SQUISH_TRACK_HEIGHT}else{height=PACK_TRACK_HEIGHT;if(this.prefs.show_insertions){height*=2}}}return height},_parse_cigar:function(cigar_str){var cigar_ops="MIDNSHP=X";var blocks=[[0,0]],cur_block=blocks[0],base_pos=0,parsed_cigar=_.map(cigar_str.match(/[0-9]+[MIDNSHP=X]/g),function(op){var op_len=parseInt(op.slice(0,-1),10),op_char=op.slice(-1);if(op_char==="N"){if(cur_block[1]!==0){cur_block=[base_pos+op_len,base_pos+op_len];blocks.push(cur_block)}}else{if("ISHP".indexOf(op_char)===-1){cur_block[1]+=op_len;base_pos+=op_len}}return[cigar_ops.indexOf(op_char),op_len]});return{blocks:blocks,cigar:parsed_cigar}},draw_read:function(ctx,mode,w_scale,y_center,tile_low,tile_high,feature_start,cigar,strand,read_seq){ctx.textAlign="center";var tile_region=[tile_low,tile_high],base_offset=0,seq_offset=0,gap=Math.round(w_scale/2),char_width_px=ctx.canvas.manager.char_width_px,block_color=(strand==="+"?this.prefs.block_color:this.prefs.reverse_strand_color),pack_mode=(mode==="Pack");var draw_last=[];if(!cigar){cigar=[[0,read_seq.length]]}for(var cig_id=0,len=cigar.length;cig_id=tile_low&&seq_start+c<=tile_high){ref_char=(this.ref_seq?this.ref_seq[seq_start-tile_low+c]:null);read_char=seq[c];if((ref_char&&(!this.prefs.show_differences||(read_char.toLowerCase!=="n"&&(ref_char.toLowerCase()!==read_char.toLowerCase()))))||(!ref_char&&!this.prefs.show_differences)){var c_start=Math.floor(Math.max(0,(seq_start+c-tile_low)*w_scale));ctx.fillStyle=this.base_color_fn(seq[c]);if(pack_mode&&w_scale>char_width_px){ctx.fillText(seq[c],c_start,y_center+9)}else{if(w_scale>0.05){ctx.fillRect(c_start-gap,y_center+(pack_mode?1:4),Math.max(1,Math.round(w_scale)),(pack_mode?PACK_FEATURE_HEIGHT:SQUISH_FEATURE_HEIGHT))}}}}}}seq_offset+=cig_len;base_offset+=cig_len;break;case"N":ctx.fillStyle=CONNECTOR_COLOR;ctx.fillRect(s_start,y_center+5,s_end-s_start,1);base_offset+=cig_len;break;case"D":ctx.fillStyle="black";ctx.fillRect(s_start,y_center+4,s_end-s_start,3);base_offset+=cig_len;break;case"P":break;case"I":var insert_x_coord=s_start-gap;if(is_overlap([seq_start,seq_start+cig_len],tile_region)){var seq=read_seq.slice(seq_offset,seq_offset+cig_len);if(this.prefs.show_insertions){var x_center=s_start-(s_end-s_start)/2;if((mode==="Pack"||this.mode==="Auto")&&read_seq!==undefined&&w_scale>char_width_px){ctx.fillStyle="yellow";ctx.fillRect(x_center-gap,y_center-9,s_end-s_start,9);draw_last[draw_last.length]={type:"triangle",data:[insert_x_coord,y_center+4,5]};ctx.fillStyle=CONNECTOR_COLOR;switch(compute_overlap([seq_start,seq_start+cig_len],tile_region)){case (OVERLAP_START):seq=seq.slice(tile_low-seq_start);break;case (OVERLAP_END):seq=seq.slice(0,seq_start-tile_high);break;case (CONTAINED_BY):break;case (CONTAINS):seq=seq.slice(tile_low-seq_start,seq_start-tile_high);break}for(var c=0,str_len=seq.length;cchar_width_px){draw_last.push({type:"text",data:[seq.length,insert_x_coord,y_center+9]})}else{}}}seq_offset+=cig_len;break}}ctx.fillStyle="yellow";var item,type,data;for(var i=0;i=tile_low&&feature[4][0]<=tile_high&&feature[4][2]){this.draw_read(ctx,mode,w_scale,y_center,tile_low,tile_high,feature[4][0],feature[4][2],feature[4][3],feature[4][4])}else{connector=false}if(feature[5][1]>=tile_low&&feature[5][0]<=tile_high&&feature[5][2]){this.draw_read(ctx,mode,w_scale,y_center,tile_low,tile_high,feature[5][0],feature[5][2],feature[5][3],feature[5][4])}else{connector=false}if(connector&&b2_start>b1_end){ctx.fillStyle=CONNECTOR_COLOR;dashedLine(ctx,b1_end,y_center+5,b2_start,y_center+5)}}else{this.draw_read(ctx,mode,w_scale,y_center,tile_low,tile_high,feature_start,feature[4],feature[5],feature[6])}if(mode==="Pack"&&feature_start>=tile_low&&feature_name!=="."){ctx.fillStyle=this.prefs.label_color;if(tile_low===0&&f_start-ctx.measureText(feature_name).width<0){ctx.textAlign="left";ctx.fillText(feature_name,f_end+LABEL_SPACING,y_center+8)}else{ctx.textAlign="right";ctx.fillText(feature_name,f_start-LABEL_SPACING,y_center+8)}}return[0,0]}});var RefBasedReadPainter=function(data,view_start,view_end,prefs,mode,alpha_scaler,height_scaler,ref_seq,base_color_fn){ReadPainter.call(this,data,view_start,view_end,prefs,mode,alpha_scaler,height_scaler,ref_seq,base_color_fn)};extend(RefBasedReadPainter.prototype,ReadPainter.prototype,FeaturePainter,{draw_read:function(ctx,mode,w_scale,y_center,tile_low,tile_high,feature_start,cigar,strand,read_seq){ctx.textAlign="center";var tile_region=[tile_low,tile_high],base_offset=0,seq_offset=0,gap=Math.round(w_scale/2),char_width_px=ctx.canvas.manager.char_width_px,block_color=(strand==="+"?this.prefs.block_color:this.prefs.reverse_strand_color),pack_mode=(mode==="Pack"),drawing_blocks=[];var draw_last=[];var t=this._parse_cigar(cigar);cigar=t.cigar;drawing_blocks=t.blocks;for(var i=0;ichar_width_px){ctx.fillText(cur_seq[c],c_start,y_center+9)}else{if(w_scale>0.05){ctx.fillRect(c_start-gap,y_center+(pack_mode?1:4),Math.max(1,Math.round(w_scale)),(pack_mode?PACK_FEATURE_HEIGHT:SQUISH_FEATURE_HEIGHT))}}}}}if(cig_op==="X"){seq_offset+=cig_len}base_offset+=cig_len;break;case"N":ctx.fillStyle=CONNECTOR_COLOR;ctx.fillRect(s_start,y_center+5,s_end-s_start,1);base_offset+=cig_len;break;case"D":ctx.fillStyle="black";ctx.fillRect(s_start,y_center+4,s_end-s_start,3);base_offset+=cig_len;break;case"I":var insert_x_coord=s_start-gap;if(is_overlap([seq_start,seq_start+cig_len],tile_region)){var seq=read_seq.slice(seq_offset,seq_offset+cig_len);if(this.prefs.show_insertions){var x_center=s_start-(s_end-s_start)/2;if((mode==="Pack"||this.mode==="Auto")&&read_seq!==undefined&&w_scale>char_width_px){ctx.fillStyle="yellow";ctx.fillRect(x_center-gap,y_center-9,s_end-s_start,9);draw_last[draw_last.length]={type:"triangle",data:[insert_x_coord,y_center+4,5]};ctx.fillStyle=CONNECTOR_COLOR;switch(compute_overlap([seq_start,seq_start+cig_len],tile_region)){case (OVERLAP_START):seq=seq.slice(tile_low-seq_start);break;case (OVERLAP_END):seq=seq.slice(0,seq_start-tile_high);break;case (CONTAINED_BY):break;case (CONTAINS):seq=seq.slice(tile_low-seq_start,seq_start-tile_high);break}for(var c=0,str_len=seq.length;cchar_width_px){draw_last.push({type:"text",data:[seq.length,insert_x_coord,y_center+9]})}else{}}}seq_offset+=cig_len;break}}ctx.fillStyle="yellow";var item,type,data;for(var i=0;i0){ctx.beginPath();ctx.arc(x_center,y_center,block2_start-x_center,Math.PI,0);ctx.stroke()}}});var Color=function(rgb,a){if(Array.isArray(rgb)){this.rgb=rgb}else{if(rgb.length==6){this.rgb=rgb.match(/.{2}/g).map(function(c){return parseInt(c,16)})}else{if(rgb.length==7){this.rgb=rgb.substring(1,7).match(/.{2}/g).map(function(c){return parseInt(c,16)})}else{this.rgb=rgb.split("").map(function(c){return parseInt(c+c,16)})}}}this.alpha=typeof(a)==="number"?a:1};Color.prototype={eval:function(){return this},toCSS:function(){if(this.alpha<1){return"rgba("+this.rgb.map(function(c){return Math.round(c)}).concat(this.alpha).join(", ")+")"}else{return"#"+this.rgb.map(function(i){i=Math.round(i);i=(i>255?255:(i<0?0:i)).toString(16);return i.length===1?"0"+i:i}).join("")}},toHSL:function(){var r=this.rgb[0]/255,g=this.rgb[1]/255,b=this.rgb[2]/255,a=this.alpha;var max=Math.max(r,g,b),min=Math.min(r,g,b);var h,s,l=(max+min)/2,d=max-min;if(max===min){h=s=0}else{s=l>0.5?d/(2-max-min):d/(max+min);switch(max){case r:h=(g-b)/d+(g255?255:(i<0?0:i)).toString(16);return i.length===1?"0"+i:i}).join("")},mix:function(color2,weight){color1=this;var p=weight;var w=p*2-1;var a=color1.toHSL().a-color2.toHSL().a;var w1=(((w*a==-1)?w:(w+a)/(1+w*a))+1)/2;var w2=1-w1;var rgb=[color1.rgb[0]*w1+color2.rgb[0]*w2,color1.rgb[1]*w1+color2.rgb[1]*w2,color1.rgb[2]*w1+color2.rgb[2]*w2];var alpha=color1.alpha*p+color2.alpha*(1-p);return new Color(rgb,alpha)}};var LinearRamp=function(start_color,end_color,start_value,end_value){this.start_color=new Color(start_color);this.end_color=new Color(end_color);this.start_value=start_value;this.end_value=end_value;this.value_range=end_value-start_value};LinearRamp.prototype.map_value=function(value){value=Math.max(value,this.start_value);value=Math.min(value,this.end_value);value=(value-this.start_value)/this.value_range;return this.start_color.mix(this.end_color,1-value).toCSS()};var SplitRamp=function(start_color,middle_color,end_color,start_value,end_value){this.positive_ramp=new LinearRamp(middle_color,end_color,0,end_value);this.negative_ramp=new LinearRamp(middle_color,start_color,0,-start_value);this.start_value=start_value;this.end_value=end_value};SplitRamp.prototype.map_value=function(value){value=Math.max(value,this.start_value);value=Math.min(value,this.end_value);if(value>=0){return this.positive_ramp.map_value(value)}else{return this.negative_ramp.map_value(-value)}};var DiagonalHeatmapPainter=function(data,view_start,view_end,prefs,mode){Painter.call(this,data,view_start,view_end,prefs,mode);var i,len;if(this.prefs.min_value===undefined){var min_value=Infinity;for(i=0,len=this.data.length;iah){$(this).insertAfter(ai);var ar=n[ai.attr("id")];ar.remove_drawable(am);ar.container.add_drawable(am);return}}}ai=null;for(al=0;al=aq&&ap.offsetY<=ah){if(ap.offsetY-aq0?al-1:al))}}}).bind("dragstart",function(){ad["border-top"]=ac.css("border-top");ad["border-bottom"]=ac.css("border-bottom");$(this).css({"border-top":"1px solid blue","border-bottom":"1px solid blue"})}).bind("dragend",function(){$(this).css(ad)})};var aa=16,G=9,D=20,A=100,I=12000,R=400,K=5000,w=100,o="Cannot display dataset due to an error. ",J="A converter for this dataset is not installed. Please check your datatypes_conf.xml file.",E="No data for this chrom/contig.",v="Preparing data. This can take a while for a large dataset. If the visualization is saved and closed, preparation will continue in the background.",y="Tool cannot be rerun: ",a="Loading data...",T="Ready for display",Q=10,H=20;function V(ad,ac){if(!ac){ac=0}var ae=Math.pow(10,ac);return Math.round(ad*ae)/ae}var r=function(ad,ac,af){if(!r.id_counter){r.id_counter=0}this.id=r.id_counter++;this.name=af.name;this.view=ad;this.container=ac;this.config=new F({track:this,params:[{key:"name",label:"Name",type:"text",default_value:this.name}],saved_values:af.prefs,onchange:function(){this.track.set_name(this.track.config.values.name)}});this.prefs=this.config.values;this.drag_handle_class=af.drag_handle_class;this.is_overview=false;this.action_icons={};this.content_visible=true;this.container_div=this.build_container_div();this.header_div=this.build_header_div();if(this.header_div){this.container_div.append(this.header_div);this.icons_div=$("
    ").css("float","left").hide().appendTo(this.header_div);this.build_action_icons(this.action_icons_def);this.header_div.append($("
    "));this.header_div.dblclick(function(ag){ag.stopPropagation()});var ae=this;this.container_div.hover(function(){ae.icons_div.show()},function(){ae.icons_div.hide()});$("
    ").appendTo(this.container_div)}};r.prototype.action_icons_def=[{name:"toggle_icon",title:"Hide/show content",css_class:"toggle",on_click_fn:function(ac){if(ac.content_visible){ac.action_icons.toggle_icon.addClass("toggle-expand").removeClass("toggle");ac.hide_contents();ac.content_visible=false}else{ac.action_icons.toggle_icon.addClass("toggle").removeClass("toggle-expand");ac.content_visible=true;ac.show_contents()}}},{name:"settings_icon",title:"Edit settings",css_class:"settings-icon",on_click_fn:function(ad){var af=function(){hide_modal();$(window).unbind("keypress.check_enter_esc")},ac=function(){ad.config.update_from_form($(".dialog-box"));hide_modal();$(window).unbind("keypress.check_enter_esc")},ae=function(ag){if((ag.keyCode||ag.which)===27){af()}else{if((ag.keyCode||ag.which)===13){ac()}}};$(window).bind("keypress.check_enter_esc",ae);show_modal("Configure",ad.config.build_form(),{Cancel:af,OK:ac})}},{name:"remove_icon",title:"Remove",css_class:"remove-icon",on_click_fn:function(ac){$(".bs-tooltip").remove();ac.remove()}}];q(r.prototype,{init:function(){},changed:function(){this.view.changed()},can_draw:function(){if(this.enabled&&this.content_visible){return true}return false},request_draw:function(){},_draw:function(){},to_dict:function(){},set_name:function(ac){this.old_name=this.name;this.name=ac;this.name_div.text(this.name)},revert_name:function(){if(this.old_name){this.name=this.old_name;this.name_div.text(this.name)}},remove:function(){this.changed();this.container.remove_drawable(this);var ac=this.view;this.container_div.hide(0,function(){$(this).remove();ac.update_intro_div()})},build_container_div:function(){},build_header_div:function(){},add_action_icon:function(ad,ai,ah,ag,ac,af){var ae=this;this.action_icons[ad]=$("").attr("href","javascript:void(0);").attr("title",ai).addClass("icon-button").addClass(ah).tooltip().click(function(){ag(ae)}).appendTo(this.icons_div);if(af){this.action_icons[ad].hide()}},build_action_icons:function(ac){var ae;for(var ad=0;ad").addClass("content-div").attr("id","group_"+this.id+"_content_div").appendTo(this.container_div);k(this.container_div,this);k(this.content_div,this);m(this.container_div,this.drag_handle_class,".group",this);this.filters_manager=new i.FiltersManager(this);this.header_div.after(this.filters_manager.parent_div);this.saved_filters_managers=[];if("drawables" in af){this.unpack_drawables(af.drawables)}if("filters" in af){var ae=this.filters_manager;this.filters_manager=new i.FiltersManager(this,af.filters);ae.parent_div.replaceWith(this.filters_manager.parent_div);if(af.filters.visible){this.setup_multitrack_filtering()}}};q(P.prototype,r.prototype,z.prototype,{action_icons_def:[r.prototype.action_icons_def[0],r.prototype.action_icons_def[1],{name:"composite_icon",title:"Show composite track",css_class:"layers-stack",on_click_fn:function(ac){$(".bs-tooltip").remove();ac.show_composite_track()}},{name:"filters_icon",title:"Filters",css_class:"filters-icon",on_click_fn:function(ac){if(ac.filters_manager.visible()){ac.filters_manager.clear_filters();ac._restore_filter_managers()}else{ac.setup_multitrack_filtering();ac.request_draw(true)}ac.filters_manager.toggle()}},r.prototype.action_icons_def[2]],build_container_div:function(){var ac=$("
    ").addClass("group").attr("id","group_"+this.id);if(this.container){this.container.content_div.append(ac)}return ac},build_header_div:function(){var ac=$("
    ").addClass("track-header");ac.append($("
    ").addClass(this.drag_handle_class));this.name_div=$("
    ").addClass("track-name").text(this.name).appendTo(ac);return ac},hide_contents:function(){this.tiles_div.hide()},show_contents:function(){this.tiles_div.show();this.request_draw()},update_icons:function(){var ae=this.drawables.length;if(ae===0){this.action_icons.composite_icon.hide();this.action_icons.filters_icon.hide()}else{if(ae===1){if(this.drawables[0] instanceof f){this.action_icons.composite_icon.show()}this.action_icons.filters_icon.hide()}else{var al,ak,ai,ao=true,ag=this.drawables[0].get_type(),ac=0;for(al=0;al1&&ac===this.drawables.length){var ap={},ad;ai=this.drawables[0];for(ak=0;ak0){this.action_icons.filters_icon.show()}else{this.action_icons.filters_icon.hide()}}else{this.action_icons.filters_icon.hide()}}}},_restore_filter_managers:function(){for(var ac=0;ac0){this.saved_filters_managers=[];for(var ac=0;ac").addClass("top-container").appendTo(ae);this.browser_content_div=$("
    ").addClass("content").css("position","relative").appendTo(ae);this.bottom_container=$("
    ").addClass("bottom-container").appendTo(ae);this.top_labeltrack=$("
    ").addClass("top-labeltrack").appendTo(this.top_container);this.viewport_container=$("
    ").addClass("viewport-container").attr("id","viewport-container").appendTo(this.browser_content_div);this.content_div=this.viewport_container;k(this.viewport_container,ac);this.intro_div=$("
    ").addClass("intro").appendTo(this.viewport_container).hide();var af=$("
    ").text("Add Datasets to Visualization").addClass("action-button").appendTo(this.intro_div).click(function(){x.select_datasets(select_datasets_url,add_track_async_url,{"f-dbkey":ac.dbkey},function(ag){ab.each(ag,function(ah){ac.add_drawable(p(ah,ac,ac))})})});this.nav_labeltrack=$("
    ").addClass("nav-labeltrack").appendTo(this.bottom_container);this.nav_container=$("
    ").addClass("trackster-nav-container").prependTo(this.top_container);this.nav=$("
    ").addClass("trackster-nav").appendTo(this.nav_container);this.overview=$("
    ").addClass("overview").appendTo(this.bottom_container);this.overview_viewport=$("
    ").addClass("overview-viewport").appendTo(this.overview);this.overview_close=$("").attr("href","javascript:void(0);").attr("title","Close overview").addClass("icon-button overview-close tooltip").hide().appendTo(this.overview_viewport);this.overview_highlight=$("
    ").addClass("overview-highlight").hide().appendTo(this.overview_viewport);this.overview_box_background=$("
    ").addClass("overview-boxback").appendTo(this.overview_viewport);this.overview_box=$("
    ").addClass("overview-box").appendTo(this.overview_viewport);this.default_overview_height=this.overview_box.height();this.nav_controls=$("
    ").addClass("nav-controls").appendTo(this.nav);this.chrom_select=$("").addClass("nav-input").hide().bind("keyup focusout",ad).appendTo(this.nav_controls);this.location_span=$("").addClass("location").attr("original-title","Click to change location").tooltip({placement:"bottom"}).appendTo(this.nav_controls);this.location_span.click(function(){ac.location_span.hide();ac.chrom_select.hide();ac.nav_input.val(ac.chrom+":"+ac.low+"-"+ac.high);ac.nav_input.css("display","inline-block");ac.nav_input.select();ac.nav_input.focus();ac.nav_input.autocomplete({source:function(ai,ag){var aj=[],ah=$.map(ac.get_drawables(),function(ak){return ak.data_manager.search_features(ai.term).success(function(al){aj=aj.concat(al)})});$.when.apply($,ah).done(function(){ag($.map(aj,function(ak){return{label:ak[0],value:ak[1]}}))})}})});if(this.vis_id!==undefined){this.hidden_input=$("").attr("type","hidden").val(this.vis_id).appendTo(this.nav_controls)}this.zo_link=$("").attr("id","zoom-out").attr("title","Zoom out").tooltip({placement:"bottom"}).click(function(){ac.zoom_out();ac.request_redraw()}).appendTo(this.nav_controls);this.zi_link=$("").attr("id","zoom-in").attr("title","Zoom in").tooltip({placement:"bottom"}).click(function(){ac.zoom_in();ac.request_redraw()}).appendTo(this.nav_controls);this.load_chroms_deferred=this.load_chroms({low:0});this.chrom_select.bind("change",function(){ac.change_chrom(ac.chrom_select.val())});this.browser_content_div.click(function(ag){$(this).find("input").trigger("blur")});this.browser_content_div.bind("dblclick",function(ag){ac.zoom_in(ag.pageX,this.viewport_container)});this.overview_box.bind("dragstart",function(ag,ah){this.current_x=ah.offsetX}).bind("drag",function(ag,ai){var aj=ai.offsetX-this.current_x;this.current_x=ai.offsetX;var ah=Math.round(aj/ac.viewport_container.width()*(ac.max_high-ac.max_low));ac.move_delta(-ah)});this.overview_close.click(function(){ac.reset_overview()});this.viewport_container.bind("draginit",function(ag,ah){if(ag.clientX>ac.viewport_container.width()-16){return false}}).bind("dragstart",function(ag,ah){ah.original_low=ac.low;ah.current_height=ag.clientY;ah.current_x=ah.offsetX}).bind("drag",function(ai,ak){var ag=$(this);var al=ak.offsetX-ak.current_x;var ah=ag.scrollTop()-(ai.clientY-ak.current_height);ag.scrollTop(ah);ak.current_height=ai.clientY;ak.current_x=ak.offsetX;var aj=Math.round(al/ac.viewport_container.width()*(ac.high-ac.low));ac.move_delta(aj)}).bind("mousewheel",function(ai,ak,ah,ag){if(ah){ah*=50;var aj=Math.round(-ah/ac.viewport_container.width()*(ac.high-ac.low));ac.move_delta(aj)}});this.top_labeltrack.bind("dragstart",function(ag,ah){return $("
    ").css({height:ac.browser_content_div.height()+ac.top_labeltrack.height()+ac.nav_labeltrack.height()+1,top:"0px",position:"absolute","background-color":"#ccf",opacity:0.5,"z-index":1000}).appendTo($(this))}).bind("drag",function(ak,al){$(al.proxy).css({left:Math.min(ak.pageX,al.startX)-ac.container.offset().left,width:Math.abs(ak.pageX-al.startX)});var ah=Math.min(ak.pageX,al.startX)-ac.container.offset().left,ag=Math.max(ak.pageX,al.startX)-ac.container.offset().left,aj=(ac.high-ac.low),ai=ac.viewport_container.width();ac.update_location(Math.round(ah/ai*aj)+ac.low,Math.round(ag/ai*aj)+ac.low)}).bind("dragend",function(al,am){var ah=Math.min(al.pageX,am.startX),ag=Math.max(al.pageX,am.startX),aj=(ac.high-ac.low),ai=ac.viewport_container.width(),ak=ac.low;ac.low=Math.round(ah/ai*aj)+ak;ac.high=Math.round(ag/ai*aj)+ak;$(am.proxy).remove();ac.request_redraw()});this.add_label_track(new W(this,{content_div:this.top_labeltrack}));this.add_label_track(new W(this,{content_div:this.nav_labeltrack}));$(window).bind("resize",function(){if(this.resize_timer){clearTimeout(this.resize_timer)}this.resize_timer=setTimeout(function(){ac.resize_window()},500)});$(document).bind("redraw",function(){ac.redraw()});this.reset();$(window).trigger("resize")},get_base_color:function(ac){return this.config.values[ac.toLowerCase()+"_color"]||this.config.values.n_color}});q(Y.prototype,z.prototype,{changed:function(){this.has_changes=true},update_intro_div:function(){if(this.drawables.length===0){this.intro_div.show()}else{this.intro_div.hide()}},trigger_navigate:function(ad,af,ac,ag){if(this.timer){clearTimeout(this.timer)}if(ag){var ae=this;this.timer=setTimeout(function(){ae.trigger("navigate",ad+":"+af+"-"+ac)},500)}else{view.trigger("navigate",ad+":"+af+"-"+ac)}},update_location:function(ac,ae){this.location_span.text(commatize(ac)+" - "+commatize(ae));this.nav_input.val(this.chrom+":"+commatize(ac)+"-"+commatize(ae));var ad=view.chrom_select.val();if(ad!==""){this.trigger_navigate(ad,view.low,view.high,true)}},load_chroms:function(ae){ae.num=w;var ac=this,ad=$.Deferred();$.ajax({url:chrom_url+"/"+this.dbkey,data:ae,dataType:"json",success:function(ag){if(ag.chrom_info.length===0){return}if(ag.reference){ac.add_label_track(new B(ac))}ac.chrom_data=ag.chrom_info;var aj='';for(var ai=0,af=ac.chrom_data.length;ai'+ah+""}if(ag.prev_chroms){aj+='"}if(ag.next_chroms){aj+='"}ac.chrom_select.html(aj);ac.chrom_start_index=ag.start_index;ad.resolve(ag.chrom_info)},error:function(){alert("Could not load chroms for this dbkey:",ac.dbkey)}});return ad},change_chrom:function(ah,ad,aj){var ae=this;if(!ae.chrom_data){ae.load_chroms_deferred.then(function(){ae.change_chrom(ah,ad,aj)});return}if(!ah||ah==="None"){return}if(ah==="previous"){ae.load_chroms({low:this.chrom_start_index-w});return}if(ah==="next"){ae.load_chroms({low:this.chrom_start_index+w});return}var ai=$.grep(ae.chrom_data,function(ak,al){return ak.chrom===ah})[0];if(ai===undefined){ae.load_chroms({chrom:ah},function(){ae.change_chrom(ah,ad,aj)});return}else{if(ah!==ae.chrom){ae.chrom=ah;ae.chrom_select.val(ae.chrom);ae.max_high=ai.len-1;ae.reset();ae.request_redraw(true);for(var ag=0,ac=ae.drawables.length;agac.max_high){ac.high=ac.max_high;ac.low=ac.max_high-ae}else{ac.high-=af;ac.low-=af}}ac.request_redraw();var ad=ac.chrom_select.val();this.trigger_navigate(ad,ac.low,ac.high,true)},add_drawable:function(ac){z.prototype.add_drawable.call(this,ac);ac.init();this.changed();this.update_intro_div()},add_label_track:function(ac){ac.view=this;ac.init();this.label_tracks.push(ac)},remove_drawable:function(ae,ad){z.prototype.remove_drawable.call(this,ae);if(ad){var ac=this;ae.container_div.hide(0,function(){$(this).remove();ac.update_intro_div()})}},reset:function(){this.low=this.max_low;this.high=this.max_high;this.viewport_container.find(".yaxislabel").remove()},request_redraw:function(ak,ac,aj,al){var ai=this,ah=(al?[al]:ai.drawables),ae;var ad;for(var ag=0;agthis.max_high){af=this.max_high}var al=this.high-this.low;if(this.high!==0&&al").addClass("dynamic-tool").hide();this.parent_div.bind("drag",function(au){au.stopPropagation()}).click(function(au){au.stopPropagation()}).bind("dblclick",function(au){au.stopPropagation()});var ao=$("
    ").appendTo(this.parent_div).text(this.name);var am=this.params;var ak=this;$.each(this.params,function(av,ay){var ax=$("
    ").addClass("param-row").appendTo(ak.parent_div);var au=$("
    ").addClass("param-label").text(ay.label).appendTo(ax);var aw=$("
    ").addClass("param-input").html(ay.html).appendTo(ax);aw.find(":input").val(ay.value);$("
    ").appendTo(ax)});this.parent_div.find("input").click(function(){$(this).select()});var at=$("
    ").addClass("param-row").appendTo(this.parent_div);var ai=$("").attr("value","Run on complete dataset").appendTo(at);var ac=$("").attr("value","Run on visible region").css("margin-left","3em").appendTo(at);ac.click(function(){ak.run_on_region()});ai.click(function(){ak.run_on_dataset()});if("visible" in af&&af.visible){this.parent_div.show()}};q(s.prototype,{update_params:function(){for(var ac=0;ac");var ae;function ad(ak,ag){for(var ao=0;ao").appendTo(ag);av.append($("").addClass("icon-button arrow-circle").appendTo(au).attr("title","Set new random color").tooltip(),ar=$("
    ").appendTo(au).hide(),aj=$("
    ").appendTo(ar),ah=$("
    ").appendTo(ar),al=$.farbtastic(aj,{width:100,height:100,callback:ap,color:at});au.append($("
    ").css("clear","both"));(function(aw){an.click(function(){aw.setColor(l.get_random_color())})})(al)}else{av.append($("").attr("id",ai).attr("name",ai).val(at))}}}}if(ae.help){av.append($("
    ").text(ae.help))}}}ad(this.params,ac);return ac},update_from_form:function(ac){var ae=this;var ad=false;$.each(this.params,function(af,ah){if(!ah.hidden){var ai="param_"+af;var ag=ac.find("#"+ai).val();if(ah.type==="float"){ag=parseFloat(ag)}else{if(ah.type==="int"){ag=parseInt(ag,10)}else{if(ah.type==="bool"){ag=ac.find("#"+ai).is(":checked")}}}if(ag!==ae.values[ah.key]){ae.values[ah.key]=ag;ad=true}}});if(ad){this.onchange();this.track.changed()}}});var b=function(ac,ag,ae,ad,af){this.track=ac;this.region=ag;this.low=ag.get("start");this.high=ag.get("end");this.resolution=ae;this.html_elt=$("
    ").append(ad).height($(ad).attr("height"));this.data=af;this.stale=false};b.prototype.predisplay_actions=function(){};var j=function(ac,ah,ae,ad,af,ag){b.call(this,ac,ah,ae,ad,af);this.max_val=ag};q(j.prototype,b.prototype);var O=function(af,an,ag,ae,ai,ap,aj,aq,ad,am){b.call(this,af,an,ag,ae,ai);this.mode=aj;this.all_slotted=ad;this.feature_mapper=am;this.has_icons=false;if(aq){this.has_icons=true;var ak=this;ae=this.html_elt.children()[0],message_div=$("
    ").addClass("tile-message").css({height:D-1,width:ae.width}).prependTo(this.html_elt);var al=new x.GenomeRegion({chrom:af.view.chrom,start:this.low,end:this.high}),ao=ai.length,ah=$("").addClass("icon more-down").attr("title","For speed, only the first "+ao+" features in this region were obtained from server. Click to get more data including depth").tooltip().appendTo(message_div),ac=$("").addClass("icon more-across").attr("title","For speed, only the first "+ao+" features in this region were obtained from server. Click to get more data excluding depth").tooltip().appendTo(message_div);ah.click(function(){ak.stale=true;af.data_manager.get_more_data(al,af.mode,ak.resolution,{},af.data_manager.DEEP_DATA_REQ);$(".bs-tooltip").hide();af.request_draw(true)}).dblclick(function(ar){ar.stopPropagation()});ac.click(function(){ak.stale=true;af.data_manager.get_more_data(al,af.mode,ak.resolution,{},af.data_manager.BROAD_DATA_REQ);$(".bs-tooltip").hide();af.request_draw(true)}).dblclick(function(ar){ar.stopPropagation()})}};q(O.prototype,b.prototype);O.prototype.predisplay_actions=function(){var ad=this,ac={};if(ad.mode!=="Pack"){return}$(this.html_elt).hover(function(){this.hovered=true;$(this).mousemove()},function(){this.hovered=false;$(this).parents(".track-content").children(".overlay").children(".feature-popup").remove()}).mousemove(function(ao){if(!this.hovered){return}var aj=$(this).offset(),an=ao.pageX-aj.left,am=ao.pageY-aj.top,at=ad.feature_mapper.get_feature_data(an,am),ak=(at?at[0]:null);$(this).parents(".track-content").children(".overlay").children(".feature-popup").each(function(){if(!ak||$(this).attr("id")!==ak.toString()){$(this).remove()}});if(at){var af=ac[ak];if(!af){var ak=at[0],ap={name:at[3],start:at[1],end:at[2],strand:at[4]},ai=ad.track.filters_manager.filters,ah;for(var al=0;al").attr("id",ak).addClass("feature-popup"),au=$(""),ar,aq,av;for(ar in ap){aq=ap[ar];av=$("").appendTo(au);$("
    ").appendTo(av).text(ar);$("").attr("align","left").appendTo(av).text(typeof(aq)==="number"?V(aq,2):aq)}af.append($("
    ").append(au));ac[ak]=af}af.appendTo($(this).parents(".track-content").children(".overlay"));var ag=an+parseInt(ad.html_elt.css("left"))-af.width()/2,ae=am+parseInt(ad.html_elt.css("top"))+7;af.css("left",ag+"px").css("top",ae+"px")}else{if(!ao.isPropagationStopped()){ao.stopPropagation();$(this).siblings().each(function(){$(this).trigger(ao)})}}}).mouseleave(function(){$(this).parents(".track-content").children(".overlay").children(".feature-popup").remove()})};var g=function(ad,ac,ae){q(ae,{drag_handle_class:"draghandle"});r.call(this,ad,ac,ae);this.dataset=new X.Dataset(ae.dataset);this.dataset_id=this.dataset.get("id");this.hda_ldda=this.dataset.get("hda_ldda");this.dataset_check_type="converted_datasets_state";this.data_url_extra_params={};this.data_query_wait=("data_query_wait" in ae?ae.data_query_wait:K);this.data_manager=("data_manager" in ae?ae.data_manager:new x.GenomeDataManager({dataset:this.dataset,genome:new x.Genome({key:ad.dbkey,chroms_info:{chrom_info:ad.chrom_data}}),data_mode_compatible:this.data_and_mode_compatible,can_subset:this.can_subset}));this.min_height_px=16;this.max_height_px=800;this.visible_height_px=0;this.content_div=$("
    ").appendTo(this.container_div);if(this.container){this.container.content_div.append(this.container_div);if(!("resize" in ae)||ae.resize){this.add_resize_handle()}}};q(g.prototype,r.prototype,{action_icons_def:[{name:"mode_icon",title:"Set display mode",css_class:"chevron-expand",on_click_fn:function(){}},r.prototype.action_icons_def[0],{name:"overview_icon",title:"Set as overview",css_class:"overview-icon",on_click_fn:function(ac){ac.view.set_overview(ac)}},r.prototype.action_icons_def[1],{name:"filters_icon",title:"Filters",css_class:"filters-icon",on_click_fn:function(ac){if(ac.filters_manager.visible()){ac.filters_manager.clear_filters()}else{ac.filters_manager.init_filters()}ac.filters_manager.toggle()}},{name:"tools_icon",title:"Tool",css_class:"hammer",on_click_fn:function(ac){ac.dynamic_tool_div.toggle();if(ac.dynamic_tool_div.is(":visible")){ac.set_name(ac.name+ac.tool_region_and_parameters_str())}else{ac.revert_name()}$(".bs-tooltip").remove()}},{name:"param_space_viz_icon",title:"Tool parameter space visualization",css_class:"arrow-split",on_click_fn:function(ac){var af='Tool: <%= track.tool.name %>
    Dataset: <%= track.name %>
    Region(s): ',ae=ab.template(af,{track:ac});var ah=function(){hide_modal();$(window).unbind("keypress.check_enter_esc")},ad=function(){var aj=$('select[name="regions"] option:selected').val(),al,ai=new x.GenomeRegion({chrom:view.chrom,start:view.low,end:view.high}),ak=ab.map($(".bookmark"),function(am){return new x.GenomeRegion({from_str:$(am).children(".position").text()})});if(aj==="cur"){al=[ai]}else{if(aj==="bookmarks"){al=ak}else{al=[ai].concat(ak)}}hide_modal();window.location.href=galaxy_paths.get("sweepster_url")+"?"+$.param({dataset_id:ac.dataset_id,hda_ldda:ac.hda_ldda,regions:JSON.stringify(new Backbone.Collection(al).toJSON())})},ag=function(ai){if((ai.keyCode||ai.which)===27){ah()}else{if((ai.keyCode||ai.which)===13){ad()}}};show_modal("Visualize tool parameter space and output from different parameter settings?",ae,{No:ah,Yes:ad})}},r.prototype.action_icons_def[2]],can_draw:function(){if(this.dataset_id&&r.prototype.can_draw.call(this)){return true}return false},build_container_div:function(){return $("
    ").addClass("track").attr("id","track_"+this.id).css("position","relative")},build_header_div:function(){var ac=$("
    ");if(this.view.editor){this.drag_div=$("
    ").addClass(this.drag_handle_class).appendTo(ac)}this.name_div=$("
    ").addClass("track-name").appendTo(ac).text(this.name).attr("id",this.name.replace(/\s+/g,"-").replace(/[^a-zA-Z0-9\-]/g,"").toLowerCase());return ac},set_dataset:function(ac){this.dataset=ac;this.data_manager.set("dataset",ac);this.dataset_id=ac.get("id");this.hda_ldda=ac.get("hda_ldda")},on_resize:function(){},add_resize_handle:function(){var ac=this;var af=false;var ae=false;var ad=$("
    ");$(ac.container_div).hover(function(){if(ac.content_visible){af=true;ad.show()}},function(){af=false;if(!ae){ad.hide()}});ad.hide().bind("dragstart",function(ag,ah){ae=true;ah.original_height=$(ac.content_div).height()}).bind("drag",function(ah,ai){var ag=Math.min(Math.max(ai.original_height+ai.deltaY,ac.min_height_px),ac.max_height_px);$(ac.tiles_div).css("height",ag);ac.visible_height_px=(ac.max_height_px===ag?0:ag);ac.on_resize()}).bind("dragend",function(ag,ah){ac.tile_cache.clear();ae=false;if(!af){ad.hide()}ac.config.values.height=ac.visible_height_px;ac.changed()}).appendTo(ac.container_div)},set_display_modes:function(af,ai){this.display_modes=af;this.mode=(ai?ai:(this.config&&this.config.values.mode?this.config.values.mode:this.display_modes[0]));this.action_icons.mode_icon.attr("title","Set display mode (now: "+this.mode+")");var ad=this,ag={};for(var ae=0,ac=ad.display_modes.length;ae").text("View error").click(function(){show_modal("Trackster Error","
    "+ag.message+"
    ",{Close:hide_modal})}));ad.tiles_div.append($("").text(" "));ad.tiles_div.append($("").text("Try again").click(function(){ad.init(true)}))}}else{if(ag==="no converter"){ad.container_div.addClass("error");ad.tiles_div.text(J)}else{if(ag==="no data"||(ag.data!==undefined&&(ag.data===null||ag.data.length===0))){ad.container_div.addClass("nodata");ad.tiles_div.text(E)}else{if(ag==="pending"){ad.container_div.addClass("pending");ad.tiles_div.html(v);setTimeout(function(){ad.init()},ad.data_query_wait)}else{if(ag==="data"||ag.status==="data"){if(ag.valid_chroms){ad.valid_chroms=ag.valid_chroms;ad.update_icons()}ad.tiles_div.text(T);if(ad.view.chrom){ad.tiles_div.text("");ad.tiles_div.css("height",ad.visible_height_px+"px");ad.enabled=true;$.when(ad.predraw_init()).done(function(){ac.resolve();ad.container_div.removeClass("nodata error pending");ad.request_draw()})}else{ac.resolve()}}}}}}});this.update_icons();return ac},predraw_init:function(){},get_drawables:function(){return this}});var M=function(ae,ad,af){g.call(this,ae,ad,af);var ac=this;m(ac.container_div,ac.drag_handle_class,".group",ac);this.filters_manager=new i.FiltersManager(this,("filters" in af?af.filters:null));this.data_manager.set("filters_manager",this.filters_manager);this.filters_available=false;this.tool=("tool" in af&&af.tool?new s(this,af.tool,af.tool_state):null);this.tile_cache=new x.Cache(Q);this.left_offset=0;if(this.header_div){this.set_filters_manager(this.filters_manager);if(this.tool){this.dynamic_tool_div=this.tool.parent_div;this.header_div.after(this.dynamic_tool_div)}}this.tiles_div=$("
    ").addClass("tiles").appendTo(this.content_div);this.overlay_div=$("
    ").addClass("overlay").appendTo(this.content_div);if(af.mode){this.change_mode(af.mode)}};q(M.prototype,r.prototype,g.prototype,{action_icons_def:g.prototype.action_icons_def.concat([{name:"show_more_rows_icon",title:"To minimize track height, not all feature rows are displayed. Click to display more rows.",css_class:"exclamation",on_click_fn:function(ac){$(".bs-tooltip").remove();ac.slotters[ac.view.resolution_px_b].max_rows*=2;ac.request_draw(true)},hide:true}]),copy:function(ac){var ad=this.to_dict();q(ad,{data_manager:this.data_manager});var ae=new this.constructor(this.view,ac,ad);ae.change_mode(this.mode);ae.enabled=this.enabled;return ae},set_filters_manager:function(ac){this.filters_manager=ac;this.header_div.after(this.filters_manager.parent_div)},to_dict:function(){return{track_type:this.get_type(),name:this.name,hda_ldda:this.hda_ldda,dataset_id:this.dataset_id,prefs:this.prefs,mode:this.mode,filters:this.filters_manager.to_dict(),tool_state:(this.tool?this.tool.state_dict():{})}},change_mode:function(ad){var ac=this;ac.mode=ad;ac.config.values.mode=ad;ac.tile_cache.clear();ac.request_draw();this.action_icons.mode_icon.attr("title","Set display mode (now: "+ac.mode+")");return ac},update_icons:function(){var ac=this;if(ac.filters_available){ac.action_icons.filters_icon.show()}else{ac.action_icons.filters_icon.hide()}if(ac.tool){ac.action_icons.tools_icon.show();ac.action_icons.param_space_viz_icon.show()}else{ac.action_icons.tools_icon.hide();ac.action_icons.param_space_viz_icon.hide()}},_gen_tile_cache_key:function(ad,ac){return ad+"_"+ac},request_draw:function(ad,ac){this.view.request_redraw(false,ad,ac,this)},before_draw:function(){},_draw:function(ad,ao){if(!this.can_draw()){return}var al=this.view.low,ah=this.view.high,aj=ah-al,ae=this.view.container.width(),aq=this.view.resolution_px_b,ag=this.view.resolution_b_px;if(this.is_overview){al=this.view.max_low;ah=this.view.max_high;ag=(view.max_high-view.max_low)/ae;aq=1/ag}this.before_draw();this.tiles_div.children().addClass("remove");var ac=Math.floor(al/(ag*R)),ak=true,ap=[],ai=function(ar){return(ar&&"track" in ar)};while((ac*R*ag)
    ");while(ac"+commatize(ac)+"
    ").css({position:"absolute",left:ah-1}));ac+=ai}this.content_div.children(":first").remove();this.content_div.append(ad)}});var f=function(ad,ac,ag){M.call(this,ad,ac,ag);this.drawables=[];if("drawables" in ag){var af;for(var ae=0;aethis.left_offset){this.left_offset=af.left_offset}}this.enabled=true}if(this.drawables.length!==0){this.set_display_modes(this.drawables[0].display_modes,this.drawables[0].mode)}this.update_icons();this.obj_type="CompositeTrack"};q(f.prototype,M.prototype,{action_icons_def:[{name:"composite_icon",title:"Show individual tracks",css_class:"layers-stack",on_click_fn:function(ac){$(".bs-tooltip").remove();ac.show_group()}}].concat(M.prototype.action_icons_def),to_dict:z.prototype.to_dict,add_drawable:z.prototype.add_drawable,unpack_drawables:z.prototype.unpack_drawables,change_mode:function(ac){M.prototype.change_mode.call(this,ac);for(var ad=0;adview.canvas_manager.char_width_px){am=view.reference_track.data_manager.get_data(ah,aq.mode,av,view.reference_track.data_url_extra_params);if(U(am)){ao=false}}ak.push(am)}if(ao){q(aw,at.more_tile_data);this.tile_predraw_init();var ag=aq.view.canvas_manager.new_canvas(),ay=ah.get("start"),ae=ah.get("end"),az=0,ar=Math.ceil((ae-ay)*al)+this.left_offset,ap=0,af=[],ax;var ac=0;for(ax=0;axap){ap=ac}}ag.width=ar;ag.height=(at.height?at.height:ap);az=0;var au=ag.getContext("2d");au.translate(this.left_offset,0);au.globalAlpha=0.5;au.globalCompositeOperation="source-over";for(ax=0;axac){ac=ad.prefs.max_value}}}for(var ae=0;aeag){ag=ac}}for(var af=0;afthis.view.canvas_manager.char_width_px){this.tiles_div.show();return M.prototype.draw_helper.call(this,ae,af,ac,ag,ah,ad)}else{this.tiles_div.hide();return null}},can_subset:function(ac){return true},draw_tile:function(af,al,ag,ad,ai,am){var ae=this.data_manager.subset_entry(af,ai),ak=ae.data;var ac=al.canvas;al.font=al.canvas.manager.default_font;al.textAlign="center";for(var ah=0,aj=ak.length;ah").text(V(ac.prefs.min_value,3)).make_text_editable({num_cols:6,on_finish:function(aj){$(".bs-tooltip").remove();var aj=parseFloat(aj);if(!isNaN(aj)){ac.set_min_value(aj)}},help_text:"Set min value"}).addClass("yaxislabel bottom").attr("id","linetrack_"+ac.dataset_id+"_minval").prependTo(ac.container_div),af=$("
    ").text(V(ac.prefs.max_value,3)).make_text_editable({num_cols:6,on_finish:function(aj){$(".bs-tooltip").remove();var aj=parseFloat(aj);if(!isNaN(aj)){ac.set_max_value(aj)}},help_text:"Set max value"}).addClass("yaxislabel top").attr("id","linetrack_"+ac.dataset_id+"_maxval").prependTo(ac.container_div)})},draw_tile:function(al,aj,ae,ad,ag,ak){var ac=aj.canvas,af=ag.get("start"),ai=ag.get("end"),ah=new L.LinePainter(al.data,af,ai,this.prefs,ae);ah.draw(aj,ac.width,ac.height,ak);return new b(this,ag,ad,ac,al.data)},can_subset:function(ac){return(ac.data[1][0]-ac.data[0][0]===1)}});var t=function(ae,ad,af){var ac=this;this.display_modes=["Heatmap"];this.mode="Heatmap";M.call(this,ae,ad,af);this.config=new F({track:this,params:[{key:"name",label:"Name",type:"text",default_value:this.name},{key:"pos_color",label:"Positive Color",type:"color",default_value:"#FF8C00"},{key:"neg_color",label:"Negative Color",type:"color",default_value:"#4169E1"},{key:"min_value",label:"Min Value",type:"float",default_value:-1},{key:"max_value",label:"Max Value",type:"float",default_value:1},{key:"mode",type:"string",default_value:this.mode,hidden:true},{key:"height",type:"int",default_value:500,hidden:true}],saved_values:af.prefs,onchange:function(){ac.set_name(ac.prefs.name);ac.vertical_range=ac.prefs.max_value-ac.prefs.min_value;ac.set_min_value(ac.prefs.min_value);ac.set_max_value(ac.prefs.max_value)}});this.prefs=this.config.values;this.visible_height_px=this.config.values.height;this.vertical_range=this.config.values.max_value-this.config.values.min_value};q(t.prototype,r.prototype,M.prototype,{on_resize:function(){this.request_draw(true)},set_min_value:function(ac){this.prefs.min_value=ac;this.tile_cache.clear();this.request_draw()},set_max_value:function(ac){this.prefs.max_value=ac;this.tile_cache.clear();this.request_draw()},draw_tile:function(ac,ae,ai,ag,ah,aj){var af=ae.canvas,ad=new L.DiagonalHeatmapPainter(ac.data,ah.get("start"),ah.get("end"),this.prefs,ai);ad.draw(ae,af.width,af.height,aj);return new b(this,ah,ag,af,ac.data)}});var c=function(af,ae,ah){var ad=this;this.display_modes=["Auto","Coverage","Dense","Squish","Pack"];M.call(this,af,ae,ah);var ag=l.get_random_color(),ac=l.get_random_color([ag,"#FFFFFF"]);this.config=new F({track:this,params:[{key:"name",label:"Name",type:"text",default_value:this.name},{key:"block_color",label:"Block color",type:"color",default_value:ag},{key:"reverse_strand_color",label:"Antisense strand color",type:"color",default_value:ac},{key:"label_color",label:"Label color",type:"color",default_value:"black"},{key:"show_counts",label:"Show summary counts",type:"bool",default_value:true,help:"Show the number of items in each bin when drawing summary histogram"},{key:"histogram_max",label:"Histogram maximum",type:"float",default_value:null,help:"clear value to set automatically"},{key:"connector_style",label:"Connector style",type:"select",default_value:"fishbones",options:[{label:"Line with arrows",value:"fishbone"},{label:"Arcs",value:"arcs"}]},{key:"mode",type:"string",default_value:this.mode,hidden:true},{key:"height",type:"int",default_value:this.visible_height_px,hidden:true}],saved_values:ah.prefs,onchange:function(){ad.set_name(ad.prefs.name);ad.tile_cache.clear();ad.set_painter_from_config();ad.request_draw()}});this.prefs=this.config.values;this.visible_height_px=this.config.values.height;this.container_div.addClass("feature-track");this.show_labels_scale=0.001;this.showing_details=false;this.summary_draw_height=30;this.slotters={};this.start_end_dct={};this.left_offset=200;this.set_painter_from_config()};q(c.prototype,r.prototype,M.prototype,{set_painter_from_config:function(){if(this.config.values.connector_style==="arcs"){this.painter=L.ArcLinkedFeaturePainter}else{this.painter=L.LinkedFeaturePainter}},before_draw:function(){this.max_height_px=0},postdraw_actions:function(ar,am,ah,ag){M.prototype.postdraw_actions.call(this,ar,ag);var al=this,ao;if(al.mode==="Coverage"){var ad=-1;for(ao=0;aoad){ad=an}}for(ao=0;ao").text(ak).make_text_editable({num_cols:12,on_finish:function(av){$(".bs-tooltip").remove();var av=parseFloat(av);al.prefs.histogram_max=(!isNaN(av)?av:null);al.tile_cache.clear();al.request_draw()},help_text:"Set max value; leave blank to use default"}).addClass("yaxislabel top").css("color",this.prefs.label_color);this.container_div.prepend(ae)}if(af instanceof O){var ap=true;for(ao=0;aoI){mode="Squish"}else{mode="Pack"}}}return mode},get_canvas_height:function(ac,ag,ah,ad){if(ag==="summary_tree"||ag==="Coverage"){return this.summary_draw_height}else{var af=this.incremental_slots(ah,ac.data,ag);var ae=new (this.painter)(null,null,null,this.prefs,ag);return Math.max(aa,ae.get_required_height(af,ad))}},draw_tile:function(am,aq,ao,ar,af,aj,ae){var ap=this,ad=aq.canvas,ay=af.get("start"),ac=af.get("end"),ag=this.left_offset;if(ao==="summary_tree"||ao==="Coverage"){return this._draw_summary_tree_tile(am,aq,af,ar,aj)}var ai=[],an=this.slotters[aj].slots;all_slotted=true;if(am.data){var ak=this.filters_manager.filters;for(var at=0,av=am.data.length;at").text("Summary").addClass("yaxislabel top").css({"font-size":ac+"px"});this.container_div.prepend(aj);var ah=aj.position().top;aj.css("top",ah+(this.prefs.summary_height-ac)/2+"px");if(this.prefs.show_sample_data){var ae="";ab.each(this.dataset.get("metadata").get("sample_names"),function(al){ae+=(al+"
    ")});var af=$("
    ").html(ae).addClass("yaxislabel top sample").css({top:ah+this.prefs.summary_height+2,});this.container_div.prepend(af)}}$(this.container_div).find(".sample").css("font-size",(this.mode==="Squish"?5:10)+"px");$(this.container_div).find(".yaxislabel").css("color",this.prefs.label_color)}else{this.container_div.find(".yaxislabel").remove()}}});var S=function(ae,ad,ag){c.call(this,ae,ad,ag);var af=l.get_random_color(),ac=l.get_random_color([af,"#ffffff"]);this.config=new F({track:this,params:[{key:"name",label:"Name",type:"text",default_value:this.name},{key:"block_color",label:"Block and sense strand color",type:"color",default_value:af},{key:"reverse_strand_color",label:"Antisense strand color",type:"color",default_value:ac},{key:"label_color",label:"Label color",type:"color",default_value:"black"},{key:"show_insertions",label:"Show insertions",type:"bool",default_value:false},{key:"show_differences",label:"Show differences only",type:"bool",default_value:true},{key:"show_counts",label:"Show summary counts",type:"bool",default_value:true},{key:"histogram_max",label:"Histogram maximum",type:"float",default_value:null,help:"Clear value to set automatically"},{key:"mode",type:"string",default_value:this.mode,hidden:true}],saved_values:ag.prefs,onchange:function(){this.track.set_name(this.track.prefs.name);this.track.tile_cache.clear();this.track.request_draw()}});this.prefs=this.config.values;this.painter=(ae.reference_track?L.RefBasedReadPainter:L.ReadPainter);this.update_icons()};q(S.prototype,r.prototype,M.prototype,c.prototype);var d={CompositeTrack:f,DrawableGroup:P,DiagonalHeatmapTrack:t,FeatureTrack:c,LineTrack:h,ReadTrack:S,VariantTrack:Z,VcfTrack:Z};var p=function(ae,ad,ac){if("copy" in ae){return ae.copy(ac)}else{var af=ae.obj_type;if(!af){af=ae.track_type}return new d[af](ad,ac,ae)}};return{TracksterView:Y,DrawableGroup:P,LineTrack:h,FeatureTrack:c,DiagonalHeatmapTrack:t,ReadTrack:S,VariantTrack:Z,CompositeTrack:f,object_from_template:p}}); \ No newline at end of file +define(["libs/underscore","viz/visualization","viz/trackster/util","viz/trackster/slotting","viz/trackster/painters","mvc/data","viz/trackster/filters"],function(ab,x,l,u,L,X,i){var q=ab.extend;var U=function(ac){return("isResolved" in ac)};var n={};var k=function(ac,ad){n[ac.attr("id")]=ad};var m=function(ac,ae,ag,af){ag=".group";var ad={};n[ac.attr("id")]=af;ac.bind("drag",{handle:"."+ae,relative:true},function(ao,ap){var an=$(this),at=$(this).parent(),ak=at.children(),am=n[$(this).attr("id")],aj,ai,aq,ah,al;ai=$(this).parents(ag);if(ai.length!==0){aq=ai.position().top;ah=aq+ai.outerHeight();if(ap.offsetYah){$(this).insertAfter(ai);var ar=n[ai.attr("id")];ar.remove_drawable(am);ar.container.add_drawable(am);return}}}ai=null;for(al=0;al=aq&&ap.offsetY<=ah){if(ap.offsetY-aq0?al-1:al))}}}).bind("dragstart",function(){ad["border-top"]=ac.css("border-top");ad["border-bottom"]=ac.css("border-bottom");$(this).css({"border-top":"1px solid blue","border-bottom":"1px solid blue"})}).bind("dragend",function(){$(this).css(ad)})};var aa=16,G=9,D=20,A=100,I=12000,R=400,K=5000,w=100,o="Cannot display dataset due to an error. ",J="A converter for this dataset is not installed. Please check your datatypes_conf.xml file.",E="No data for this chrom/contig.",v="Preparing data. This can take a while for a large dataset. If the visualization is saved and closed, preparation will continue in the background.",y="Tool cannot be rerun: ",a="Loading data...",T="Ready for display",Q=10,H=20;function V(ad,ac){if(!ac){ac=0}var ae=Math.pow(10,ac);return Math.round(ad*ae)/ae}var r=function(ad,ac,af){if(!r.id_counter){r.id_counter=0}this.id=r.id_counter++;this.name=af.name;this.view=ad;this.container=ac;this.config=new F({track:this,params:[{key:"name",label:"Name",type:"text",default_value:this.name}],saved_values:af.prefs,onchange:function(){this.track.set_name(this.track.config.values.name)}});this.prefs=this.config.values;this.drag_handle_class=af.drag_handle_class;this.is_overview=false;this.action_icons={};this.content_visible=true;this.container_div=this.build_container_div();this.header_div=this.build_header_div();if(this.header_div){this.container_div.append(this.header_div);this.icons_div=$("
    ").css("float","left").hide().appendTo(this.header_div);this.build_action_icons(this.action_icons_def);this.header_div.append($("
    "));this.header_div.dblclick(function(ag){ag.stopPropagation()});var ae=this;this.container_div.hover(function(){ae.icons_div.show()},function(){ae.icons_div.hide()});$("
    ").appendTo(this.container_div)}};r.prototype.action_icons_def=[{name:"toggle_icon",title:"Hide/show content",css_class:"toggle",on_click_fn:function(ac){if(ac.content_visible){ac.action_icons.toggle_icon.addClass("toggle-expand").removeClass("toggle");ac.hide_contents();ac.content_visible=false}else{ac.action_icons.toggle_icon.addClass("toggle").removeClass("toggle-expand");ac.content_visible=true;ac.show_contents()}}},{name:"settings_icon",title:"Edit settings",css_class:"settings-icon",on_click_fn:function(ad){var af=function(){hide_modal();$(window).unbind("keypress.check_enter_esc")},ac=function(){ad.config.update_from_form($(".dialog-box"));hide_modal();$(window).unbind("keypress.check_enter_esc")},ae=function(ag){if((ag.keyCode||ag.which)===27){af()}else{if((ag.keyCode||ag.which)===13){ac()}}};$(window).bind("keypress.check_enter_esc",ae);show_modal("Configure",ad.config.build_form(),{Cancel:af,OK:ac})}},{name:"remove_icon",title:"Remove",css_class:"remove-icon",on_click_fn:function(ac){$(".bs-tooltip").remove();ac.remove()}}];q(r.prototype,{init:function(){},changed:function(){this.view.changed()},can_draw:function(){if(this.enabled&&this.content_visible){return true}return false},request_draw:function(){},_draw:function(){},to_dict:function(){},set_name:function(ac){this.old_name=this.name;this.name=ac;this.name_div.text(this.name)},revert_name:function(){if(this.old_name){this.name=this.old_name;this.name_div.text(this.name)}},remove:function(){this.changed();this.container.remove_drawable(this);var ac=this.view;this.container_div.hide(0,function(){$(this).remove();ac.update_intro_div()})},build_container_div:function(){},build_header_div:function(){},add_action_icon:function(ad,ai,ah,ag,ac,af){var ae=this;this.action_icons[ad]=$("").attr("href","javascript:void(0);").attr("title",ai).addClass("icon-button").addClass(ah).tooltip().click(function(){ag(ae)}).appendTo(this.icons_div);if(af){this.action_icons[ad].hide()}},build_action_icons:function(ac){var ae;for(var ad=0;ad").addClass("content-div").attr("id","group_"+this.id+"_content_div").appendTo(this.container_div);k(this.container_div,this);k(this.content_div,this);m(this.container_div,this.drag_handle_class,".group",this);this.filters_manager=new i.FiltersManager(this);this.header_div.after(this.filters_manager.parent_div);this.saved_filters_managers=[];if("drawables" in af){this.unpack_drawables(af.drawables)}if("filters" in af){var ae=this.filters_manager;this.filters_manager=new i.FiltersManager(this,af.filters);ae.parent_div.replaceWith(this.filters_manager.parent_div);if(af.filters.visible){this.setup_multitrack_filtering()}}};q(P.prototype,r.prototype,z.prototype,{action_icons_def:[r.prototype.action_icons_def[0],r.prototype.action_icons_def[1],{name:"composite_icon",title:"Show composite track",css_class:"layers-stack",on_click_fn:function(ac){$(".bs-tooltip").remove();ac.show_composite_track()}},{name:"filters_icon",title:"Filters",css_class:"filters-icon",on_click_fn:function(ac){if(ac.filters_manager.visible()){ac.filters_manager.clear_filters();ac._restore_filter_managers()}else{ac.setup_multitrack_filtering();ac.request_draw(true)}ac.filters_manager.toggle()}},r.prototype.action_icons_def[2]],build_container_div:function(){var ac=$("
    ").addClass("group").attr("id","group_"+this.id);if(this.container){this.container.content_div.append(ac)}return ac},build_header_div:function(){var ac=$("
    ").addClass("track-header");ac.append($("
    ").addClass(this.drag_handle_class));this.name_div=$("
    ").addClass("track-name").text(this.name).appendTo(ac);return ac},hide_contents:function(){this.tiles_div.hide()},show_contents:function(){this.tiles_div.show();this.request_draw()},update_icons:function(){var ae=this.drawables.length;if(ae===0){this.action_icons.composite_icon.hide();this.action_icons.filters_icon.hide()}else{if(ae===1){if(this.drawables[0] instanceof f){this.action_icons.composite_icon.show()}this.action_icons.filters_icon.hide()}else{var al,ak,ai,ao=true,ag=this.drawables[0].get_type(),ac=0;for(al=0;al1&&ac===this.drawables.length){var ap={},ad;ai=this.drawables[0];for(ak=0;ak0){this.action_icons.filters_icon.show()}else{this.action_icons.filters_icon.hide()}}else{this.action_icons.filters_icon.hide()}}}},_restore_filter_managers:function(){for(var ac=0;ac0){this.saved_filters_managers=[];for(var ac=0;ac").addClass("top-container").appendTo(ae);this.browser_content_div=$("
    ").addClass("content").css("position","relative").appendTo(ae);this.bottom_container=$("
    ").addClass("bottom-container").appendTo(ae);this.top_labeltrack=$("
    ").addClass("top-labeltrack").appendTo(this.top_container);this.viewport_container=$("
    ").addClass("viewport-container").attr("id","viewport-container").appendTo(this.browser_content_div);this.content_div=this.viewport_container;k(this.viewport_container,ac);this.intro_div=$("
    ").addClass("intro").appendTo(this.viewport_container).hide();var af=$("
    ").text("Add Datasets to Visualization").addClass("action-button").appendTo(this.intro_div).click(function(){x.select_datasets(select_datasets_url,add_track_async_url,{"f-dbkey":ac.dbkey},function(ag){ab.each(ag,function(ah){ac.add_drawable(p(ah,ac,ac))})})});this.nav_labeltrack=$("
    ").addClass("nav-labeltrack").appendTo(this.bottom_container);this.nav_container=$("
    ").addClass("trackster-nav-container").prependTo(this.top_container);this.nav=$("
    ").addClass("trackster-nav").appendTo(this.nav_container);this.overview=$("
    ").addClass("overview").appendTo(this.bottom_container);this.overview_viewport=$("
    ").addClass("overview-viewport").appendTo(this.overview);this.overview_close=$("").attr("href","javascript:void(0);").attr("title","Close overview").addClass("icon-button overview-close tooltip").hide().appendTo(this.overview_viewport);this.overview_highlight=$("
    ").addClass("overview-highlight").hide().appendTo(this.overview_viewport);this.overview_box_background=$("
    ").addClass("overview-boxback").appendTo(this.overview_viewport);this.overview_box=$("
    ").addClass("overview-box").appendTo(this.overview_viewport);this.default_overview_height=this.overview_box.height();this.nav_controls=$("
    ").addClass("nav-controls").appendTo(this.nav);this.chrom_select=$("").addClass("nav-input").hide().bind("keyup focusout",ad).appendTo(this.nav_controls);this.location_span=$("").addClass("location").attr("original-title","Click to change location").tooltip({placement:"bottom"}).appendTo(this.nav_controls);this.location_span.click(function(){ac.location_span.hide();ac.chrom_select.hide();ac.nav_input.val(ac.chrom+":"+ac.low+"-"+ac.high);ac.nav_input.css("display","inline-block");ac.nav_input.select();ac.nav_input.focus();ac.nav_input.autocomplete({source:function(ai,ag){var aj=[],ah=$.map(ac.get_tracks(c),function(ak){return ak.data_manager.search_features(ai.term).success(function(al){aj=aj.concat(al)})});$.when.apply($,ah).done(function(){ag($.map(aj,function(ak){return{label:ak[0],value:ak[1]}}))})}})});if(this.vis_id!==undefined){this.hidden_input=$("").attr("type","hidden").val(this.vis_id).appendTo(this.nav_controls)}this.zo_link=$("").attr("id","zoom-out").attr("title","Zoom out").tooltip({placement:"bottom"}).click(function(){ac.zoom_out();ac.request_redraw()}).appendTo(this.nav_controls);this.zi_link=$("").attr("id","zoom-in").attr("title","Zoom in").tooltip({placement:"bottom"}).click(function(){ac.zoom_in();ac.request_redraw()}).appendTo(this.nav_controls);this.load_chroms_deferred=this.load_chroms({low:0});this.chrom_select.bind("change",function(){ac.change_chrom(ac.chrom_select.val())});this.browser_content_div.click(function(ag){$(this).find("input").trigger("blur")});this.browser_content_div.bind("dblclick",function(ag){ac.zoom_in(ag.pageX,this.viewport_container)});this.overview_box.bind("dragstart",function(ag,ah){this.current_x=ah.offsetX}).bind("drag",function(ag,ai){var aj=ai.offsetX-this.current_x;this.current_x=ai.offsetX;var ah=Math.round(aj/ac.viewport_container.width()*(ac.max_high-ac.max_low));ac.move_delta(-ah)});this.overview_close.click(function(){ac.reset_overview()});this.viewport_container.bind("draginit",function(ag,ah){if(ag.clientX>ac.viewport_container.width()-16){return false}}).bind("dragstart",function(ag,ah){ah.original_low=ac.low;ah.current_height=ag.clientY;ah.current_x=ah.offsetX}).bind("drag",function(ai,ak){var ag=$(this);var al=ak.offsetX-ak.current_x;var ah=ag.scrollTop()-(ai.clientY-ak.current_height);ag.scrollTop(ah);ak.current_height=ai.clientY;ak.current_x=ak.offsetX;var aj=Math.round(al/ac.viewport_container.width()*(ac.high-ac.low));ac.move_delta(aj)}).bind("mousewheel",function(ai,ak,ah,ag){if(ah){ah*=50;var aj=Math.round(-ah/ac.viewport_container.width()*(ac.high-ac.low));ac.move_delta(aj)}});this.top_labeltrack.bind("dragstart",function(ag,ah){return $("
    ").css({height:ac.browser_content_div.height()+ac.top_labeltrack.height()+ac.nav_labeltrack.height()+1,top:"0px",position:"absolute","background-color":"#ccf",opacity:0.5,"z-index":1000}).appendTo($(this))}).bind("drag",function(ak,al){$(al.proxy).css({left:Math.min(ak.pageX,al.startX)-ac.container.offset().left,width:Math.abs(ak.pageX-al.startX)});var ah=Math.min(ak.pageX,al.startX)-ac.container.offset().left,ag=Math.max(ak.pageX,al.startX)-ac.container.offset().left,aj=(ac.high-ac.low),ai=ac.viewport_container.width();ac.update_location(Math.round(ah/ai*aj)+ac.low,Math.round(ag/ai*aj)+ac.low)}).bind("dragend",function(al,am){var ah=Math.min(al.pageX,am.startX),ag=Math.max(al.pageX,am.startX),aj=(ac.high-ac.low),ai=ac.viewport_container.width(),ak=ac.low;ac.low=Math.round(ah/ai*aj)+ak;ac.high=Math.round(ag/ai*aj)+ak;$(am.proxy).remove();ac.request_redraw()});this.add_label_track(new W(this,{content_div:this.top_labeltrack}));this.add_label_track(new W(this,{content_div:this.nav_labeltrack}));$(window).bind("resize",function(){if(this.resize_timer){clearTimeout(this.resize_timer)}this.resize_timer=setTimeout(function(){ac.resize_window()},500)});$(document).bind("redraw",function(){ac.redraw()});this.reset();$(window).trigger("resize")},get_base_color:function(ac){return this.config.values[ac.toLowerCase()+"_color"]||this.config.values.n_color}});q(Y.prototype,z.prototype,{changed:function(){this.has_changes=true},update_intro_div:function(){if(this.drawables.length===0){this.intro_div.show()}else{this.intro_div.hide()}},trigger_navigate:function(ad,af,ac,ag){if(this.timer){clearTimeout(this.timer)}if(ag){var ae=this;this.timer=setTimeout(function(){ae.trigger("navigate",ad+":"+af+"-"+ac)},500)}else{view.trigger("navigate",ad+":"+af+"-"+ac)}},update_location:function(ac,ae){this.location_span.text(commatize(ac)+" - "+commatize(ae));this.nav_input.val(this.chrom+":"+commatize(ac)+"-"+commatize(ae));var ad=view.chrom_select.val();if(ad!==""){this.trigger_navigate(ad,view.low,view.high,true)}},load_chroms:function(ae){ae.num=w;var ac=this,ad=$.Deferred();$.ajax({url:chrom_url+"/"+this.dbkey,data:ae,dataType:"json",success:function(ag){if(ag.chrom_info.length===0){return}if(ag.reference){ac.add_label_track(new B(ac))}ac.chrom_data=ag.chrom_info;var aj='';for(var ai=0,af=ac.chrom_data.length;ai'+ah+""}if(ag.prev_chroms){aj+='"}if(ag.next_chroms){aj+='"}ac.chrom_select.html(aj);ac.chrom_start_index=ag.start_index;ad.resolve(ag.chrom_info)},error:function(){alert("Could not load chroms for this dbkey:",ac.dbkey)}});return ad},change_chrom:function(ah,ad,aj){var ae=this;if(!ae.chrom_data){ae.load_chroms_deferred.then(function(){ae.change_chrom(ah,ad,aj)});return}if(!ah||ah==="None"){return}if(ah==="previous"){ae.load_chroms({low:this.chrom_start_index-w});return}if(ah==="next"){ae.load_chroms({low:this.chrom_start_index+w});return}var ai=$.grep(ae.chrom_data,function(ak,al){return ak.chrom===ah})[0];if(ai===undefined){ae.load_chroms({chrom:ah},function(){ae.change_chrom(ah,ad,aj)});return}else{if(ah!==ae.chrom){ae.chrom=ah;ae.chrom_select.val(ae.chrom);ae.max_high=ai.len-1;ae.reset();for(var ag=0,ac=ae.drawables.length;agac.max_high){ac.high=ac.max_high;ac.low=ac.max_high-ae}else{ac.high-=af;ac.low-=af}}ac.request_redraw();var ad=ac.chrom_select.val();this.trigger_navigate(ad,ac.low,ac.high,true)},add_drawable:function(ac){z.prototype.add_drawable.call(this,ac);ac.init();this.changed();this.update_intro_div()},add_label_track:function(ac){ac.view=this;ac.init();this.label_tracks.push(ac)},remove_drawable:function(ae,ad){z.prototype.remove_drawable.call(this,ae);if(ad){var ac=this;ae.container_div.hide(0,function(){$(this).remove();ac.update_intro_div()})}},reset:function(){this.low=this.max_low;this.high=this.max_high;this.viewport_container.find(".yaxislabel").remove()},request_redraw:function(ac,aj,ak){var ai=this,ag=(ak?[ak]:ai.drawables),ae;var ad;for(var ah=0;ahthis.max_high){af=this.max_high}var al=this.high-this.low;if(this.high!==0&&al").addClass("dynamic-tool").hide();this.parent_div.bind("drag",function(au){au.stopPropagation()}).click(function(au){au.stopPropagation()}).bind("dblclick",function(au){au.stopPropagation()});var ao=$("
    ").appendTo(this.parent_div).text(this.name);var am=this.params;var ak=this;$.each(this.params,function(av,ay){var ax=$("
    ").addClass("param-row").appendTo(ak.parent_div);var au=$("
    ").addClass("param-label").text(ay.label).appendTo(ax);var aw=$("
    ").addClass("param-input").html(ay.html).appendTo(ax);aw.find(":input").val(ay.value);$("
    ").appendTo(ax)});this.parent_div.find("input").click(function(){$(this).select()});var at=$("
    ").addClass("param-row").appendTo(this.parent_div);var ai=$("").attr("value","Run on complete dataset").appendTo(at);var ac=$("").attr("value","Run on visible region").css("margin-left","3em").appendTo(at);ac.click(function(){ak.run_on_region()});ai.click(function(){ak.run_on_dataset()});if("visible" in af&&af.visible){this.parent_div.show()}};q(s.prototype,{update_params:function(){for(var ac=0;ac");var ae;function ad(ak,ag){for(var ao=0;ao").appendTo(ag);av.append($("").addClass("icon-button arrow-circle").appendTo(au).attr("title","Set new random color").tooltip(),ar=$("
    ").appendTo(au).hide(),aj=$("
    ").appendTo(ar),ah=$("
    ").appendTo(ar),al=$.farbtastic(aj,{width:100,height:100,callback:ap,color:at});au.append($("
    ").css("clear","both"));(function(aw){an.click(function(){aw.setColor(l.get_random_color())})})(al)}else{av.append($("").attr("id",ai).attr("name",ai).val(at))}}}}if(ae.help){av.append($("
    ").text(ae.help))}}}ad(this.params,ac);return ac},update_from_form:function(ac){var ae=this;var ad=false;$.each(this.params,function(af,ah){if(!ah.hidden){var ai="param_"+af;var ag=ac.find("#"+ai).val();if(ah.type==="float"){ag=parseFloat(ag)}else{if(ah.type==="int"){ag=parseInt(ag,10)}else{if(ah.type==="bool"){ag=ac.find("#"+ai).is(":checked")}}}if(ag!==ae.values[ah.key]){ae.values[ah.key]=ag;ad=true}}});if(ad){this.onchange();this.track.changed()}}});var b=function(ac,ag,ae,ad,af){this.track=ac;this.region=ag;this.low=ag.get("start");this.high=ag.get("end");this.resolution=ae;this.html_elt=$("
    ").append(ad).height($(ad).attr("height"));this.data=af;this.stale=false};b.prototype.predisplay_actions=function(){};var j=function(ac,ah,ae,ad,af,ag){b.call(this,ac,ah,ae,ad,af);this.max_val=ag};q(j.prototype,b.prototype);var O=function(af,an,ag,ae,ai,ap,aj,aq,ad,am){b.call(this,af,an,ag,ae,ai);this.mode=aj;this.all_slotted=ad;this.feature_mapper=am;this.has_icons=false;if(aq){this.has_icons=true;var ak=this;ae=this.html_elt.children()[0],message_div=$("
    ").addClass("tile-message").css({height:D-1,width:ae.width}).prependTo(this.html_elt);var al=new x.GenomeRegion({chrom:af.view.chrom,start:this.low,end:this.high}),ao=ai.length,ah=$("").addClass("icon more-down").attr("title","For speed, only the first "+ao+" features in this region were obtained from server. Click to get more data including depth").tooltip().appendTo(message_div),ac=$("").addClass("icon more-across").attr("title","For speed, only the first "+ao+" features in this region were obtained from server. Click to get more data excluding depth").tooltip().appendTo(message_div);ah.click(function(){ak.stale=true;af.data_manager.get_more_data(al,af.mode,ak.resolution,{},af.data_manager.DEEP_DATA_REQ);$(".bs-tooltip").hide();af.request_draw(false,true)}).dblclick(function(ar){ar.stopPropagation()});ac.click(function(){ak.stale=true;af.data_manager.get_more_data(al,af.mode,ak.resolution,{},af.data_manager.BROAD_DATA_REQ);$(".bs-tooltip").hide();af.request_draw(false,true)}).dblclick(function(ar){ar.stopPropagation()})}};q(O.prototype,b.prototype);O.prototype.predisplay_actions=function(){var ad=this,ac={};if(ad.mode!=="Pack"){return}$(this.html_elt).hover(function(){this.hovered=true;$(this).mousemove()},function(){this.hovered=false;$(this).parents(".track-content").children(".overlay").children(".feature-popup").remove()}).mousemove(function(ao){if(!this.hovered){return}var aj=$(this).offset(),an=ao.pageX-aj.left,am=ao.pageY-aj.top,at=ad.feature_mapper.get_feature_data(an,am),ak=(at?at[0]:null);$(this).parents(".track-content").children(".overlay").children(".feature-popup").each(function(){if(!ak||$(this).attr("id")!==ak.toString()){$(this).remove()}});if(at){var af=ac[ak];if(!af){var ak=at[0],ap={name:at[3],start:at[1],end:at[2],strand:at[4]},ai=ad.track.filters_manager.filters,ah;for(var al=0;al").attr("id",ak).addClass("feature-popup"),au=$(""),ar,aq,av;for(ar in ap){aq=ap[ar];av=$("").appendTo(au);$(" From 76b31da015a7f0bbca8b5564c1b1e525b9682fb3 Mon Sep 17 00:00:00 2001 From: Dave Bouvier Date: Thu, 9 May 2013 14:56:19 -0400 Subject: [PATCH 372/523] Fix Galaxy side user creation in tool shed functional tests. --- test/tool_shed/base/twilltestcase.py | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/test/tool_shed/base/twilltestcase.py b/test/tool_shed/base/twilltestcase.py index b5f819a1691..bcf0ded36dc 100644 --- a/test/tool_shed/base/twilltestcase.py +++ b/test/tool_shed/base/twilltestcase.py @@ -312,12 +312,7 @@ class ShedTwillTestCase( TwillTestCase ): def create_user_in_galaxy( self, cntrller='user', email='test@bx.psu.edu', password='testuser', username='admin-user', redirect='' ): self.visit_galaxy_url( "/user/create?cntrller=%s&use_panels=False" % cntrller ) - tc.fv( '1', 'email', email ) - tc.fv( '1', 'redirect', redirect ) - tc.fv( '1', 'password', password ) - tc.fv( '1', 'confirm', password ) - tc.fv( '1', 'username', username ) - tc.submit( 'create_user_button' ) + self.submit_form( '1', 'create_user_button', email=email, password=password, confirm=password, username=username, redirect=redirect ) previously_created = False username_taken = False invalid_username = False @@ -509,10 +504,7 @@ class ShedTwillTestCase( TwillTestCase ): self.create_user_in_galaxy( email=email, password=password, username=username, redirect=redirect ) if previously_created: self.visit_galaxy_url( "/user/login?use_panels=False" ) - tc.fv( '1', 'email', email ) - tc.fv( '1', 'redirect', redirect ) - tc.fv( '1', 'password', password ) - tc.submit( 'login_button' ) + self.submit_form( '1', 'login_button', email=email, redirect=redirect, password=password ) def galaxy_logout( self ): self.home() From bb5e51a166a0a5981eb1ff6b1a15ab3690c082af Mon Sep 17 00:00:00 2001 From: Greg Von Kuster Date: Thu, 9 May 2013 15:43:28 -0400 Subject: [PATCH 373/523] Fix a typo in a tool shed template. --- templates/webapps/tool_shed/repository/common.mako | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/webapps/tool_shed/repository/common.mako b/templates/webapps/tool_shed/repository/common.mako index e6ae8b75111..dcfda5b987a 100644 --- a/templates/webapps/tool_shed/repository/common.mako +++ b/templates/webapps/tool_shed/repository/common.mako @@ -733,7 +733,7 @@ %else: <% if string_as_bool( str( tool_dependency.is_orphan ) ): - is_orpan = 'yes' + is_orphan = 'yes' else: is_orphan = 'no' %> From d25ab705b2b37dac3e842a63b363f4cd5f772811 Mon Sep 17 00:00:00 2001 From: Jeremy Goecks Date: Fri, 10 May 2013 09:38:51 -0400 Subject: [PATCH 374/523] Select2 enhancements: (a) bump up z-index so that select2 works in modal dialogs; (b) add select2 to base_panels for pervasive access and remove from individual templates; and (c) use select2 in Trackster and discontinue use of custom jQuery combobox. --- static/style/blue/base.css | 4 ++-- static/style/select2.less | 4 ++-- templates/base/base_panels.mako | 1 + templates/webapps/galaxy/tool_form.mako | 2 +- templates/webapps/galaxy/tracks/browser.mako | 5 +---- templates/webapps/galaxy/workflow/run.mako | 1 - 6 files changed, 7 insertions(+), 10 deletions(-) diff --git a/static/style/blue/base.css b/static/style/blue/base.css index af84258cdfc..d652030680f 100644 --- a/static/style/blue/base.css +++ b/static/style/blue/base.css @@ -831,11 +831,11 @@ ul.icons{list-style-type:none;text-indent:-0.75em;}ul.icons li [class^="fa-icon- .select2-container .select2-choice span{margin-right:26px;display:block;overflow:hidden;white-space:nowrap;-o-text-overflow:ellipsis;-ms-text-overflow:ellipsis;text-overflow:ellipsis;} .select2-container .select2-choice abbr{display:block;position:absolute;right:26px;top:8px;width:12px;height:12px;font-size:1px;background:url('../images/select2.png') right top no-repeat;cursor:pointer;text-decoration:none;border:0;outline:0;} .select2-container .select2-choice abbr:hover{background-position:right -11px;cursor:pointer;} -.select2-drop{background:#fff;color:#000;border:1px solid #aaa;border-top:0;position:absolute;top:100%;-webkit-box-shadow:0 4px 5px rgba(0, 0, 0, 0.15);-moz-box-shadow:0 4px 5px rgba(0, 0, 0, 0.15);-o-box-shadow:0 4px 5px rgba(0, 0, 0, 0.15);box-shadow:0 4px 5px rgba(0, 0, 0, 0.15);z-index:9999;width:100%;margin-top:-1px;-webkit-border-radius:0 0 4px 4px;-moz-border-radius:0 0 4px 4px;border-radius:0 0 4px 4px;} +.select2-drop{background:#fff;color:#000;border:1px solid #aaa;border-top:0;position:absolute;top:100%;-webkit-box-shadow:0 4px 5px rgba(0, 0, 0, 0.15);-moz-box-shadow:0 4px 5px rgba(0, 0, 0, 0.15);-o-box-shadow:0 4px 5px rgba(0, 0, 0, 0.15);box-shadow:0 4px 5px rgba(0, 0, 0, 0.15);z-index:24999;width:100%;margin-top:-1px;-webkit-border-radius:0 0 4px 4px;-moz-border-radius:0 0 4px 4px;border-radius:0 0 4px 4px;} .select2-drop.select2-drop-above{-webkit-border-radius:4px 4px 0px 0px;-moz-border-radius:4px 4px 0px 0px;border-radius:4px 4px 0px 0px;margin-top:1px;border-top:1px solid #aaa;border-bottom:0;-webkit-box-shadow:0 -4px 5px rgba(0, 0, 0, 0.15);-moz-box-shadow:0 -4px 5px rgba(0, 0, 0, 0.15);-o-box-shadow:0 -4px 5px rgba(0, 0, 0, 0.15);box-shadow:0 -4px 5px rgba(0, 0, 0, 0.15);} .select2-container .select2-choice div{-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0;-moz-background-clip:padding;-webkit-background-clip:padding-box;background-clip:padding-box;background:#ccc;background-image:-webkit-gradient(linear, left bottom, left top, color-stop(0, #cccccc), color-stop(0.6, #eeeeee));background-image:-webkit-linear-gradient(center bottom, #cccccc 0%, #eeeeee 60%);background-image:-moz-linear-gradient(center bottom, #cccccc 0%, #eeeeee 60%);background-image:-o-linear-gradient(bottom, #cccccc 0%, #eeeeee 60%);background-image:-ms-linear-gradient(top, #cccccc 0%, #eeeeee 60%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#cccccc', endColorstr='#eeeeee', GradientType=0);background-image:linear-gradient(top, #cccccc 0%, #eeeeee 60%);border-left:1px solid #aaa;position:absolute;right:0;top:0;display:block;height:100%;width:18px;} .select2-container .select2-choice div b{background:url('../images/select2.png') no-repeat 0 1px;display:block;width:100%;height:100%;} -.select2-search{display:inline-block;white-space:nowrap;z-index:10000;min-height:26px;width:100%;margin:0;padding-left:4px;padding-right:4px;} +.select2-search{display:inline-block;white-space:nowrap;z-index:25000;min-height:26px;width:100%;margin:0;padding-left:4px;padding-right:4px;} .select2-search-hidden{display:block;position:absolute;left:-10000px;} .select2-search input{background:#ffffff url('../images/select2.png') no-repeat 100% -22px;background:url('../images/select2.png') no-repeat 100% -22px,-webkit-gradient(linear, left bottom, left top, color-stop(0.85, #ffffff), color-stop(0.99, #eeeeee));background:url('../images/select2.png') no-repeat 100% -22px,-webkit-linear-gradient(center bottom, #ffffff 85%, #eeeeee 99%);background:url('../images/select2.png') no-repeat 100% -22px,-moz-linear-gradient(center bottom, #ffffff 85%, #eeeeee 99%);background:url('../images/select2.png') no-repeat 100% -22px,-o-linear-gradient(bottom, #ffffff 85%, #eeeeee 99%);background:url('../images/select2.png') no-repeat 100% -22px,-ms-linear-gradient(top, #ffffff 85%, #eeeeee 99%);background:url('../images/select2.png') no-repeat 100% -22px,linear-gradient(top, #ffffff 85%, #eeeeee 99%);padding:4px 20px 4px 5px;outline:0;border:1px solid #aaa;font-family:sans-serif;font-size:1em;width:100%;margin:0;height:auto !important;min-height:26px;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;border-radius:0;-moz-border-radius:0;-webkit-border-radius:0;} .select2-drop.select2-drop-above .select2-search input{margin-top:4px;} diff --git a/static/style/select2.less b/static/style/select2.less index 8f544edb6b4..9d3bbde6f21 100644 --- a/static/style/select2.less +++ b/static/style/select2.less @@ -109,7 +109,7 @@ Version: 3.2 Timestamp: Mon Sep 10 10:38:04 PDT 2012 -moz-box-shadow: 0 4px 5px rgba(0, 0, 0, .15); -o-box-shadow: 0 4px 5px rgba(0, 0, 0, .15); box-shadow: 0 4px 5px rgba(0, 0, 0, .15); - z-index: 9999; + z-index: 24999; width:100%; margin-top:-1px; @@ -166,7 +166,7 @@ Version: 3.2 Timestamp: Mon Sep 10 10:38:04 PDT 2012 .select2-search { display: inline-block; white-space: nowrap; - z-index: 10000; + z-index: 25000; min-height: 26px; width: 100%; margin: 0; diff --git a/templates/base/base_panels.mako b/templates/base/base_panels.mako index 0722438540c..c9e1491b0d8 100644 --- a/templates/base/base_panels.mako +++ b/templates/base/base_panels.mako @@ -52,6 +52,7 @@ ${h.js( 'libs/jquery/jquery', 'libs/json2', + 'libs/jquery/select2', 'libs/bootstrap', 'libs/underscore', 'libs/backbone/backbone', diff --git a/templates/webapps/galaxy/tool_form.mako b/templates/webapps/galaxy/tool_form.mako index 9bcfe54af3c..02f08d1852d 100644 --- a/templates/webapps/galaxy/tool_form.mako +++ b/templates/webapps/galaxy/tool_form.mako @@ -13,7 +13,7 @@ <%def name="javascripts()"> ${parent.javascripts()} - ${h.js( "galaxy.panels", "libs/jquery/select2", "libs/jquery/jstorage" )} + ${h.js( "galaxy.panels", "libs/jquery/jstorage" )} + + +<%def name="print_var( name, var )"> +%if var is not None: + <% t = str( type( var ) )[1:-1] %> +

    ${name}: ${t}, ${var}

    +%else: +

    No ${name}

    +%endif + + +<%def name="body()"> + <% + import pprint + print self + print self.context + pprint.pprint( self.context.kwargs, indent=4 ) + %> + <% + vars_to_print = [ + ( 'default', default ), + ( 'string', string ), + ( 'boolean', boolean ), + ( 'integer', integer ), + ( 'float', float ), + ( 'json', json ), + ] + %> + %for name, var in vars_to_print: + ${print_var( name, var )} + %endfor + + %if visualization: +

    ${visualization.title}

    +

    id: ${trans.security.encode_id( visualization.id )}

    +

    dbkey: ${visualization.dbkey}

    +

    config: +

    ${h.to_json_string( visualization.latest_revision.config, sort_keys=True, indent=( 4 * ' ' ) )}
    +

    + %endif + + %if dataset: +

    ${dataset.name}

    +

    id: ${trans.security.encode_id( dataset.id )}

    +

    history id: ${trans.security.encode_id( dataset.history.id )}

    +
    +            ${h.to_json_string( process_hda( dataset ), sort_keys=True, indent=( 4 * ' ' ) )}
    +        
    + %endif + + %if dataset_instance: +

    ${dataset_instance.name}

    +

    id: ${trans.security.encode_id( dataset_instance.id )}

    + %if hda_ldda == 'hda': +

    history id: ${trans.security.encode_id( dataset_instance.history.id )}

    +
    +            ${h.to_json_string( process_hda( dataset_instance ), sort_keys=True, indent=( 4 * ' ' ) )}
    +        
    + %else: +

    (LibraryDatasetDatasetAssociation)

    + %endif + %endif + + %if query_args: +
      + %for key, val in query_args.items(): +
    • ${key} : ${val}
    • + %endfor +
    + %endif + + diff --git a/universe_wsgi.ini.sample b/universe_wsgi.ini.sample index 65585890f00..24b5f76b41b 100644 --- a/universe_wsgi.ini.sample +++ b/universe_wsgi.ini.sample @@ -171,6 +171,10 @@ paste.app_factory = galaxy.web.buildapp:app_factory # Galaxy. #datatypes_config_file = datatypes_conf.xml +# Visualizations config file, defines what visualizations apply to particular data and how to pass them +# the necessary parameters +#visualizations_conf_path = visualizations_conf.xml + # Each job is given a unique empty directory as its current working directory. # This option defines in what parent directory those directories will be # created. diff --git a/visualizations_conf.xml.sample b/visualizations_conf.xml.sample new file mode 100644 index 00000000000..612bc6f61ac --- /dev/null +++ b/visualizations_conf.xml.sample @@ -0,0 +1,292 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +]> + + + + + + + + HistoryDatasetAssociation + data.Data + dataset_id + hda_ldda + dbkey + + + LibraryDatasetDatasetAssociation + data.Data + dataset_id + hda_ldda + + + + id + dataset_id + genome_region + dbkey + + tracks + + _top + + + + + + HistoryDatasetAssociation + data.Data + dataset_id + hda_ldda + + + LibraryDatasetDatasetAssociation + data.Data + dataset_id + hda_ldda + + + + id + dataset_id + hda_ldda + dbkey + + + _top + + + + + + + + HistoryDatasetAssociation + data.Newick + data.Nexus + dataset_id + + + + dataset_id + tree_index + + + _top + + + + + + HistoryDatasetAssociation + tabular.Tabular + dataset_id + + + + dataset_id + + + + + + + From 77d6acd4a6e0a4756ed746d28b73270bded6bfe7 Mon Sep 17 00:00:00 2001 From: Carl Eberhard Date: Fri, 17 May 2013 14:50:27 -0400 Subject: [PATCH 452/523] API/datasets.show: allow _raw_data to assign a DataProvider by name --- lib/galaxy/webapps/galaxy/api/datasets.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/lib/galaxy/webapps/galaxy/api/datasets.py b/lib/galaxy/webapps/galaxy/api/datasets.py index 8e38f85fce8..8fbbd68990b 100644 --- a/lib/galaxy/webapps/galaxy/api/datasets.py +++ b/lib/galaxy/webapps/galaxy/api/datasets.py @@ -23,7 +23,7 @@ class DatasetsController( BaseAPIController, UsesVisualizationMixin, UsesHistory return 'not implemented' @web.expose_api - def show( self, trans, id, hda_ldda='hda', data_type=None, **kwd ): + def show( self, trans, id, hda_ldda='hda', data_type=None, provider=None, **kwd ): """ GET /api/datasets/{encoded_dataset_id} Displays information about and/or content of a dataset. @@ -46,7 +46,7 @@ class DatasetsController( BaseAPIController, UsesVisualizationMixin, UsesHistory elif data_type == 'features': rval = self._search_features( trans, dataset, kwd.get( 'query' ) ) elif data_type == 'raw_data': - rval = self._raw_data( trans, dataset, **kwd ) + rval = self._raw_data( trans, dataset, provider, **kwd ) elif data_type == 'track_config': rval = self.get_new_track_config( trans, dataset ) elif data_type == 'genome_data': @@ -201,7 +201,7 @@ class DatasetsController( BaseAPIController, UsesVisualizationMixin, UsesHistory result.update( { 'dataset_type': data_provider.dataset_type, 'extra_info': extra_info } ) return result - def _raw_data( self, trans, dataset, **kwargs ): + def _raw_data( self, trans, dataset, provider=None, **kwargs ): """ Uses original (raw) dataset to return data. This method is useful when the dataset is not yet indexed and hence using data would @@ -212,8 +212,15 @@ class DatasetsController( BaseAPIController, UsesVisualizationMixin, UsesHistory if msg: return msg + registry = trans.app.data_provider_registry + # allow the caller to specifiy which provider is used + if provider and provider in registry.dataset_type_name_to_data_provider: + data_provider = registry.dataset_type_name_to_data_provider[ provider ]( dataset ) + # or have it look up by datatype + else: + data_provider = registry.get_data_provider( trans, raw=True, original_dataset=dataset ) + # Return data. - data_provider = trans.app.data_provider_registry.get_data_provider( trans, raw=True, original_dataset=dataset ) data = data_provider.get_data( **kwargs ) return data From 7559e4611a18eda045e5e0cab45de24c1f6d30dc Mon Sep 17 00:00:00 2001 From: Carl Eberhard Date: Fri, 17 May 2013 15:06:51 -0400 Subject: [PATCH 453/523] Visualizations API: rough draft of index, show, create, and update; Browser tests: tests for visualizations API --- .../webapps/galaxy/api/visualizations.py | 253 +++++++++++++++- test/casperjs/api-visualizations-tests.js | 282 ++++++++++++++++++ test/casperjs/modules/api.js | 56 ++++ 3 files changed, 582 insertions(+), 9 deletions(-) create mode 100644 test/casperjs/api-visualizations-tests.js diff --git a/lib/galaxy/webapps/galaxy/api/visualizations.py b/lib/galaxy/webapps/galaxy/api/visualizations.py index e43eee789ef..ad40ba53ae3 100644 --- a/lib/galaxy/webapps/galaxy/api/visualizations.py +++ b/lib/galaxy/webapps/galaxy/api/visualizations.py @@ -1,28 +1,263 @@ -from galaxy import web -from galaxy.web.base.controller import BaseController, BaseAPIController +""" +Visualizations resource control over the API. -class VisualizationsController( BaseAPIController ): +NOTE!: this is a work in progress and functionality and data structures +may change often. +""" + +import pkg_resources +pkg_resources.require( 'SQLAlchemy >= 0.4' ) +from sqlalchemy import or_ + +from galaxy import web, util +from galaxy.web.base.controller import BaseAPIController, UsesVisualizationMixin +from galaxy.model.item_attrs import UsesAnnotations +from galaxy.exceptions import ( ItemAccessibilityException, ItemDeletionException, ItemOwnershipException, + MessageException ) + +from galaxy.web import url_for + +import logging +log = logging.getLogger( __name__ ) + +class VisualizationsController( BaseAPIController, UsesVisualizationMixin, UsesAnnotations ): """ RESTful controller for interactions with visualizations. """ @web.expose_api - def index( self, trans, **kwds ): + def index( self, trans, **kwargs ): """ GET /api/visualizations: """ - pass + #TODO: search for vizsesses that apply to an object (sending model class and id? - how to do this?) + rval = [] + try: + if not trans.user: + raise ItemAccessibilityException( 'You must be logged in to access visualizations' ) + user = trans.user + + #TODO: search for: title, made by user, creation time range, type (vis name), dbkey, etc. + #TODO: limit, offset, order_by + #TODO: deleted + + # this is the default search - user's vis, vis shared with user, published vis + visualizations = self.get_visualizations_by_user( trans, user ) + visualizations += self.get_visualizations_shared_with_user( trans, user ) + visualizations += self.get_published_visualizations( trans, exclude_user=user ) + #TODO: the admin case - everything + + for visualization in visualizations: + item = self.get_visualization_summary_dict( visualization ) + item = trans.security.encode_dict_ids( item ) + item[ 'url' ] = web.url_for( 'visualization', id=item[ 'id' ] ) + rval.append( item ) + + except ItemAccessibilityException, exception: + trans.response.status = 403 + rval = { 'error': str( exception ) } + if trans.debug: + log.exception( str( exception ) ) + + except Exception, exception: + trans.response.status = 500 + rval = { 'error': str( exception ) } + log.exception( 'visualizations index failed: %s' %( str( exception ) ) ) + + return rval @web.json - def show( self, trans, id, **kwd ): + def show( self, trans, id, **kwargs ): """ GET /api/visualizations/{viz_id} """ - pass + #TODO: revisions should be a contents/nested controller like viz/xxx/r/xxx)? + # the important thing is the config + rval = {} + #TODO:?? /api/visualizations/registry -> json of registry.listings? + try: + visualization = self.get_visualization( trans, id, check_ownership=False, check_accessible=True ) + dictionary = trans.security.encode_dict_ids( self.get_visualization_dict( visualization ) ) + dictionary[ 'url' ] = url_for( controller='visualization', + action="display_by_username_and_slug", username=visualization.user.username, slug=visualization.slug ) + dictionary[ 'annotation' ] = self.get_item_annotation_str( trans.sa_session, trans.user, visualization ) + + # need to encode ids in revisions as well + encoded_revisions = [] + for revision in dictionary[ 'revisions' ]: + #NOTE: does not encode ids inside the configs + encoded_revisions.append( trans.security.encode_id( revision ) ) + dictionary[ 'revisions' ] = encoded_revisions + dictionary[ 'latest_revision' ] = trans.security.encode_dict_ids( dictionary[ 'latest_revision' ] ) + + rval = dictionary + + except ( ItemAccessibilityException, ItemDeletionException ), exception: + trans.response.status = 403 + rval = { 'error': str( exception ) } + if trans.debug: + log.exception( 'visualization show forbidden (%s): %s' %( id, str( exception ) ) ) + + except Exception, exception: + trans.response.status = 500 + rval = { 'error': str( exception ) } + log.exception( 'visualization show failed (%s): %s' %( id, str( exception ) ) ) + + return rval @web.expose_api - def create( self, trans, payload, **kwd ): + def create( self, trans, payload, **kwargs ): """ POST /api/visualizations + creates a new visualization using the given payload + + POST /api/visualizations?import_id={encoded_visualization_id} + imports a copy of an existing visualization into the user's workspace """ - pass \ No newline at end of file + rval = None + try: + if 'import_id' in payload: + import_id = payload( 'import_id' ) + visualization = self.import_visualization( trans, import_id, user=trans.user ) + + else: + payload = self._validate_and_parse_payload( payload ) + payload[ 'save' ] = True + # create needs defaults like wizard needs food - generate defaults - this will err if given a weird key? + visualization = self.create_visualization( trans, **payload ) + + rval = { 'id' : trans.security.encode_id( visualization.id ) } + + #TODO: exception boilerplate + except ( ItemAccessibilityException, ItemDeletionException ), exception: + trans.response.status = 403 + rval = { 'error': str( exception ) } + if trans.debug: + log.exception( str( exception ) ) + + except ( ValueError, AttributeError ), exception: + trans.response.status = 400 + rval = { 'error': str( exception ) } + if trans.debug: + log.exception( str( exception ) ) + + except Exception, exception: + trans.response.status = 500 + rval = { 'error': str( exception ) } + log.exception( 'creation of visualization failed: %s' %( str( exception ) ) ) + + return rval + + @web.expose_api + def update( self, trans, id, payload, **kwargs ): + """ + PUT /api/visualizations/{encoded_visualization_id} + """ + rval = None + try: + payload = self._validate_and_parse_payload( payload ) + + # there's a differentiation here between updating the visualiztion and creating a new revision + # that needs to be handled clearly here + # or alternately, using a different controller like PUT /api/visualizations/{id}/r/{id} + + #TODO: consider allowing direct alteration of revisions title (without a new revision) + # only create a new revsion on a different config + + # only update owned visualizations + visualization = self.get_visualization( trans, id, check_ownership=True ) + title = payload.get( 'title', visualization.latest_revision.title ) + dbkey = payload.get( 'dbkey', visualization.latest_revision.dbkey ) + config = payload.get( 'config', visualization.latest_revision.config ) + + latest_config = visualization.latest_revision.config + if( ( title != visualization.latest_revision.title ) + or ( dbkey != visualization.latest_revision.dbkey ) + or ( util.json.to_json_string( config ) != util.json.to_json_string( latest_config ) ) ): + revision = self.add_visualization_revision( trans, visualization, config, title, dbkey ) + #TODO: need to somehow get what changed here? + rval = { 'id' : revision.id } + + except ( ItemAccessibilityException, ItemDeletionException ), exception: + trans.response.status = 403 + rval = { 'error': str( exception ) } + if trans.debug: + log.exception( str( exception ) ) + + except ( ValueError, AttributeError ), exception: + trans.response.status = 400 + rval = { 'error': str( exception ) } + if trans.debug: + log.exception( str( exception ) ) + + except Exception, exception: + trans.response.status = 500 + rval = { 'error': str( exception ) } + log.exception( 'update of visualization (%s) failed: %s' %( id, str( exception ) ) ) + + return rval + + def _validate_and_parse_payload( self, payload ): + """ + Validate and parse incomming data payload for a visualization. + """ + # This layer handles (most of the stricter idiot proofing): + # - unknown/unallowed keys + # - changing data keys from api key to attribute name + # - protection against bad data form/type + # - protection against malicious data content + # all other conversions and processing (such as permissions, etc.) should happen down the line + + # keys listed here don't error when attempting to set, but fail silently + # this allows PUT'ing an entire model back to the server without attribute errors on uneditable attrs + valid_but_uneditable_keys = ( + 'id', 'model_class' + #TODO: fill out when we create get_api_value, get_dict, whatevs + ) + #TODO: deleted + #TODO: importable + + validated_payload = {} + for key, val in payload.items(): + if key == 'config': + if not isinstance( val, dict ): + raise ValueError( '%s must be a dictionary (JSON): %s' %( key, str( type( val ) ) ) ) + + elif key == 'annotation': + if not ( isinstance( val, str ) or isinstance( val, unicode ) ): + raise ValueError( '%s must be a string or unicode: %s' %( key, str( type( val ) ) ) ) + val = util.sanitize_html.sanitize_html( val, 'utf-8' ) + + # these are keys that actually only be *updated* at the revision level and not here + # (they are still valid for create, tho) + elif key == 'title': + if not ( isinstance( val, str ) or isinstance( val, unicode ) ): + raise ValueError( '%s must be a string or unicode: %s' %( key, str( type( val ) ) ) ) + val = util.sanitize_html.sanitize_html( val, 'utf-8' ) + elif key == 'slug': + if not isinstance( val, str ): + raise ValueError( '%s must be a string: %s' %( key, str( type( val ) ) ) ) + val = util.sanitize_html.sanitize_html( val, 'utf-8' ) + elif key == 'type': + if not isinstance( val, str ): + raise ValueError( '%s must be a string: %s' %( key, str( type( val ) ) ) ) + val = util.sanitize_html.sanitize_html( val, 'utf-8' ) + #TODO: validate types in VALID_TYPES/registry names at the mixin/model level? + elif key == 'dbkey': + if not ( isinstance( val, str ) or isinstance( val, unicode ) ): + raise ValueError( '%s must be a string or unicode: %s' %( key, str( type( val ) ) ) ) + val = util.sanitize_html.sanitize_html( val, 'utf-8' ) + + elif key not in valid_but_uneditable_keys: + raise AttributeError( 'unknown key: %s' %( str( key ) ) ) + + validated_payload[ key ] = val + return validated_payload + + #@web.expose_api + #def delete( self, trans, id, **kwd ): + # """ + # DELETE /api/visualizations/{encoded_history_id} + # Deletes a visualization from the database + # """ + # pass diff --git a/test/casperjs/api-visualizations-tests.js b/test/casperjs/api-visualizations-tests.js new file mode 100644 index 00000000000..58c67cc8cfc --- /dev/null +++ b/test/casperjs/api-visualizations-tests.js @@ -0,0 +1,282 @@ +/* Utility to load a specific page and output html, page text, or a screenshot + * Optionally wait for some time, text, or dom selector + */ +try { + //...if there's a better way - please let me know, universe + var scriptDir = require( 'system' ).args[3] + // remove the script filename + .replace( /[\w|\.|\-|_]*$/, '' ) + // if given rel. path, prepend the curr dir + .replace( /^(?!\/)/, './' ), + spaceghost = require( scriptDir + 'spaceghost' ).create({ + // script options here (can be overridden by CLI) + //verbose: true, + //logLevel: debug, + scriptDir: scriptDir + }); + +} catch( error ){ + console.debug( error ); + phantom.exit( 1 ); +} +spaceghost.start(); + + +// =================================================================== SET UP +var utils = require( 'utils' ); + +var email = spaceghost.user.getRandomEmail(), + password = '123456'; +if( spaceghost.fixtureData.testUser ){ + email = spaceghost.fixtureData.testUser.email; + password = spaceghost.fixtureData.testUser.password; +} +spaceghost.user.loginOrRegisterUser( email, password ); + +function hasKeys( object, keysArray ){ + if( !utils.isObject( object ) ){ return false; } + for( var i=0; i= 1, 'Has at least one visualization' ); + + var firstVisualization = visualizationIndex[0]; + this.test.assert( hasKeys( firstVisualization, indexKeys ), 'Has the proper keys' ); + this.test.assert( this.api.isEncodedId( firstVisualization.id ), 'Id appears well-formed' ); + + //TODO: index searching + //TODO: anon user + //TODO: admin user + + // ------------------------------------------------------------------------------------------- SHOW + this.test.comment( 'show should get a visualization details object' ); + var visualizationShow = this.api.visualizations.show( firstVisualization.id ); + this.debug( this.jsonStr( visualizationShow ) ); + this.test.assert( hasKeys( visualizationShow, showKeys ), 'Has the proper keys' ); + this.test.assert( visualizationShow.model_class === 'Visualization', + 'Has the proper model_class: ' + visualizationShow.model_class ); + + this.test.comment( 'a visualization details object should contain an array of revision ids' ); + var revisions = visualizationShow.revisions; + this.test.assert( utils.isArray( revisions ), 'revisions is an array' ); + this.test.assert( revisions.length >= 1, 'revisions has at least one entry' ); + var areIds = true; + revisions.forEach( function( revision ){ + if( !spaceghost.api.isEncodedId( revision ) ){ areIds = false; } + }); + this.test.assert( areIds, 'all revisions are ids' ); + + this.test.comment( 'a visualization details object should contain a subobject of the latest revision' ); + var latestRevision = visualizationShow.latest_revision; + this.test.assert( utils.isObject( latestRevision ), 'latestRevision is an object' ); + this.test.assert( hasKeys( latestRevision, revisionKeys ), 'latestRevision has the proper keys' ); + this.test.assert( latestRevision.model_class === 'VisualizationRevision', + 'Has the proper model_class: ' + latestRevision.model_class ); + this.test.assert( latestRevision.visualization_id === visualizationShow.id, + 'revision visualization_id matches containing visualization id: ' + latestRevision.visualization_id ); + this.test.assert( visualizationShow.revisions.indexOf( latestRevision.id ) !== -1, + 'found latest_revision id in revisions' ); + + this.test.comment( 'a visualization revision should contain a subobject for the config' ); + var config = latestRevision.config; + this.test.assert( utils.isObject( config ), 'config is an object:\n' + this.jsonStr( config ) ); + + //TODO: url in visualizationIndex == show url + //TODO: non existing id throws error + //TODO: anon user + //TODO: user1 has no permissions to show user2 + + // ------------------------------------------------------------------------------------------- CREATE + this.test.comment( 'Calling create should create a new visualization and allow setting the name' ); + var visualizationData = { + title : 'Created Visualization', + // needs to be unique + slug : 'created-visualization-' + Date.now(), + type : 'test', + dbkey : 'hg17', + annotation : 'invisible visualization', + config : { + x : 10, + y : 12 + } + }; + var created = this.api.visualizations.create( visualizationData ); + this.debug( 'returned from create:\n' + this.jsonStr( created ) ); + this.test.assert( this.api.isEncodedId( created.id ), "create returned an id: " + created.id ); + + // check v. show + visualizationShow = this.api.visualizations.show( created.id ); + this.debug( 'visualizationShow:\n' + this.jsonStr( visualizationShow ) ); + // config is re-located into a revision and won't be there + this.test.assert( compareObjs( visualizationShow, visualizationData, [ 'config' ] ), + "show results seem to match create data" ); + + // the following errors are produced within base.controller.UsesVisualizationsMixin._create_visualization + this.test.comment( 'Calling create with a non-unique slug will cause an API error' ); + assertRaises( function(){ + created = this.api.visualizations.create( visualizationData ); + }, 400, 'visualization identifier must be unique' ); + + this.test.comment( 'Calling create with no title will cause an API error' ); + visualizationData.title = ''; + assertRaises( function(){ + created = this.api.visualizations.create( visualizationData ); + }, 400, 'visualization name is required' ); + visualizationData.title = 'Created Visualization'; + + this.test.comment( 'Calling create with improper characters in the slug will cause an API error' ); + var oldSlug = visualizationData.slug; + visualizationData.slug = '123_()'; + assertRaises( function(){ + created = this.api.visualizations.create( visualizationData ); + }, 400, "visualization identifier must consist of only lowercase letters, numbers, and the '-' character" ); + visualizationData.slug = oldSlug; + + this.test.comment( 'Calling create with an unrecognized key will cause an API error' ); + visualizationData.bler = 'blah'; + assertRaises( function(){ + created = this.api.visualizations.create( visualizationData ); + }, 400, 'unknown key: bler' ); + delete visualizationData.bler; + + this.test.comment( 'Calling create with an unparsable JSON config will cause an API error' ); + visualizationData.config = '3 = nime'; + assertRaises( function(){ + created = this.api.visualizations.create( visualizationData ); + }, 400, 'config must be a dictionary (JSON)' ); + + // ------------------------------------------------------------------------------------------ UPDATE + // ........................................................................................... idiot proofing + this.test.comment( 'updating using a nonsense key should fail with an error' ); + assertRaises( function(){ + returned = this.api.visualizations.update( created.id, { bler : 'blah' }); + }, 400, 'unknown key: bler' ); + + this.test.comment( 'updating by attempting to change type should cause an error' ); + assertRaises( function(){ + returned = this.api.visualizations.update( created.id, { title : 30 }); + }, 400, 'title must be a string or unicode' ); + //TODO: the other types... + + // ........................................................................................... title + //this.test.comment( 'update should create a new visualization revision' ); + // + //this.test.comment( 'updating with a new title should NOT change the visualization title...' ); + //latestRevision = this.api.visualizations.show( created.id ).latest_revision; + //returned = this.api.visualizations.update( created.id, { + // title : 'New title' + //}); + //visualizationShow = this.api.visualizations.show( created.id ); + //this.debug( this.jsonStr( visualizationShow ) ); + //this.test.assert( visualizationShow.title === visualizationData.title, + // "Title does not set via update: " + visualizationShow.title ); + +}); + +// =================================================================== +spaceghost.run( function(){ +}); diff --git a/test/casperjs/modules/api.js b/test/casperjs/modules/api.js index f628deb99cf..d03e2e7db1b 100644 --- a/test/casperjs/modules/api.js +++ b/test/casperjs/modules/api.js @@ -24,6 +24,7 @@ var API = function API( spaceghost, apikey ){ this.tools = new ToolsAPI( this ); this.workflows = new WorkflowsAPI( this ); this.users = new UsersAPI( this ); + this.visualizations = new VisualizationsAPI( this ); }; exports.API = API; @@ -73,6 +74,7 @@ API.prototype._ajax = function _ajax( url, options ){ if( resp.status !== 200 ){ // grrr... this doesn't lose the \n\r\t //throw new APIError( resp.responseText.replace( /[\s\n\r\t]+/gm, ' ' ).replace( /"/, '' ) ); + this.spaceghost.debug( 'api error response status code: ' + resp.status ); throw new APIError( resp.responseText, resp.status ); } return JSON.parse( resp.responseText ); @@ -490,3 +492,57 @@ UsersAPI.prototype.update = function create( id, payload ){ }; +// =================================================================== HISTORIES +var VisualizationsAPI = function VisualizationsAPI( api ){ + this.api = api; +}; +VisualizationsAPI.prototype.toString = function toString(){ + return this.api + '.VisualizationsAPI'; +}; + +// ------------------------------------------------------------------- +VisualizationsAPI.prototype.urlTpls = { + index : 'api/visualizations', + show : 'api/visualizations/%s', + create : 'api/visualizations', + //delete_ : 'api/visualizations/%s', + //undelete: 'api/visualizations/deleted/%s/undelete', + update : 'api/visualizations/%s' +}; + +VisualizationsAPI.prototype.index = function index(){ + this.api.spaceghost.info( 'visualizations.index' ); + + return this.api._ajax( this.urlTpls.index ); +}; + +VisualizationsAPI.prototype.show = function show( id ){ + this.api.spaceghost.info( 'visualizations.show' ); + + return this.api._ajax( utils.format( this.urlTpls.show, this.api.ensureId( id ) ) ); +}; + +VisualizationsAPI.prototype.create = function create( payload ){ + this.api.spaceghost.info( 'visualizations.create: ' + this.api.spaceghost.jsonStr( payload ) ); + + // py.payload <-> ajax.data + payload = this.api.ensureObject( payload ); + return this.api._ajax( utils.format( this.urlTpls.create ), { + type : 'POST', + data : payload + }); +}; + +VisualizationsAPI.prototype.update = function create( id, payload ){ + this.api.spaceghost.info( 'visualizations.update: ' + id + ',' + this.api.spaceghost.jsonStr( payload ) ); + + // py.payload <-> ajax.data + id = this.api.ensureId( id ); + payload = this.api.ensureObject( payload ); + url = utils.format( this.urlTpls.update, id ); + + return this.api._ajax( url, { + type : 'PUT', + data : payload + }); +}; From 483cfb667b22b2d6950fa0d23ade05143df5fe29 Mon Sep 17 00:00:00 2001 From: Carl Eberhard Date: Fri, 17 May 2013 16:30:50 -0400 Subject: [PATCH 454/523] Bugfix: remove DEFAULT_ORDER_BY in visualizations mixin. --- lib/galaxy/web/base/controller.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/lib/galaxy/web/base/controller.py b/lib/galaxy/web/base/controller.py index c00b0524aa3..006cec81a98 100644 --- a/lib/galaxy/web/base/controller.py +++ b/lib/galaxy/web/base/controller.py @@ -689,7 +689,6 @@ class UsesVisualizationMixin( UsesHistoryDatasetAssociationMixin, UsesLibraryMix Mixin for controllers that use Visualization objects. """ - DEFAULT_ORDER_BY = [ model.Visualization.title ] viz_types = [ "trackster" ] def get_visualization( self, trans, id, check_ownership=True, check_accessible=False ): @@ -715,8 +714,10 @@ class UsesVisualizationMixin( UsesHistoryDatasetAssociationMixin, UsesLibraryMix Set `query_only` to return just the query for further filtering or processing. """ + #TODO: move into model (as class attr) + DEFAULT_ORDER_BY = [ model.Visualization.title ] if not order_by: - order_by = self.DEFAULT_ORDER_BY + order_by = DEFAULT_ORDER_BY if not isinstance( order_by, list ): order_by = [ order_by ] query = trans.sa_session.query( model.Visualization ) @@ -736,8 +737,9 @@ class UsesVisualizationMixin( UsesHistoryDatasetAssociationMixin, UsesLibraryMix Set `query_only` to return just the query for further filtering or processing. """ + DEFAULT_ORDER_BY = [ model.Visualization.title ] if not order_by: - order_by = self.DEFAULT_ORDER_BY + order_by = DEFAULT_ORDER_BY if not isinstance( order_by, list ): order_by = [ order_by ] query = trans.sa_session.query( model.Visualization ).join( model.VisualizationUserShareAssociation ) @@ -760,8 +762,9 @@ class UsesVisualizationMixin( UsesHistoryDatasetAssociationMixin, UsesLibraryMix Set `query_only` to return just the query for further filtering or processing. """ + DEFAULT_ORDER_BY = [ model.Visualization.title ] if not order_by: - order_by = self.DEFAULT_ORDER_BY + order_by = DEFAULT_ORDER_BY if not isinstance( order_by, list ): order_by = [ order_by ] query = trans.sa_session.query( model.Visualization ) From 3a55b4aaabf1fb60b7302eb429f284bf1bf6738e Mon Sep 17 00:00:00 2001 From: Dave Bouvier Date: Fri, 17 May 2013 16:42:48 -0400 Subject: [PATCH 455/523] Functional tests for automatically determining the target changeset revision in repository dependencies. --- test/tool_shed/base/twilltestcase.py | 35 ++ .../test_0460_upload_to_repository.py | 485 ++++++++++++++++++ test/tool_shed/functional_tests.py | 3 +- .../0460_files/repository_dependencies.xml | 4 + .../repository_dependencies_in_root.tar | Bin 0 -> 2048 bytes .../repository_dependencies_in_subfolder.tar | Bin 0 -> 2560 bytes .../0460_files/tool_dependencies.xml | 6 + .../0460_files/tool_dependencies_in_root.tar | Bin 0 -> 2048 bytes .../tool_dependencies_in_subfolder.tar | Bin 0 -> 2560 bytes test/tool_shed/test_data/filtering/README | 3 +- 10 files changed, 532 insertions(+), 4 deletions(-) create mode 100644 test/tool_shed/functional/test_0460_upload_to_repository.py create mode 100644 test/tool_shed/test_data/0460_files/repository_dependencies.xml create mode 100644 test/tool_shed/test_data/0460_files/repository_dependencies_in_root.tar create mode 100644 test/tool_shed/test_data/0460_files/repository_dependencies_in_subfolder.tar create mode 100644 test/tool_shed/test_data/0460_files/tool_dependencies.xml create mode 100644 test/tool_shed/test_data/0460_files/tool_dependencies_in_root.tar create mode 100644 test/tool_shed/test_data/0460_files/tool_dependencies_in_subfolder.tar diff --git a/test/tool_shed/base/twilltestcase.py b/test/tool_shed/base/twilltestcase.py index efdcc41efbe..eaebb1e6d8c 100644 --- a/test/tool_shed/base/twilltestcase.py +++ b/test/tool_shed/base/twilltestcase.py @@ -1119,6 +1119,41 @@ class ShedTwillTestCase( TwillTestCase ): # time.sleep( 1 ) # continue + def upload_url( self, + repository, + url, + filepath, + valid_tools_only, + uncompress_file, + remove_repo_files_not_in_tar, + commit_message, + strings_displayed=[], + strings_not_displayed=[] ): + removed_message = 'files were removed from the repository' + if remove_repo_files_not_in_tar: + if not self.repository_is_new( repository ): + if removed_message not in strings_displayed: + strings_displayed.append( removed_message ) + else: + if removed_message not in strings_not_displayed: + strings_not_displayed.append( removed_message ) + self.visit_url( '/upload/upload?repository_id=%s' % self.security.encode_id( repository.id ) ) + if valid_tools_only: + strings_displayed.extend( [ 'has been successfully', 'uploaded to the repository.' ] ) + tc.fv( "1", "url", url ) + if uncompress_file: + tc.fv( 1, 'uncompress_file', 'Yes' ) + else: + tc.fv( 1, 'uncompress_file', 'No' ) + if not self.repository_is_new( repository ): + if remove_repo_files_not_in_tar: + tc.fv( 1, 'remove_repo_files_not_in_tar', 'Yes' ) + else: + tc.fv( 1, 'remove_repo_files_not_in_tar', 'No' ) + tc.fv( 1, 'commit_message', commit_message ) + tc.submit( "upload_button" ) + self.check_for_strings( strings_displayed, strings_not_displayed ) + def verify_installed_repositories( self, installed_repositories=[], uninstalled_repositories=[] ): for repository_name, repository_owner in installed_repositories: galaxy_repository = test_db_util.get_installed_repository_by_name_owner( repository_name, repository_owner ) diff --git a/test/tool_shed/functional/test_0460_upload_to_repository.py b/test/tool_shed/functional/test_0460_upload_to_repository.py new file mode 100644 index 00000000000..4e795ad32e7 --- /dev/null +++ b/test/tool_shed/functional/test_0460_upload_to_repository.py @@ -0,0 +1,485 @@ +from tool_shed.base.twilltestcase import ShedTwillTestCase, common, os +import tool_shed.base.test_db_util as test_db_util + +import logging +log = logging.getLogger( __name__ ) + +category_name = 'Test 0460 Automatic repository revision completion' +category_description = 'Test 0460 Automatic repository revision completion' +datatypes_repository_name = 'emboss_datatypes_0460' +datatypes_repository_description = "Galaxy applicable data formats used by Emboss tools." +datatypes_repository_long_description = "Galaxy applicable data formats used by Emboss tools. This repository contains no tools." +bwa_repository_name = 'package_bwa_0_5_9_0460' +bwa_repository_description = "Contains a tool dependency definition that downloads and compiles version 0.5.9 of the BWA package" +bwa_repository_long_description = "bwa (alignment via Burrows-Wheeler transformation) 0.5.9-r16 by Heng Li" + +''' +For all steps, verify that the generated dependency points to the tip of the specified repository. + +1) Create and populate emboss_datatypes_0460. + +2) Create and populate package_bwa_0_5_9_0460 + +3) Create complex_dependency_test_1_0460, complex_dependency_test_2_0460, complex_dependency_test_3_0460, + complex_dependency_test_4_0460, complex_dependency_test_5_0460. + +4) Upload an uncompressed tool_dependencies.xml to complex_dependency_test_1_0460 that specifies a complex + repository dependency on package_bwa_0_5_9_0460 without a specified changeset revision or tool shed url. + +5) Upload a tarball to complex_dependency_test_2_0460 with a tool_dependencies.xml in the root of the tarball. + +6) Upload a tarball to complex_dependency_test_3_0460 with a tool_dependencies.xml in a subfolder within the tarball. + +7) Create hg_tool_dependency_0460 and hg_subfolder_tool_dependency_0460 and populate with orphan tool dependencies. + +8) Upload to complex_dependency_test_4_0460 using the url hg:///repos/user1/hg_tool_dependency_0460. + +9) Upload to complex_dependency_test_5_0460 using the url hg:///repos/user1/hg_subfolder_tool_dependency_0460. + +10) Create repository_dependency_test_1_0460, repository_dependency_test_2_0460, repository_dependency_test_3_0460, + repository_dependency_test_4_0460, repository_dependency_test_4_0460. + +11) Upload an uncompressed repository_dependencies.xml to repository_dependency_test_1_0460 that specifies a + repository dependency on emboss_datatypes_0460 without a specified changeset revision or tool shed url. + +12) Upload a tarball to repository_dependency_test_1_0460 with a repository_dependencies.xml in the root of the tarball. + +13) Upload a tarball to repository_dependency_test_1_0460 with a repository_dependencies.xml in a subfolder within the tarball. + +14) Create hg_repository_dependency_0460 and populate with repository_dependencies.xml. + +15) Upload to repository_dependency_test_4_0460 using the url hg:///repos/user1/hg_repository_dependency_0460. + +16) Upload to repository_dependency_test_5_0460 using the url hg:///repos/user1/hg_repository_dependency_0460. +''' + +class TestAutomaticDependencyRevision( ShedTwillTestCase ): + '''Test defining repository dependencies without specifying the changeset revision.''' + + def test_0000_initiate_users( self ): + """Create necessary user accounts and login as an admin user.""" + self.logout() + self.login( email=common.test_user_1_email, username=common.test_user_1_name ) + test_user_1 = test_db_util.get_user( common.test_user_1_email ) + assert test_user_1 is not None, 'Problem retrieving user with email %s from the database' % common.test_user_1_email + test_user_1_private_role = test_db_util.get_private_role( test_user_1 ) + self.logout() + self.login( email=common.test_user_2_email, username=common.test_user_2_name ) + test_user_2 = test_db_util.get_user( common.test_user_2_email ) + assert test_user_2 is not None, 'Problem retrieving user with email %s from the database' % common.test_user_2_email + test_user_2_private_role = test_db_util.get_private_role( test_user_2 ) + self.logout() + self.login( email=common.admin_email, username=common.admin_username ) + admin_user = test_db_util.get_user( common.admin_email ) + assert admin_user is not None, 'Problem retrieving user with email %s from the database' % common.admin_email + admin_user_private_role = test_db_util.get_private_role( admin_user ) + + def test_0005_create_datatypes_repository( self ): + '''Create and populate the emboss_datatypes_0460 repository''' + ''' + This is step 1 - Create and populate emboss_datatypes_0460. + ''' + self.create_category( name=category_name, description=category_description ) + self.logout() + self.login( email=common.test_user_1_email, username=common.test_user_1_name ) + category = test_db_util.get_category_by_name( category_name ) + repository = self.get_or_create_repository( name=datatypes_repository_name, + description=datatypes_repository_description, + long_description=datatypes_repository_long_description, + owner=common.test_user_1_name, + category_id=self.security.encode_id( category.id ), + strings_displayed=[] ) + self.upload_file( repository, + filename='emboss/datatypes/datatypes_conf.xml', + filepath=None, + valid_tools_only=True, + uncompress_file=False, + remove_repo_files_not_in_tar=False, + commit_message='Populate emboss_datatypes_0460 with datatype definitions.', + strings_displayed=[], + strings_not_displayed=[] ) + + def test_0010_create_bwa_package_repository( self ): + '''Create and populate the package_bwa_0_5_9_0460 repository.''' + ''' + This is step 2 - Create and populate package_bwa_0_5_9_0460. + ''' + category = test_db_util.get_category_by_name( category_name ) + repository = self.get_or_create_repository( name=bwa_repository_name, + description=bwa_repository_description, + long_description=bwa_repository_long_description, + owner=common.test_user_1_name, + category_id=self.security.encode_id( category.id ), + strings_displayed=[] ) + self.upload_file( repository, + filename='bwa/complex/tool_dependencies.xml', + filepath=None, + valid_tools_only=True, + uncompress_file=False, + remove_repo_files_not_in_tar=False, + commit_message='Populate package_bwa_0_5_9_0460 with an orphan tool dependency definition.', + strings_displayed=[], + strings_not_displayed=[] ) + + def test_0015_create_tool_dependency_repositories( self ): + '''Create repositories for testing complex dependency generation.''' + ''' + This is step 3 - Create complex_dependency_test_1_0460, complex_dependency_test_2_0460, complex_dependency_test_3_0460, + complex_dependency_test_4_0460, complex_dependency_test_5_0460. Each of these repositories will be populated in a way + that tests a different way to achieve the same resulting dependency structure using complex tool dependencies. + The different methods being tested are: + - Upload an uncompressed tool_dependencies.xml to the root of the repository. + - Upload a tool_dependencies.xml in a tarball, not in a subfolder. + - Upload a tool_dependencies.xml in a subfolder within a tarball. + - Upload via url, with the tool_dependencies.xml in the root of another repository. + - Upload via url, with the tool_dependencies.xml in a subfolder within another repository. + ''' + category = test_db_util.get_category_by_name( category_name ) + repository_base_name = 'complex_dependency_test_%d_0460' + repository_base_description = 'Test #%d for complex repository dependency definitions.' + repository_base_long_description = 'Test #%d for complex repository dependency definitions.' + for number in range( 1, 6 ): + repository = self.get_or_create_repository( name=repository_base_name % number, + description=repository_base_description % number, + long_description=repository_base_long_description % number, + owner=common.test_user_1_name, + category_id=self.security.encode_id( category.id ), + strings_displayed=[] ) + + def test_0020_populate_complex_dependency_test_1_0460( self ): + '''Populate complex_dependency_test_1_0460.''' + ''' + This is step 4 - Upload an uncompressed tool_dependencies.xml to complex_dependency_test_1_0460 that specifies + a complex repository dependency on package_bwa_0_5_9_0460 without a specified changeset revision or tool shed url. + ''' + repository = test_db_util.get_repository_by_name_and_owner( 'complex_dependency_test_1_0460', common.test_user_1_name ) + package_repository = test_db_util.get_repository_by_name_and_owner( 'package_bwa_0_5_9_0460', common.test_user_1_name ) + self.upload_file( repository, + filename='0460_files/tool_dependencies.xml', + filepath=None, + valid_tools_only=True, + uncompress_file=False, + remove_repo_files_not_in_tar=False, + commit_message='Uploaded complex repository dependency definition.', + strings_displayed=[], + strings_not_displayed=[] ) + changeset_revision = self.get_repository_tip( package_repository ) + strings_displayed = [ 'package_bwa_0_5_9_0460', 'bwa', '0.5.9', 'package', changeset_revision ] + self.display_manage_repository_page( repository, strings_displayed=strings_displayed ) + self.display_repository_file_contents( repository, filename='tool_dependencies.xml', strings_displayed=[ changeset_revision ] ) + + def test_0025_populate_complex_dependency_test_2_0460( self ): + '''Populate complex_dependency_test_2_0460.''' + ''' + This is step 5 - Upload an tarball with tool_dependencies.xml to complex_dependency_test_2_0460 that specifies + a complex repository dependency on package_bwa_0_5_9_0460 without a specified changeset revision or tool shed url. + ''' + repository = test_db_util.get_repository_by_name_and_owner( 'complex_dependency_test_2_0460', common.test_user_1_name ) + package_repository = test_db_util.get_repository_by_name_and_owner( 'package_bwa_0_5_9_0460', common.test_user_1_name ) + self.upload_file( repository, + filename='0460_files/tool_dependencies_in_root.tar', + filepath=None, + valid_tools_only=True, + uncompress_file=False, + remove_repo_files_not_in_tar=True, + commit_message='Uploaded complex repository dependency definition.', + strings_displayed=[], + strings_not_displayed=[] ) + changeset_revision = self.get_repository_tip( package_repository ) + strings_displayed = [ 'package_bwa_0_5_9_0460', 'bwa', '0.5.9', 'package', changeset_revision ] + self.display_manage_repository_page( repository, strings_displayed=strings_displayed ) + self.display_repository_file_contents( repository, filename='tool_dependencies.xml', strings_displayed=[ changeset_revision ] ) + + def test_0030_populate_complex_dependency_test_3_0460( self ): + '''Populate complex_dependency_test_3_0460.''' + ''' + This is step 6 - Upload an tarball with tool_dependencies.xml in a subfolder to complex_dependency_test_3_0460 that + specifies a complex repository dependency on package_bwa_0_5_9_0460 without a specified changeset revision or tool shed url. + ''' + repository = test_db_util.get_repository_by_name_and_owner( 'complex_dependency_test_3_0460', common.test_user_1_name ) + package_repository = test_db_util.get_repository_by_name_and_owner( 'package_bwa_0_5_9_0460', common.test_user_1_name ) + self.upload_file( repository, + filename='0460_files/tool_dependencies_in_subfolder.tar', + filepath=None, + valid_tools_only=True, + uncompress_file=False, + remove_repo_files_not_in_tar=True, + commit_message='Uploaded complex repository dependency definition.', + strings_displayed=[], + strings_not_displayed=[] ) + changeset_revision = self.get_repository_tip( package_repository ) + strings_displayed = [ 'package_bwa_0_5_9_0460', 'bwa', '0.5.9', 'package', changeset_revision ] + self.display_manage_repository_page( repository, strings_displayed=strings_displayed ) + self.display_repository_file_contents( repository, + filename='tool_dependencies.xml', + filepath='subfolder', + strings_displayed=[ changeset_revision ] ) + + def test_0035_create_repositories_for_url_upload( self ): + '''Create and populate hg_tool_dependency_0460 and hg_subfolder_tool_dependency_0460.''' + ''' + This is step 7 - Create hg_tool_dependency_0460 and hg_subfolder_tool_dependency_0460 and populate with orphan tool dependencies. + ''' + category = test_db_util.get_category_by_name( category_name ) + repository = self.get_or_create_repository( name='hg_tool_dependency_0460', + description=bwa_repository_description, + long_description=bwa_repository_long_description, + owner=common.test_user_1_name, + category_id=self.security.encode_id( category.id ), + strings_displayed=[] ) + self.upload_file( repository, + filename='0460_files/tool_dependencies.xml', + filepath=None, + valid_tools_only=True, + uncompress_file=False, + remove_repo_files_not_in_tar=False, + commit_message='Populate hg_tool_dependency_0460 with an orphan tool dependency definition.', + strings_displayed=[], + strings_not_displayed=[] ) + repository = self.get_or_create_repository( name='hg_subfolder_tool_dependency_0460', + description=bwa_repository_description, + long_description=bwa_repository_long_description, + owner=common.test_user_1_name, + category_id=self.security.encode_id( category.id ), + strings_displayed=[] ) + self.upload_file( repository, + filename='0460_files/tool_dependencies_in_subfolder.tar', + filepath=None, + valid_tools_only=True, + uncompress_file=False, + remove_repo_files_not_in_tar=False, + commit_message='Populate hg_subfolder_tool_dependency_0460 with an orphan tool dependency definition.', + strings_displayed=[], + strings_not_displayed=[] ) + + def test_0040_url_upload_to_complex_test( self ): + '''Populate complex_dependency_test_4_0460.''' + ''' + This is step 8 - Upload to complex_dependency_test_4_0460 using the url hg:///repos/user1/hg_tool_dependency_0460. + ''' + url = 'hg://%s:%s/repos/user1/hg_tool_dependency_0460' % ( self.host, self.port ) + repository = test_db_util.get_repository_by_name_and_owner( 'complex_dependency_test_4_0460', common.test_user_1_name ) + package_repository = test_db_util.get_repository_by_name_and_owner( 'package_bwa_0_5_9_0460', common.test_user_1_name ) + self.upload_url( repository, + url=url, + filepath=None, + valid_tools_only=True, + uncompress_file=False, + remove_repo_files_not_in_tar=True, + commit_message='Uploaded complex repository dependency definition.', + strings_displayed=[], + strings_not_displayed=[] ) + changeset_revision = self.get_repository_tip( package_repository ) + strings_displayed = [ 'package_bwa_0_5_9_0460', 'bwa', '0.5.9', 'package', changeset_revision ] + self.display_manage_repository_page( repository, strings_displayed=strings_displayed ) + self.display_repository_file_contents( repository, + filename='tool_dependencies.xml', + strings_displayed=[ changeset_revision ] ) + + def test_0045_url_upload_to_complex_test( self ): + '''Populate complex_dependency_test_4_0460.''' + ''' + This is step 9 - Upload to complex_dependency_test_5_0460 using the url hg:///repos/user1/hg_subfolder_tool_dependency_0460. + ''' + url = 'hg://%s:%s/repos/user1/hg_subfolder_tool_dependency_0460' % ( self.host, self.port ) + repository = test_db_util.get_repository_by_name_and_owner( 'complex_dependency_test_5_0460', common.test_user_1_name ) + package_repository = test_db_util.get_repository_by_name_and_owner( 'package_bwa_0_5_9_0460', common.test_user_1_name ) + self.upload_url( repository, + url=url, + filepath=None, + valid_tools_only=True, + uncompress_file=False, + remove_repo_files_not_in_tar=True, + commit_message='Uploaded complex repository dependency definition.', + strings_displayed=[], + strings_not_displayed=[] ) + changeset_revision = self.get_repository_tip( package_repository ) + strings_displayed = [ 'package_bwa_0_5_9_0460', 'bwa', '0.5.9', 'package', changeset_revision ] + self.display_manage_repository_page( repository, strings_displayed=strings_displayed ) + self.display_repository_file_contents( repository, + filename='tool_dependencies.xml', + filepath='subfolder', + strings_displayed=[ changeset_revision ] ) + + def test_0050_create_repositories_for_simple_dependencies( self ): + '''Create repositories for testing simple dependency generation.''' + ''' + This is step 10 - Create repository_dependency_test_1_0460, repository_dependency_test_2_0460, repository_dependency_test_3_0460, + repository_dependency_test_4_0460, repository_dependency_test_4_0460.. Each of these repositories will be populated in a way + that tests a different way to achieve the same resulting dependency structure using complex tool dependencies. + The different methods being tested are: + - Upload an uncompressed repository_dependencies.xml to the root of the repository. + - Upload a repository_dependencies.xml in a tarball, not in a subfolder. + - Upload a repository_dependencies.xml in a subfolder within a tarball. + - Upload via url, with the repository_dependencies.xml in the root of another repository. + - Upload via url, with the repository_dependencies.xml in a subfolder within another repository. + ''' + category = test_db_util.get_category_by_name( category_name ) + repository_base_name = 'repository_dependency_test_%d_0460' + repository_base_description = 'Test #%d for repository dependency definitions.' + repository_base_long_description = 'Test #%d for repository dependency definitions.' + for number in range( 1, 6 ): + repository = self.get_or_create_repository( name=repository_base_name % number, + description=repository_base_description % number, + long_description=repository_base_long_description % number, + owner=common.test_user_1_name, + category_id=self.security.encode_id( category.id ), + strings_displayed=[] ) + + + def test_0055_populate_repository_dependency_test_1_0460( self ): + '''Populate repository_dependency_test_1_0460.''' + ''' + This is step 11 - Upload an uncompressed repository_dependencies.xml to repository_dependency_test_1_0460 that specifies a + repository dependency on emboss_datatypes_0460 without a specified changeset revision or tool shed url. + ''' + repository = test_db_util.get_repository_by_name_and_owner( 'repository_dependency_test_1_0460', common.test_user_1_name ) + package_repository = test_db_util.get_repository_by_name_and_owner( 'emboss_datatypes_0460', common.test_user_1_name ) + self.upload_file( repository, + filename='0460_files/repository_dependencies.xml', + filepath=None, + valid_tools_only=True, + uncompress_file=False, + remove_repo_files_not_in_tar=False, + commit_message='Uploaded complex repository dependency definition.', + strings_displayed=[], + strings_not_displayed=[] ) + changeset_revision = self.get_repository_tip( package_repository ) + strings_displayed = [ 'emboss_datatypes_0460', 'user1', changeset_revision ] + self.display_manage_repository_page( repository, strings_displayed=strings_displayed ) + self.display_repository_file_contents( repository, filename='repository_dependencies.xml', strings_displayed=[ changeset_revision ] ) + + def test_0060_populate_repository_dependency_test_2_0460( self ): + '''Populate repository_dependency_test_2_0460.''' + ''' + This is step 12 - Upload a tarball to repository_dependency_test_2_0460 with a repository_dependencies.xml in the root of the tarball. + ''' + repository = test_db_util.get_repository_by_name_and_owner( 'repository_dependency_test_2_0460', common.test_user_1_name ) + package_repository = test_db_util.get_repository_by_name_and_owner( 'emboss_datatypes_0460', common.test_user_1_name ) + self.upload_file( repository, + filename='0460_files/repository_dependencies_in_root.tar', + filepath=None, + valid_tools_only=True, + uncompress_file=False, + remove_repo_files_not_in_tar=True, + commit_message='Uploaded complex repository dependency definition.', + strings_displayed=[], + strings_not_displayed=[] ) + changeset_revision = self.get_repository_tip( package_repository ) + strings_displayed = [ 'emboss_datatypes_0460', 'user1', changeset_revision ] + self.display_manage_repository_page( repository, strings_displayed=strings_displayed ) + self.display_repository_file_contents( repository, filename='repository_dependencies.xml', strings_displayed=[ changeset_revision ] ) + + def test_0065_populate_repository_dependency_test_3_0460( self ): + '''Populate repository_dependency_test_3_0460.''' + ''' + This is step 13 - Upload a tarball to repository_dependency_test_3_0460 with a repository_dependencies.xml in a + subfolder within the tarball. + ''' + repository = test_db_util.get_repository_by_name_and_owner( 'repository_dependency_test_3_0460', common.test_user_1_name ) + package_repository = test_db_util.get_repository_by_name_and_owner( 'emboss_datatypes_0460', common.test_user_1_name ) + self.upload_file( repository, + filename='0460_files/repository_dependencies_in_subfolder.tar', + filepath=None, + valid_tools_only=True, + uncompress_file=False, + remove_repo_files_not_in_tar=True, + commit_message='Uploaded complex repository dependency definition.', + strings_displayed=[], + strings_not_displayed=[] ) + changeset_revision = self.get_repository_tip( package_repository ) + strings_displayed = [ 'emboss_datatypes_0460', 'user1', changeset_revision ] + self.display_manage_repository_page( repository, strings_displayed=strings_displayed ) + self.display_repository_file_contents( repository, + filename='repository_dependencies.xml', + filepath='subfolder', + strings_displayed=[ changeset_revision ] ) + + def test_0070_create_repositories_for_url_upload( self ): + '''Create and populate hg_repository_dependency_0460 and hg_subfolder_repository_dependency_0460.''' + ''' + This is step 14 - Create hg_repository_dependency_0460 and hg_subfolder_repository_dependency_0460 and populate + with repository dependencies. + ''' + category = test_db_util.get_category_by_name( category_name ) + repository = self.get_or_create_repository( name='hg_repository_dependency_0460', + description=bwa_repository_description, + long_description=bwa_repository_long_description, + owner=common.test_user_1_name, + category_id=self.security.encode_id( category.id ), + strings_displayed=[] ) + self.upload_file( repository, + filename='0460_files/repository_dependencies.xml', + filepath=None, + valid_tools_only=True, + uncompress_file=False, + remove_repo_files_not_in_tar=False, + commit_message='Populate hg_repository_dependency_0460 with an orphan tool dependency definition.', + strings_displayed=[], + strings_not_displayed=[] ) + repository = self.get_or_create_repository( name='hg_subfolder_repository_dependency_0460', + description=bwa_repository_description, + long_description=bwa_repository_long_description, + owner=common.test_user_1_name, + category_id=self.security.encode_id( category.id ), + strings_displayed=[] ) + self.upload_file( repository, + filename='0460_files/repository_dependencies_in_subfolder.tar', + filepath=None, + valid_tools_only=True, + uncompress_file=False, + remove_repo_files_not_in_tar=False, + commit_message='Populate hg_subfolder_repository_dependency_0460 with an orphan tool dependency definition.', + strings_displayed=[], + strings_not_displayed=[] ) + + def test_0075_url_upload_to_complex_test( self ): + '''Populate repository_dependency_test_4_0460.''' + ''' + This is step 15 - Upload to repository_dependency_test_4_0460 using the url + hg:///repos/user1/hg_repository_dependency_0460. + ''' + url = 'hg://%s:%s/repos/user1/hg_repository_dependency_0460' % ( self.host, self.port ) + repository = test_db_util.get_repository_by_name_and_owner( 'repository_dependency_test_4_0460', common.test_user_1_name ) + package_repository = test_db_util.get_repository_by_name_and_owner( 'emboss_datatypes_0460', common.test_user_1_name ) + self.upload_url( repository, + url=url, + filepath=None, + valid_tools_only=True, + uncompress_file=False, + remove_repo_files_not_in_tar=True, + commit_message='Uploaded repository dependency definition.', + strings_displayed=[], + strings_not_displayed=[] ) + changeset_revision = self.get_repository_tip( package_repository ) + strings_displayed = [ 'emboss_datatypes_0460', 'user1', changeset_revision ] + self.display_manage_repository_page( repository, strings_displayed=strings_displayed ) + self.display_repository_file_contents( repository, + filename='repository_dependencies.xml', + strings_displayed=[ changeset_revision ] ) + + def test_0080_url_upload_to_complex_test( self ): + '''Populate repository_dependency_test_4_0460.''' + ''' + This is step 16 - Upload to repository_dependency_test_5_0460 using the url + hg:///repos/user1/hg_subfolder_repository_dependency_0460. + ''' + url = 'hg://%s:%s/repos/user1/hg_subfolder_repository_dependency_0460' % ( self.host, self.port ) + repository = test_db_util.get_repository_by_name_and_owner( 'repository_dependency_test_5_0460', common.test_user_1_name ) + package_repository = test_db_util.get_repository_by_name_and_owner( 'emboss_datatypes_0460', common.test_user_1_name ) + self.upload_url( repository, + url=url, + filepath=None, + valid_tools_only=True, + uncompress_file=False, + remove_repo_files_not_in_tar=True, + commit_message='Uploaded repository dependency definition.', + strings_displayed=[], + strings_not_displayed=[] ) + changeset_revision = self.get_repository_tip( package_repository ) + strings_displayed = [ 'emboss_datatypes_0460', 'user1', changeset_revision ] + self.display_manage_repository_page( repository, strings_displayed=strings_displayed ) + self.display_repository_file_contents( repository, + filename='repository_dependencies.xml', + filepath='subfolder', + strings_displayed=[ changeset_revision ] ) \ No newline at end of file diff --git a/test/tool_shed/functional_tests.py b/test/tool_shed/functional_tests.py index 0d531082274..f9c1ffff324 100644 --- a/test/tool_shed/functional_tests.py +++ b/test/tool_shed/functional_tests.py @@ -15,7 +15,7 @@ new_path = [ os.path.join( cwd, "lib" ) ] new_path.extend( sys.path[1:] ) sys.path = new_path -from galaxy import eggs +from galaxy import eggs, model eggs.require( "nose" ) eggs.require( "NoseHTML" ) @@ -38,7 +38,6 @@ import galaxy.webapps.tool_shed.app from galaxy.webapps.tool_shed.app import UniverseApplication as ToolshedUniverseApplication from galaxy.webapps.tool_shed import buildapp as toolshedbuildapp # This is for the galaxy application. -import galaxy.app from galaxy.app import UniverseApplication as GalaxyUniverseApplication from galaxy.web import buildapp as galaxybuildapp diff --git a/test/tool_shed/test_data/0460_files/repository_dependencies.xml b/test/tool_shed/test_data/0460_files/repository_dependencies.xml new file mode 100644 index 00000000000..f39196b0b3b --- /dev/null +++ b/test/tool_shed/test_data/0460_files/repository_dependencies.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/test/tool_shed/test_data/0460_files/repository_dependencies_in_root.tar b/test/tool_shed/test_data/0460_files/repository_dependencies_in_root.tar new file mode 100644 index 0000000000000000000000000000000000000000..e39a9db1cb8c4457904977fb2ff56625d85ffc13 GIT binary patch literal 2048 zcmeHF%L>9U5bW8nSn>h7sWDz`{ge{T3I&^#Y<%?ZT`Hodp1dfX%aCOlW+$7dkKE>% zhQ?D*!BaT4#LIhkF1`gsw8lUoytSK=v3$N|gjk{mU23y2c8%5oYHM*@0DddLl+T5; zOA^hyD^2M{b~;T6#!c|K918ykc6<40xKiRahDzxrs(roKrPo==Er2IJrnb+cLe&cp zycGik*HNXYJ4EKjyX^AN6E|oqDu}m0sZtlFq!p~hd-Z8v|42`B%(;QNfw_TyYv2Kt C7D>1O literal 0 HcmV?d00001 diff --git a/test/tool_shed/test_data/0460_files/repository_dependencies_in_subfolder.tar b/test/tool_shed/test_data/0460_files/repository_dependencies_in_subfolder.tar new file mode 100644 index 0000000000000000000000000000000000000000..eb82fe8e928d78512d7e7b796025a433c3c2356a GIT binary patch literal 2560 zcmeHFOOC=Y4DFgzSUCW7(hnP2j#3rkN|k6+#Vt&4KcOI6#4L~)q>(1x*l}JQ`#tlp zugp=C4TFyvB5sldf{RbD9#uS@U1~CrO2$c?Mv;sFWhB)mfaGHxEea3Zm)5nW%M&fq zPoaZV)l+s(+~&9p=?vzJza8;ie@mU=x;J(#G`l~s + + + + + \ No newline at end of file diff --git a/test/tool_shed/test_data/0460_files/tool_dependencies_in_root.tar b/test/tool_shed/test_data/0460_files/tool_dependencies_in_root.tar new file mode 100644 index 0000000000000000000000000000000000000000..8dc097bf94087990f98094f4286c9d8018284d7d GIT binary patch literal 2048 zcmeH_-3r1m425^?Q{Q2#higqh0+Ow@5*?8+bMQ7 zkhI#K@jWqA3EOTz0R1K~EC-bTB+3=NH#=j9jdv9nDe%>DR(4O{;^9VAz$mjYAT@C2FAhV+PZ5LQEE{KKBTizn US2cg)?Id$@U~*t`;Fle^18E>r*8l(j literal 0 HcmV?d00001 diff --git a/test/tool_shed/test_data/0460_files/tool_dependencies_in_subfolder.tar b/test/tool_shed/test_data/0460_files/tool_dependencies_in_subfolder.tar new file mode 100644 index 0000000000000000000000000000000000000000..515f4d993cb50c866be978aacf72e0aa1771bb3c GIT binary patch literal 2560 zcmeHF+m3@E5cRoVLHGa#1)3(>ehQ&=+?cguKzsT7ovK;e?Iymo$tLT(0K*yP%sKdO zU)U-K7X@z%#@Jd)65LibtSlD&8B=`_9&;guN@Ar5;|WhxK;+vP-96m-);Oe=n-kon zpSB1qil^+980WYRi9x1|zrFL{{}KD->Omykz2lobF8Ra3PyX`7{{0!IaieRLTIeeNAAhkI{M)zk!r%D#e za6<=R4|dR4U$)j=NBmf=u}(8B^+q$H7$x>xgUe{=!Es8Wu~#(0kxcZH&5WhX^`gL{ Lz@osSz`O!qg}82f literal 0 HcmV?d00001 diff --git a/test/tool_shed/test_data/filtering/README b/test/tool_shed/test_data/filtering/README index 9ad35df358c..c030be8ce43 100644 --- a/test/tool_shed/test_data/filtering/README +++ b/test/tool_shed/test_data/filtering/README @@ -1,2 +1 @@ -These characters should not result in a unicode decoding error: ®Ãâ�âèà -.... \ No newline at end of file +This is a third readme file. \ No newline at end of file From 4f93f0a04d0d19d4afa116f3bf33d75bd0b624a2 Mon Sep 17 00:00:00 2001 From: Daniel Blankenberg Date: Fri, 17 May 2013 17:10:50 -0400 Subject: [PATCH 456/523] Add get_file_size and shrink_stream_by_size to galaxy.util --- lib/galaxy/util/__init__.py | 61 ++++++++++++++++++++++++++++++++++++- 1 file changed, 60 insertions(+), 1 deletion(-) diff --git a/lib/galaxy/util/__init__.py b/lib/galaxy/util/__init__.py index 54feaa4816c..14a07701800 100644 --- a/lib/galaxy/util/__init__.py +++ b/lib/galaxy/util/__init__.py @@ -24,6 +24,11 @@ import wchartype log = logging.getLogger(__name__) _lock = threading.RLock() +CHUNK_SIZE = 65536 #64k + +DATABASE_MAX_STRING_SIZE = 32768 +DATABASE_MAX_STRING_SIZE_PRETTY = '32K' + gzip_magic = '\037\213' bz2_magic = 'BZh' DEFAULT_ENCODING = 'utf-8' @@ -102,7 +107,7 @@ def file_iter(fname, sep=None): if line and line[0] != '#': yield line.split(sep) -def file_reader(fp, chunk_size=65536): +def file_reader( fp, chunk_size=CHUNK_SIZE ): """This generator yields the open fileobject in chunks (default 64k). Closes the file at the end""" while 1: data = fp.read(chunk_size) @@ -152,6 +157,60 @@ def pretty_print_xml( elem, level=0 ): elem.tail = i + pad return elem +def get_file_size( value, default=None ): + try: + #try built-in + return os.path.getsize( value ) + except: + try: + #try built-in one name attribute + return os.path.getsize( value.name ) + except: + try: + #try tell() of end of object + offset = value.tell() + value.seek( 0, 2 ) + rval = value.tell() + value.seek( offset ) + return rval + except: + #return default value + return default + +def shrink_stream_by_size( value, size, join_by="..", left_larger=True, beginning_on_size_error=False, end_on_size_error=False ): + rval = '' + if get_file_size( value ) > size: + start = value.tell() + len_join_by = len( join_by ) + min_size = len_join_by + 2 + if size < min_size: + if beginning_on_size_error: + rval = value.read( size ) + value.seek( start ) + return rval + elif end_on_size_error: + value.seek( -size, 2 ) + rval = value.read( size ) + value.seek( start ) + return rval + raise ValueError( 'With the provided join_by value (%s), the minimum size value is %i.' % ( join_by, min_size ) ) + left_index = right_index = int( ( size - len_join_by ) / 2 ) + if left_index + right_index + len_join_by < size: + if left_larger: + left_index += 1 + else: + right_index += 1 + rval = value.read( left_index ) + join_by + value.seek( -right_index, 2 ) + rval += value.read( right_index ) + else: + while True: + data = value.read( CHUNK_SIZE ) + if not data: + break + rval += data + return rval + def shrink_string_by_size( value, size, join_by="..", left_larger=True, beginning_on_size_error=False, end_on_size_error=False ): if len( value ) > size: len_join_by = len( join_by ) From edfa0bdbc5d161b4aa2566964d9844be1090d972 Mon Sep 17 00:00:00 2001 From: Daniel Blankenberg Date: Fri, 17 May 2013 17:10:51 -0400 Subject: [PATCH 457/523] Update job runner code to use start and end slices of long stderr and stdout. --- lib/galaxy/jobs/__init__.py | 6 +++--- lib/galaxy/jobs/runners/__init__.py | 5 +++-- lib/galaxy/jobs/runners/local.py | 5 +++-- lib/galaxy/jobs/runners/pbs.py | 5 +++-- 4 files changed, 12 insertions(+), 9 deletions(-) diff --git a/lib/galaxy/jobs/__init__.py b/lib/galaxy/jobs/__init__.py index f48cb2023a5..6e032e20875 100644 --- a/lib/galaxy/jobs/__init__.py +++ b/lib/galaxy/jobs/__init__.py @@ -27,15 +27,15 @@ from galaxy.util.json import from_json_string log = logging.getLogger( __name__ ) +DATABASE_MAX_STRING_SIZE = util.DATABASE_MAX_STRING_SIZE +DATABASE_MAX_STRING_SIZE_PRETTY = util.DATABASE_MAX_STRING_SIZE_PRETTY + # This file, if created in the job's working directory, will be used for # setting advanced metadata properties on the job and its associated outputs. # This interface is currently experimental, is only used by the upload tool, # and should eventually become API'd TOOL_PROVIDED_JOB_METADATA_FILE = 'galaxy.json' -DATABASE_MAX_STRING_SIZE = 32768 -DATABASE_MAX_STRING_SIZE_PRETTY = '32K' - class Sleeper( object ): """ Provides a 'sleep' method that sleeps for a number of seconds *unless* diff --git a/lib/galaxy/jobs/runners/__init__.py b/lib/galaxy/jobs/runners/__init__.py index 3c9fc928098..c76964b93e3 100644 --- a/lib/galaxy/jobs/runners/__init__.py +++ b/lib/galaxy/jobs/runners/__init__.py @@ -12,6 +12,7 @@ from Queue import Queue, Empty import galaxy.jobs from galaxy import model +from galaxy.util import DATABASE_MAX_STRING_SIZE, shrink_stream_by_size log = logging.getLogger( __name__ ) @@ -383,8 +384,8 @@ class AsynchronousJobRunner( BaseJobRunner ): which_try = 0 while which_try < (self.app.config.retry_job_output_collection + 1): try: - stdout = file( job_state.output_file, "r" ).read( 32768 ) - stderr = file( job_state.error_file, "r" ).read( 32768 ) + stdout = shrink_stream_by_size( file( job_state.output_file, "r" ), DATABASE_MAX_STRING_SIZE, join_by="\n..\n", left_larger=True, beginning_on_size_error=True ) + stderr = shrink_stream_by_size( file( job_state.error_file, "r" ), DATABASE_MAX_STRING_SIZE, join_by="\n..\n", left_larger=True, beginning_on_size_error=True ) which_try = (self.app.config.retry_job_output_collection + 1) except Exception, e: if which_try == self.app.config.retry_job_output_collection: diff --git a/lib/galaxy/jobs/runners/local.py b/lib/galaxy/jobs/runners/local.py index b02def96335..71bcb44bd94 100644 --- a/lib/galaxy/jobs/runners/local.py +++ b/lib/galaxy/jobs/runners/local.py @@ -12,6 +12,7 @@ from time import sleep from galaxy import model from galaxy.jobs.runners import BaseJobRunner +from galaxy.util import DATABASE_MAX_STRING_SIZE, shrink_stream_by_size log = logging.getLogger( __name__ ) @@ -78,8 +79,8 @@ class LocalJobRunner( BaseJobRunner ): exit_code = proc.wait() stdout_file.seek( 0 ) stderr_file.seek( 0 ) - stdout = stdout_file.read( 32768 ) - stderr = stderr_file.read( 32768 ) + stdout = shrink_stream_by_size( stdout_file, DATABASE_MAX_STRING_SIZE, join_by="\n..\n", left_larger=True, beginning_on_size_error=True ) + stderr = shrink_stream_by_size( stderr_file, DATABASE_MAX_STRING_SIZE, join_by="\n..\n", left_larger=True, beginning_on_size_error=True ) stdout_file.close() stderr_file.close() log.debug('execution finished: %s' % command_line) diff --git a/lib/galaxy/jobs/runners/pbs.py b/lib/galaxy/jobs/runners/pbs.py index a9685107fc3..2dc474a41f6 100644 --- a/lib/galaxy/jobs/runners/pbs.py +++ b/lib/galaxy/jobs/runners/pbs.py @@ -5,6 +5,7 @@ from Queue import Queue, Empty from galaxy import model from galaxy.datatypes.data import nice_size from galaxy.util.bunch import Bunch +from galaxy.util import DATABASE_MAX_STRING_SIZE, shrink_stream_by_size from galaxy.jobs import JobDestination from galaxy.jobs.runners import AsynchronousJobState, AsynchronousJobRunner @@ -522,8 +523,8 @@ class PBSJobRunner( AsynchronousJobRunner ): ofh = file(ofile, "r") efh = file(efile, "r") ecfh = file(ecfile, "r") - stdout = ofh.read( 32768 ) - stderr = efh.read( 32768 ) + stdout = shrink_stream_by_size( ofh, DATABASE_MAX_STRING_SIZE, join_by="\n..\n", left_larger=True, beginning_on_size_error=True ) + stderr = shrink_stream_by_size( efh, DATABASE_MAX_STRING_SIZE, join_by="\n..\n", left_larger=True, beginning_on_size_error=True ) # This should be an 8-bit exit code, but read ahead anyway: exit_code_str = ecfh.read(32) except: From 97a64db9ab33ef25d5f10f4a980dfc8c057bdf4d Mon Sep 17 00:00:00 2001 From: Jeremy Goecks Date: Sun, 19 May 2013 11:59:50 -0400 Subject: [PATCH 458/523] Add library normalization and dispersion options to Cuffdiff wrapper. --- tools/ngs_rna/cuffdiff_wrapper.py | 6 ++++++ tools/ngs_rna/cuffdiff_wrapper.xml | 14 ++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/tools/ngs_rna/cuffdiff_wrapper.py b/tools/ngs_rna/cuffdiff_wrapper.py index bbcefa3650d..4ba463df1b8 100644 --- a/tools/ngs_rna/cuffdiff_wrapper.py +++ b/tools/ngs_rna/cuffdiff_wrapper.py @@ -62,6 +62,8 @@ def __main__(): parser.add_option( '-c', '--min-alignment-count', dest='min_alignment_count', help='The minimum number of alignments in a locus for needed to conduct significance testing on changes in that locus observed between samples. If no testing is performed, changes in the locus are deemed not signficant, and the locus\' observed changes don\'t contribute to correction for multiple testing. The default is 1,000 fragment alignments (up to 2,000 paired reads).' ) parser.add_option( '--FDR', dest='FDR', help='The allowed false discovery rate. The default is 0.05.' ) parser.add_option( '-u', '--multi-read-correct', dest='multi_read_correct', action="store_true", help='Tells Cufflinks to do an initial estimation procedure to more accurately weight reads mapping to multiple locations in the genome') + parser.add_option( '--library-norm-method', dest='library_norm_method' ) + parser.add_option( '--dispersion-method', dest='dispersion_method' ) # Advanced Options: parser.add_option( '--num-importance-samples', dest='num_importance_samples', help='Sets the number of importance samples generated for each locus during abundance estimation. Default: 1000' ) @@ -143,6 +145,10 @@ def __main__(): cmd = "cuffdiff --no-update-check -q" # Add options. + if options.library_norm_method: + cmd += ( " --library-norm-method %s" % options.library_norm_method ) + if options.dispersion_method: + cmd += ( " --dispersion-method %s" % options.dispersion_method ) if options.inner_dist_std_dev: cmd += ( " -s %i" % int ( options.inner_dist_std_dev ) ) if options.num_threads: diff --git a/tools/ngs_rna/cuffdiff_wrapper.xml b/tools/ngs_rna/cuffdiff_wrapper.xml index e8cce23c66e..2be89aa3928 100644 --- a/tools/ngs_rna/cuffdiff_wrapper.xml +++ b/tools/ngs_rna/cuffdiff_wrapper.xml @@ -9,6 +9,8 @@ --FDR=$fdr --num-threads="4" --min-alignment-count=$min_alignment_count + --library-norm-method=$library_norm_method + --dispersion-method=$dispersion_method --isoforms_fpkm_tracking_output=$isoforms_fpkm_tracking --genes_fpkm_tracking_output=$genes_fpkm_tracking @@ -92,6 +94,18 @@ + + + + + + + + + + + + From aef326d8bdb34724d5a4f061402cadef78669a5d Mon Sep 17 00:00:00 2001 From: John Chilton Date: Mon, 20 May 2013 08:25:03 -0500 Subject: [PATCH 459/523] Implement setting up tool dependency action (setup_virtualenv) to automatically setup python virtual environments. Install requirements from file requirements.txt of downloaded bundle: - or - Install requirements from specified file from downloaded bundle: tools/requirements.txt - or - Manually specify contents of requirements.txt file to create dynamically. pyyaml==3.2.0 lxml==2.3.0 Other small refactorings to support this. Including: - Rework expansion of $INSTALL_DIR in tool_dependency.xml files to use string.Template and allow additional variables. $system_install now expands to the install dir to mimic what what we do with CloudBioLinux Galaxy tool dependency creation. Ultimately, would like to have $tool_version to expand as well, since most downloads differ only based on this version which must be set anyway. - Refactor a more general function, create_or_update_env_shell_file_with_command, out of create_or_update_env_shell_file. Use this more general function to implement Python virtualenv activation in tool dependencies. - Implement shell quoting create_or_update_env_shell_file_with_command to handle special characters in paths. --- .../tool_dependencies/common_util.py | 28 +++++++++++++++---- .../tool_dependencies/fabric_util.py | 25 +++++++++++++++++ .../tool_dependencies/install_util.py | 27 ++++++++++++++++-- 3 files changed, 71 insertions(+), 9 deletions(-) diff --git a/lib/tool_shed/galaxy_install/tool_dependencies/common_util.py b/lib/tool_shed/galaxy_install/tool_dependencies/common_util.py index 0bf454053ad..f26e809859b 100644 --- a/lib/tool_shed/galaxy_install/tool_dependencies/common_util.py +++ b/lib/tool_shed/galaxy_install/tool_dependencies/common_util.py @@ -47,19 +47,28 @@ def create_or_update_env_shell_file( install_dir, env_var_dict ): changed_value = '%s' % env_var_value elif env_var_action == 'append_to': changed_value = '$%s:%s' % ( env_var_name, env_var_value ) + line = "%s=%s; export %s" % (env_var_name, changed_value, env_var_name) + return create_or_update_env_shell_file_with_command(install_dir, line) + + +def create_or_update_env_shell_file_with_command( install_dir, command ): + """ + Return a shell expression which when executed will create or update + a Galaxy env.sh dependency file in the specified install_dir containing + the supplied command. + """ env_shell_file_path = '%s/env.sh' % install_dir if os.path.exists( env_shell_file_path ): write_action = '>>' else: write_action = '>' - cmd = "echo '%s=%s; export %s' %s %s;chmod +x %s" % ( env_var_name, - changed_value, - env_var_name, - write_action, - env_shell_file_path, - env_shell_file_path ) + cmd = "echo %s %s %s;chmod +x %s" % ( __shellquote(command), + write_action, + __shellquote(env_shell_file_path), + __shellquote(env_shell_file_path)) return cmd + def extract_tar( file_name, file_path ): if isgzip( file_name ) or isbz2( file_name ): # Open for reading with transparent compression. @@ -233,3 +242,10 @@ def zipfile_ok( path_to_archive ): if not member_path.startswith( basename ): return False return True + + +def __shellquote(s): + """ + Quote and escape the supplied string for use in shell expressions. + """ + return "'" + s.replace("'", "'\\''") + "'" diff --git a/lib/tool_shed/galaxy_install/tool_dependencies/fabric_util.py b/lib/tool_shed/galaxy_install/tool_dependencies/fabric_util.py index 5c9c4600bac..824d60171b5 100644 --- a/lib/tool_shed/galaxy_install/tool_dependencies/fabric_util.py +++ b/lib/tool_shed/galaxy_install/tool_dependencies/fabric_util.py @@ -140,6 +140,31 @@ def install_and_build_package( app, tool_dependency, actions_dict ): # the environment setting in each of which will be injected into the environment for all # tags that follow this tag set in the tool_dependencies.xml file. env_shell_file_paths = action_dict[ 'env_shell_file_paths' ] + elif action_type == 'setup_virtualenv': + requirements = action_dict[ 'requirements' ] + if os.path.exists( os.path.join( dir, requirements ) ): + # requirements specified as path to a file + requirements_path = requirements + else: + # requirements specified directly in XML, create a file with these for pip. + requirements_path = os.path.join( install_dir, "requirements.txt" ) + with open( requirements_path, "w" ) as f: + f.write( requirements ) + venv_directory = os.path.join( install_dir, "venv" ) + # TODO: Consider making --no-site-packages optional. + setup_command = "virtualenv --no-site-packages '%s'" % venv_directory + # POSIXLY_CORRECT forces shell commands . and source to have the same + # and well defined behavior in bash/zsh. + activate_command = "POSIXLY_CORRECT=1; . %s" % os.path.join( venv_directory, "bin", "activate" ) + install_command = "pip install -r '%s'" % requirements_path + full_setup_command = "%s; %s; %s" % ( setup_command, activate_command, install_command ) + return_code = handle_command( app, tool_dependency, install_dir, full_setup_command ) + if return_code: + return + modify_env_command = common_util.create_or_update_env_shell_file_with_command( install_dir, activate_command ) + return_code = handle_command( app, tool_dependency, install_dir, modify_env_command ) + if return_code: + return elif action_type == 'shell_command': with settings( warn_only=True ): cmd = '' diff --git a/lib/tool_shed/galaxy_install/tool_dependencies/install_util.py b/lib/tool_shed/galaxy_install/tool_dependencies/install_util.py index 10e32e2130f..fb755c46202 100644 --- a/lib/tool_shed/galaxy_install/tool_dependencies/install_util.py +++ b/lib/tool_shed/galaxy_install/tool_dependencies/install_util.py @@ -3,6 +3,7 @@ import os import sys import subprocess import tempfile +from string import Template import common_util import fabric_util import tool_shed.util.shed_util_common as suc @@ -317,6 +318,17 @@ def install_package( app, elem, tool_shed_repository, tool_dependencies=None ): def install_via_fabric( app, tool_dependency, actions_elem, install_dir, package_name=None, proprietary_fabfile_path=None, **kwd ): """Parse a tool_dependency.xml file's tag set to gather information for the installation via fabric.""" sa_session = app.model.context.current + + def evaluate_template( text ): + """ Substitute variables defined in XML blocks obtained loaded from + dependencies file. """ + # TODO: Add tool_version substitution for compat with CloudBioLinux. + substitutions = { + "INSTALL_DIR": install_dir, + "system_install": install_dir, # Added for compat with CloudBioLinux + } + return Template( text ).safe_substitute( substitutions ) + if not os.path.exists( install_dir ): os.makedirs( install_dir ) actions_dict = dict( install_dir=install_dir ) @@ -328,7 +340,7 @@ def install_via_fabric( app, tool_dependency, actions_elem, install_dir, package action_type = action_elem.get( 'type', 'shell_command' ) if action_type == 'shell_command': # make - action_elem_text = action_elem.text.replace( '$INSTALL_DIR', install_dir ) + action_elem_text = evaluate_template( action_elem.text ) if action_elem_text: action_dict[ 'command' ] = action_elem_text else: @@ -351,7 +363,7 @@ def install_via_fabric( app, tool_dependency, actions_elem, install_dir, package elif action_type == 'make_directory': # $INSTALL_DIR/lib/python if action_elem.text: - action_dict[ 'full_path' ] = action_elem.text.replace( '$INSTALL_DIR', install_dir ) + action_dict[ 'full_path' ] = evaluate_template( action_elem.text ) else: continue elif action_type in [ 'move_directory_files', 'move_file' ]: @@ -364,7 +376,7 @@ def install_via_fabric( app, tool_dependency, actions_elem, install_dir, package # $INSTALL_DIR/bin # for move_elem in action_elem: - move_elem_text = move_elem.text.replace( '$INSTALL_DIR', install_dir ) + move_elem_text = evaluate_template( move_elem.text ) if move_elem_text: action_dict[ move_elem.tag ] = move_elem_text elif action_type == 'set_environment': @@ -402,6 +414,15 @@ def install_via_fabric( app, tool_dependency, actions_elem, install_dir, package action_dict[ 'env_shell_file_paths' ] = all_env_shell_file_paths else: continue + elif action_type == 'setup_virtualenv': + # + ## Install requirements from file requirements.txt of downloaded bundle - or - + # tools/requirements.txt + ## Install requirements from specified file from downloaded bundle -or - + # pyyaml==3.2.0 + # lxml==2.3.0 + ## Manually specify contents of requirements.txt file to create dynamically. + action_dict[ 'requirements' ] = evaluate_template( action_elem.text or 'requirements.txt' ) else: log.debug( "Unsupported action type '%s'. Not proceeding." % str( action_type ) ) raise Exception( "Unsupported action type '%s' in tool dependency definition." % str( action_type ) ) From b67157f3544fb44f5550bf789a29b57ddcfb987c Mon Sep 17 00:00:00 2001 From: Dave Bouvier Date: Mon, 20 May 2013 12:04:05 -0400 Subject: [PATCH 460/523] Remove backward compatibility code, fix error with incorrect tool test status being reported to the tool shed. --- .../functional_tests.py | 47 +++++++------------ 1 file changed, 17 insertions(+), 30 deletions(-) diff --git a/test/install_and_test_tool_shed_repositories/functional_tests.py b/test/install_and_test_tool_shed_repositories/functional_tests.py index 02acf635876..913a7c7b0cc 100644 --- a/test/install_and_test_tool_shed_repositories/functional_tests.py +++ b/test/install_and_test_tool_shed_repositories/functional_tests.py @@ -280,20 +280,18 @@ def get_tool_test_results_from_api( tool_shed_url, metadata_revision_id ): api_url = get_api_url( base=tool_shed_url, parts=api_path ) repository_metadata = json_from_url( api_url ) tool_test_results = repository_metadata.get( 'tool_test_results', {} ) - # Compatibility code, remove before next release. - if not tool_test_results: - tool_test_results = repository_metadata.get( 'tool_test_errors', {} ) + # If, for some reason, the script that checks for functional tests has not run, tool_test_results will be None. + if tool_test_results is None: + return dict() return tool_test_results def is_latest_downloadable_revision( url, repository_info_dict ): api_url_parts = [ 'api', 'repositories', 'get_ordered_installable_revisions' ] params = urllib.urlencode( dict( name=repository_info_dict[ 'name' ], owner=repository_info_dict[ 'owner' ] ) ) api_url = get_api_url( url, api_url_parts, params ) - # TODO: Remove this try/catch wrapping when the main tool shed is updated. - try: - changeset_revisions = json_from_url( api_url ) - except Exception, e: - return True + changeset_revisions = json_from_url( api_url ) + # The get_ordered_installable_revisions returns a list of changeset hashes, with the last hash in the list + # being the most recent installable revision. latest_revision = changeset_revisions.pop() return str( repository_info_dict[ 'changeset_revision' ] ) == str( latest_revision ) @@ -312,17 +310,6 @@ def register_test_result( url, metadata_id, test_results_dict, repository_info_d Update the repository metadata tool_test_results and appropriate flags using the API. ''' params[ 'tool_test_results' ] = test_results_dict - # BEGIN compatibility code. - # TODO: The repository_revisions API controller ignores any received parameter that is not present in the database schema, - # but the compatibility code here should be removed when the main tool shed is updated with the new database migration version. - params[ 'tool_test_errors' ] = test_results_dict - if test_results_dict[ 'failed_tests' ]: - params[ 'tool_test_errors' ][ 'test_errors' ] = test_results_dict[ 'failed_tests' ] - if test_results_dict[ 'passed_tests' ]: - params[ 'tool_test_errors' ][ 'tests_passed' ] = test_results_dict[ 'passed_tests' ] - # Copying the missing_test_components key is not necessary, since the main tool shed is running its own version of the - # check repositories script, which correctly updates the invalid_tests key. - # END compatibility code. if '-info_only' in sys.argv: return {} else: @@ -760,8 +747,8 @@ def main(): repository_status[ 'missing_test_components' ].append( missing_components ) # Record the status of this repository in the tool shed. set_do_not_test = not is_latest_downloadable_revision( galaxy_tool_shed_url, repository_info_dict ) - params[ 'tools_functionally_correct' ] = 'false' - params[ 'missing_test_components' ] = 'true' + params[ 'tools_functionally_correct' ] = False + params[ 'missing_test_components' ] = True params[ 'do_not_test' ] = str( set_do_not_test ) register_test_result( galaxy_tool_shed_url, metadata_revision_id, @@ -795,9 +782,9 @@ def main(): error_message=dependency.error_message ) repository_status[ 'installation_errors' ][ 'repository_dependencies' ].append( test_result ) # Record the status of this repository in the tool shed. - params[ 'tools_functionally_correct' ] = 'false' - params[ 'do_not_test' ] = 'false' - params[ 'test_install_error' ] = 'true' + params[ 'tools_functionally_correct' ] = False + params[ 'do_not_test' ] = False + params[ 'test_install_error' ] = True register_test_result( galaxy_tool_shed_url, metadata_revision_id, repository_status, @@ -848,8 +835,8 @@ def main(): # controller with the status of the test. This also sets the do_not_test and tools_functionally correct flags, and # updates the time_last_tested field to today's date. repositories_passed.append( dict( name=name, owner=owner, changeset_revision=changeset_revision ) ) - params[ 'tools_functionally_correct' ] = 'true' - params[ 'do_not_test' ] = 'false' + params[ 'tools_functionally_correct' ] = True + params[ 'do_not_test' ] = False register_test_result( galaxy_tool_shed_url, metadata_revision_id, repository_status, @@ -900,7 +887,7 @@ def main(): # field to today's date. repositories_failed.append( dict( name=name, owner=owner, changeset_revision=changeset_revision ) ) set_do_not_test = not is_latest_downloadable_revision( galaxy_tool_shed_url, repository_info_dict ) - params[ 'tools_functionally_correct' ] = 'false' + params[ 'tools_functionally_correct' ] = False params[ 'do_not_test' ] = str( set_do_not_test ) register_test_result( galaxy_tool_shed_url, metadata_revision_id, @@ -933,9 +920,9 @@ def main(): changeset_revision=repository.changeset_revision, error_message=repository.error_message ) repository_status[ 'installation_errors' ][ 'repository_dependencies' ].append( test_result ) - params[ 'tools_functionally_correct' ] = 'false' - params[ 'test_install_error' ] = 'true' - params[ 'do_not_test' ] = 'false' + params[ 'tools_functionally_correct' ] = False + params[ 'test_install_error' ] = True + params[ 'do_not_test' ] = False register_test_result( galaxy_tool_shed_url, metadata_revision_id, repository_status, From 508f4a8d2d1a16d588095d1ac8eb2d51aceaacdf Mon Sep 17 00:00:00 2001 From: Nate Coraor Date: Mon, 20 May 2013 13:16:04 -0400 Subject: [PATCH 461/523] Do not abort on failures in dependency installation. --- lib/tool_shed/galaxy_install/tool_dependencies/fabric_util.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/tool_shed/galaxy_install/tool_dependencies/fabric_util.py b/lib/tool_shed/galaxy_install/tool_dependencies/fabric_util.py index 824d60171b5..5437ed870e8 100644 --- a/lib/tool_shed/galaxy_install/tool_dependencies/fabric_util.py +++ b/lib/tool_shed/galaxy_install/tool_dependencies/fabric_util.py @@ -30,7 +30,8 @@ def check_fabric_version(): def handle_command( app, tool_dependency, install_dir, cmd ): sa_session = app.model.context.current - output = local( cmd, capture=True ) + with settings( warn_only=True ): + output = local( cmd, capture=True ) log_results( cmd, output, os.path.join( install_dir, INSTALLATION_LOG ) ) if output.return_code: tool_dependency.status = app.model.ToolDependency.installation_status.ERROR From 9768337e19784431f1b1ffc5b35ce243a7956067 Mon Sep 17 00:00:00 2001 From: Jeremy Goecks Date: Mon, 20 May 2013 13:41:58 -0400 Subject: [PATCH 462/523] Trackster: add base functions for manipulating modes. --- static/scripts/viz/trackster/tracks.js | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/static/scripts/viz/trackster/tracks.js b/static/scripts/viz/trackster/tracks.js index 12feb213883..902f92d09da 100644 --- a/static/scripts/viz/trackster/tracks.js +++ b/static/scripts/viz/trackster/tracks.js @@ -3070,6 +3070,20 @@ extend(TiledTrack.prototype, Drawable.prototype, Track.prototype, { } }, + /** + * Returns appropriate display mode based on data. + */ + get_mode: function(data) { + return this.mode; + }, + + /** + * Update track interface to show display mode being used. + */ + update_auto_mode: function( display_mode ) { + // FIXME: needs to be implemented. + }, + /** * Returns a list of drawables to draw. Defaults to current track. */ @@ -3925,6 +3939,9 @@ extend(FeatureTrack.prototype, Drawable.prototype, TiledTrack.prototype, { } }, + /** + * Update track interface to show display mode being used. + */ update_auto_mode: function( mode ) { var mode; if ( this.mode === "Auto" ) { From 8ce1e977fa0376ae8bec012c4ac26f4996c48ec2 Mon Sep 17 00:00:00 2001 From: Jeremy Goecks Date: Mon, 20 May 2013 13:46:46 -0400 Subject: [PATCH 463/523] Do not abbreviate dataaset name for tool data inputs. --- lib/galaxy/tools/parameters/basic.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/lib/galaxy/tools/parameters/basic.py b/lib/galaxy/tools/parameters/basic.py index a093bb0e879..35ace7dee92 100644 --- a/lib/galaxy/tools/parameters/basic.py +++ b/lib/galaxy/tools/parameters/basic.py @@ -1451,10 +1451,7 @@ class DataToolParameter( ToolParameter ): def dataset_collector( hdas, parent_hid ): current_user_roles = trans.get_current_user_roles() for i, hda in enumerate( hdas ): - if len( hda.name ) > 30: - hda_name = '%s..%s' % ( hda.name[:17], hda.name[-11:] ) - else: - hda_name = hda.name + hda_name = hda.name if parent_hid is not None: hid = "%s.%d" % ( parent_hid, i + 1 ) else: From 834cbd658265a528e0fd2e74f83a9b7800a702cd Mon Sep 17 00:00:00 2001 From: Jeremy Goecks Date: Mon, 20 May 2013 13:48:08 -0400 Subject: [PATCH 464/523] Pack script. --- static/scripts/packed/viz/trackster/tracks.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/static/scripts/packed/viz/trackster/tracks.js b/static/scripts/packed/viz/trackster/tracks.js index 468da781e8d..930cf23ce3f 100644 --- a/static/scripts/packed/viz/trackster/tracks.js +++ b/static/scripts/packed/viz/trackster/tracks.js @@ -1 +1 @@ -define(["libs/underscore","viz/visualization","viz/trackster/util","viz/trackster/slotting","viz/trackster/painters","mvc/data","viz/trackster/filters"],function(ab,w,k,t,L,X,i){var p=ab.extend;var m={};var j=function(ac,ad){m[ac.attr("id")]=ad};var l=function(ac,ae,ag,af){ag=".group";var ad={};m[ac.attr("id")]=af;ac.bind("drag",{handle:"."+ae,relative:true},function(ao,ap){var an=$(this),at=$(this).parent(),ak=at.children(),am=m[$(this).attr("id")],aj,ai,aq,ah,al;ai=$(this).parents(ag);if(ai.length!==0){aq=ai.position().top;ah=aq+ai.outerHeight();if(ap.offsetYah){$(this).insertAfter(ai);var ar=m[ai.attr("id")];ar.remove_drawable(am);ar.container.add_drawable(am);return}}}ai=null;for(al=0;al=aq&&ap.offsetY<=ah){if(ap.offsetY-aq0?al-1:al))}}}).bind("dragstart",function(){ad["border-top"]=ac.css("border-top");ad["border-bottom"]=ac.css("border-bottom");$(this).css({"border-top":"1px solid blue","border-bottom":"1px solid blue"})}).bind("dragend",function(){$(this).css(ad)})};var aa=16,G=9,D=20,z=100,I=12000,S=400,K=5000,v=100,n="Cannot display dataset due to an error. ",J="A converter for this dataset is not installed. Please check your datatypes_conf.xml file.",E="No data for this chrom/contig.",u="Preparing data. This can take a while for a large dataset. If the visualization is saved and closed, preparation will continue in the background.",x="Tool cannot be rerun: ",a="Loading data...",U="Ready for display",R=10,H=20,A=["Histogram","Line","Filled","Intensity"];function V(ad,ac){if(!ac){ac=0}var ae=Math.pow(10,ac);return Math.round(ad*ae)/ae}var q=function(ad,ac,af){if(!q.id_counter){q.id_counter=0}this.id=q.id_counter++;this.name=af.name;this.view=ad;this.container=ac;this.config=new F({track:this,params:[{key:"name",label:"Name",type:"text",default_value:this.name}],saved_values:af.prefs,onchange:function(){this.track.set_name(this.track.config.values.name)}});this.prefs=this.config.values;this.drag_handle_class=af.drag_handle_class;this.is_overview=false;this.action_icons={};this.content_visible=true;this.container_div=this.build_container_div();this.header_div=this.build_header_div();if(this.header_div){this.container_div.append(this.header_div);this.icons_div=$("
    ").css("float","left").hide().appendTo(this.header_div);this.build_action_icons(this.action_icons_def);this.header_div.append($("
    "));this.header_div.dblclick(function(ag){ag.stopPropagation()});var ae=this;this.container_div.hover(function(){ae.icons_div.show()},function(){ae.icons_div.hide()});$("
    ").appendTo(this.container_div)}};q.prototype.action_icons_def=[{name:"toggle_icon",title:"Hide/show content",css_class:"toggle",on_click_fn:function(ac){if(ac.content_visible){ac.action_icons.toggle_icon.addClass("toggle-expand").removeClass("toggle");ac.hide_contents();ac.content_visible=false}else{ac.action_icons.toggle_icon.addClass("toggle").removeClass("toggle-expand");ac.content_visible=true;ac.show_contents()}}},{name:"settings_icon",title:"Edit settings",css_class:"settings-icon",on_click_fn:function(ad){var af=function(){hide_modal();$(window).unbind("keypress.check_enter_esc")},ac=function(){ad.config.update_from_form($(".dialog-box"));hide_modal();$(window).unbind("keypress.check_enter_esc")},ae=function(ag){if((ag.keyCode||ag.which)===27){af()}else{if((ag.keyCode||ag.which)===13){ac()}}};$(window).bind("keypress.check_enter_esc",ae);show_modal("Configure",ad.config.build_form(),{Cancel:af,OK:ac})}},{name:"remove_icon",title:"Remove",css_class:"remove-icon",on_click_fn:function(ac){$(".bs-tooltip").remove();ac.remove()}}];p(q.prototype,{init:function(){},changed:function(){this.view.changed()},can_draw:function(){if(this.enabled&&this.content_visible){return true}return false},request_draw:function(){},_draw:function(){},to_dict:function(){},set_name:function(ac){this.old_name=this.name;this.name=ac;this.name_div.text(this.name)},revert_name:function(){if(this.old_name){this.name=this.old_name;this.name_div.text(this.name)}},remove:function(){this.changed();this.container.remove_drawable(this);var ac=this.view;this.container_div.hide(0,function(){$(this).remove();ac.update_intro_div()})},build_container_div:function(){},build_header_div:function(){},add_action_icon:function(ad,ai,ah,ag,ac,af){var ae=this;this.action_icons[ad]=$("").attr("href","javascript:void(0);").attr("title",ai).addClass("icon-button").addClass(ah).tooltip().click(function(){ag(ae)}).appendTo(this.icons_div);if(af){this.action_icons[ad].hide()}},build_action_icons:function(ac){var ae;for(var ad=0;ad").addClass("content-div").attr("id","group_"+this.id+"_content_div").appendTo(this.container_div);j(this.container_div,this);j(this.content_div,this);l(this.container_div,this.drag_handle_class,".group",this);this.filters_manager=new i.FiltersManager(this);this.header_div.after(this.filters_manager.parent_div);this.saved_filters_managers=[];if("drawables" in af){this.unpack_drawables(af.drawables)}if("filters" in af){var ae=this.filters_manager;this.filters_manager=new i.FiltersManager(this,af.filters);ae.parent_div.replaceWith(this.filters_manager.parent_div);if(af.filters.visible){this.setup_multitrack_filtering()}}};p(Q.prototype,q.prototype,y.prototype,{action_icons_def:[q.prototype.action_icons_def[0],q.prototype.action_icons_def[1],{name:"composite_icon",title:"Show composite track",css_class:"layers-stack",on_click_fn:function(ac){$(".bs-tooltip").remove();ac.show_composite_track()}},{name:"filters_icon",title:"Filters",css_class:"filters-icon",on_click_fn:function(ac){if(ac.filters_manager.visible()){ac.filters_manager.clear_filters();ac._restore_filter_managers()}else{ac.setup_multitrack_filtering();ac.request_draw(true)}ac.filters_manager.toggle()}},q.prototype.action_icons_def[2]],build_container_div:function(){var ac=$("
    ").addClass("group").attr("id","group_"+this.id);if(this.container){this.container.content_div.append(ac)}return ac},build_header_div:function(){var ac=$("
    ").addClass("track-header");ac.append($("
    ").addClass(this.drag_handle_class));this.name_div=$("
    ").addClass("track-name").text(this.name).appendTo(ac);return ac},hide_contents:function(){this.tiles_div.hide()},show_contents:function(){this.tiles_div.show();this.request_draw()},update_icons:function(){var ae=this.drawables.length;if(ae===0){this.action_icons.composite_icon.hide();this.action_icons.filters_icon.hide()}else{if(ae===1){if(this.drawables[0] instanceof f){this.action_icons.composite_icon.show()}this.action_icons.filters_icon.hide()}else{var al,ak,ai,ao=true,ag=this.drawables[0].get_type(),ac=0;for(al=0;al1&&ac===this.drawables.length){var ap={},ad;ai=this.drawables[0];for(ak=0;ak0){this.action_icons.filters_icon.show()}else{this.action_icons.filters_icon.hide()}}else{this.action_icons.filters_icon.hide()}}}},_restore_filter_managers:function(){for(var ac=0;ac0){this.saved_filters_managers=[];for(var ac=0;ac").addClass("top-container").appendTo(ae);this.browser_content_div=$("
    ").addClass("content").css("position","relative").appendTo(ae);this.bottom_container=$("
    ").addClass("bottom-container").appendTo(ae);this.top_labeltrack=$("
    ").addClass("top-labeltrack").appendTo(this.top_container);this.viewport_container=$("
    ").addClass("viewport-container").attr("id","viewport-container").appendTo(this.browser_content_div);this.content_div=this.viewport_container;j(this.viewport_container,ac);this.intro_div=$("
    ").addClass("intro").appendTo(this.viewport_container).hide();var af=$("
    ").text("Add Datasets to Visualization").addClass("action-button").appendTo(this.intro_div).click(function(){w.select_datasets(select_datasets_url,add_track_async_url,{"f-dbkey":ac.dbkey},function(ag){ab.each(ag,function(ah){ac.add_drawable(o(ah,ac,ac))})})});this.nav_labeltrack=$("
    ").addClass("nav-labeltrack").appendTo(this.bottom_container);this.nav_container=$("
    ").addClass("trackster-nav-container").prependTo(this.top_container);this.nav=$("
    ").addClass("trackster-nav").appendTo(this.nav_container);this.overview=$("
    ").addClass("overview").appendTo(this.bottom_container);this.overview_viewport=$("
    ").addClass("overview-viewport").appendTo(this.overview);this.overview_close=$("").attr("href","javascript:void(0);").attr("title","Close overview").addClass("icon-button overview-close tooltip").hide().appendTo(this.overview_viewport);this.overview_highlight=$("
    ").addClass("overview-highlight").hide().appendTo(this.overview_viewport);this.overview_box_background=$("
    ").addClass("overview-boxback").appendTo(this.overview_viewport);this.overview_box=$("
    ").addClass("overview-box").appendTo(this.overview_viewport);this.default_overview_height=this.overview_box.height();this.nav_controls=$("
    ").addClass("nav-controls").appendTo(this.nav);this.chrom_select=$("").addClass("nav-input").hide().bind("keyup focusout",ad).appendTo(this.nav_controls);this.location_span=$("").addClass("location").attr("original-title","Click to change location").tooltip({placement:"bottom"}).appendTo(this.nav_controls);this.location_span.click(function(){ac.location_span.hide();ac.chrom_select.hide();ac.nav_input.val(ac.chrom+":"+ac.low+"-"+ac.high);ac.nav_input.css("display","inline-block");ac.nav_input.select();ac.nav_input.focus();ac.nav_input.autocomplete({source:function(ai,ag){var aj=[],ah=$.map(ac.get_tracks(c),function(ak){return ak.data_manager.search_features(ai.term).success(function(al){aj=aj.concat(al)})});$.when.apply($,ah).done(function(){ag($.map(aj,function(ak){return{label:ak[0],value:ak[1]}}))})}})});if(this.vis_id!==undefined){this.hidden_input=$("").attr("type","hidden").val(this.vis_id).appendTo(this.nav_controls)}this.zo_link=$("").attr("id","zoom-out").attr("title","Zoom out").tooltip({placement:"bottom"}).click(function(){ac.zoom_out();ac.request_redraw()}).appendTo(this.nav_controls);this.zi_link=$("").attr("id","zoom-in").attr("title","Zoom in").tooltip({placement:"bottom"}).click(function(){ac.zoom_in();ac.request_redraw()}).appendTo(this.nav_controls);this.load_chroms_deferred=this.load_chroms({low:0});this.chrom_select.bind("change",function(){ac.change_chrom(ac.chrom_select.val())});this.browser_content_div.click(function(ag){$(this).find("input").trigger("blur")});this.browser_content_div.bind("dblclick",function(ag){ac.zoom_in(ag.pageX,this.viewport_container)});this.overview_box.bind("dragstart",function(ag,ah){this.current_x=ah.offsetX}).bind("drag",function(ag,ai){var aj=ai.offsetX-this.current_x;this.current_x=ai.offsetX;var ah=Math.round(aj/ac.viewport_container.width()*(ac.max_high-ac.max_low));ac.move_delta(-ah)});this.overview_close.click(function(){ac.reset_overview()});this.viewport_container.bind("draginit",function(ag,ah){if(ag.clientX>ac.viewport_container.width()-16){return false}}).bind("dragstart",function(ag,ah){ah.original_low=ac.low;ah.current_height=ag.clientY;ah.current_x=ah.offsetX}).bind("drag",function(ai,ak){var ag=$(this);var al=ak.offsetX-ak.current_x;var ah=ag.scrollTop()-(ai.clientY-ak.current_height);ag.scrollTop(ah);ak.current_height=ai.clientY;ak.current_x=ak.offsetX;var aj=Math.round(al/ac.viewport_container.width()*(ac.high-ac.low));ac.move_delta(aj)}).bind("mousewheel",function(ai,ak,ah,ag){if(ah){ah*=50;var aj=Math.round(-ah/ac.viewport_container.width()*(ac.high-ac.low));ac.move_delta(aj)}});this.top_labeltrack.bind("dragstart",function(ag,ah){return $("
    ").css({height:ac.browser_content_div.height()+ac.top_labeltrack.height()+ac.nav_labeltrack.height()+1,top:"0px",position:"absolute","background-color":"#ccf",opacity:0.5,"z-index":1000}).appendTo($(this))}).bind("drag",function(ak,al){$(al.proxy).css({left:Math.min(ak.pageX,al.startX)-ac.container.offset().left,width:Math.abs(ak.pageX-al.startX)});var ah=Math.min(ak.pageX,al.startX)-ac.container.offset().left,ag=Math.max(ak.pageX,al.startX)-ac.container.offset().left,aj=(ac.high-ac.low),ai=ac.viewport_container.width();ac.update_location(Math.round(ah/ai*aj)+ac.low,Math.round(ag/ai*aj)+ac.low)}).bind("dragend",function(al,am){var ah=Math.min(al.pageX,am.startX),ag=Math.max(al.pageX,am.startX),aj=(ac.high-ac.low),ai=ac.viewport_container.width(),ak=ac.low;ac.low=Math.round(ah/ai*aj)+ak;ac.high=Math.round(ag/ai*aj)+ak;$(am.proxy).remove();ac.request_redraw()});this.add_label_track(new W(this,{content_div:this.top_labeltrack}));this.add_label_track(new W(this,{content_div:this.nav_labeltrack}));$(window).bind("resize",function(){if(this.resize_timer){clearTimeout(this.resize_timer)}this.resize_timer=setTimeout(function(){ac.resize_window()},500)});$(document).bind("redraw",function(){ac.redraw()});this.reset();$(window).trigger("resize")},get_base_color:function(ac){return this.config.values[ac.toLowerCase()+"_color"]||this.config.values.n_color}});p(Y.prototype,y.prototype,{changed:function(){this.has_changes=true},update_intro_div:function(){if(this.drawables.length===0){this.intro_div.show()}else{this.intro_div.hide()}},trigger_navigate:function(ad,af,ac,ag){if(this.timer){clearTimeout(this.timer)}if(ag){var ae=this;this.timer=setTimeout(function(){ae.trigger("navigate",ad+":"+af+"-"+ac)},500)}else{view.trigger("navigate",ad+":"+af+"-"+ac)}},update_location:function(ac,ae){this.location_span.text(commatize(ac)+" - "+commatize(ae));this.nav_input.val(this.chrom+":"+commatize(ac)+"-"+commatize(ae));var ad=view.chrom_select.val();if(ad!==""){this.trigger_navigate(ad,view.low,view.high,true)}},load_chroms:function(ae){ae.num=v;var ac=this,ad=$.Deferred();$.ajax({url:chrom_url+"/"+this.dbkey,data:ae,dataType:"json",success:function(ag){if(ag.chrom_info.length===0){return}if(ag.reference){ac.add_label_track(new B(ac))}ac.chrom_data=ag.chrom_info;var aj='';for(var ai=0,af=ac.chrom_data.length;ai'+ah+""}if(ag.prev_chroms){aj+='"}if(ag.next_chroms){aj+='"}ac.chrom_select.html(aj);ac.chrom_start_index=ag.start_index;ad.resolve(ag.chrom_info)},error:function(){alert("Could not load chroms for this dbkey:",ac.dbkey)}});return ad},change_chrom:function(ah,ad,aj){var ae=this;if(!ae.chrom_data){ae.load_chroms_deferred.then(function(){ae.change_chrom(ah,ad,aj)});return}if(!ah||ah==="None"){return}if(ah==="previous"){ae.load_chroms({low:this.chrom_start_index-v});return}if(ah==="next"){ae.load_chroms({low:this.chrom_start_index+v});return}var ai=$.grep(ae.chrom_data,function(ak,al){return ak.chrom===ah})[0];if(ai===undefined){ae.load_chroms({chrom:ah},function(){ae.change_chrom(ah,ad,aj)});return}else{if(ah!==ae.chrom){ae.chrom=ah;ae.chrom_select.val(ae.chrom);ae.max_high=ai.len-1;ae.reset();for(var ag=0,ac=ae.drawables.length;agac.max_high){ac.high=ac.max_high;ac.low=ac.max_high-ae}else{ac.high-=af;ac.low-=af}}ac.request_redraw();var ad=ac.chrom_select.val();this.trigger_navigate(ad,ac.low,ac.high,true)},add_drawable:function(ac){y.prototype.add_drawable.call(this,ac);ac.init();this.changed();this.update_intro_div()},add_label_track:function(ac){ac.view=this;ac.init();this.label_tracks.push(ac)},remove_drawable:function(ae,ad){y.prototype.remove_drawable.call(this,ae);if(ad){var ac=this;ae.container_div.hide(0,function(){$(this).remove();ac.update_intro_div()})}},reset:function(){this.low=this.max_low;this.high=this.max_high;this.viewport_container.find(".yaxislabel").remove()},request_redraw:function(ac,aj,ak){var ai=this,ag=(ak?[ak]:ai.drawables),ae;var ad;for(var ah=0;ahthis.max_high){af=this.max_high}var al=this.high-this.low;if(this.high!==0&&al").addClass("dynamic-tool").hide();this.parent_div.bind("drag",function(au){au.stopPropagation()}).click(function(au){au.stopPropagation()}).bind("dblclick",function(au){au.stopPropagation()});var ao=$("
    ").appendTo(this.parent_div).text(this.name);var am=this.params;var ak=this;$.each(this.params,function(av,ay){var ax=$("
    ").addClass("param-row").appendTo(ak.parent_div);var au=$("
    ").addClass("param-label").text(ay.label).appendTo(ax);var aw=$("
    ").addClass("param-input").html(ay.html).appendTo(ax);aw.find(":input").val(ay.value);$("
    ").appendTo(ax)});this.parent_div.find("input").click(function(){$(this).select()});var at=$("
    ").addClass("param-row").appendTo(this.parent_div);var ai=$("").attr("value","Run on complete dataset").appendTo(at);var ac=$("").attr("value","Run on visible region").css("margin-left","3em").appendTo(at);ac.click(function(){ak.run_on_region()});ai.click(function(){ak.run_on_dataset()});if("visible" in af&&af.visible){this.parent_div.show()}};p(r.prototype,{update_params:function(){for(var ac=0;ac");var ae;function ad(ak,ag){for(var ao=0;ao").appendTo(ag);av.append($("").addClass("icon-button arrow-circle").appendTo(au).attr("title","Set new random color").tooltip(),ar=$("
    ").appendTo(au).hide(),aj=$("
    ").appendTo(ar),ah=$("
    ").appendTo(ar),al=$.farbtastic(aj,{width:100,height:100,callback:ap,color:at});au.append($("
    ").css("clear","both"));(function(aw){an.click(function(){aw.setColor(k.get_random_color())})})(al)}else{av.append($("").attr("id",ai).attr("name",ai).val(at))}}}}if(ae.help){av.append($("
    ").text(ae.help))}}}ad(this.params,ac);return ac},update_from_form:function(ac){var ae=this;var ad=false;$.each(this.params,function(af,ah){if(!ah.hidden){var ai="param_"+af;var ag=ac.find("#"+ai).val();if(ah.type==="float"){ag=parseFloat(ag)}else{if(ah.type==="int"){ag=parseInt(ag,10)}else{if(ah.type==="bool"){ag=ac.find("#"+ai).is(":checked")}}}if(ag!==ae.values[ah.key]){ae.values[ah.key]=ag;ad=true}}});if(ad){this.onchange();this.track.changed()}}});var b=function(ac,ag,ae,ad,af){this.track=ac;this.region=ag;this.low=ag.get("start");this.high=ag.get("end");this.resolution=ae;this.html_elt=$("
    ").append(ad).height($(ad).attr("height"));this.data=af;this.stale=false};b.prototype.predisplay_actions=function(){};var M=function(ac,ag,ae,ad,af){b.call(this,ac,ag,ae,ad,af)};M.prototype.predisplay_actions=function(){};var P=function(af,an,ag,ae,ai,ap,aj,aq,ad,am){b.call(this,af,an,ag,ae,ai);this.mode=aj;this.all_slotted=ad;this.feature_mapper=am;this.has_icons=false;if(aq){this.has_icons=true;var ak=this;ae=this.html_elt.children()[0],message_div=$("
    ").addClass("tile-message").css({height:D-1,width:ae.width}).prependTo(this.html_elt);var al=new w.GenomeRegion({chrom:af.view.chrom,start:this.low,end:this.high}),ao=ai.length,ah=$("").addClass("icon more-down").attr("title","For speed, only the first "+ao+" features in this region were obtained from server. Click to get more data including depth").tooltip().appendTo(message_div),ac=$("").addClass("icon more-across").attr("title","For speed, only the first "+ao+" features in this region were obtained from server. Click to get more data excluding depth").tooltip().appendTo(message_div);ah.click(function(){ak.stale=true;af.data_manager.get_more_data(al,af.mode,ak.resolution,{},af.data_manager.DEEP_DATA_REQ);$(".bs-tooltip").hide();af.request_draw(false,true)}).dblclick(function(ar){ar.stopPropagation()});ac.click(function(){ak.stale=true;af.data_manager.get_more_data(al,af.mode,ak.resolution,{},af.data_manager.BROAD_DATA_REQ);$(".bs-tooltip").hide();af.request_draw(false,true)}).dblclick(function(ar){ar.stopPropagation()})}};p(P.prototype,b.prototype);P.prototype.predisplay_actions=function(){var ad=this,ac={};if(ad.mode!=="Pack"){return}$(this.html_elt).hover(function(){this.hovered=true;$(this).mousemove()},function(){this.hovered=false;$(this).parents(".track-content").children(".overlay").children(".feature-popup").remove()}).mousemove(function(ao){if(!this.hovered){return}var aj=$(this).offset(),an=ao.pageX-aj.left,am=ao.pageY-aj.top,at=ad.feature_mapper.get_feature_data(an,am),ak=(at?at[0]:null);$(this).parents(".track-content").children(".overlay").children(".feature-popup").each(function(){if(!ak||$(this).attr("id")!==ak.toString()){$(this).remove()}});if(at){var af=ac[ak];if(!af){var ak=at[0],ap={name:at[3],start:at[1],end:at[2],strand:at[4]},ai=ad.track.filters_manager.filters,ah;for(var al=0;al").attr("id",ak).addClass("feature-popup"),au=$("
    ").appendTo(av).text(ar);$("").attr("align","left").appendTo(av).text(typeof(aq)==="number"?V(aq,2):aq)}af.append($("
    ").append(au));ac[ak]=af}af.appendTo($(this).parents(".track-content").children(".overlay"));var ag=an+parseInt(ad.html_elt.css("left"))-af.width()/2,ae=am+parseInt(ad.html_elt.css("top"))+7;af.css("left",ag+"px").css("top",ae+"px")}else{if(!ao.isPropagationStopped()){ao.stopPropagation();$(this).siblings().each(function(){$(this).trigger(ao)})}}}).mouseleave(function(){$(this).parents(".track-content").children(".overlay").children(".feature-popup").remove()})};var g=function(ad,ac,ae){q(ae,{drag_handle_class:"draghandle"});r.call(this,ad,ac,ae);this.dataset=new X.Dataset(ae.dataset);this.dataset_check_type="converted_datasets_state";this.data_url_extra_params={};this.data_query_wait=("data_query_wait" in ae?ae.data_query_wait:K);this.data_manager=("data_manager" in ae?ae.data_manager:new x.GenomeDataManager({dataset:this.dataset,genome:new x.Genome({key:ad.dbkey,chroms_info:{chrom_info:ad.chrom_data}}),data_mode_compatible:this.data_and_mode_compatible,can_subset:this.can_subset}));this.min_height_px=16;this.max_height_px=800;this.visible_height_px=0;this.content_div=$("
    ").appendTo(this.container_div);if(this.container){this.container.content_div.append(this.container_div);if(!("resize" in ae)||ae.resize){this.add_resize_handle()}}};q(g.prototype,r.prototype,{action_icons_def:[{name:"mode_icon",title:"Set display mode",css_class:"chevron-expand",on_click_fn:function(){}},r.prototype.action_icons_def[0],{name:"overview_icon",title:"Set as overview",css_class:"overview-icon",on_click_fn:function(ac){ac.view.set_overview(ac)}},r.prototype.action_icons_def[1],{name:"filters_icon",title:"Filters",css_class:"filters-icon",on_click_fn:function(ac){if(ac.filters_manager.visible()){ac.filters_manager.clear_filters()}else{ac.filters_manager.init_filters()}ac.filters_manager.toggle()}},{name:"tools_icon",title:"Tool",css_class:"hammer",on_click_fn:function(ac){ac.dynamic_tool_div.toggle();if(ac.dynamic_tool_div.is(":visible")){ac.set_name(ac.name+ac.tool_region_and_parameters_str())}else{ac.revert_name()}$(".bs-tooltip").remove()}},{name:"param_space_viz_icon",title:"Tool parameter space visualization",css_class:"arrow-split",on_click_fn:function(ac){var af='Tool: <%= track.tool.name %>
    Dataset: <%= track.name %>
    Region(s): ',ae=ab.template(af,{track:ac});var ah=function(){hide_modal();$(window).unbind("keypress.check_enter_esc")},ad=function(){var aj=$('select[name="regions"] option:selected').val(),al,ai=new x.GenomeRegion({chrom:view.chrom,start:view.low,end:view.high}),ak=ab.map($(".bookmark"),function(am){return new x.GenomeRegion({from_str:$(am).children(".position").text()})});if(aj==="cur"){al=[ai]}else{if(aj==="bookmarks"){al=ak}else{al=[ai].concat(ak)}}hide_modal();window.location.href=galaxy_paths.get("sweepster_url")+"?"+$.param({dataset_id:ac.dataset.id,hda_ldda:ac.dataset.get("hda_ldda"),regions:JSON.stringify(new Backbone.Collection(al).toJSON())})},ag=function(ai){if((ai.keyCode||ai.which)===27){ah()}else{if((ai.keyCode||ai.which)===13){ad()}}};show_modal("Visualize tool parameter space and output from different parameter settings?",ae,{No:ah,Yes:ad})}},r.prototype.action_icons_def[2]],can_draw:function(){if(this.dataset.id&&r.prototype.can_draw.call(this)){return true}return false},build_container_div:function(){return $("
    ").addClass("track").attr("id","track_"+this.id).css("position","relative")},build_header_div:function(){var ac=$("
    ");if(this.view.editor){this.drag_div=$("
    ").addClass(this.drag_handle_class).appendTo(ac)}this.name_div=$("
    ").addClass("track-name").appendTo(ac).text(this.name).attr("id",this.name.replace(/\s+/g,"-").replace(/[^a-zA-Z0-9\-]/g,"").toLowerCase());return ac},set_dataset:function(ac){this.dataset=ac;this.data_manager.set("dataset",ac)},on_resize:function(){this.request_draw(true)},add_resize_handle:function(){var ac=this;var af=false;var ae=false;var ad=$("
    ");$(ac.container_div).hover(function(){if(ac.content_visible){af=true;ad.show()}},function(){af=false;if(!ae){ad.hide()}});ad.hide().bind("dragstart",function(ag,ah){ae=true;ah.original_height=$(ac.content_div).height()}).bind("drag",function(ah,ai){var ag=Math.min(Math.max(ai.original_height+ai.deltaY,ac.min_height_px),ac.max_height_px);$(ac.tiles_div).css("height",ag);ac.visible_height_px=(ac.max_height_px===ag?0:ag);ac.on_resize()}).bind("dragend",function(ag,ah){ac.tile_cache.clear();ae=false;if(!af){ad.hide()}ac.config.values.height=ac.visible_height_px;ac.changed()}).appendTo(ac.container_div)},set_display_modes:function(af,ai){this.display_modes=af;this.mode=(ai?ai:(this.config&&this.config.values.mode?this.config.values.mode:this.display_modes[0]));this.action_icons.mode_icon.attr("title","Set display mode (now: "+this.mode+")");var ad=this,ag={};for(var ae=0,ac=ad.display_modes.length;ae").text("View error").click(function(){show_modal("Trackster Error","
    "+ag.message+"
    ",{Close:hide_modal})}));ad.tiles_div.append($("").text(" "));ad.tiles_div.append($("").text("Try again").click(function(){ad.init(true)}))}}else{if(ag==="no converter"){ad.container_div.addClass("error");ad.tiles_div.text(J)}else{if(ag==="no data"||(ag.data!==undefined&&(ag.data===null||ag.data.length===0))){ad.container_div.addClass("nodata");ad.tiles_div.text(E)}else{if(ag==="pending"){ad.container_div.addClass("pending");ad.tiles_div.html(v);setTimeout(function(){ad.init()},ad.data_query_wait)}else{if(ag==="data"||ag.status==="data"){if(ag.valid_chroms){ad.valid_chroms=ag.valid_chroms;ad.update_icons()}ad.tiles_div.text(T);if(ad.view.chrom){ad.tiles_div.text("");ad.tiles_div.css("height",ad.visible_height_px+"px");ad.enabled=true;$.when(ad.predraw_init()).done(function(){ac.resolve();ad.container_div.removeClass("nodata error pending");ad.request_draw()})}else{ac.resolve()}}}}}}});this.update_icons();return ac},predraw_init:function(){},get_drawables:function(){return this}});var M=function(ae,ad,af){g.call(this,ae,ad,af);var ac=this;m(ac.container_div,ac.drag_handle_class,".group",ac);this.filters_manager=new i.FiltersManager(this,("filters" in af?af.filters:null));this.data_manager.set("filters_manager",this.filters_manager);this.filters_available=false;this.tool=("tool" in af&&af.tool?new s(this,af.tool,af.tool_state):null);this.tile_cache=new x.Cache(Q);this.left_offset=0;if(this.header_div){this.set_filters_manager(this.filters_manager);if(this.tool){this.dynamic_tool_div=this.tool.parent_div;this.header_div.after(this.dynamic_tool_div)}}this.tiles_div=$("
    ").addClass("tiles").appendTo(this.content_div);this.overlay_div=$("
    ").addClass("overlay").appendTo(this.content_div);if(af.mode){this.change_mode(af.mode)}};q(M.prototype,r.prototype,g.prototype,{action_icons_def:g.prototype.action_icons_def.concat([{name:"show_more_rows_icon",title:"To minimize track height, not all feature rows are displayed. Click to display more rows.",css_class:"exclamation",on_click_fn:function(ac){$(".bs-tooltip").remove();ac.slotters[ac.view.resolution_px_b].max_rows*=2;ac.request_draw(true)},hide:true}]),copy:function(ac){var ad=this.to_dict();q(ad,{data_manager:this.data_manager});var ae=new this.constructor(this.view,ac,ad);ae.change_mode(this.mode);ae.enabled=this.enabled;return ae},set_filters_manager:function(ac){this.filters_manager=ac;this.header_div.after(this.filters_manager.parent_div)},to_dict:function(){return{track_type:this.get_type(),name:this.name,dataset:{id:this.dataset.id,hda_ldda:this.dataset.get("hda_ldda")},prefs:this.prefs,mode:this.mode,filters:this.filters_manager.to_dict(),tool_state:(this.tool?this.tool.state_dict():{})}},change_mode:function(ad){var ac=this;ac.mode=ad;ac.config.values.mode=ad;ac.request_draw(true);this.action_icons.mode_icon.attr("title","Set display mode (now: "+ac.mode+")");return ac},update_icons:function(){var ac=this;if(ac.filters_available){ac.action_icons.filters_icon.show()}else{ac.action_icons.filters_icon.hide()}if(ac.tool){ac.action_icons.tools_icon.show();ac.action_icons.param_space_viz_icon.show()}else{ac.action_icons.tools_icon.hide();ac.action_icons.param_space_viz_icon.hide()}},_gen_tile_cache_key:function(ad,ac){return ad+"_"+ac},request_draw:function(ae,ad,ac){if(ae){this.tile_cache.clear()}this.view.request_redraw(ad,ac,this)},before_draw:function(){this.max_height_px=0},_draw:function(ad,an){if(!this.can_draw()){return}var al=this.view.low,ah=this.view.high,ak=ah-al,ae=this.view.container.width(),ap=this.view.resolution_px_b,ag=this.view.resolution_b_px;if(this.is_overview){al=this.view.max_low;ah=this.view.max_high;ag=(view.max_high-view.max_low)/ae;ap=1/ag}this.before_draw();this.tiles_div.children().addClass("remove");var ac=Math.floor(al/(ag*R)),am,ai,aj=[],ao=[];while((ac*R*ag)").text(aj).make_text_editable({num_cols:12,on_finish:function(ak){$(".bs-tooltip").remove();var ak=parseFloat(ak);ac.prefs[ad]=(!isNaN(ak)?ak:null);ah()},help_text:"Set "+ai+" value"}).addClass("yaxislabel "+ag).css("color",this.prefs.label_color);this.container_div.prepend(ae)}},postdraw_actions:function(ae,af,aj,ac){var ag=ab.find(ae,function(ak){return ak.has_icons});if(ag){ab.each(ae,function(ak){if(!ak.has_icons){ak.html_elt.css("padding-top",D)}})}var ad=this,ah=ae[0];if(ah instanceof j){var ai=(this.prefs.histogram_max?this.prefs.histogram_max:ah.max_val);this._add_yaxis_label("max",ai,"histogram_max")}},draw_helper:function(ac,af,ar,ag,ah,ap){var an=this,ax=this._gen_tile_cache_key(ah,af),aj=function(ay){return(ay&&"track" in ay)};if(!ap){ap={}}var aw=(ac?undefined:an.tile_cache.get_elt(ax));if(aw){if(aj(aw)){an.show_tile(aw,ag,ah)}return aw}var al=true;var at=an.data_manager.get_data(af,an.mode,ar,an.data_url_extra_params);if(U(at)){al=false}var ai;if(view.reference_track){ai=view.reference_track.data_manager.get_data(af,an.mode,ar,view.reference_track.data_url_extra_params);if(U(ai)){al=false}else{ai=view.reference_track.data_manager.subset_entry(ai,af)}}if(al){q(at,ap.more_tile_data);var ak=an.mode;if(ak==="Auto"&&an.get_mode){ak=an.get_mode(at);an.update_auto_mode(ak)}var ae=an.view.canvas_manager.new_canvas(),au=af.get("start"),ad=af.get("end"),ao=Math.ceil((ad-au)*ah)+an.left_offset,am=an.get_canvas_height(at,ak,ah,ao);ae.width=ao;ae.height=am;var aq=ae.getContext("2d");aq.translate(this.left_offset,0);var aw=an.draw_tile(at,aq,ak,ar,af,ah,ai);if(aw!==undefined){an.tile_cache.set_elt(ax,aw);an.show_tile(aw,ag,ah)}return aw}var av=$.Deferred();an.tile_cache.set_elt(ax,av);$.when(at,ai).then(function(){aw=an.draw_helper(true,af,ar,ag,ah,ap);av.resolve(aw)});return av},get_canvas_height:function(ac,ae,af,ad){return this.visible_height_px},_draw_summary_tree_tile:function(ac,ae,ag,af,ah){var ad=new L.SummaryTreePainter(ac,ag.get("start"),ag.get("end"),this.prefs);ad.draw(ae,ae.canvas.width,ae.canvas.height,ah);return new j(this,ag,af,ae.canvas,ac.data,ac.max)},draw_tile:function(ac,ad,ah,af,ag,ai,ae){console.log("Warning: TiledTrack.draw_tile() not implemented.")},show_tile:function(ae,ah,ai){var ad=this,ac=ae.html_elt;ae.predisplay_actions();var ag=(ae.low-(this.is_overview?this.view.max_low:this.view.low))*ai;if(this.left_offset){ag-=this.left_offset}ac.css({position:"absolute",top:0,left:ag});if(ac.hasClass("remove")){ac.removeClass("remove")}else{ah.append(ac)}ae.html_elt.height("auto");this.max_height_px=Math.max(this.max_height_px,ae.html_elt.height());ae.html_elt.parent().children().css("height",this.max_height_px+"px");var af=this.max_height_px;if(this.visible_height_px!==0){af=Math.min(this.max_height_px,this.visible_height_px)}this.tiles_div.css("height",af+"px")},_get_tile_bounds:function(ac,ad){var af=Math.floor(ac*R*ad),ag=Math.ceil(R*ad),ae=(af+ag<=this.view.max_high?af+ag:this.view.max_high);return new x.GenomeRegion({chrom:this.view.chrom,start:af,end:ae})},tool_region_and_parameters_str:function(ae){var ac=this,ad=(ae!==undefined?ae.toString():"all");return" - region=["+ad+"], parameters=["+ac.tool.get_param_values().join(", ")+"]"},data_and_mode_compatible:function(ac,ad){return true},can_subset:function(ac){return false},init_for_tool_data:function(){this.data_manager.set("data_type","raw_data");this.data_query_wait=1000;this.dataset_check_type="state";this.normal_postdraw_actions=this.postdraw_actions;this.postdraw_actions=function(ae,af,ah,ac){var ad=this;ad.normal_postdraw_actions(ae,af,ah,ac);ad.dataset_check_type="converted_datasets_state";ad.data_query_wait=K;var ag=new l.ServerStateDeferred({url:ad.dataset_state_url,url_params:{dataset_id:ad.dataset.id,hda_ldda:ad.dataset.get("hda_ldda")},interval:ad.data_query_wait,success_fn:function(ai){return ai!=="pending"}});$.when(ag.go()).then(function(){ad.data_manager.set("data_type","data")});ad.postdraw_actions=ad.normal_postdraw_actions}}});var W=function(ad,ac){var ae={resize:false};g.call(this,ad,ac,ae);this.container_div.addClass("label-track")};q(W.prototype,g.prototype,{build_header_div:function(){},init:function(){this.enabled=true},_draw:function(){var ae=this.view,af=ae.high-ae.low,ai=Math.floor(Math.pow(10,Math.floor(Math.log(af)/Math.log(10)))),ac=Math.floor(ae.low/ai)*ai,ag=this.view.container.width(),ad=$("
    ");while(ac"+commatize(ac)+"
    ").css({position:"absolute",left:ah-1}));ac+=ai}this.content_div.children(":first").remove();this.content_div.append(ad)}});var f=function(ad,ac,ag){M.call(this,ad,ac,ag);this.drawables=[];if("drawables" in ag){var af;for(var ae=0;aethis.left_offset){this.left_offset=af.left_offset}}this.enabled=true}if(this.drawables.length!==0){this.set_display_modes(this.drawables[0].display_modes,this.drawables[0].mode)}this.update_icons();this.obj_type="CompositeTrack"};q(f.prototype,M.prototype,{action_icons_def:[{name:"composite_icon",title:"Show individual tracks",css_class:"layers-stack",on_click_fn:function(ac){$(".bs-tooltip").remove();ac.show_group()}}].concat(M.prototype.action_icons_def),to_dict:z.prototype.to_dict,add_drawable:z.prototype.add_drawable,unpack_drawables:z.prototype.unpack_drawables,change_mode:function(ac){M.prototype.change_mode.call(this,ac);for(var ad=0;adview.canvas_manager.char_width_px){al=view.reference_track.data_manager.get_data(ah,aq.mode,av,view.reference_track.data_url_extra_params);if(U(al)){ao=false}}aj.push(al)}if(ao){q(aw,at.more_tile_data);var ag=aq.view.canvas_manager.new_canvas(),ay=ah.get("start"),ae=ah.get("end"),az=0,ar=Math.ceil((ae-ay)*ak)+this.left_offset,ap=0,af=[],ax;var ac=0;for(ax=0;axap){ap=ac}}ag.width=ar;ag.height=(at.height?at.height:ap);az=0;var au=ag.getContext("2d");au.translate(this.left_offset,0);au.globalAlpha=0.5;au.globalCompositeOperation="source-over";for(ax=0;axac){ac=ad.prefs.max_value}}}this.prefs.min_value=af;this.prefs.max_value=ac;for(var ae=0;aeag){ag=aj}}for(var ae=0;aethis.view.canvas_manager.char_width_px){this.tiles_div.show();return M.prototype.draw_helper.call(this,ae,af,ac,ag,ah,ad)}else{this.tiles_div.hide();return null}},can_subset:function(ac){return true},draw_tile:function(af,al,ag,ad,ai,am){var ae=this.data_manager.subset_entry(af,ai),ak=ae.data;var ac=al.canvas;al.font=al.canvas.manager.default_font;al.textAlign="center";for(var ah=0,aj=ak.length;ahak){ak=an}}for(ah=0;ahI){mode="Squish"}else{mode="Pack"}}}return mode},get_canvas_height:function(ac,ag,ah,ad){if(ag==="summary_tree"||ag==="Coverage"){return this.summary_draw_height}else{var af=this.incremental_slots(ah,ac.data,ag);var ae=new (this.painter)(null,null,null,this.prefs,ag);return Math.max(aa,ae.get_required_height(af,ad))}},draw_tile:function(am,aq,ao,ar,af,aj,ae){var ap=this,ad=aq.canvas,ay=af.get("start"),ac=af.get("end"),ag=this.left_offset;if(ao==="summary_tree"||ao==="Coverage"){return this._draw_summary_tree_tile(am,aq,af,ar,aj)}var ai=[],an=this.slotters[aj].slots;all_slotted=true;if(am.data){var ak=this.filters_manager.filters;for(var at=0,av=am.data.length;at").text("Summary").addClass("yaxislabel variant top").css({"font-size":ac+"px",top:(this.prefs.summary_height-ac)/2+"px"}));if(this.prefs.show_sample_data){var ag=this.dataset.get("metadata").get("sample_names").join("
    ");this.tiles_div.prepend($("
    ").html(ag).addClass("yaxislabel variant top sample").css({top:this.prefs.summary_height+2,}))}}ac=(this.mode==="Squish"?5:10)+"px";$(this.tiles_div).find(".sample").css({"font-size":ac,"line-height":ac});$(this.tiles_div).find(".yaxislabel").css("color",this.prefs.label_color)}else{this.container_div.find(".yaxislabel.variant").remove()}}});var S=function(ae,ad,ag){c.call(this,ae,ad,ag);var af=l.get_random_color(),ac=l.get_random_color([af,"#ffffff"]);this.config=new F({track:this,params:[{key:"name",label:"Name",type:"text",default_value:this.name},{key:"block_color",label:"Block and sense strand color",type:"color",default_value:af},{key:"reverse_strand_color",label:"Antisense strand color",type:"color",default_value:ac},{key:"label_color",label:"Label color",type:"color",default_value:"black"},{key:"show_insertions",label:"Show insertions",type:"bool",default_value:false},{key:"show_differences",label:"Show differences only",type:"bool",default_value:true},{key:"show_counts",label:"Show summary counts",type:"bool",default_value:true},{key:"histogram_max",label:"Histogram maximum",type:"float",default_value:null,help:"Clear value to set automatically"},{key:"mode",type:"string",default_value:this.mode,hidden:true}],saved_values:ag.prefs,onchange:function(){this.track.set_name(this.track.prefs.name);this.track.request_draw(true)}});this.prefs=this.config.values;this.painter=(ae.reference_track?L.RefBasedReadPainter:L.ReadPainter);this.update_icons()};q(S.prototype,r.prototype,M.prototype,c.prototype);var d={CompositeTrack:f,DrawableGroup:P,DiagonalHeatmapTrack:t,FeatureTrack:c,LineTrack:h,ReadTrack:S,VariantTrack:Z,VcfTrack:Z};var p=function(ae,ad,ac){if("copy" in ae){return ae.copy(ac)}else{var af=ae.obj_type;if(!af){af=ae.track_type}return new d[af](ad,ac,ae)}};return{TracksterView:Y,DrawableGroup:P,LineTrack:h,FeatureTrack:c,DiagonalHeatmapTrack:t,ReadTrack:S,VariantTrack:Z,CompositeTrack:f,object_from_template:p}}); \ No newline at end of file From 12b858f9cdbe7c4a6dcea5437af7c307269cc94d Mon Sep 17 00:00:00 2001 From: Daniel Blankenberg Date: Wed, 8 May 2013 10:58:52 -0400 Subject: [PATCH 359/523] Make form input auto-focus declarative. --- static/scripts/galaxy.base.js | 5 ----- static/scripts/packed/galaxy.base.js | 2 +- templates/base.mako | 12 ++++++++++++ 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/static/scripts/galaxy.base.js b/static/scripts/galaxy.base.js index 5b83996b5df..aed5dd12253 100644 --- a/static/scripts/galaxy.base.js +++ b/static/scripts/galaxy.base.js @@ -705,9 +705,4 @@ $(document).ready( function() { return anchor; }); - // Auto Focus on first item on form - if ( $("*:focus").html() == null ) { - $(":input:not([type=hidden]):visible:enabled:first").focus(); - } - }); diff --git a/static/scripts/packed/galaxy.base.js b/static/scripts/packed/galaxy.base.js index 88d9625c27f..15dcddd97d2 100644 --- a/static/scripts/packed/galaxy.base.js +++ b/static/scripts/packed/galaxy.base.js @@ -1 +1 @@ -(function(){var b=0;var c=["ms","moz","webkit","o"];for(var a=0;a");var f=b.data("menu_options");if(obj_length(f)<=0){$("
  • No Options.
  • ").appendTo(g)}$.each(f,function(j,i){if(i){g.append($("
  • ").append($("").html(j).click(i)))}else{g.append($("
  • ").addClass("head").append($("").html(j)))}});var h=$("
    ").append(g).appendTo("body");var e=d.pageX-h.width()/2;e=Math.min(e,$(document).scrollLeft()+$(window).width()-$(h).width()-5);e=Math.max(e,$(document).scrollLeft()+5);h.css({top:d.pageY,left:e})},10);setTimeout(function(){var f=function(h){$(h).bind("click.close_popup",function(){$(".popmenu-wrapper").remove();h.unbind("click.close_popup")})};f($(window.document));f($(window.top.document));for(var e=window.top.frames.length;e--;){var g=$(window.top.frames[e].document);f(g)}},50);return false})}function make_popup_menus(a){a=a||document;$(a).find("div[popupmenu]").each(function(){var b={};var d=$(this);d.find("a").each(function(){var g=$(this),i=g.get(0),e=i.getAttribute("confirm"),f=i.getAttribute("href"),h=i.getAttribute("target");if(!f){b[g.text()]=null}else{b[g.text()]=function(){if(!e||confirm(e)){var j;if(h==="_parent"){window.parent.location=f}else{if(h==="_top"){window.top.location=f}else{if(h==="demo"){if(j===undefined||j.closed){j=window.open(f,h);j.creator=self}}else{window.location=f}}}}}}});var c=$(a).find("#"+d.attr("popupmenu"));c.find("a").bind("click",function(f){f.stopPropagation();return true});make_popupmenu(c,b);c.addClass("popup");d.remove()})}function naturalSort(j,h){var p=/(-?[0-9\.]+)/g,k=j.toString().toLowerCase()||"",g=h.toString().toLowerCase()||"",l=String.fromCharCode(0),n=k.replace(p,l+"$1"+l).split(l),e=g.replace(p,l+"$1"+l).split(l),d=(new Date(k)).getTime(),o=d?(new Date(g)).getTime():null;if(o){if(do){return 1}}}var m,f;for(var i=0,c=Math.max(n.length,e.length);if){return 1}}}return 0}$.fn.refresh_select2=function(){var b=$(this);var a={width:"resolve",closeOnSelect:!b.is("[MULTIPLE]")};return b.select2(a)};function replace_big_select_inputs(a,c,b){if(!jQuery.fn.select2){return}if(a===undefined){a=20}if(c===undefined){c=3000}b=b||$("select");b.each(function(){var e=$(this);var d=e.find("option").length;if((dc)){return}if(e.hasClass("no-autocomplete")){return}e.refresh_select2()})}$.fn.make_text_editable=function(g){var d=("num_cols" in g?g.num_cols:30),c=("num_rows" in g?g.num_rows:4),e=("use_textarea" in g?g.use_textarea:false),b=("on_finish" in g?g.on_finish:null),f=("help_text" in g?g.help_text:null);var a=$(this);a.addClass("editable-text").click(function(l){if($(this).children(":input").length>0){return}a.removeClass("editable-text");var i=function(m){a.find(":input").remove();if(m!==""){a.text(m)}else{a.html("
    ")}a.addClass("editable-text");if(b){b(m)}};var h=a.text(),k,j;if(e){k=$("").attr({rows:i,cols:c}).text($.trim(k))}else{j=$("").attr({value:$.trim(k),size:c})}j.attr("id","renaming-active");j.blur(function(){$(this).remove();l.show();if(b){b(j)}});j.keyup(function(n){if(n.keyCode===27){$(this).trigger("blur")}else{if(n.keyCode===13){var m={};m[a]=$(this).val();$(this).trigger("blur");$.ajax({url:e,data:m,error:function(){alert("Text editing for elt "+f+" failed")},success:function(o){if(o!==""){l.text(o)}else{l.html("None")}if(b){b(j)}}})}}});if(g){g(j)}l.hide();j.insertAfter(l);j.focus();j.select();return})}function init_history_items(d,a,c){var b=function(){try{var e=$.jStorage.get("history_expand_state");if(e){for(var g in e){$("#"+g+" div.historyItemBody").show()}}}catch(f){$.jStorage.deleteKey("history_expand_state")}if($.browser.mozilla){$("div.historyItemBody").each(function(){if(!$(this).is(":visible")){$(this).find("pre.peek").css("overflow","hidden")}})}d.each(function(){var j=this.id,h=$(this).children("div.historyItemBody"),i=h.find("pre.peek");$(this).find(".historyItemTitleBar > .historyItemTitle").wrap("").click(function(){var k;if(h.is(":visible")){if($.browser.mozilla){i.css("overflow","hidden")}h.slideUp("fast");if(!c){k=$.jStorage.get("history_expand_state");if(k){delete k[j];$.jStorage.set("history_expand_state",k)}}}else{h.slideDown("fast",function(){if($.browser.mozilla){i.css("overflow","auto")}});if(!c){k=$.jStorage.get("history_expand_state");if(!k){k={}}k[j]=true;$.jStorage.set("history_expand_state",k)}}return false})});$("#top-links > a.toggle").click(function(){var h=$.jStorage.get("history_expand_state");if(!h){h={}}$("div.historyItemBody:visible").each(function(){if($.browser.mozilla){$(this).find("pre.peek").css("overflow","hidden")}$(this).slideUp("fast");if(h){delete h[$(this).parent().attr("id")]}});$.jStorage.set("history_expand_state",h)}).show()};b()}function commatize(b){b+="";var a=/(\d+)(\d{3})/;while(a.test(b)){b=b.replace(a,"$1,$2")}return b}function reset_tool_search(a){var c=$("#galaxy_tools").contents();if(c.length===0){c=$(document)}$(this).removeClass("search_active");c.find(".toolTitle").removeClass("search_match");c.find(".toolSectionBody").hide();c.find(".toolTitle").show();c.find(".toolPanelLabel").show();c.find(".toolSectionWrapper").each(function(){if($(this).attr("id")!=="recently_used_wrapper"){$(this).show()}else{if($(this).hasClass("user_pref_visible")){$(this).show()}}});c.find("#search-no-results").hide();c.find("#search-spinner").hide();if(a){var b=c.find("#tool-search-query");b.val("search tools")}}var GalaxyAsync=function(a){this.url_dict={};this.log_action=(a===undefined?false:a)};GalaxyAsync.prototype.set_func_url=function(a,b){this.url_dict[a]=b};GalaxyAsync.prototype.set_user_pref=function(a,b){var c=this.url_dict[arguments.callee];if(c===undefined){return false}$.ajax({url:c,data:{pref_name:a,pref_value:b},error:function(){return false},success:function(){return true}})};GalaxyAsync.prototype.log_user_action=function(c,b,d){if(!this.log_action){return}var a=this.url_dict[arguments.callee];if(a===undefined){return false}$.ajax({url:a,data:{action:c,context:b,params:d},error:function(){return false},success:function(){return true}})};$(document).ready(function(){$("select[refresh_on_change='true']").change(function(){var a=$(this),e=a.val(),d=false,c=a.attr("refresh_on_change_values");if(c){c=c.split(",");var b=a.attr("last_selected_value");if($.inArray(e,c)===-1&&$.inArray(b,c)===-1){return}}$(window).trigger("refresh_on_change");$(document).trigger("convert_to_values");a.get(0).form.submit()});$(":checkbox[refresh_on_change='true']").click(function(){var a=$(this),e=a.val(),d=false,c=a.attr("refresh_on_change_values");if(c){c=c.split(",");var b=a.attr("last_selected_value");if($.inArray(e,c)===-1&&$.inArray(b,c)===-1){return}}$(window).trigger("refresh_on_change");a.get(0).form.submit()});$("a[confirm]").click(function(){return confirm($(this).attr("confirm"))});if($.fn.tooltip){$(".tooltip").tooltip({placement:"top"})}make_popup_menus();replace_big_select_inputs(20,1500);$("a").click(function(){var b=$(this);var c=(parent.frames&&parent.frames.galaxy_main);if((b.attr("target")=="galaxy_main")&&(!c)){var a=b.attr("href");if(a.indexOf("?")==-1){a+="?"}else{a+="&"}a+="use_panels=True";b.attr("href",a);b.attr("target","_self")}return b});if($("*:focus").html()==null){$(":input:not([type=hidden]):visible:enabled:first").focus()}}); \ No newline at end of file +(function(){var b=0;var c=["ms","moz","webkit","o"];for(var a=0;a");var f=b.data("menu_options");if(obj_length(f)<=0){$("
  • No Options.
  • ").appendTo(g)}$.each(f,function(j,i){if(i){g.append($("
  • ").append($("").html(j).click(i)))}else{g.append($("
  • ").addClass("head").append($("").html(j)))}});var h=$("
    ").append(g).appendTo("body");var e=d.pageX-h.width()/2;e=Math.min(e,$(document).scrollLeft()+$(window).width()-$(h).width()-5);e=Math.max(e,$(document).scrollLeft()+5);h.css({top:d.pageY,left:e})},10);setTimeout(function(){var f=function(h){$(h).bind("click.close_popup",function(){$(".popmenu-wrapper").remove();h.unbind("click.close_popup")})};f($(window.document));f($(window.top.document));for(var e=window.top.frames.length;e--;){var g=$(window.top.frames[e].document);f(g)}},50);return false})}function make_popup_menus(a){a=a||document;$(a).find("div[popupmenu]").each(function(){var b={};var d=$(this);d.find("a").each(function(){var g=$(this),i=g.get(0),e=i.getAttribute("confirm"),f=i.getAttribute("href"),h=i.getAttribute("target");if(!f){b[g.text()]=null}else{b[g.text()]=function(){if(!e||confirm(e)){var j;if(h==="_parent"){window.parent.location=f}else{if(h==="_top"){window.top.location=f}else{if(h==="demo"){if(j===undefined||j.closed){j=window.open(f,h);j.creator=self}}else{window.location=f}}}}}}});var c=$(a).find("#"+d.attr("popupmenu"));c.find("a").bind("click",function(f){f.stopPropagation();return true});make_popupmenu(c,b);c.addClass("popup");d.remove()})}function naturalSort(j,h){var p=/(-?[0-9\.]+)/g,k=j.toString().toLowerCase()||"",g=h.toString().toLowerCase()||"",l=String.fromCharCode(0),n=k.replace(p,l+"$1"+l).split(l),e=g.replace(p,l+"$1"+l).split(l),d=(new Date(k)).getTime(),o=d?(new Date(g)).getTime():null;if(o){if(do){return 1}}}var m,f;for(var i=0,c=Math.max(n.length,e.length);if){return 1}}}return 0}$.fn.refresh_select2=function(){var b=$(this);var a={width:"resolve",closeOnSelect:!b.is("[MULTIPLE]")};return b.select2(a)};function replace_big_select_inputs(a,c,b){if(!jQuery.fn.select2){return}if(a===undefined){a=20}if(c===undefined){c=3000}b=b||$("select");b.each(function(){var e=$(this);var d=e.find("option").length;if((dc)){return}if(e.hasClass("no-autocomplete")){return}e.refresh_select2()})}$.fn.make_text_editable=function(g){var d=("num_cols" in g?g.num_cols:30),c=("num_rows" in g?g.num_rows:4),e=("use_textarea" in g?g.use_textarea:false),b=("on_finish" in g?g.on_finish:null),f=("help_text" in g?g.help_text:null);var a=$(this);a.addClass("editable-text").click(function(l){if($(this).children(":input").length>0){return}a.removeClass("editable-text");var i=function(m){a.find(":input").remove();if(m!==""){a.text(m)}else{a.html("
    ")}a.addClass("editable-text");if(b){b(m)}};var h=a.text(),k,j;if(e){k=$("").attr({rows:i,cols:c}).text($.trim(k))}else{j=$("").attr({value:$.trim(k),size:c})}j.attr("id","renaming-active");j.blur(function(){$(this).remove();l.show();if(b){b(j)}});j.keyup(function(n){if(n.keyCode===27){$(this).trigger("blur")}else{if(n.keyCode===13){var m={};m[a]=$(this).val();$(this).trigger("blur");$.ajax({url:e,data:m,error:function(){alert("Text editing for elt "+f+" failed")},success:function(o){if(o!==""){l.text(o)}else{l.html("None")}if(b){b(j)}}})}}});if(g){g(j)}l.hide();j.insertAfter(l);j.focus();j.select();return})}function init_history_items(d,a,c){var b=function(){try{var e=$.jStorage.get("history_expand_state");if(e){for(var g in e){$("#"+g+" div.historyItemBody").show()}}}catch(f){$.jStorage.deleteKey("history_expand_state")}if($.browser.mozilla){$("div.historyItemBody").each(function(){if(!$(this).is(":visible")){$(this).find("pre.peek").css("overflow","hidden")}})}d.each(function(){var j=this.id,h=$(this).children("div.historyItemBody"),i=h.find("pre.peek");$(this).find(".historyItemTitleBar > .historyItemTitle").wrap("").click(function(){var k;if(h.is(":visible")){if($.browser.mozilla){i.css("overflow","hidden")}h.slideUp("fast");if(!c){k=$.jStorage.get("history_expand_state");if(k){delete k[j];$.jStorage.set("history_expand_state",k)}}}else{h.slideDown("fast",function(){if($.browser.mozilla){i.css("overflow","auto")}});if(!c){k=$.jStorage.get("history_expand_state");if(!k){k={}}k[j]=true;$.jStorage.set("history_expand_state",k)}}return false})});$("#top-links > a.toggle").click(function(){var h=$.jStorage.get("history_expand_state");if(!h){h={}}$("div.historyItemBody:visible").each(function(){if($.browser.mozilla){$(this).find("pre.peek").css("overflow","hidden")}$(this).slideUp("fast");if(h){delete h[$(this).parent().attr("id")]}});$.jStorage.set("history_expand_state",h)}).show()};b()}function commatize(b){b+="";var a=/(\d+)(\d{3})/;while(a.test(b)){b=b.replace(a,"$1,$2")}return b}function reset_tool_search(a){var c=$("#galaxy_tools").contents();if(c.length===0){c=$(document)}$(this).removeClass("search_active");c.find(".toolTitle").removeClass("search_match");c.find(".toolSectionBody").hide();c.find(".toolTitle").show();c.find(".toolPanelLabel").show();c.find(".toolSectionWrapper").each(function(){if($(this).attr("id")!=="recently_used_wrapper"){$(this).show()}else{if($(this).hasClass("user_pref_visible")){$(this).show()}}});c.find("#search-no-results").hide();c.find("#search-spinner").hide();if(a){var b=c.find("#tool-search-query");b.val("search tools")}}var GalaxyAsync=function(a){this.url_dict={};this.log_action=(a===undefined?false:a)};GalaxyAsync.prototype.set_func_url=function(a,b){this.url_dict[a]=b};GalaxyAsync.prototype.set_user_pref=function(a,b){var c=this.url_dict[arguments.callee];if(c===undefined){return false}$.ajax({url:c,data:{pref_name:a,pref_value:b},error:function(){return false},success:function(){return true}})};GalaxyAsync.prototype.log_user_action=function(c,b,d){if(!this.log_action){return}var a=this.url_dict[arguments.callee];if(a===undefined){return false}$.ajax({url:a,data:{action:c,context:b,params:d},error:function(){return false},success:function(){return true}})};$(document).ready(function(){$("select[refresh_on_change='true']").change(function(){var a=$(this),e=a.val(),d=false,c=a.attr("refresh_on_change_values");if(c){c=c.split(",");var b=a.attr("last_selected_value");if($.inArray(e,c)===-1&&$.inArray(b,c)===-1){return}}$(window).trigger("refresh_on_change");$(document).trigger("convert_to_values");a.get(0).form.submit()});$(":checkbox[refresh_on_change='true']").click(function(){var a=$(this),e=a.val(),d=false,c=a.attr("refresh_on_change_values");if(c){c=c.split(",");var b=a.attr("last_selected_value");if($.inArray(e,c)===-1&&$.inArray(b,c)===-1){return}}$(window).trigger("refresh_on_change");a.get(0).form.submit()});$("a[confirm]").click(function(){return confirm($(this).attr("confirm"))});if($.fn.tooltip){$(".tooltip").tooltip({placement:"top"})}make_popup_menus();replace_big_select_inputs(20,1500);$("a").click(function(){var b=$(this);var c=(parent.frames&&parent.frames.galaxy_main);if((b.attr("target")=="galaxy_main")&&(!c)){var a=b.attr("href");if(a.indexOf("?")==-1){a+="?"}else{a+="&"}a+="use_panels=True";b.attr("href",a);b.attr("target","_self")}return b})}); \ No newline at end of file diff --git a/templates/base.mako b/templates/base.mako index f5084ae8a6d..d6753e77623 100644 --- a/templates/base.mako +++ b/templates/base.mako @@ -73,6 +73,18 @@ image_path: '${h.url_for( "/static/images" )}' }); + + %if not form_input_auto_focus is UNDEFINED and form_input_auto_focus: + + %endif + ## Additional metas can be defined by templates inheriting from this one. From f42a7f6d1c712bb218efaa4d2daedb94863990e7 Mon Sep 17 00:00:00 2001 From: Daniel Blankenberg Date: Wed, 8 May 2013 10:58:52 -0400 Subject: [PATCH 360/523] Auto focus form inputs on new tool runs and user login. --- lib/galaxy/webapps/galaxy/controllers/tool_runner.py | 1 + lib/galaxy/webapps/galaxy/controllers/user.py | 1 + 2 files changed, 2 insertions(+) diff --git a/lib/galaxy/webapps/galaxy/controllers/tool_runner.py b/lib/galaxy/webapps/galaxy/controllers/tool_runner.py index fea1b596378..a3153ef0b5f 100644 --- a/lib/galaxy/webapps/galaxy/controllers/tool_runner.py +++ b/lib/galaxy/webapps/galaxy/controllers/tool_runner.py @@ -94,6 +94,7 @@ class ToolRunner( BaseUIController ): tool=tool, util=galaxy.util, add_frame=add_frame, + form_input_auto_focus=True, **vars ) @web.expose diff --git a/lib/galaxy/webapps/galaxy/controllers/user.py b/lib/galaxy/webapps/galaxy/controllers/user.py index f8129e2d36b..34651dd2617 100644 --- a/lib/galaxy/webapps/galaxy/controllers/user.py +++ b/lib/galaxy/webapps/galaxy/controllers/user.py @@ -463,6 +463,7 @@ class User( BaseUIController, UsesFormDefinitionsMixin ): message=message, status=status, openid_providers=trans.app.openid_providers, + form_input_auto_focus=True, active_view="user" ) def __validate_login( self, trans, **kwd ): message = kwd.get( 'message', '' ) From de421433f3ef25db7cf7337f2407c3e09cae27dd Mon Sep 17 00:00:00 2001 From: Jeremy Goecks Date: Wed, 8 May 2013 12:37:37 -0400 Subject: [PATCH 361/523] Add missing Tophat2 test output. From 35a052221c3f3a83cc2936c08eefdf4ce9e31766 Mon Sep 17 00:00:00 2001 From: Daniel Blankenberg Date: Wed, 8 May 2013 13:22:02 -0400 Subject: [PATCH 362/523] Allow dataset owners and admins to view deleted datasets. --- lib/galaxy/webapps/galaxy/controllers/dataset.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/galaxy/webapps/galaxy/controllers/dataset.py b/lib/galaxy/webapps/galaxy/controllers/dataset.py index a4a0029fcb1..cf19e8019f4 100644 --- a/lib/galaxy/webapps/galaxy/controllers/dataset.py +++ b/lib/galaxy/webapps/galaxy/controllers/dataset.py @@ -290,7 +290,7 @@ class DatasetInterface( BaseUIController, UsesAnnotations, UsesHistoryMixin, Use raise paste.httpexceptions.HTTPRequestRangeNotSatisfiable( "Invalid reference dataset id: %s." % str( hda_id ) ) if not self._can_access_dataset( trans, data ): return trans.show_error_message( "You are not allowed to access this dataset" ) - if data.deleted: + if data.deleted and not ( trans.user_is_admin() or ( data.history and trans.get_user() == data.history.user ) ): return trans.show_error_message( "The dataset you are attempting to view has been deleted." ) if data.state == trans.model.Dataset.states.UPLOAD: return trans.show_error_message( "Please wait until this dataset finishes uploading before attempting to view it." ) From de71c1f8e349caa9c516fcff7aa1763ed16497c3 Mon Sep 17 00:00:00 2001 From: Daniel Blankenberg Date: Wed, 8 May 2013 13:30:05 -0400 Subject: [PATCH 363/523] Display a warning to users when previewing a deleted dataset. Allow dataset owner to undelete dataset. --- templates/webapps/galaxy/dataset/display.mako | 12 ++++++++++++ templates/webapps/galaxy/dataset/large_file.mako | 3 +++ .../webapps/galaxy/dataset/tabular_chunked.mako | 4 +++- 3 files changed, 18 insertions(+), 1 deletion(-) diff --git a/templates/webapps/galaxy/dataset/display.mako b/templates/webapps/galaxy/dataset/display.mako index 6a62e4d2910..55ac07c2a90 100644 --- a/templates/webapps/galaxy/dataset/display.mako +++ b/templates/webapps/galaxy/dataset/display.mako @@ -61,6 +61,7 @@ <%def name="render_item( data, data_to_render )"> ## Chunkable data is rendered in JavaScript above; render unchunkable data below. + ${ render_deleted_data_message( data ) } %if not data.datatype.CHUNKABLE and data_to_render: %if truncated:
    @@ -75,6 +76,17 @@ %endif +<%def name="render_deleted_data_message( data )"> + %if data.deleted: +
    + You are viewing a deleted dataset. + %if data.history and data.history.user == trans.get_user(): +
    + Undelete + %endif +
    + %endif + <%def name="center_panel()">
    diff --git a/templates/webapps/galaxy/dataset/large_file.mako b/templates/webapps/galaxy/dataset/large_file.mako index 240b585d4b9..4777503d9ae 100644 --- a/templates/webapps/galaxy/dataset/large_file.mako +++ b/templates/webapps/galaxy/dataset/large_file.mako @@ -1,4 +1,7 @@ <%inherit file="/base.mako"/> +<%namespace file="/dataset/display.mako" import="render_deleted_data_message" /> + +${ render_deleted_data_message( data ) }
    This dataset is large and only the first megabyte is shown below.
    diff --git a/templates/webapps/galaxy/dataset/tabular_chunked.mako b/templates/webapps/galaxy/dataset/tabular_chunked.mako index a5c84099497..57d578bc835 100644 --- a/templates/webapps/galaxy/dataset/tabular_chunked.mako +++ b/templates/webapps/galaxy/dataset/tabular_chunked.mako @@ -1,5 +1,5 @@ <%inherit file="/base.mako"/> - +<%namespace file="/dataset/display.mako" import="render_deleted_data_message" /> <%def name="title()">Dataset Display @@ -37,3 +37,5 @@ <%def name="stylesheets()"> ${parent.stylesheets()} + +${ render_deleted_data_message( dataset ) } \ No newline at end of file From 930f938d3ca389f845381d3432ddfa912f0bbdad Mon Sep 17 00:00:00 2001 From: Daniel Blankenberg Date: Wed, 8 May 2013 14:22:34 -0400 Subject: [PATCH 364/523] Display error message when trying to view / download purged datasets. --- lib/galaxy/webapps/galaxy/controllers/dataset.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/galaxy/webapps/galaxy/controllers/dataset.py b/lib/galaxy/webapps/galaxy/controllers/dataset.py index cf19e8019f4..7fa9bd0bd03 100644 --- a/lib/galaxy/webapps/galaxy/controllers/dataset.py +++ b/lib/galaxy/webapps/galaxy/controllers/dataset.py @@ -290,6 +290,8 @@ class DatasetInterface( BaseUIController, UsesAnnotations, UsesHistoryMixin, Use raise paste.httpexceptions.HTTPRequestRangeNotSatisfiable( "Invalid reference dataset id: %s." % str( hda_id ) ) if not self._can_access_dataset( trans, data ): return trans.show_error_message( "You are not allowed to access this dataset" ) + if data.purged: + return trans.show_error_message( "The dataset you are attempting to view has been purged." ) if data.deleted and not ( trans.user_is_admin() or ( data.history and trans.get_user() == data.history.user ) ): return trans.show_error_message( "The dataset you are attempting to view has been deleted." ) if data.state == trans.model.Dataset.states.UPLOAD: From 2b87ccac659ed67808982fbc8597f984e4cdabd7 Mon Sep 17 00:00:00 2001 From: Daniel Blankenberg Date: Wed, 8 May 2013 14:37:33 -0400 Subject: [PATCH 365/523] Fix for incorrect template inheritance causing the tool shed base_panels to be displayed in the galaxy app when logging in after logout. --- templates/user/login.mako | 27 ++++++++++----------------- 1 file changed, 10 insertions(+), 17 deletions(-) diff --git a/templates/user/login.mako b/templates/user/login.mako index d0f78503d14..03ba4131374 100644 --- a/templates/user/login.mako +++ b/templates/user/login.mako @@ -1,20 +1,13 @@ -%if trans.webapp.name == 'galaxy': - <%! - def inherit(context): - if context.get('use_panels'): - return '/webapps/galaxy/base_panels.mako' - else: - return '/base.mako' - %> -%elif trans.webapp.name == 'tool_shed': - <%! - def inherit(context): - if context.get('use_panels'): - return '/webapps/tool_shed/base_panels.mako' - else: - return '/base.mako' - %> -%endif +<%! +#This is a hack, we should restructure templates to avoid this. +def inherit(context): + if context.get('trans').webapp.name == 'galaxy': + return '/webapps/galaxy/base_panels.mako' + elif context.get('trans').webapp.name == 'tool_shed': + return '/webapps/tool_shed/base_panels.mako' + else: + return '/base.mako' +%> <%inherit file="${inherit(context)}"/> From aa0c5c86eb09154c89556104c6ef955106bc98af Mon Sep 17 00:00:00 2001 From: Dave Bouvier Date: Wed, 8 May 2013 15:48:16 -0400 Subject: [PATCH 366/523] Fix for setting metadata externally when running functional tests. --- scripts/functional_tests.py | 54 ++++++++++++++++++++----------------- 1 file changed, 29 insertions(+), 25 deletions(-) diff --git a/scripts/functional_tests.py b/scripts/functional_tests.py index 4b17930663d..a0a7abc183a 100644 --- a/scripts/functional_tests.py +++ b/scripts/functional_tests.py @@ -197,9 +197,11 @@ def main(): db_path = os.environ['GALAXY_TEST_DBPATH'] else: tempdir = tempfile.mkdtemp() - db_path = os.path.join( tempdir, 'database' ) - file_path = os.path.join( db_path, 'files' ) - new_file_path = os.path.join( db_path, 'tmp' ) + db_path = tempfile.mkdtemp( prefix='database_', dir=tempdir ) + # FIXME: This is a workaround for cases where metadata is being set externally. + file_path = os.path.join( 'database', 'files' ) + new_file_path = tempfile.mkdtemp( prefix='new_files_path_', dir=tempdir ) + job_working_directory = tempfile.mkdtemp( prefix='job_working_directory_', dir=tempdir ) if 'GALAXY_TEST_DBURI' in os.environ: database_connection = os.environ['GALAXY_TEST_DBURI'] else: @@ -207,7 +209,8 @@ def main(): kwargs = {} for dir in file_path, new_file_path: try: - os.makedirs( dir ) + if not os.path.exists( dir ): + os.makedirs( dir ) except OSError: pass @@ -226,30 +229,31 @@ def main(): kwargs[ 'object_store' ] = 'distributed' kwargs[ 'distributed_object_store_config_file' ] = 'distributed_object_store_conf.xml.sample' # Build the Universe Application - app = UniverseApplication( job_queue_workers = 5, - id_secret = 'changethisinproductiontoo', - template_path = "templates", - database_connection = database_connection, - file_path = file_path, - new_file_path = new_file_path, - tool_path = tool_path, - update_integrated_tool_panel = False, - tool_config_file = tool_config_file, - datatype_converters_config_file = "datatype_converters_conf.xml.sample", - tool_parse_help = False, - test_conf = "test.conf", - tool_data_table_config_path = tool_data_table_config_path, - shed_tool_data_table_config = shed_tool_data_table_config, - log_destination = "stdout", - use_heartbeat = False, + app = UniverseApplication( admin_users = 'test@bx.psu.edu', + allow_library_path_paste = True, allow_user_creation = True, allow_user_deletion = True, - admin_users = 'test@bx.psu.edu', - allow_library_path_paste = True, - library_import_dir = library_import_dir, - user_library_import_dir = user_library_import_dir, + database_connection = database_connection, + datatype_converters_config_file = "datatype_converters_conf.xml.sample", + file_path = file_path, global_conf = global_conf, - running_functional_tests=True, + id_secret = 'changethisinproductiontoo', + job_queue_workers = 5, + job_working_directory = job_working_directory, + library_import_dir = library_import_dir, + log_destination = "stdout", + new_file_path = new_file_path, + running_functional_tests = True, + shed_tool_data_table_config = shed_tool_data_table_config, + template_path = "templates", + test_conf = "test.conf", + tool_config_file = tool_config_file, + tool_data_table_config_path = tool_data_table_config_path, + tool_path = tool_path, + tool_parse_help = False, + update_integrated_tool_panel = False, + use_heartbeat = False, + user_library_import_dir = user_library_import_dir, **kwargs ) log.info( "Embedded Universe application started" ) From 1a338323d43467f6e6181a5edb19391d3ed5e600 Mon Sep 17 00:00:00 2001 From: Dave Bouvier Date: Wed, 8 May 2013 16:50:00 -0400 Subject: [PATCH 367/523] Update login methods in twilltestcase to be more robust. --- test/base/twilltestcase.py | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/test/base/twilltestcase.py b/test/base/twilltestcase.py index fd6d3371615..a42b4a823d5 100644 --- a/test/base/twilltestcase.py +++ b/test/base/twilltestcase.py @@ -1068,10 +1068,8 @@ class TwillTestCase( unittest.TestCase ): # HACK: don't use panels because late_javascripts() messes up the twill browser and it # can't find form fields (and hence user can't be logged in). self.visit_url( "%s/user/login?use_panels=False" % self.url ) - tc.fv( '1', 'email', email ) - tc.fv( '1', 'redirect', redirect ) - tc.fv( '1', 'password', password ) - tc.submit( 'login_button' ) + self.submit_form( 1, 'login_button', email=email, redirect=redirect, password=password ) + def logout( self ): self.home() self.visit_page( "user/logout" ) @@ -1368,12 +1366,7 @@ class TwillTestCase( unittest.TestCase ): # HACK: don't use panels because late_javascripts() messes up the twill browser and it # can't find form fields (and hence user can't be logged in). self.visit_url( "%s/user/create?cntrller=admin" % self.url ) - tc.fv( '1', 'email', email ) - tc.fv( '1', 'redirect', redirect ) - tc.fv( '1', 'password', password ) - tc.fv( '1', 'confirm', password ) - tc.fv( '1', 'username', username ) - tc.submit( 'create_user_button' ) + self.submit_form( 1, 'create_user_button', email=email, redirect=redirect, password=password, confirm=password, username=username ) previously_created = False username_taken = False invalid_username = False @@ -1396,6 +1389,7 @@ class TwillTestCase( unittest.TestCase ): except: pass return previously_created, username_taken, invalid_username + def reset_password_as_admin( self, user_id, password='testreset' ): """Reset a user password""" self.home() From 1c19a78e989461bd7d41802f8d4cce4447b18b11 Mon Sep 17 00:00:00 2001 From: Daniel Blankenberg Date: Thu, 9 May 2013 10:49:02 -0400 Subject: [PATCH 368/523] Unicode fixes for previewing large datasets. --- lib/galaxy/datatypes/tabular.py | 2 +- templates/webapps/galaxy/dataset/large_file.mako | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/galaxy/datatypes/tabular.py b/lib/galaxy/datatypes/tabular.py index 148cd22b0d7..8d0559a1526 100644 --- a/lib/galaxy/datatypes/tabular.py +++ b/lib/galaxy/datatypes/tabular.py @@ -265,7 +265,7 @@ class Tabular( data.Text ): while cursor and ck_data[-1] != '\n': ck_data += cursor cursor = f.read(1) - return to_json_string({'ck_data': ck_data, 'ck_index': ck_index+1}) + return to_json_string( { 'ck_data': util.unicodify( ck_data ), 'ck_index': ck_index + 1 } ) def display_data(self, trans, dataset, preview=False, filename=None, to_ext=None, chunk=None): preview = util.string_as_bool( preview ) diff --git a/templates/webapps/galaxy/dataset/large_file.mako b/templates/webapps/galaxy/dataset/large_file.mako index 4777503d9ae..68148d3cb6f 100644 --- a/templates/webapps/galaxy/dataset/large_file.mako +++ b/templates/webapps/galaxy/dataset/large_file.mako @@ -10,5 +10,5 @@ ${ render_deleted_data_message( data ) }
    -${ unicode( truncated_data, 'utf-8' ) | h }
    +${ util.unicodify( truncated_data ) | h }
     
    From 4f3acda1ca8faed51a386c2d2edbd7e57a165415 Mon Sep 17 00:00:00 2001 From: Jeremy Goecks Date: Thu, 9 May 2013 11:44:45 -0400 Subject: [PATCH 369/523] Visualization framework: replace summary_tree with bigwig datatype for all feature track coverage data, yielding better performance and a standardized approach for working with coverage data. Update Trackster to work with bigwig coverage data. --- datatypes_conf.xml.sample | 24 +- lib/galaxy/datatypes/binary.py | 2 +- .../converters/bam_to_bigwig_converter.xml | 9 +- .../bam_to_summary_tree_converter.xml | 14 - .../bed_gff_or_vcf_to_bigwig_converter.xml | 25 ++ .../bed_to_summary_tree_converter.xml | 14 - .../encodepeak_to_summary_tree_converter.xml | 20 -- .../gff_to_summary_tree_converter.xml | 14 - .../interval_to_summary_tree_converter.py | 63 ----- .../interval_to_summary_tree_converter.xml | 20 -- .../sam_or_bam_to_summary_tree_converter.py | 42 --- .../sam_to_summary_tree_converter.xml | 14 - .../vcf_to_summary_tree_converter.py | 30 --- .../vcf_to_summary_tree_converter.xml | 14 - lib/galaxy/datatypes/interval.py | 10 +- lib/galaxy/datatypes/tabular.py | 4 +- .../visualization/data_providers/genome.py | 74 ------ .../visualization/data_providers/registry.py | 1 - lib/galaxy/visualization/tracks/summary.py | 117 --------- lib/galaxy/webapps/galaxy/api/datasets.py | 49 ++-- static/scripts/utils/config.js | 1 - static/scripts/viz/trackster/painters.js | 44 ---- static/scripts/viz/trackster/tracks.js | 246 +++++++++++------- static/scripts/viz/visualization.js | 5 +- 24 files changed, 227 insertions(+), 629 deletions(-) delete mode 100644 lib/galaxy/datatypes/converters/bam_to_summary_tree_converter.xml create mode 100644 lib/galaxy/datatypes/converters/bed_gff_or_vcf_to_bigwig_converter.xml delete mode 100644 lib/galaxy/datatypes/converters/bed_to_summary_tree_converter.xml delete mode 100644 lib/galaxy/datatypes/converters/encodepeak_to_summary_tree_converter.xml delete mode 100644 lib/galaxy/datatypes/converters/gff_to_summary_tree_converter.xml delete mode 100644 lib/galaxy/datatypes/converters/interval_to_summary_tree_converter.py delete mode 100644 lib/galaxy/datatypes/converters/interval_to_summary_tree_converter.xml delete mode 100644 lib/galaxy/datatypes/converters/sam_or_bam_to_summary_tree_converter.py delete mode 100644 lib/galaxy/datatypes/converters/sam_to_summary_tree_converter.xml delete mode 100644 lib/galaxy/datatypes/converters/vcf_to_summary_tree_converter.py delete mode 100644 lib/galaxy/datatypes/converters/vcf_to_summary_tree_converter.xml delete mode 100644 lib/galaxy/visualization/tracks/summary.py diff --git a/datatypes_conf.xml.sample b/datatypes_conf.xml.sample index 30c8fd0786c..b0d3481001f 100644 --- a/datatypes_conf.xml.sample +++ b/datatypes_conf.xml.sample @@ -9,12 +9,7 @@ - - + @@ -25,7 +20,7 @@ - + @@ -51,7 +46,7 @@ - + @@ -93,7 +88,7 @@ - + @@ -103,7 +98,7 @@ - + @@ -115,7 +110,7 @@ - + @@ -156,7 +151,7 @@ - + @@ -172,7 +167,7 @@ - + @@ -190,7 +185,7 @@ - + @@ -203,7 +198,6 @@ - diff --git a/lib/galaxy/datatypes/binary.py b/lib/galaxy/datatypes/binary.py index 5cbe223c81e..2a478d7f6df 100644 --- a/lib/galaxy/datatypes/binary.py +++ b/lib/galaxy/datatypes/binary.py @@ -103,7 +103,7 @@ class Bam( Binary ): """Class describing a BAM binary file""" file_ext = "bam" track_type = "ReadTrack" - data_sources = { "data": "bai", "index": [ "bigwig", "summary_tree" ] } + data_sources = { "data": "bai", "index": "bigwig" } MetadataElement( name="bam_index", desc="BAM Index File", param=metadata.FileParameter, file_ext="bai", readonly=True, no_value=None, visible=False, optional=True ) diff --git a/lib/galaxy/datatypes/converters/bam_to_bigwig_converter.xml b/lib/galaxy/datatypes/converters/bam_to_bigwig_converter.xml index 739cca3d268..35f7b224083 100644 --- a/lib/galaxy/datatypes/converters/bam_to_bigwig_converter.xml +++ b/lib/galaxy/datatypes/converters/bam_to_bigwig_converter.xml @@ -1,7 +1,14 @@
    "),ar,aq,av;for(ar in ap){aq=ap[ar];av=$("").appendTo(au);$("
    ").appendTo(av).text(ar);$("").attr("align","left").appendTo(av).text(typeof(aq)==="number"?V(aq,2):aq)}af.append($("
    ").append(au));ac[ak]=af}af.appendTo($(this).parents(".track-content").children(".overlay"));var ag=an+parseInt(ad.html_elt.css("left"))-af.width()/2,ae=am+parseInt(ad.html_elt.css("top"))+7;af.css("left",ag+"px").css("top",ae+"px")}else{if(!ao.isPropagationStopped()){ao.stopPropagation();$(this).siblings().each(function(){$(this).trigger(ao)})}}}).mouseleave(function(){$(this).parents(".track-content").children(".overlay").children(".feature-popup").remove()})};var g=function(ad,ac,ae){p(ae,{drag_handle_class:"draghandle"});q.call(this,ad,ac,ae);this.dataset=new X.Dataset(ae.dataset);this.dataset_check_type="converted_datasets_state";this.data_url_extra_params={};this.data_query_wait=("data_query_wait" in ae?ae.data_query_wait:K);this.data_manager=("data_manager" in ae?ae.data_manager:new w.GenomeDataManager({dataset:this.dataset,genome:new w.Genome({key:ad.dbkey,chroms_info:{chrom_info:ad.chrom_data}}),data_mode_compatible:this.data_and_mode_compatible,can_subset:this.can_subset}));this.min_height_px=16;this.max_height_px=800;this.visible_height_px=0;this.content_div=$("
    ").appendTo(this.container_div);if(this.container){this.container.content_div.append(this.container_div);if(!("resize" in ae)||ae.resize){this.add_resize_handle()}}};p(g.prototype,q.prototype,{action_icons_def:[{name:"mode_icon",title:"Set display mode",css_class:"chevron-expand",on_click_fn:function(){}},q.prototype.action_icons_def[0],{name:"overview_icon",title:"Set as overview",css_class:"overview-icon",on_click_fn:function(ac){ac.view.set_overview(ac)}},q.prototype.action_icons_def[1],{name:"filters_icon",title:"Filters",css_class:"filters-icon",on_click_fn:function(ac){if(ac.filters_manager.visible()){ac.filters_manager.clear_filters()}else{ac.filters_manager.init_filters()}ac.filters_manager.toggle()}},{name:"tools_icon",title:"Tool",css_class:"hammer",on_click_fn:function(ac){ac.dynamic_tool_div.toggle();if(ac.dynamic_tool_div.is(":visible")){ac.set_name(ac.name+ac.tool_region_and_parameters_str())}else{ac.revert_name()}$(".bs-tooltip").remove()}},{name:"param_space_viz_icon",title:"Tool parameter space visualization",css_class:"arrow-split",on_click_fn:function(ac){var af='Tool: <%= track.tool.name %>
    Dataset: <%= track.name %>
    Region(s): ',ae=ab.template(af,{track:ac});var ah=function(){hide_modal();$(window).unbind("keypress.check_enter_esc")},ad=function(){var aj=$('select[name="regions"] option:selected').val(),al,ai=new w.GenomeRegion({chrom:view.chrom,start:view.low,end:view.high}),ak=ab.map($(".bookmark"),function(am){return new w.GenomeRegion({from_str:$(am).children(".position").text()})});if(aj==="cur"){al=[ai]}else{if(aj==="bookmarks"){al=ak}else{al=[ai].concat(ak)}}hide_modal();window.location.href=galaxy_paths.get("sweepster_url")+"?"+$.param({dataset_id:ac.dataset.id,hda_ldda:ac.dataset.get("hda_ldda"),regions:JSON.stringify(new Backbone.Collection(al).toJSON())})},ag=function(ai){if((ai.keyCode||ai.which)===27){ah()}else{if((ai.keyCode||ai.which)===13){ad()}}};show_modal("Visualize tool parameter space and output from different parameter settings?",ae,{No:ah,Yes:ad})}},q.prototype.action_icons_def[2]],can_draw:function(){if(this.dataset.id&&q.prototype.can_draw.call(this)){return true}return false},build_container_div:function(){return $("
    ").addClass("track").attr("id","track_"+this.id).css("position","relative")},build_header_div:function(){var ac=$("
    ");if(this.view.editor){this.drag_div=$("
    ").addClass(this.drag_handle_class).appendTo(ac)}this.name_div=$("
    ").addClass("track-name").appendTo(ac).text(this.name).attr("id",this.name.replace(/\s+/g,"-").replace(/[^a-zA-Z0-9\-]/g,"").toLowerCase());return ac},set_dataset:function(ac){this.dataset=ac;this.data_manager.set("dataset",ac)},on_resize:function(){this.request_draw(true)},add_resize_handle:function(){var ac=this;var af=false;var ae=false;var ad=$("
    ");$(ac.container_div).hover(function(){if(ac.content_visible){af=true;ad.show()}},function(){af=false;if(!ae){ad.hide()}});ad.hide().bind("dragstart",function(ag,ah){ae=true;ah.original_height=$(ac.content_div).height()}).bind("drag",function(ah,ai){var ag=Math.min(Math.max(ai.original_height+ai.deltaY,ac.min_height_px),ac.max_height_px);$(ac.tiles_div).css("height",ag);ac.visible_height_px=(ac.max_height_px===ag?0:ag);ac.on_resize()}).bind("dragend",function(ag,ah){ac.tile_cache.clear();ae=false;if(!af){ad.hide()}ac.config.values.height=ac.visible_height_px;ac.changed()}).appendTo(ac.container_div)},set_display_modes:function(af,ai){this.display_modes=af;this.mode=(ai?ai:(this.config&&this.config.values.mode?this.config.values.mode:this.display_modes[0]));this.action_icons.mode_icon.attr("title","Set display mode (now: "+this.mode+")");var ad=this,ag={};for(var ae=0,ac=ad.display_modes.length;ae").text("View error").click(function(){show_modal("Trackster Error","
    "+ag.message+"
    ",{Close:hide_modal})}));ad.tiles_div.append($("").text(" "));ad.tiles_div.append($("").text("Try again").click(function(){ad.init(true)}))}}else{if(ag==="no converter"){ad.container_div.addClass("error");ad.tiles_div.text(J)}else{if(ag==="no data"||(ag.data!==undefined&&(ag.data===null||ag.data.length===0))){ad.container_div.addClass("nodata");ad.tiles_div.text(E)}else{if(ag==="pending"){ad.container_div.addClass("pending");ad.tiles_div.html(u);setTimeout(function(){ad.init()},ad.data_query_wait)}else{if(ag==="data"||ag.status==="data"){if(ag.valid_chroms){ad.valid_chroms=ag.valid_chroms;ad.update_icons()}ad.tiles_div.text(U);if(ad.view.chrom){ad.tiles_div.text("");ad.tiles_div.css("height",ad.visible_height_px+"px");ad.enabled=true;$.when.apply($,ad.predraw_init()).done(function(){ac.resolve();ad.container_div.removeClass("nodata error pending");ad.request_draw()})}else{ac.resolve()}}}}}}});this.update_icons();return ac},predraw_init:function(){var ac=this;return $.getJSON(ac.dataset.url(),{data_type:"data",stats:true,chrom:ac.view.chrom,low:0,high:ac.view.max_high,hda_ldda:ac.dataset.get("hda_ldda")},function(ad){ac.container_div.addClass("line-track");var af=ad.data;if(af&&af.min&&af.max){var ae=af.min,ag=af.max;ae=Math.floor(Math.min(0,Math.max(ae,af.mean-2*af.sd)));ag=Math.ceil(Math.max(0,Math.min(ag,af.mean+2*af.sd)));ac.prefs.min_value=ae;ac.prefs.max_value=ag}})},get_drawables:function(){return this}});var N=function(ae,ad,af){g.call(this,ae,ad,af);var ac=this;l(ac.container_div,ac.drag_handle_class,".group",ac);this.filters_manager=new i.FiltersManager(this,("filters" in af?af.filters:null));this.data_manager.set("filters_manager",this.filters_manager);this.filters_available=false;this.tool=("tool" in af&&af.tool?new r(this,af.tool,af.tool_state):null);this.tile_cache=new w.Cache(R);this.left_offset=0;if(this.header_div){this.set_filters_manager(this.filters_manager);if(this.tool){this.dynamic_tool_div=this.tool.parent_div;this.header_div.after(this.dynamic_tool_div)}}this.tiles_div=$("
    ").addClass("tiles").appendTo(this.content_div);this.overlay_div=$("
    ").addClass("overlay").appendTo(this.content_div);if(af.mode){this.change_mode(af.mode)}};p(N.prototype,q.prototype,g.prototype,{action_icons_def:g.prototype.action_icons_def.concat([{name:"show_more_rows_icon",title:"To minimize track height, not all feature rows are displayed. Click to display more rows.",css_class:"exclamation",on_click_fn:function(ac){$(".bs-tooltip").remove();ac.slotters[ac.view.resolution_px_b].max_rows*=2;ac.request_draw(true)},hide:true}]),copy:function(ac){var ad=this.to_dict();p(ad,{data_manager:this.data_manager});var ae=new this.constructor(this.view,ac,ad);ae.change_mode(this.mode);ae.enabled=this.enabled;return ae},set_filters_manager:function(ac){this.filters_manager=ac;this.header_div.after(this.filters_manager.parent_div)},to_dict:function(){return{track_type:this.get_type(),name:this.name,dataset:{id:this.dataset.id,hda_ldda:this.dataset.get("hda_ldda")},prefs:this.prefs,mode:this.mode,filters:this.filters_manager.to_dict(),tool_state:(this.tool?this.tool.state_dict():{})}},set_min_max:function(){var ac=this;return $.getJSON(ac.dataset.url(),{data_type:"data",stats:true,chrom:ac.view.chrom,low:0,high:ac.view.max_high,hda_ldda:ac.dataset.get("hda_ldda")},function(ad){var af=ad.data;if(isNaN(parseFloat(ac.prefs.min_value))||isNaN(parseFloat(ac.prefs.max_value))){var ae=af.min,ag=af.max;ae=Math.floor(Math.min(0,Math.max(ae,af.mean-2*af.sd)));ag=Math.ceil(Math.max(0,Math.min(ag,af.mean+2*af.sd)));ac.prefs.min_value=ae;ac.prefs.max_value=ag}})},change_mode:function(ad){var ac=this;ac.mode=ad;ac.config.values.mode=ad;if(ad==="Auto"){this.data_manager.clear()}ac.request_draw(true);this.action_icons.mode_icon.attr("title","Set display mode (now: "+ac.mode+")");return ac},update_icons:function(){var ac=this;if(ac.filters_available){ac.action_icons.filters_icon.show()}else{ac.action_icons.filters_icon.hide()}if(ac.tool){ac.action_icons.tools_icon.show();ac.action_icons.param_space_viz_icon.show()}else{ac.action_icons.tools_icon.hide();ac.action_icons.param_space_viz_icon.hide()}},_gen_tile_cache_key:function(ad,ac){return ad+"_"+ac},request_draw:function(ae,ad,ac){if(ae){this.tile_cache.clear()}this.view.request_redraw(ad,ac,this)},before_draw:function(){this.max_height_px=0},_draw:function(ad,an){if(!this.can_draw()){return}var al=this.view.low,ah=this.view.high,ak=ah-al,ae=this.view.container.width(),ap=this.view.resolution_px_b,ag=this.view.resolution_b_px;if(this.is_overview){al=this.view.max_low;ah=this.view.max_high;ag=(view.max_high-view.max_low)/ae;ap=1/ag}this.before_draw();this.tiles_div.children().addClass("remove");var ac=Math.floor(al/(ag*S)),am,ai,aj=[],ao=[];while((ac*S*ag)").text(ad.prefs[ac]).make_text_editable({num_cols:12,on_finish:function(aj){$(".bs-tooltip").remove();var aj=V(parseFloat(aj),3);ad.prefs[ac]=(!isNaN(aj)?aj:null);ah()},help_text:"Set "+ai+" value"}).addClass("yaxislabel "+ag).css("color",this.prefs.label_color);this.container_div.prepend(ae)}},postdraw_actions:function(af,ag,ai,ac){var ae=ab.filter(af,function(aj){return !(aj instanceof M)});if(ae.length>0&&ae.length1){aB.globalAlpha=0.5;aB.globalCompositeOperation="source-over"}ab.each(ah,function(aF,aE){az=aF.draw_tile(aw[aE],aB,ax[aE],ag,am,ap,aC)});if(az!==undefined){af.tile_cache.set_elt(an,az);af.show_tile(az,ao,ap)}ac.resolve(az)});return ac},get_canvas_height:function(ac,ae,af,ad){return this.visible_height_px},_draw_line_track_tile:function(ac,ae,ai,ag,ah,aj){var af=ae.canvas,ad=new L.LinePainter(ac.data,ah.get("start"),ah.get("end"),this.prefs,ai);ad.draw(ae,af.width,af.height,aj);return new M(this,ah,ag,af,ac.data)},draw_tile:function(ac,ad,ah,af,ag,ai,ae){console.log("Warning: TiledTrack.draw_tile() not implemented.")},show_tile:function(ae,ah,ai){var ad=this,ac=ae.html_elt;ae.predisplay_actions();var ag=(ae.low-(this.is_overview?this.view.max_low:this.view.low))*ai;if(this.left_offset){ag-=this.left_offset}ac.css({position:"absolute",top:0,left:ag});if(ac.hasClass("remove")){ac.removeClass("remove")}else{ah.append(ac)}ae.html_elt.height("auto");this.max_height_px=Math.max(this.max_height_px,ae.html_elt.height());ae.html_elt.parent().children().css("height",this.max_height_px+"px");var af=this.max_height_px;if(this.visible_height_px!==0){af=Math.min(this.max_height_px,this.visible_height_px)}this.tiles_div.css("height",af+"px")},_get_tile_bounds:function(ac,ad){var af=Math.floor(ac*S*ad),ag=Math.ceil(S*ad),ae=(af+ag<=this.view.max_high?af+ag:this.view.max_high);return new w.GenomeRegion({chrom:this.view.chrom,start:af,end:ae})},tool_region_and_parameters_str:function(ae){var ac=this,ad=(ae!==undefined?ae.toString():"all");return" - region=["+ad+"], parameters=["+ac.tool.get_param_values().join(", ")+"]"},data_and_mode_compatible:function(ac,ad){return true},can_subset:function(ac){return false},init_for_tool_data:function(){this.data_manager.set("data_type","raw_data");this.data_query_wait=1000;this.dataset_check_type="state";this.normal_postdraw_actions=this.postdraw_actions;this.postdraw_actions=function(ae,af,ah,ac){var ad=this;ad.normal_postdraw_actions(ae,af,ah,ac);ad.dataset_check_type="converted_datasets_state";ad.data_query_wait=K;var ag=new k.ServerStateDeferred({url:ad.dataset_state_url,url_params:{dataset_id:ad.dataset.id,hda_ldda:ad.dataset.get("hda_ldda")},interval:ad.data_query_wait,success_fn:function(ai){return ai!=="pending"}});$.when(ag.go()).then(function(){ad.data_manager.set("data_type","data")});ad.postdraw_actions=ad.normal_postdraw_actions}}});var W=function(ad,ac){var ae={resize:false};g.call(this,ad,ac,ae);this.container_div.addClass("label-track")};p(W.prototype,g.prototype,{build_header_div:function(){},init:function(){this.enabled=true},predraw_init:function(){},_draw:function(){var ae=this.view,af=ae.high-ae.low,ai=Math.floor(Math.pow(10,Math.floor(Math.log(af)/Math.log(10)))),ac=Math.floor(ae.low/ai)*ai,ag=this.view.container.width(),ad=$("
    ");while(ac"+commatize(ac)+"
    ").css({position:"absolute",left:ah-1}));ac+=ai}this.content_div.children(":first").remove();this.content_div.append(ad)}});var f=function(ad,ac,ag){this.display_modes=A;N.call(this,ad,ac,ag);this.drawables=[];if("drawables" in ag){var af;for(var ae=0;aethis.left_offset){this.left_offset=af.left_offset}}this.enabled=true}ab.each(this.drawables,function(ah){if(ah instanceof c||ah instanceof T){ah.change_mode("Coverage")}});this.update_icons();this.obj_type="CompositeTrack"};p(f.prototype,N.prototype,{action_icons_def:[{name:"composite_icon",title:"Show individual tracks",css_class:"layers-stack",on_click_fn:function(ac){$(".bs-tooltip").remove();ac.show_group()}}].concat(N.prototype.action_icons_def),to_dict:y.prototype.to_dict,add_drawable:y.prototype.add_drawable,unpack_drawables:y.prototype.unpack_drawables,change_mode:function(ac){N.prototype.change_mode.call(this,ac);for(var ad=0;adag){ag=aj}}for(var ae=0;aethis.view.canvas_manager.char_width_px){this.tiles_div.show();return N.prototype.draw_helper.call(this,ae,af,ac,ag,ah,ad)}else{this.tiles_div.hide();return null}},can_subset:function(ac){return true},draw_tile:function(af,al,ag,ad,ai,am){var ae=this.data_manager.subset_entry(af,ai),ak=ae.data;var ac=al.canvas;al.font=al.canvas.manager.default_font;al.textAlign="center";for(var ah=0,aj=ak.length;ahI){mode="Squish"}else{mode="Pack"}}return mode},get_canvas_height:function(ac,ag,ah,ad){if(ag==="Coverage"||ac.dataset_type==="bigwig"){return this.summary_draw_height}else{var af=this.incremental_slots(ah,ac.data,ag);var ae=new (this.painter)(null,null,null,this.prefs,ag);return Math.max(aa,ae.get_required_height(af,ad))}},draw_tile:function(am,aq,ao,ar,af,aj,ae){var ap=this,ad=aq.canvas,ay=af.get("start"),ac=af.get("end"),ag=this.left_offset;if(am.dataset_type==="bigwig"){return this._draw_line_track_tile(am,aq,ao,ar,af,aj)}var ai=[],an=this.slotters[aj].slots;all_slotted=true;if(am.data){var ak=this.filters_manager.filters;for(var at=0,av=am.data.length;at").text("Summary").addClass("yaxislabel variant top").css({"font-size":ac+"px",top:(this.prefs.summary_height-ac)/2+"px"}));if(this.prefs.show_sample_data){var ag=this.dataset.get("metadata").get("sample_names").join("
    ");this.tiles_div.prepend($("
    ").html(ag).addClass("yaxislabel variant top sample").css({top:this.prefs.summary_height+2,}))}}ac=(this.mode==="Squish"?5:10)+"px";$(this.tiles_div).find(".sample").css({"font-size":ac,"line-height":ac});$(this.tiles_div).find(".yaxislabel").css("color",this.prefs.label_color)}else{this.container_div.find(".yaxislabel.variant").remove()}}});var T=function(ae,ad,ag){c.call(this,ae,ad,ag);var af=k.get_random_color(),ac=k.get_random_color([af,"#ffffff"]);this.config=new F({track:this,params:[{key:"name",label:"Name",type:"text",default_value:this.name},{key:"block_color",label:"Block and sense strand color",type:"color",default_value:af},{key:"reverse_strand_color",label:"Antisense strand color",type:"color",default_value:ac},{key:"label_color",label:"Label color",type:"color",default_value:"black"},{key:"show_insertions",label:"Show insertions",type:"bool",default_value:false},{key:"show_differences",label:"Show differences only",type:"bool",default_value:true},{key:"show_counts",label:"Show summary counts",type:"bool",default_value:true},{key:"mode",type:"string",default_value:this.mode,hidden:true},{key:"min_value",label:"Histogram minimum",type:"float",default_value:null,help:"clear value to set automatically"},{key:"max_value",label:"Histogram maximum",type:"float",default_value:null,help:"clear value to set automatically"}],saved_values:ag.prefs,onchange:function(){this.track.set_name(this.track.prefs.name);this.track.request_draw(true)}});this.prefs=this.config.values;this.painter=(ae.reference_track?L.RefBasedReadPainter:L.ReadPainter);this.update_icons()};p(T.prototype,q.prototype,N.prototype,c.prototype);var d={CompositeTrack:f,DrawableGroup:Q,DiagonalHeatmapTrack:s,FeatureTrack:c,LineTrack:h,ReadTrack:T,VariantTrack:Z,VcfTrack:Z};var o=function(ae,ad,ac){if("copy" in ae){return ae.copy(ac)}else{var af=ae.obj_type;if(!af){af=ae.track_type}return new d[af](ad,ac,ae)}};return{TracksterView:Y,DrawableGroup:Q,LineTrack:h,FeatureTrack:c,DiagonalHeatmapTrack:s,ReadTrack:T,VariantTrack:Z,CompositeTrack:f,object_from_template:o}}); \ No newline at end of file +define(["libs/underscore","viz/visualization","viz/trackster/util","viz/trackster/slotting","viz/trackster/painters","mvc/data","viz/trackster/filters"],function(ab,w,k,t,L,X,i){var p=ab.extend;var m={};var j=function(ac,ad){m[ac.attr("id")]=ad};var l=function(ac,ae,ag,af){ag=".group";var ad={};m[ac.attr("id")]=af;ac.bind("drag",{handle:"."+ae,relative:true},function(ao,ap){var an=$(this),at=$(this).parent(),ak=at.children(),am=m[$(this).attr("id")],aj,ai,aq,ah,al;ai=$(this).parents(ag);if(ai.length!==0){aq=ai.position().top;ah=aq+ai.outerHeight();if(ap.offsetYah){$(this).insertAfter(ai);var ar=m[ai.attr("id")];ar.remove_drawable(am);ar.container.add_drawable(am);return}}}ai=null;for(al=0;al=aq&&ap.offsetY<=ah){if(ap.offsetY-aq0?al-1:al))}}}).bind("dragstart",function(){ad["border-top"]=ac.css("border-top");ad["border-bottom"]=ac.css("border-bottom");$(this).css({"border-top":"1px solid blue","border-bottom":"1px solid blue"})}).bind("dragend",function(){$(this).css(ad)})};var aa=16,G=9,D=20,z=100,I=12000,S=400,K=5000,v=100,n="Cannot display dataset due to an error. ",J="A converter for this dataset is not installed. Please check your datatypes_conf.xml file.",E="No data for this chrom/contig.",u="Preparing data. This can take a while for a large dataset. If the visualization is saved and closed, preparation will continue in the background.",x="Tool cannot be rerun: ",a="Loading data...",U="Ready for display",R=10,H=20,A=["Histogram","Line","Filled","Intensity"];function V(ad,ac){if(!ac){ac=0}var ae=Math.pow(10,ac);return Math.round(ad*ae)/ae}var q=function(ad,ac,af){if(!q.id_counter){q.id_counter=0}this.id=q.id_counter++;this.name=af.name;this.view=ad;this.container=ac;this.config=new F({track:this,params:[{key:"name",label:"Name",type:"text",default_value:this.name}],saved_values:af.prefs,onchange:function(){this.track.set_name(this.track.config.values.name)}});this.prefs=this.config.values;this.drag_handle_class=af.drag_handle_class;this.is_overview=false;this.action_icons={};this.content_visible=true;this.container_div=this.build_container_div();this.header_div=this.build_header_div();if(this.header_div){this.container_div.append(this.header_div);this.icons_div=$("
    ").css("float","left").hide().appendTo(this.header_div);this.build_action_icons(this.action_icons_def);this.header_div.append($("
    "));this.header_div.dblclick(function(ag){ag.stopPropagation()});var ae=this;this.container_div.hover(function(){ae.icons_div.show()},function(){ae.icons_div.hide()});$("
    ").appendTo(this.container_div)}};q.prototype.action_icons_def=[{name:"toggle_icon",title:"Hide/show content",css_class:"toggle",on_click_fn:function(ac){if(ac.content_visible){ac.action_icons.toggle_icon.addClass("toggle-expand").removeClass("toggle");ac.hide_contents();ac.content_visible=false}else{ac.action_icons.toggle_icon.addClass("toggle").removeClass("toggle-expand");ac.content_visible=true;ac.show_contents()}}},{name:"settings_icon",title:"Edit settings",css_class:"settings-icon",on_click_fn:function(ad){var af=function(){hide_modal();$(window).unbind("keypress.check_enter_esc")},ac=function(){ad.config.update_from_form($(".dialog-box"));hide_modal();$(window).unbind("keypress.check_enter_esc")},ae=function(ag){if((ag.keyCode||ag.which)===27){af()}else{if((ag.keyCode||ag.which)===13){ac()}}};$(window).bind("keypress.check_enter_esc",ae);show_modal("Configure",ad.config.build_form(),{Cancel:af,OK:ac})}},{name:"remove_icon",title:"Remove",css_class:"remove-icon",on_click_fn:function(ac){$(".bs-tooltip").remove();ac.remove()}}];p(q.prototype,{init:function(){},changed:function(){this.view.changed()},can_draw:function(){if(this.enabled&&this.content_visible){return true}return false},request_draw:function(){},_draw:function(){},to_dict:function(){},set_name:function(ac){this.old_name=this.name;this.name=ac;this.name_div.text(this.name)},revert_name:function(){if(this.old_name){this.name=this.old_name;this.name_div.text(this.name)}},remove:function(){this.changed();this.container.remove_drawable(this);var ac=this.view;this.container_div.hide(0,function(){$(this).remove();ac.update_intro_div()})},build_container_div:function(){},build_header_div:function(){},add_action_icon:function(ad,ai,ah,ag,ac,af){var ae=this;this.action_icons[ad]=$("").attr("href","javascript:void(0);").attr("title",ai).addClass("icon-button").addClass(ah).tooltip().click(function(){ag(ae)}).appendTo(this.icons_div);if(af){this.action_icons[ad].hide()}},build_action_icons:function(ac){var ae;for(var ad=0;ad").addClass("content-div").attr("id","group_"+this.id+"_content_div").appendTo(this.container_div);j(this.container_div,this);j(this.content_div,this);l(this.container_div,this.drag_handle_class,".group",this);this.filters_manager=new i.FiltersManager(this);this.header_div.after(this.filters_manager.parent_div);this.saved_filters_managers=[];if("drawables" in af){this.unpack_drawables(af.drawables)}if("filters" in af){var ae=this.filters_manager;this.filters_manager=new i.FiltersManager(this,af.filters);ae.parent_div.replaceWith(this.filters_manager.parent_div);if(af.filters.visible){this.setup_multitrack_filtering()}}};p(Q.prototype,q.prototype,y.prototype,{action_icons_def:[q.prototype.action_icons_def[0],q.prototype.action_icons_def[1],{name:"composite_icon",title:"Show composite track",css_class:"layers-stack",on_click_fn:function(ac){$(".bs-tooltip").remove();ac.show_composite_track()}},{name:"filters_icon",title:"Filters",css_class:"filters-icon",on_click_fn:function(ac){if(ac.filters_manager.visible()){ac.filters_manager.clear_filters();ac._restore_filter_managers()}else{ac.setup_multitrack_filtering();ac.request_draw(true)}ac.filters_manager.toggle()}},q.prototype.action_icons_def[2]],build_container_div:function(){var ac=$("
    ").addClass("group").attr("id","group_"+this.id);if(this.container){this.container.content_div.append(ac)}return ac},build_header_div:function(){var ac=$("
    ").addClass("track-header");ac.append($("
    ").addClass(this.drag_handle_class));this.name_div=$("
    ").addClass("track-name").text(this.name).appendTo(ac);return ac},hide_contents:function(){this.tiles_div.hide()},show_contents:function(){this.tiles_div.show();this.request_draw()},update_icons:function(){var ae=this.drawables.length;if(ae===0){this.action_icons.composite_icon.hide();this.action_icons.filters_icon.hide()}else{if(ae===1){if(this.drawables[0] instanceof f){this.action_icons.composite_icon.show()}this.action_icons.filters_icon.hide()}else{var al,ak,ai,ao=true,ag=this.drawables[0].get_type(),ac=0;for(al=0;al1&&ac===this.drawables.length){var ap={},ad;ai=this.drawables[0];for(ak=0;ak0){this.action_icons.filters_icon.show()}else{this.action_icons.filters_icon.hide()}}else{this.action_icons.filters_icon.hide()}}}},_restore_filter_managers:function(){for(var ac=0;ac0){this.saved_filters_managers=[];for(var ac=0;ac").addClass("top-container").appendTo(ae);this.browser_content_div=$("
    ").addClass("content").css("position","relative").appendTo(ae);this.bottom_container=$("
    ").addClass("bottom-container").appendTo(ae);this.top_labeltrack=$("
    ").addClass("top-labeltrack").appendTo(this.top_container);this.viewport_container=$("
    ").addClass("viewport-container").attr("id","viewport-container").appendTo(this.browser_content_div);this.content_div=this.viewport_container;j(this.viewport_container,ac);this.intro_div=$("
    ").addClass("intro").appendTo(this.viewport_container).hide();var af=$("
    ").text("Add Datasets to Visualization").addClass("action-button").appendTo(this.intro_div).click(function(){w.select_datasets(select_datasets_url,add_track_async_url,{"f-dbkey":ac.dbkey},function(ag){ab.each(ag,function(ah){ac.add_drawable(o(ah,ac,ac))})})});this.nav_labeltrack=$("
    ").addClass("nav-labeltrack").appendTo(this.bottom_container);this.nav_container=$("
    ").addClass("trackster-nav-container").prependTo(this.top_container);this.nav=$("
    ").addClass("trackster-nav").appendTo(this.nav_container);this.overview=$("
    ").addClass("overview").appendTo(this.bottom_container);this.overview_viewport=$("
    ").addClass("overview-viewport").appendTo(this.overview);this.overview_close=$("").attr("href","javascript:void(0);").attr("title","Close overview").addClass("icon-button overview-close tooltip").hide().appendTo(this.overview_viewport);this.overview_highlight=$("
    ").addClass("overview-highlight").hide().appendTo(this.overview_viewport);this.overview_box_background=$("
    ").addClass("overview-boxback").appendTo(this.overview_viewport);this.overview_box=$("
    ").addClass("overview-box").appendTo(this.overview_viewport);this.default_overview_height=this.overview_box.height();this.nav_controls=$("
    ").addClass("nav-controls").appendTo(this.nav);this.chrom_select=$("").addClass("nav-input").hide().bind("keyup focusout",ad).appendTo(this.nav_controls);this.location_span=$("").addClass("location").attr("original-title","Click to change location").tooltip({placement:"bottom"}).appendTo(this.nav_controls);this.location_span.click(function(){ac.location_span.hide();ac.chrom_select.hide();ac.nav_input.val(ac.chrom+":"+ac.low+"-"+ac.high);ac.nav_input.css("display","inline-block");ac.nav_input.select();ac.nav_input.focus();ac.nav_input.autocomplete({source:function(ai,ag){var aj=[],ah=$.map(ac.get_tracks(c),function(ak){return ak.data_manager.search_features(ai.term).success(function(al){aj=aj.concat(al)})});$.when.apply($,ah).done(function(){ag($.map(aj,function(ak){return{label:ak[0],value:ak[1]}}))})}})});if(this.vis_id!==undefined){this.hidden_input=$("").attr("type","hidden").val(this.vis_id).appendTo(this.nav_controls)}this.zo_link=$("").attr("id","zoom-out").attr("title","Zoom out").tooltip({placement:"bottom"}).click(function(){ac.zoom_out();ac.request_redraw()}).appendTo(this.nav_controls);this.zi_link=$("").attr("id","zoom-in").attr("title","Zoom in").tooltip({placement:"bottom"}).click(function(){ac.zoom_in();ac.request_redraw()}).appendTo(this.nav_controls);this.load_chroms_deferred=this.load_chroms({low:0});this.chrom_select.bind("change",function(){ac.change_chrom(ac.chrom_select.val())});this.browser_content_div.click(function(ag){$(this).find("input").trigger("blur")});this.browser_content_div.bind("dblclick",function(ag){ac.zoom_in(ag.pageX,this.viewport_container)});this.overview_box.bind("dragstart",function(ag,ah){this.current_x=ah.offsetX}).bind("drag",function(ag,ai){var aj=ai.offsetX-this.current_x;this.current_x=ai.offsetX;var ah=Math.round(aj/ac.viewport_container.width()*(ac.max_high-ac.max_low));ac.move_delta(-ah)});this.overview_close.click(function(){ac.reset_overview()});this.viewport_container.bind("draginit",function(ag,ah){if(ag.clientX>ac.viewport_container.width()-16){return false}}).bind("dragstart",function(ag,ah){ah.original_low=ac.low;ah.current_height=ag.clientY;ah.current_x=ah.offsetX}).bind("drag",function(ai,ak){var ag=$(this);var al=ak.offsetX-ak.current_x;var ah=ag.scrollTop()-(ai.clientY-ak.current_height);ag.scrollTop(ah);ak.current_height=ai.clientY;ak.current_x=ak.offsetX;var aj=Math.round(al/ac.viewport_container.width()*(ac.high-ac.low));ac.move_delta(aj)}).bind("mousewheel",function(ai,ak,ah,ag){if(ah){ah*=50;var aj=Math.round(-ah/ac.viewport_container.width()*(ac.high-ac.low));ac.move_delta(aj)}});this.top_labeltrack.bind("dragstart",function(ag,ah){return $("
    ").css({height:ac.browser_content_div.height()+ac.top_labeltrack.height()+ac.nav_labeltrack.height()+1,top:"0px",position:"absolute","background-color":"#ccf",opacity:0.5,"z-index":1000}).appendTo($(this))}).bind("drag",function(ak,al){$(al.proxy).css({left:Math.min(ak.pageX,al.startX)-ac.container.offset().left,width:Math.abs(ak.pageX-al.startX)});var ah=Math.min(ak.pageX,al.startX)-ac.container.offset().left,ag=Math.max(ak.pageX,al.startX)-ac.container.offset().left,aj=(ac.high-ac.low),ai=ac.viewport_container.width();ac.update_location(Math.round(ah/ai*aj)+ac.low,Math.round(ag/ai*aj)+ac.low)}).bind("dragend",function(al,am){var ah=Math.min(al.pageX,am.startX),ag=Math.max(al.pageX,am.startX),aj=(ac.high-ac.low),ai=ac.viewport_container.width(),ak=ac.low;ac.low=Math.round(ah/ai*aj)+ak;ac.high=Math.round(ag/ai*aj)+ak;$(am.proxy).remove();ac.request_redraw()});this.add_label_track(new W(this,{content_div:this.top_labeltrack}));this.add_label_track(new W(this,{content_div:this.nav_labeltrack}));$(window).bind("resize",function(){if(this.resize_timer){clearTimeout(this.resize_timer)}this.resize_timer=setTimeout(function(){ac.resize_window()},500)});$(document).bind("redraw",function(){ac.redraw()});this.reset();$(window).trigger("resize")},get_base_color:function(ac){return this.config.values[ac.toLowerCase()+"_color"]||this.config.values.n_color}});p(Y.prototype,y.prototype,{changed:function(){this.has_changes=true},update_intro_div:function(){if(this.drawables.length===0){this.intro_div.show()}else{this.intro_div.hide()}},trigger_navigate:function(ad,af,ac,ag){if(this.timer){clearTimeout(this.timer)}if(ag){var ae=this;this.timer=setTimeout(function(){ae.trigger("navigate",ad+":"+af+"-"+ac)},500)}else{view.trigger("navigate",ad+":"+af+"-"+ac)}},update_location:function(ac,ae){this.location_span.text(commatize(ac)+" - "+commatize(ae));this.nav_input.val(this.chrom+":"+commatize(ac)+"-"+commatize(ae));var ad=view.chrom_select.val();if(ad!==""){this.trigger_navigate(ad,view.low,view.high,true)}},load_chroms:function(ae){ae.num=v;var ac=this,ad=$.Deferred();$.ajax({url:chrom_url+"/"+this.dbkey,data:ae,dataType:"json",success:function(ag){if(ag.chrom_info.length===0){return}if(ag.reference){ac.add_label_track(new B(ac))}ac.chrom_data=ag.chrom_info;var aj='';for(var ai=0,af=ac.chrom_data.length;ai'+ah+""}if(ag.prev_chroms){aj+='"}if(ag.next_chroms){aj+='"}ac.chrom_select.html(aj);ac.chrom_start_index=ag.start_index;ad.resolve(ag.chrom_info)},error:function(){alert("Could not load chroms for this dbkey:",ac.dbkey)}});return ad},change_chrom:function(ah,ad,aj){var ae=this;if(!ae.chrom_data){ae.load_chroms_deferred.then(function(){ae.change_chrom(ah,ad,aj)});return}if(!ah||ah==="None"){return}if(ah==="previous"){ae.load_chroms({low:this.chrom_start_index-v});return}if(ah==="next"){ae.load_chroms({low:this.chrom_start_index+v});return}var ai=$.grep(ae.chrom_data,function(ak,al){return ak.chrom===ah})[0];if(ai===undefined){ae.load_chroms({chrom:ah},function(){ae.change_chrom(ah,ad,aj)});return}else{if(ah!==ae.chrom){ae.chrom=ah;ae.chrom_select.val(ae.chrom);ae.max_high=ai.len-1;ae.reset();for(var ag=0,ac=ae.drawables.length;agac.max_high){ac.high=ac.max_high;ac.low=ac.max_high-ae}else{ac.high-=af;ac.low-=af}}ac.request_redraw();var ad=ac.chrom_select.val();this.trigger_navigate(ad,ac.low,ac.high,true)},add_drawable:function(ac){y.prototype.add_drawable.call(this,ac);ac.init();this.changed();this.update_intro_div()},add_label_track:function(ac){ac.view=this;ac.init();this.label_tracks.push(ac)},remove_drawable:function(ae,ad){y.prototype.remove_drawable.call(this,ae);if(ad){var ac=this;ae.container_div.hide(0,function(){$(this).remove();ac.update_intro_div()})}},reset:function(){this.low=this.max_low;this.high=this.max_high;this.viewport_container.find(".yaxislabel").remove()},request_redraw:function(ac,aj,ak){var ai=this,ag=(ak?[ak]:ai.drawables),ae;var ad;for(var ah=0;ahthis.max_high){af=this.max_high}var al=this.high-this.low;if(this.high!==0&&al").addClass("dynamic-tool").hide();this.parent_div.bind("drag",function(au){au.stopPropagation()}).click(function(au){au.stopPropagation()}).bind("dblclick",function(au){au.stopPropagation()});var ao=$("
    ").appendTo(this.parent_div).text(this.name);var am=this.params;var ak=this;$.each(this.params,function(av,ay){var ax=$("
    ").addClass("param-row").appendTo(ak.parent_div);var au=$("
    ").addClass("param-label").text(ay.label).appendTo(ax);var aw=$("
    ").addClass("param-input").html(ay.html).appendTo(ax);aw.find(":input").val(ay.value);$("
    ").appendTo(ax)});this.parent_div.find("input").click(function(){$(this).select()});var at=$("
    ").addClass("param-row").appendTo(this.parent_div);var ai=$("").attr("value","Run on complete dataset").appendTo(at);var ac=$("").attr("value","Run on visible region").css("margin-left","3em").appendTo(at);ac.click(function(){ak.run_on_region()});ai.click(function(){ak.run_on_dataset()});if("visible" in af&&af.visible){this.parent_div.show()}};p(r.prototype,{update_params:function(){for(var ac=0;ac");var ae;function ad(ak,ag){for(var ao=0;ao").appendTo(ag);av.append($("").addClass("icon-button arrow-circle").appendTo(au).attr("title","Set new random color").tooltip(),ar=$("
    ").appendTo(au).hide(),aj=$("
    ").appendTo(ar),ah=$("
    ").appendTo(ar),al=$.farbtastic(aj,{width:100,height:100,callback:ap,color:at});au.append($("
    ").css("clear","both"));(function(aw){an.click(function(){aw.setColor(k.get_random_color())})})(al)}else{av.append($("").attr("id",ai).attr("name",ai).val(at))}}}}if(ae.help){av.append($("
    ").text(ae.help))}}}ad(this.params,ac);return ac},update_from_form:function(ac){var ae=this;var ad=false;$.each(this.params,function(af,ah){if(!ah.hidden){var ai="param_"+af;var ag=ac.find("#"+ai).val();if(ah.type==="float"){ag=parseFloat(ag)}else{if(ah.type==="int"){ag=parseInt(ag,10)}else{if(ah.type==="bool"){ag=ac.find("#"+ai).is(":checked")}}}if(ag!==ae.values[ah.key]){ae.values[ah.key]=ag;ad=true}}});if(ad){this.onchange();this.track.changed()}}});var b=function(ac,ag,ae,ad,af){this.track=ac;this.region=ag;this.low=ag.get("start");this.high=ag.get("end");this.resolution=ae;this.html_elt=$("
    ").append(ad).height($(ad).attr("height"));this.data=af;this.stale=false};b.prototype.predisplay_actions=function(){};var M=function(ac,ag,ae,ad,af){b.call(this,ac,ag,ae,ad,af)};M.prototype.predisplay_actions=function(){};var P=function(af,an,ag,ae,ai,ap,aj,aq,ad,am){b.call(this,af,an,ag,ae,ai);this.mode=aj;this.all_slotted=ad;this.feature_mapper=am;this.has_icons=false;if(aq){this.has_icons=true;var ak=this;ae=this.html_elt.children()[0],message_div=$("
    ").addClass("tile-message").css({height:D-1,width:ae.width}).prependTo(this.html_elt);var al=new w.GenomeRegion({chrom:af.view.chrom,start:this.low,end:this.high}),ao=ai.length,ah=$("").addClass("icon more-down").attr("title","For speed, only the first "+ao+" features in this region were obtained from server. Click to get more data including depth").tooltip().appendTo(message_div),ac=$("").addClass("icon more-across").attr("title","For speed, only the first "+ao+" features in this region were obtained from server. Click to get more data excluding depth").tooltip().appendTo(message_div);ah.click(function(){ak.stale=true;af.data_manager.get_more_data(al,af.mode,ak.resolution,{},af.data_manager.DEEP_DATA_REQ);$(".bs-tooltip").hide();af.request_draw(false,true)}).dblclick(function(ar){ar.stopPropagation()});ac.click(function(){ak.stale=true;af.data_manager.get_more_data(al,af.mode,ak.resolution,{},af.data_manager.BROAD_DATA_REQ);$(".bs-tooltip").hide();af.request_draw(false,true)}).dblclick(function(ar){ar.stopPropagation()})}};p(P.prototype,b.prototype);P.prototype.predisplay_actions=function(){var ad=this,ac={};if(ad.mode!=="Pack"){return}$(this.html_elt).hover(function(){this.hovered=true;$(this).mousemove()},function(){this.hovered=false;$(this).parents(".track-content").children(".overlay").children(".feature-popup").remove()}).mousemove(function(ao){if(!this.hovered){return}var aj=$(this).offset(),an=ao.pageX-aj.left,am=ao.pageY-aj.top,at=ad.feature_mapper.get_feature_data(an,am),ak=(at?at[0]:null);$(this).parents(".track-content").children(".overlay").children(".feature-popup").each(function(){if(!ak||$(this).attr("id")!==ak.toString()){$(this).remove()}});if(at){var af=ac[ak];if(!af){var ak=at[0],ap={name:at[3],start:at[1],end:at[2],strand:at[4]},ai=ad.track.filters_manager.filters,ah;for(var al=0;al").attr("id",ak).addClass("feature-popup"),au=$(""),ar,aq,av;for(ar in ap){aq=ap[ar];av=$("").appendTo(au);$("
    ").appendTo(av).text(ar);$("").attr("align","left").appendTo(av).text(typeof(aq)==="number"?V(aq,2):aq)}af.append($("
    ").append(au));ac[ak]=af}af.appendTo($(this).parents(".track-content").children(".overlay"));var ag=an+parseInt(ad.html_elt.css("left"))-af.width()/2,ae=am+parseInt(ad.html_elt.css("top"))+7;af.css("left",ag+"px").css("top",ae+"px")}else{if(!ao.isPropagationStopped()){ao.stopPropagation();$(this).siblings().each(function(){$(this).trigger(ao)})}}}).mouseleave(function(){$(this).parents(".track-content").children(".overlay").children(".feature-popup").remove()})};var g=function(ad,ac,ae){p(ae,{drag_handle_class:"draghandle"});q.call(this,ad,ac,ae);this.dataset=new X.Dataset(ae.dataset);this.dataset_check_type="converted_datasets_state";this.data_url_extra_params={};this.data_query_wait=("data_query_wait" in ae?ae.data_query_wait:K);this.data_manager=("data_manager" in ae?ae.data_manager:new w.GenomeDataManager({dataset:this.dataset,genome:new w.Genome({key:ad.dbkey,chroms_info:{chrom_info:ad.chrom_data}}),data_mode_compatible:this.data_and_mode_compatible,can_subset:this.can_subset}));this.min_height_px=16;this.max_height_px=800;this.visible_height_px=0;this.content_div=$("
    ").appendTo(this.container_div);if(this.container){this.container.content_div.append(this.container_div);if(!("resize" in ae)||ae.resize){this.add_resize_handle()}}};p(g.prototype,q.prototype,{action_icons_def:[{name:"mode_icon",title:"Set display mode",css_class:"chevron-expand",on_click_fn:function(){}},q.prototype.action_icons_def[0],{name:"overview_icon",title:"Set as overview",css_class:"overview-icon",on_click_fn:function(ac){ac.view.set_overview(ac)}},q.prototype.action_icons_def[1],{name:"filters_icon",title:"Filters",css_class:"filters-icon",on_click_fn:function(ac){if(ac.filters_manager.visible()){ac.filters_manager.clear_filters()}else{ac.filters_manager.init_filters()}ac.filters_manager.toggle()}},{name:"tools_icon",title:"Tool",css_class:"hammer",on_click_fn:function(ac){ac.dynamic_tool_div.toggle();if(ac.dynamic_tool_div.is(":visible")){ac.set_name(ac.name+ac.tool_region_and_parameters_str())}else{ac.revert_name()}$(".bs-tooltip").remove()}},{name:"param_space_viz_icon",title:"Tool parameter space visualization",css_class:"arrow-split",on_click_fn:function(ac){var af='Tool: <%= track.tool.name %>
    Dataset: <%= track.name %>
    Region(s): ',ae=ab.template(af,{track:ac});var ah=function(){hide_modal();$(window).unbind("keypress.check_enter_esc")},ad=function(){var aj=$('select[name="regions"] option:selected').val(),al,ai=new w.GenomeRegion({chrom:view.chrom,start:view.low,end:view.high}),ak=ab.map($(".bookmark"),function(am){return new w.GenomeRegion({from_str:$(am).children(".position").text()})});if(aj==="cur"){al=[ai]}else{if(aj==="bookmarks"){al=ak}else{al=[ai].concat(ak)}}hide_modal();window.location.href=galaxy_paths.get("sweepster_url")+"?"+$.param({dataset_id:ac.dataset.id,hda_ldda:ac.dataset.get("hda_ldda"),regions:JSON.stringify(new Backbone.Collection(al).toJSON())})},ag=function(ai){if((ai.keyCode||ai.which)===27){ah()}else{if((ai.keyCode||ai.which)===13){ad()}}};show_modal("Visualize tool parameter space and output from different parameter settings?",ae,{No:ah,Yes:ad})}},q.prototype.action_icons_def[2]],can_draw:function(){if(this.dataset.id&&q.prototype.can_draw.call(this)){return true}return false},build_container_div:function(){return $("
    ").addClass("track").attr("id","track_"+this.id).css("position","relative")},build_header_div:function(){var ac=$("
    ");if(this.view.editor){this.drag_div=$("
    ").addClass(this.drag_handle_class).appendTo(ac)}this.name_div=$("
    ").addClass("track-name").appendTo(ac).text(this.name).attr("id",this.name.replace(/\s+/g,"-").replace(/[^a-zA-Z0-9\-]/g,"").toLowerCase());return ac},set_dataset:function(ac){this.dataset=ac;this.data_manager.set("dataset",ac)},on_resize:function(){this.request_draw(true)},add_resize_handle:function(){var ac=this;var af=false;var ae=false;var ad=$("
    ");$(ac.container_div).hover(function(){if(ac.content_visible){af=true;ad.show()}},function(){af=false;if(!ae){ad.hide()}});ad.hide().bind("dragstart",function(ag,ah){ae=true;ah.original_height=$(ac.content_div).height()}).bind("drag",function(ah,ai){var ag=Math.min(Math.max(ai.original_height+ai.deltaY,ac.min_height_px),ac.max_height_px);$(ac.tiles_div).css("height",ag);ac.visible_height_px=(ac.max_height_px===ag?0:ag);ac.on_resize()}).bind("dragend",function(ag,ah){ac.tile_cache.clear();ae=false;if(!af){ad.hide()}ac.config.values.height=ac.visible_height_px;ac.changed()}).appendTo(ac.container_div)},set_display_modes:function(af,ai){this.display_modes=af;this.mode=(ai?ai:(this.config&&this.config.values.mode?this.config.values.mode:this.display_modes[0]));this.action_icons.mode_icon.attr("title","Set display mode (now: "+this.mode+")");var ad=this,ag={};for(var ae=0,ac=ad.display_modes.length;ae").text("View error").click(function(){show_modal("Trackster Error","
    "+ag.message+"
    ",{Close:hide_modal})}));ad.tiles_div.append($("").text(" "));ad.tiles_div.append($("").text("Try again").click(function(){ad.init(true)}))}}else{if(ag==="no converter"){ad.container_div.addClass("error");ad.tiles_div.text(J)}else{if(ag==="no data"||(ag.data!==undefined&&(ag.data===null||ag.data.length===0))){ad.container_div.addClass("nodata");ad.tiles_div.text(E)}else{if(ag==="pending"){ad.container_div.addClass("pending");ad.tiles_div.html(u);setTimeout(function(){ad.init()},ad.data_query_wait)}else{if(ag==="data"||ag.status==="data"){if(ag.valid_chroms){ad.valid_chroms=ag.valid_chroms;ad.update_icons()}ad.tiles_div.text(U);if(ad.view.chrom){ad.tiles_div.text("");ad.tiles_div.css("height",ad.visible_height_px+"px");ad.enabled=true;$.when.apply($,ad.predraw_init()).done(function(){ac.resolve();ad.container_div.removeClass("nodata error pending");ad.request_draw()})}else{ac.resolve()}}}}}}});this.update_icons();return ac},predraw_init:function(){var ac=this;return $.getJSON(ac.dataset.url(),{data_type:"data",stats:true,chrom:ac.view.chrom,low:0,high:ac.view.max_high,hda_ldda:ac.dataset.get("hda_ldda")},function(ad){ac.container_div.addClass("line-track");var af=ad.data;if(af&&af.min&&af.max){var ae=af.min,ag=af.max;ae=Math.floor(Math.min(0,Math.max(ae,af.mean-2*af.sd)));ag=Math.ceil(Math.max(0,Math.min(ag,af.mean+2*af.sd)));ac.prefs.min_value=ae;ac.prefs.max_value=ag}})},get_drawables:function(){return this}});var N=function(ae,ad,af){g.call(this,ae,ad,af);var ac=this;l(ac.container_div,ac.drag_handle_class,".group",ac);this.filters_manager=new i.FiltersManager(this,("filters" in af?af.filters:null));this.data_manager.set("filters_manager",this.filters_manager);this.filters_available=false;this.tool=("tool" in af&&af.tool?new r(this,af.tool,af.tool_state):null);this.tile_cache=new w.Cache(R);this.left_offset=0;if(this.header_div){this.set_filters_manager(this.filters_manager);if(this.tool){this.dynamic_tool_div=this.tool.parent_div;this.header_div.after(this.dynamic_tool_div)}}this.tiles_div=$("
    ").addClass("tiles").appendTo(this.content_div);this.overlay_div=$("
    ").addClass("overlay").appendTo(this.content_div);if(af.mode){this.change_mode(af.mode)}};p(N.prototype,q.prototype,g.prototype,{action_icons_def:g.prototype.action_icons_def.concat([{name:"show_more_rows_icon",title:"To minimize track height, not all feature rows are displayed. Click to display more rows.",css_class:"exclamation",on_click_fn:function(ac){$(".bs-tooltip").remove();ac.slotters[ac.view.resolution_px_b].max_rows*=2;ac.request_draw(true)},hide:true}]),copy:function(ac){var ad=this.to_dict();p(ad,{data_manager:this.data_manager});var ae=new this.constructor(this.view,ac,ad);ae.change_mode(this.mode);ae.enabled=this.enabled;return ae},set_filters_manager:function(ac){this.filters_manager=ac;this.header_div.after(this.filters_manager.parent_div)},to_dict:function(){return{track_type:this.get_type(),name:this.name,dataset:{id:this.dataset.id,hda_ldda:this.dataset.get("hda_ldda")},prefs:this.prefs,mode:this.mode,filters:this.filters_manager.to_dict(),tool_state:(this.tool?this.tool.state_dict():{})}},set_min_max:function(){var ac=this;return $.getJSON(ac.dataset.url(),{data_type:"data",stats:true,chrom:ac.view.chrom,low:0,high:ac.view.max_high,hda_ldda:ac.dataset.get("hda_ldda")},function(ad){var af=ad.data;if(isNaN(parseFloat(ac.prefs.min_value))||isNaN(parseFloat(ac.prefs.max_value))){var ae=af.min,ag=af.max;ae=Math.floor(Math.min(0,Math.max(ae,af.mean-2*af.sd)));ag=Math.ceil(Math.max(0,Math.min(ag,af.mean+2*af.sd)));ac.prefs.min_value=ae;ac.prefs.max_value=ag}})},change_mode:function(ad){var ac=this;ac.mode=ad;ac.config.values.mode=ad;if(ad==="Auto"){this.data_manager.clear()}ac.request_draw(true);this.action_icons.mode_icon.attr("title","Set display mode (now: "+ac.mode+")");return ac},update_icons:function(){var ac=this;if(ac.filters_available){ac.action_icons.filters_icon.show()}else{ac.action_icons.filters_icon.hide()}if(ac.tool){ac.action_icons.tools_icon.show();ac.action_icons.param_space_viz_icon.show()}else{ac.action_icons.tools_icon.hide();ac.action_icons.param_space_viz_icon.hide()}},_gen_tile_cache_key:function(ad,ac){return ad+"_"+ac},request_draw:function(ae,ad,ac){if(ae){this.tile_cache.clear()}this.view.request_redraw(ad,ac,this)},before_draw:function(){this.max_height_px=0},_draw:function(ad,an){if(!this.can_draw()){return}var al=this.view.low,ah=this.view.high,ak=ah-al,ae=this.view.container.width(),ap=this.view.resolution_px_b,ag=this.view.resolution_b_px;if(this.is_overview){al=this.view.max_low;ah=this.view.max_high;ag=(view.max_high-view.max_low)/ae;ap=1/ag}this.before_draw();this.tiles_div.children().addClass("remove");var ac=Math.floor(al/(ag*S)),am,ai,aj=[],ao=[];while((ac*S*ag)").text(ad.prefs[ac]).make_text_editable({num_cols:12,on_finish:function(aj){$(".bs-tooltip").remove();var aj=V(parseFloat(aj),3);ad.prefs[ac]=(!isNaN(aj)?aj:null);ah()},help_text:"Set "+ai+" value"}).addClass("yaxislabel "+ag).css("color",this.prefs.label_color);this.container_div.prepend(ae)}},postdraw_actions:function(af,ag,ai,ac){var ae=ab.filter(af,function(aj){return !(aj instanceof M)});if(ae.length>0&&ae.length1){aB.globalAlpha=0.5;aB.globalCompositeOperation="source-over"}ab.each(ah,function(aF,aE){az=aF.draw_tile(aw[aE],aB,ax[aE],ag,am,ap,aC)});if(az!==undefined){af.tile_cache.set_elt(an,az);af.show_tile(az,ao,ap)}ac.resolve(az)});return ac},get_canvas_height:function(ac,ae,af,ad){return this.visible_height_px},_draw_line_track_tile:function(ac,ae,ai,ag,ah,aj){var af=ae.canvas,ad=new L.LinePainter(ac.data,ah.get("start"),ah.get("end"),this.prefs,ai);ad.draw(ae,af.width,af.height,aj);return new M(this,ah,ag,af,ac.data)},draw_tile:function(ac,ad,ah,af,ag,ai,ae){console.log("Warning: TiledTrack.draw_tile() not implemented.")},show_tile:function(ae,ah,ai){var ad=this,ac=ae.html_elt;ae.predisplay_actions();var ag=(ae.low-(this.is_overview?this.view.max_low:this.view.low))*ai;if(this.left_offset){ag-=this.left_offset}ac.css({position:"absolute",top:0,left:ag});if(ac.hasClass("remove")){ac.removeClass("remove")}else{ah.append(ac)}ae.html_elt.height("auto");this.max_height_px=Math.max(this.max_height_px,ae.html_elt.height());ae.html_elt.parent().children().css("height",this.max_height_px+"px");var af=this.max_height_px;if(this.visible_height_px!==0){af=Math.min(this.max_height_px,this.visible_height_px)}this.tiles_div.css("height",af+"px")},_get_tile_bounds:function(ac,ad){var af=Math.floor(ac*S*ad),ag=Math.ceil(S*ad),ae=(af+ag<=this.view.max_high?af+ag:this.view.max_high);return new w.GenomeRegion({chrom:this.view.chrom,start:af,end:ae})},tool_region_and_parameters_str:function(ae){var ac=this,ad=(ae!==undefined?ae.toString():"all");return" - region=["+ad+"], parameters=["+ac.tool.get_param_values().join(", ")+"]"},data_and_mode_compatible:function(ac,ad){return true},can_subset:function(ac){return false},init_for_tool_data:function(){this.data_manager.set("data_type","raw_data");this.data_query_wait=1000;this.dataset_check_type="state";this.normal_postdraw_actions=this.postdraw_actions;this.postdraw_actions=function(ae,af,ah,ac){var ad=this;ad.normal_postdraw_actions(ae,af,ah,ac);ad.dataset_check_type="converted_datasets_state";ad.data_query_wait=K;var ag=new k.ServerStateDeferred({url:ad.dataset_state_url,url_params:{dataset_id:ad.dataset.id,hda_ldda:ad.dataset.get("hda_ldda")},interval:ad.data_query_wait,success_fn:function(ai){return ai!=="pending"}});$.when(ag.go()).then(function(){ad.data_manager.set("data_type","data")});ad.postdraw_actions=ad.normal_postdraw_actions}}});var W=function(ad,ac){var ae={resize:false};g.call(this,ad,ac,ae);this.container_div.addClass("label-track")};p(W.prototype,g.prototype,{build_header_div:function(){},init:function(){this.enabled=true},predraw_init:function(){},_draw:function(){var ae=this.view,af=ae.high-ae.low,ai=Math.floor(Math.pow(10,Math.floor(Math.log(af)/Math.log(10)))),ac=Math.floor(ae.low/ai)*ai,ag=this.view.container.width(),ad=$("
    ");while(ac"+commatize(ac)+"
    ").css({position:"absolute",left:ah-1}));ac+=ai}this.content_div.children(":first").remove();this.content_div.append(ad)}});var f=function(ad,ac,ag){this.display_modes=A;N.call(this,ad,ac,ag);this.drawables=[];if("drawables" in ag){var af;for(var ae=0;aethis.left_offset){this.left_offset=af.left_offset}}this.enabled=true}ab.each(this.drawables,function(ah){if(ah instanceof c||ah instanceof T){ah.change_mode("Coverage")}});this.update_icons();this.obj_type="CompositeTrack"};p(f.prototype,N.prototype,{action_icons_def:[{name:"composite_icon",title:"Show individual tracks",css_class:"layers-stack",on_click_fn:function(ac){$(".bs-tooltip").remove();ac.show_group()}}].concat(N.prototype.action_icons_def),to_dict:y.prototype.to_dict,add_drawable:y.prototype.add_drawable,unpack_drawables:y.prototype.unpack_drawables,change_mode:function(ac){N.prototype.change_mode.call(this,ac);for(var ad=0;adag){ag=aj}}for(var ae=0;aethis.view.canvas_manager.char_width_px){this.tiles_div.show();return N.prototype.draw_helper.call(this,ae,af,ac,ag,ah,ad)}else{this.tiles_div.hide();return null}},can_subset:function(ac){return true},draw_tile:function(af,al,ag,ad,ai,am){var ae=this.data_manager.subset_entry(af,ai),ak=ae.data;var ac=al.canvas;al.font=al.canvas.manager.default_font;al.textAlign="center";for(var ah=0,aj=ak.length;ahI){mode="Squish"}else{mode="Pack"}}return mode},get_canvas_height:function(ac,ag,ah,ad){if(ag==="Coverage"||ac.dataset_type==="bigwig"){return this.summary_draw_height}else{var af=this.incremental_slots(ah,ac.data,ag);var ae=new (this.painter)(null,null,null,this.prefs,ag);return Math.max(aa,ae.get_required_height(af,ad))}},draw_tile:function(am,aq,ao,ar,af,aj,ae){var ap=this,ad=aq.canvas,ay=af.get("start"),ac=af.get("end"),ag=this.left_offset;if(am.dataset_type==="bigwig"){return this._draw_line_track_tile(am,aq,ao,ar,af,aj)}var ai=[],an=this.slotters[aj].slots;all_slotted=true;if(am.data){var ak=this.filters_manager.filters;for(var at=0,av=am.data.length;at").text("Summary").addClass("yaxislabel variant top").css({"font-size":ac+"px",top:(this.prefs.summary_height-ac)/2+"px"}));if(this.prefs.show_sample_data){var ag=this.dataset.get("metadata").get("sample_names").join("
    ");this.tiles_div.prepend($("
    ").html(ag).addClass("yaxislabel variant top sample").css({top:this.prefs.summary_height+2,}))}}ac=(this.mode==="Squish"?5:10)+"px";$(this.tiles_div).find(".sample").css({"font-size":ac,"line-height":ac});$(this.tiles_div).find(".yaxislabel").css("color",this.prefs.label_color)}else{this.container_div.find(".yaxislabel.variant").remove()}}});var T=function(ae,ad,ag){c.call(this,ae,ad,ag);var af=k.get_random_color(),ac=k.get_random_color([af,"#ffffff"]);this.config=new F({track:this,params:[{key:"name",label:"Name",type:"text",default_value:this.name},{key:"block_color",label:"Block and sense strand color",type:"color",default_value:af},{key:"reverse_strand_color",label:"Antisense strand color",type:"color",default_value:ac},{key:"label_color",label:"Label color",type:"color",default_value:"black"},{key:"show_insertions",label:"Show insertions",type:"bool",default_value:false},{key:"show_differences",label:"Show differences only",type:"bool",default_value:true},{key:"show_counts",label:"Show summary counts",type:"bool",default_value:true},{key:"mode",type:"string",default_value:this.mode,hidden:true},{key:"min_value",label:"Histogram minimum",type:"float",default_value:null,help:"clear value to set automatically"},{key:"max_value",label:"Histogram maximum",type:"float",default_value:null,help:"clear value to set automatically"}],saved_values:ag.prefs,onchange:function(){this.track.set_name(this.track.prefs.name);this.track.request_draw(true)}});this.prefs=this.config.values;this.painter=(ae.reference_track?L.RefBasedReadPainter:L.ReadPainter);this.update_icons()};p(T.prototype,q.prototype,N.prototype,c.prototype);var d={CompositeTrack:f,DrawableGroup:Q,DiagonalHeatmapTrack:s,FeatureTrack:c,LineTrack:h,ReadTrack:T,VariantTrack:Z,VcfTrack:Z};var o=function(ae,ad,ac){if("copy" in ae){return ae.copy(ac)}else{var af=ae.obj_type;if(!af){af=ae.track_type}return new d[af](ad,ac,ae)}};return{TracksterView:Y,DrawableGroup:Q,LineTrack:h,FeatureTrack:c,DiagonalHeatmapTrack:s,ReadTrack:T,VariantTrack:Z,CompositeTrack:f,object_from_template:o}}); \ No newline at end of file From b696637b83ba5ec28847f47e6ac671106f9ca1c8 Mon Sep 17 00:00:00 2001 From: Nate Coraor Date: Mon, 20 May 2013 14:55:57 -0400 Subject: [PATCH 465/523] Changes to the setup_virtualenv tool_dependency action: - Download and use a local copy of virtualenv rather than rely on virtualenv being on the $PATH. - Do not use activate (except when installing packages e.g. with pip) since only one virtualenv can be active at a time. This allows for the use of multiple dependencies that use virtualenv. - Support setup_virtualenv with complex dependencies. --- .../tool_dependencies/fabric_util.py | 41 +++++++++++++++++-- .../tool_dependencies/install_util.py | 21 +++++++--- 2 files changed, 54 insertions(+), 8 deletions(-) diff --git a/lib/tool_shed/galaxy_install/tool_dependencies/fabric_util.py b/lib/tool_shed/galaxy_install/tool_dependencies/fabric_util.py index 5437ed870e8..21118c844b8 100644 --- a/lib/tool_shed/galaxy_install/tool_dependencies/fabric_util.py +++ b/lib/tool_shed/galaxy_install/tool_dependencies/fabric_util.py @@ -6,6 +6,7 @@ import logging import os import shutil import tempfile +import shutil from contextlib import contextmanager from galaxy import eggs @@ -22,13 +23,14 @@ from fabric.api import settings log = logging.getLogger( __name__ ) INSTALLATION_LOG = 'INSTALLATION.log' +VIRTUALENV_URL = 'https://pypi.python.org/packages/source/v/virtualenv/virtualenv-1.9.1.tar.gz' def check_fabric_version(): version = env.version if int( version.split( "." )[ 0 ] ) < 1: raise NotImplementedError( "Install Fabric version 1.0 or later." ) -def handle_command( app, tool_dependency, install_dir, cmd ): +def handle_command( app, tool_dependency, install_dir, cmd, return_output=False ): sa_session = app.model.context.current with settings( warn_only=True ): output = local( cmd, capture=True ) @@ -43,8 +45,25 @@ def handle_command( app, tool_dependency, install_dir, cmd ): tool_dependency.error_message = "Unknown error occurred executing shell command %s, return_code: %s" % ( str( cmd ), str( output.return_code ) ) sa_session.add( tool_dependency ) sa_session.flush() + if return_output: + return output return output.return_code +def install_virtualenv( app, venv_dir ): + if not os.path.exists( venv_dir ): + with make_tmp_dir() as work_dir: + downloaded_filename = VIRTUALENV_URL.rsplit('/', 1)[-1] + downloaded_file_path = common_util.url_download( work_dir, downloaded_filename, VIRTUALENV_URL ) + if common_util.istar( downloaded_file_path ): + common_util.extract_tar( downloaded_file_path, work_dir ) + dir = common_util.tar_extraction_directory( work_dir, downloaded_filename ) + else: + log.error( "Failed to download virtualenv: Downloaded file '%s' is not a tar file", downloaded_filename ) + return False + full_path_to_dir = os.path.abspath( os.path.join( work_dir, dir ) ) + shutil.move( full_path_to_dir, venv_dir ) + return True + def install_and_build_package( app, tool_dependency, actions_dict ): """Install a Galaxy tool dependency package either via a url or a mercurial or git clone command.""" sa_session = app.model.context.current @@ -142,6 +161,11 @@ def install_and_build_package( app, tool_dependency, actions_dict ): # tags that follow this tag set in the tool_dependencies.xml file. env_shell_file_paths = action_dict[ 'env_shell_file_paths' ] elif action_type == 'setup_virtualenv': + # TODO: maybe should be configurable + venv_src_directory = os.path.abspath( os.path.join( app.config.tool_dependency_dir, '__virtualenv_src' ) ) + if not install_virtualenv( app, venv_src_directory ): + log.error( 'Unable to install virtualenv' ) + return requirements = action_dict[ 'requirements' ] if os.path.exists( os.path.join( dir, requirements ) ): # requirements specified as path to a file @@ -153,7 +177,7 @@ def install_and_build_package( app, tool_dependency, actions_dict ): f.write( requirements ) venv_directory = os.path.join( install_dir, "venv" ) # TODO: Consider making --no-site-packages optional. - setup_command = "virtualenv --no-site-packages '%s'" % venv_directory + setup_command = "python %s/virtualenv.py --no-site-packages '%s'" % (venv_src_directory, venv_directory) # POSIXLY_CORRECT forces shell commands . and source to have the same # and well defined behavior in bash/zsh. activate_command = "POSIXLY_CORRECT=1; . %s" % os.path.join( venv_directory, "bin", "activate" ) @@ -162,7 +186,18 @@ def install_and_build_package( app, tool_dependency, actions_dict ): return_code = handle_command( app, tool_dependency, install_dir, full_setup_command ) if return_code: return - modify_env_command = common_util.create_or_update_env_shell_file_with_command( install_dir, activate_command ) + site_packages_command = "%s -c 'import os, sys; print os.path.join(sys.prefix, \"lib\", \"python\" + sys.version[:3], \"site-packages\")'" % os.path.join( venv_directory, "bin", "python" ) + output = handle_command( app, tool_dependency, install_dir, site_packages_command, return_output=True ) + if output.return_code: + return + if not os.path.exists( output.stdout ): + log.error( "virtualenv's site-packages directory '%s' does not exist", output.stdout ) + return + modify_env_command = common_util.create_or_update_env_shell_file( install_dir, dict( name="PYTHONPATH", action="prepend_to", value=output.stdout ) ) + return_code = handle_command( app, tool_dependency, install_dir, modify_env_command ) + if return_code: + return + modify_env_command = common_util.create_or_update_env_shell_file( install_dir, dict( name="PATH", action="prepend_to", value=os.path.join( venv_directory, "bin" ) ) ) return_code = handle_command( app, tool_dependency, install_dir, modify_env_command ) if return_code: return diff --git a/lib/tool_shed/galaxy_install/tool_dependencies/install_util.py b/lib/tool_shed/galaxy_install/tool_dependencies/install_util.py index fb755c46202..3ad9d38c875 100644 --- a/lib/tool_shed/galaxy_install/tool_dependencies/install_util.py +++ b/lib/tool_shed/galaxy_install/tool_dependencies/install_util.py @@ -137,21 +137,32 @@ def handle_set_environment_entry_for_package( app, install_dir, tool_shed_reposi for actions_elem in package_elem: for action_elem in actions_elem: action_type = action_elem.get( 'type', 'shell_command' ) + env_var_dicts = [] if action_type == 'set_environment': # # $INSTALL_DIR/lib/python # $INSTALL_DIR/bin # - env_var_dicts = [] for env_elem in action_elem: if env_elem.tag == 'environment_variable': env_var_dict = common_util.create_env_var_dict( env_elem, tool_dependency_install_dir=install_dir ) if env_var_dict: env_var_dicts.append( env_var_dict ) - if env_var_dicts: - action_dict[ env_elem.tag ] = env_var_dicts - actions.append( ( action_type, action_dict ) ) - return tool_dependency, actions + elif action_type == 'setup_virtualenv': + # Add the virtualenv's site-pacakges to PYTHONPATH and bin to PATH. This is a bit hackish. + site_packages_command = "%s -c 'import os, sys; print os.path.join(sys.prefix, \"lib\", \"python\" + sys.version[:3], \"site-packages\")'" % os.path.join( install_dir, "venv", "bin", "python" ) + output = fabric_util.handle_command( app, tool_dependency, install_dir, site_packages_command, return_output=True ) + if output.return_code: + log.error( 'Dependency includes a setup_virtualenv action but venv python is broken:', output.stderr ) + elif not os.path.exists( output.stdout ): + log.error( "virtualenv's site-packages directory '%s' does not exist", output.stdout ) + else: + env_var_dicts.append( dict( name="PYTHONPATH", action="prepend_to", value=output.stdout ) ) + env_var_dicts.append( dict( name="PATH", action="prepend_to", value=os.path.join( install_dir, 'venv', 'bin' ) ) ) + if env_var_dicts: + action_dict[ 'environment_variable' ] = env_var_dicts + actions.append( ( 'set_environment', action_dict ) ) + return tool_dependency, actions else: raise NotImplementedError( 'Only install version 1.0 is currently supported (i.e., change your tag to be ).' ) return None, actions From 2140a14bf099497faceac17afdaff854dd075ef8 Mon Sep 17 00:00:00 2001 From: Jeremy Goecks Date: Mon, 20 May 2013 16:16:03 -0400 Subject: [PATCH 466/523] Trackster: transparency and state fixes for variant painter. --- static/scripts/packed/viz/trackster/painters.js | 2 +- static/scripts/viz/trackster/painters.js | 9 +++++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/static/scripts/packed/viz/trackster/painters.js b/static/scripts/packed/viz/trackster/painters.js index edc5eefbb17..10a62538a3e 100644 --- a/static/scripts/packed/viz/trackster/painters.js +++ b/static/scripts/packed/viz/trackster/painters.js @@ -1 +1 @@ -define(["libs/underscore"],function(_){var extend=_.extend;var BEFORE=1001,CONTAINS=1002,OVERLAP_START=1003,OVERLAP_END=1004,CONTAINED_BY=1005,AFTER=1006;var compute_overlap=function(first_region,second_region){var first_start=first_region[0],first_end=first_region[1],second_start=second_region[0],second_end=second_region[1],overlap;if(first_startsecond_end){overlap=AFTER}else{if(first_end<=second_end){overlap=CONTAINED_BY}else{overlap=OVERLAP_END}}}return overlap};var is_overlap=function(first_region,second_region){var overlap=compute_overlap(first_region,second_region);return(overlap!==BEFORE&&overlap!==AFTER)};var dashedLine=function(ctx,x1,y1,x2,y2,dashLen){if(dashLen===undefined){dashLen=4}var dX=x2-x1;var dY=y2-y1;var dashes=Math.floor(Math.sqrt(dX*dX+dY*dY)/dashLen);var dashX=dX/dashes;var dashY=dY/dashes;var q;for(q=0;q1){delta_x_px=Math.ceil((data[1][0]-data[0][0])*w_scale)}else{delta_x_px=10}var painter_color=this.prefs.block_color||this.prefs.color,pref_color=parseInt(painter_color.slice(1),16),pref_r=(pref_color&16711680)>>16,pref_g=(pref_color&65280)>>8,pref_b=pref_color&255;for(var i=0,len=data.length;imax_value){top_overflow=true;y=max_value}}if(mode==="Histogram"){y=Math.round(y/vertical_range*height_px);ctx.fillRect(x_scaled,y_zero,delta_x_px,-y)}else{if(mode==="Intensity"){var saturation=(y-min_value)/vertical_range,new_r=Math.round(pref_r+(255-pref_r)*(1-saturation)),new_g=Math.round(pref_g+(255-pref_g)*(1-saturation)),new_b=Math.round(pref_b+(255-pref_b)*(1-saturation));ctx.fillStyle="rgb("+new_r+","+new_g+","+new_b+")";ctx.fillRect(x_scaled,0,delta_x_px,height_px)}else{y=Math.round(height_px-(y-min_value)/vertical_range*height_px);if(in_path){ctx.lineTo(x_scaled,y)}else{in_path=true;if(mode==="Filled"){ctx.moveTo(x_scaled,height_px);ctx.lineTo(x_scaled,y)}else{ctx.moveTo(x_scaled,y)}}}}ctx.fillStyle=this.prefs.overflow_color;if(top_overflow||bot_overflow){var overflow_x;if(mode==="Histogram"||mode==="Intensity"){overflow_x=delta_x_px}else{x_scaled-=2;overflow_x=4}if(top_overflow){ctx.fillRect(x_scaled,0,overflow_x,3)}if(bot_overflow){ctx.fillRect(x_scaled,height_px-3,overflow_x,3)}}ctx.fillStyle=painter_color}if(mode==="Filled"){if(in_path){ctx.lineTo(x_scaled,y_zero);ctx.lineTo(0,y_zero)}ctx.fill()}else{ctx.stroke()}ctx.restore()};var FeaturePositionMapper=function(slot_height){this.feature_positions={};this.slot_height=slot_height;this.translation=0;this.y_translation=0};FeaturePositionMapper.prototype.map_feature_data=function(feature_data,slot,x_start,x_end){if(!this.feature_positions[slot]){this.feature_positions[slot]=[]}this.feature_positions[slot].push({data:feature_data,x_start:x_start,x_end:x_end})};FeaturePositionMapper.prototype.get_feature_data=function(x,y){var slot=Math.floor((y-this.y_translation)/this.slot_height),feature_dict;if(!this.feature_positions[slot]){return null}x+=this.translation;for(var i=0;i=feature_dict.x_start&&x<=feature_dict.x_end){return feature_dict.data}}};var FeaturePainter=function(data,view_start,view_end,prefs,mode,alpha_scaler,height_scaler){Painter.call(this,data,view_start,view_end,prefs,mode);this.alpha_scaler=(alpha_scaler?alpha_scaler:new Scaler());this.height_scaler=(height_scaler?height_scaler:new Scaler())};FeaturePainter.prototype.default_prefs={block_color:"#FFF",connector_color:"#FFF"};extend(FeaturePainter.prototype,{get_required_height:function(rows_required,width){var required_height=this.get_row_height(),y_scale=required_height,mode=this.mode;if(mode==="no_detail"||mode==="Squish"||mode==="Pack"){required_height=rows_required*y_scale}return required_height+this.get_top_padding(width)+this.get_bottom_padding(width)},get_top_padding:function(width){return 0},get_bottom_padding:function(width){return Math.max(Math.round(this.get_row_height()/2),5)},draw:function(ctx,width,height,w_scale,slots){var data=this.data,view_start=this.view_start,view_end=this.view_end;ctx.save();ctx.fillStyle=this.prefs.block_color;ctx.textAlign="right";var y_scale=this.get_row_height(),feature_mapper=new FeaturePositionMapper(y_scale),x_draw_coords;for(var i=0,len=data.length;iview_start)&&(this.mode==="Dense"||slot!==null)){x_draw_coords=this.draw_element(ctx,this.mode,feature,slot,view_start,view_end,w_scale,y_scale,width);feature_mapper.map_feature_data(feature,slot,x_draw_coords[0],x_draw_coords[1])}}ctx.restore();feature_mapper.y_translation=this.get_top_padding(width);return feature_mapper},draw_element:function(ctx,mode,feature,slot,tile_low,tile_high,w_scale,y_scale,width){console.log("WARNING: Unimplemented function.");return[0,0]}});var DENSE_TRACK_HEIGHT=10,NO_DETAIL_TRACK_HEIGHT=3,SQUISH_TRACK_HEIGHT=5,PACK_TRACK_HEIGHT=10,NO_DETAIL_FEATURE_HEIGHT=1,DENSE_FEATURE_HEIGHT=9,SQUISH_FEATURE_HEIGHT=3,PACK_FEATURE_HEIGHT=9,LABEL_SPACING=2,CONNECTOR_COLOR="#ccc";var LinkedFeaturePainter=function(data,view_start,view_end,prefs,mode,alpha_scaler,height_scaler){FeaturePainter.call(this,data,view_start,view_end,prefs,mode,alpha_scaler,height_scaler);this.draw_background_connector=true;this.draw_individual_connectors=false};extend(LinkedFeaturePainter.prototype,FeaturePainter.prototype,{get_row_height:function(){var mode=this.mode,height;if(mode==="Dense"){height=DENSE_TRACK_HEIGHT}else{if(mode==="no_detail"){height=NO_DETAIL_TRACK_HEIGHT}else{if(mode==="Squish"){height=SQUISH_TRACK_HEIGHT}else{height=PACK_TRACK_HEIGHT}}}return height},draw_element:function(ctx,mode,feature,slot,tile_low,tile_high,w_scale,y_scale,width){var feature_uid=feature[0],feature_start=feature[1],feature_end=feature[2],feature_name=feature[3],feature_strand=feature[4],f_start=Math.floor(Math.max(0,(feature_start-tile_low-0.5)*w_scale)),f_end=Math.ceil(Math.min(width,Math.max(0,(feature_end-tile_low-0.5)*w_scale))),draw_start=f_start,draw_end=f_end,y_center=(mode==="Dense"?0:(0+slot))*y_scale+this.get_top_padding(width),thickness,y_start,thick_start=null,thick_end=null,block_color=(!feature_strand||feature_strand==="+"||feature_strand==="."?this.prefs.block_color:this.prefs.reverse_strand_color);label_color=this.prefs.label_color;ctx.globalAlpha=this.alpha_scaler.gen_val(feature);if(mode==="Dense"){slot=1}if(mode==="no_detail"){ctx.fillStyle=block_color;ctx.fillRect(f_start,y_center+5,f_end-f_start,NO_DETAIL_FEATURE_HEIGHT)}else{var feature_ts=feature[5],feature_te=feature[6],feature_blocks=feature[7],full_height=true;if(feature_ts&&feature_te){thick_start=Math.floor(Math.max(0,(feature_ts-tile_low)*w_scale));thick_end=Math.ceil(Math.min(width,Math.max(0,(feature_te-tile_low)*w_scale)))}var thin_height,thick_height;if(mode==="Squish"){thin_height=1;thick_height=SQUISH_FEATURE_HEIGHT;full_height=false}else{if(mode==="Dense"){thin_height=5;thick_height=DENSE_FEATURE_HEIGHT}else{thin_height=5;thick_height=PACK_FEATURE_HEIGHT}}if(!feature_blocks){ctx.fillStyle=block_color;ctx.fillRect(f_start,y_center+1,f_end-f_start,thick_height);if(feature_strand&&full_height){if(feature_strand==="+"){ctx.fillStyle=ctx.canvas.manager.get_pattern("right_strand_inv")}else{if(feature_strand==="-"){ctx.fillStyle=ctx.canvas.manager.get_pattern("left_strand_inv")}}ctx.fillRect(f_start,y_center+1,f_end-f_start,thick_height)}}else{var cur_y_center,cur_height;if(mode==="Squish"||mode==="Dense"){cur_y_center=y_center+Math.floor(SQUISH_FEATURE_HEIGHT/2)+1;cur_height=1}else{if(feature_strand){cur_y_center=y_center;cur_height=thick_height}else{cur_y_center+=(SQUISH_FEATURE_HEIGHT/2)+1;cur_height=1}}if(this.draw_background_connector){if(mode==="Squish"||mode==="Dense"){ctx.fillStyle=CONNECTOR_COLOR}else{if(feature_strand){if(feature_strand==="+"){ctx.fillStyle=ctx.canvas.manager.get_pattern("right_strand")}else{if(feature_strand==="-"){ctx.fillStyle=ctx.canvas.manager.get_pattern("left_strand")}}}else{ctx.fillStyle=CONNECTOR_COLOR}}ctx.fillRect(f_start,cur_y_center,f_end-f_start,cur_height)}var start_and_height;for(var k=0,k_len=feature_blocks.length;kblock_end){continue}ctx.fillStyle=block_color;ctx.fillRect(block_start,y_center+(thick_height-thin_height)/2+1,block_end-block_start,thin_height);if(thick_start!==undefined&&feature_te>feature_ts&&!(block_start>thick_end||block_endtile_low){ctx.fillStyle=label_color;if(tile_low===0&&f_start-ctx.measureText(feature_name).width<0){ctx.textAlign="left";ctx.fillText(feature_name,f_end+LABEL_SPACING,y_center+8);draw_end+=ctx.measureText(feature_name).width+LABEL_SPACING}else{ctx.textAlign="right";ctx.fillText(feature_name,f_start-LABEL_SPACING,y_center+8);draw_start-=ctx.measureText(feature_name).width+LABEL_SPACING}}}ctx.globalAlpha=1;return[draw_start,draw_end]}});var ReadPainter=function(data,view_start,view_end,prefs,mode,alpha_scaler,height_scaler,ref_seq,base_color_fn){FeaturePainter.call(this,data,view_start,view_end,prefs,mode,alpha_scaler,height_scaler);this.ref_seq=(ref_seq?ref_seq.data:null);this.base_color_fn=base_color_fn};extend(ReadPainter.prototype,FeaturePainter.prototype,{get_row_height:function(){var height,mode=this.mode;if(mode==="Dense"){height=DENSE_TRACK_HEIGHT}else{if(mode==="Squish"){height=SQUISH_TRACK_HEIGHT}else{height=PACK_TRACK_HEIGHT;if(this.prefs.show_insertions){height*=2}}}return height},_parse_cigar:function(cigar_str){var cigar_ops="MIDNSHP=X";var blocks=[[0,0]],cur_block=blocks[0],base_pos=0,parsed_cigar=_.map(cigar_str.match(/[0-9]+[MIDNSHP=X]/g),function(op){var op_len=parseInt(op.slice(0,-1),10),op_char=op.slice(-1);if(op_char==="N"){if(cur_block[1]!==0){cur_block=[base_pos+op_len,base_pos+op_len];blocks.push(cur_block)}}else{if("ISHP".indexOf(op_char)===-1){cur_block[1]+=op_len;base_pos+=op_len}}return[cigar_ops.indexOf(op_char),op_len]});return{blocks:blocks,cigar:parsed_cigar}},draw_read:function(ctx,mode,w_scale,y_center,tile_low,tile_high,feature_start,cigar,strand,read_seq){ctx.textAlign="center";var tile_region=[tile_low,tile_high],base_offset=0,seq_offset=0,gap=Math.round(w_scale/2),char_width_px=ctx.canvas.manager.char_width_px,block_color=(strand==="+"?this.prefs.block_color:this.prefs.reverse_strand_color),pack_mode=(mode==="Pack");var draw_last=[];if(!cigar){cigar=[[0,read_seq.length]]}for(var cig_id=0,len=cigar.length;cig_id=tile_low&&seq_start+c<=tile_high){ref_char=(this.ref_seq?this.ref_seq[seq_start-tile_low+c]:null);read_char=seq[c];if((ref_char&&(!this.prefs.show_differences||(read_char.toLowerCase!=="n"&&(ref_char.toLowerCase()!==read_char.toLowerCase()))))||(!ref_char&&!this.prefs.show_differences)){var c_start=Math.floor(Math.max(0,(seq_start+c-tile_low)*w_scale));ctx.fillStyle=this.base_color_fn(seq[c]);if(pack_mode&&w_scale>char_width_px){ctx.fillText(seq[c],c_start,y_center+9)}else{if(w_scale>0.05){ctx.fillRect(c_start-gap,y_center+(pack_mode?1:4),Math.max(1,Math.round(w_scale)),(pack_mode?PACK_FEATURE_HEIGHT:SQUISH_FEATURE_HEIGHT))}}}}}}seq_offset+=cig_len;base_offset+=cig_len;break;case"N":ctx.fillStyle=CONNECTOR_COLOR;ctx.fillRect(s_start,y_center+5,s_end-s_start,1);base_offset+=cig_len;break;case"D":ctx.fillStyle="black";ctx.fillRect(s_start,y_center+4,s_end-s_start,3);base_offset+=cig_len;break;case"P":break;case"I":var insert_x_coord=s_start-gap;if(is_overlap([seq_start,seq_start+cig_len],tile_region)){var seq=read_seq.slice(seq_offset,seq_offset+cig_len);if(this.prefs.show_insertions){var x_center=s_start-(s_end-s_start)/2;if((mode==="Pack"||this.mode==="Auto")&&read_seq!==undefined&&w_scale>char_width_px){ctx.fillStyle="yellow";ctx.fillRect(x_center-gap,y_center-9,s_end-s_start,9);draw_last[draw_last.length]={type:"triangle",data:[insert_x_coord,y_center+4,5]};ctx.fillStyle=CONNECTOR_COLOR;switch(compute_overlap([seq_start,seq_start+cig_len],tile_region)){case (OVERLAP_START):seq=seq.slice(tile_low-seq_start);break;case (OVERLAP_END):seq=seq.slice(0,seq_start-tile_high);break;case (CONTAINED_BY):break;case (CONTAINS):seq=seq.slice(tile_low-seq_start,seq_start-tile_high);break}for(var c=0,str_len=seq.length;cchar_width_px){draw_last.push({type:"text",data:[seq.length,insert_x_coord,y_center+9]})}else{}}}seq_offset+=cig_len;break}}ctx.fillStyle="yellow";var item,type,data;for(var i=0;i=tile_low&&feature[4][0]<=tile_high&&feature[4][2]){this.draw_read(ctx,mode,w_scale,y_center,tile_low,tile_high,feature[4][0],feature[4][2],feature[4][3],feature[4][4])}else{connector=false}if(feature[5][1]>=tile_low&&feature[5][0]<=tile_high&&feature[5][2]){this.draw_read(ctx,mode,w_scale,y_center,tile_low,tile_high,feature[5][0],feature[5][2],feature[5][3],feature[5][4])}else{connector=false}if(connector&&b2_start>b1_end){ctx.fillStyle=CONNECTOR_COLOR;dashedLine(ctx,b1_end,y_center+5,b2_start,y_center+5)}}else{this.draw_read(ctx,mode,w_scale,y_center,tile_low,tile_high,feature_start,feature[4],feature[5],feature[6])}if(mode==="Pack"&&feature_start>=tile_low&&feature_name!=="."){ctx.fillStyle=this.prefs.label_color;if(tile_low===0&&f_start-ctx.measureText(feature_name).width<0){ctx.textAlign="left";ctx.fillText(feature_name,f_end+LABEL_SPACING,y_center+8)}else{ctx.textAlign="right";ctx.fillText(feature_name,f_start-LABEL_SPACING,y_center+8)}}return[0,0]}});var RefBasedReadPainter=function(data,view_start,view_end,prefs,mode,alpha_scaler,height_scaler,ref_seq,base_color_fn){ReadPainter.call(this,data,view_start,view_end,prefs,mode,alpha_scaler,height_scaler,ref_seq,base_color_fn)};extend(RefBasedReadPainter.prototype,ReadPainter.prototype,FeaturePainter,{draw_read:function(ctx,mode,w_scale,y_center,tile_low,tile_high,feature_start,cigar,strand,read_seq){ctx.textAlign="center";var tile_region=[tile_low,tile_high],base_offset=0,seq_offset=0,gap=Math.round(w_scale/2),char_width_px=ctx.canvas.manager.char_width_px,block_color=(strand==="+"?this.prefs.block_color:this.prefs.reverse_strand_color),pack_mode=(mode==="Pack"),drawing_blocks=[];var draw_last=[];var t=this._parse_cigar(cigar);cigar=t.cigar;drawing_blocks=t.blocks;for(var i=0;ichar_width_px){ctx.fillText(cur_seq[c],c_start,y_center+9)}else{if(w_scale>0.05){ctx.fillRect(c_start-gap,y_center+(pack_mode?1:4),Math.max(1,Math.round(w_scale)),(pack_mode?PACK_FEATURE_HEIGHT:SQUISH_FEATURE_HEIGHT))}}}}}if(cig_op==="X"){seq_offset+=cig_len}base_offset+=cig_len;break;case"N":ctx.fillStyle=CONNECTOR_COLOR;ctx.fillRect(s_start,y_center+5,s_end-s_start,1);base_offset+=cig_len;break;case"D":ctx.fillStyle="black";ctx.fillRect(s_start,y_center+4,s_end-s_start,3);base_offset+=cig_len;break;case"I":var insert_x_coord=s_start-gap;if(is_overlap([seq_start,seq_start+cig_len],tile_region)){var seq=read_seq.slice(seq_offset,seq_offset+cig_len);if(this.prefs.show_insertions){var x_center=s_start-(s_end-s_start)/2;if((mode==="Pack"||this.mode==="Auto")&&read_seq!==undefined&&w_scale>char_width_px){ctx.fillStyle="yellow";ctx.fillRect(x_center-gap,y_center-9,s_end-s_start,9);draw_last[draw_last.length]={type:"triangle",data:[insert_x_coord,y_center+4,5]};ctx.fillStyle=CONNECTOR_COLOR;switch(compute_overlap([seq_start,seq_start+cig_len],tile_region)){case (OVERLAP_START):seq=seq.slice(tile_low-seq_start);break;case (OVERLAP_END):seq=seq.slice(0,seq_start-tile_high);break;case (CONTAINED_BY):break;case (CONTAINS):seq=seq.slice(tile_low-seq_start,seq_start-tile_high);break}for(var c=0,str_len=seq.length;cchar_width_px){draw_last.push({type:"text",data:[seq.length,insert_x_coord,y_center+9]})}else{}}}seq_offset+=cig_len;break}}ctx.fillStyle="yellow";var item,type,data;for(var i=0;i0){ctx.beginPath();ctx.arc(x_center,y_center,block2_start-x_center,Math.PI,0);ctx.stroke()}}});var Color=function(rgb,a){if(Array.isArray(rgb)){this.rgb=rgb}else{if(rgb.length==6){this.rgb=rgb.match(/.{2}/g).map(function(c){return parseInt(c,16)})}else{if(rgb.length==7){this.rgb=rgb.substring(1,7).match(/.{2}/g).map(function(c){return parseInt(c,16)})}else{this.rgb=rgb.split("").map(function(c){return parseInt(c+c,16)})}}}this.alpha=typeof(a)==="number"?a:1};Color.prototype={eval:function(){return this},toCSS:function(){if(this.alpha<1){return"rgba("+this.rgb.map(function(c){return Math.round(c)}).concat(this.alpha).join(", ")+")"}else{return"#"+this.rgb.map(function(i){i=Math.round(i);i=(i>255?255:(i<0?0:i)).toString(16);return i.length===1?"0"+i:i}).join("")}},toHSL:function(){var r=this.rgb[0]/255,g=this.rgb[1]/255,b=this.rgb[2]/255,a=this.alpha;var max=Math.max(r,g,b),min=Math.min(r,g,b);var h,s,l=(max+min)/2,d=max-min;if(max===min){h=s=0}else{s=l>0.5?d/(2-max-min):d/(max+min);switch(max){case r:h=(g-b)/d+(g255?255:(i<0?0:i)).toString(16);return i.length===1?"0"+i:i}).join("")},mix:function(color2,weight){color1=this;var p=weight;var w=p*2-1;var a=color1.toHSL().a-color2.toHSL().a;var w1=(((w*a==-1)?w:(w+a)/(1+w*a))+1)/2;var w2=1-w1;var rgb=[color1.rgb[0]*w1+color2.rgb[0]*w2,color1.rgb[1]*w1+color2.rgb[1]*w2,color1.rgb[2]*w1+color2.rgb[2]*w2];var alpha=color1.alpha*p+color2.alpha*(1-p);return new Color(rgb,alpha)}};var LinearRamp=function(start_color,end_color,start_value,end_value){this.start_color=new Color(start_color);this.end_color=new Color(end_color);this.start_value=start_value;this.end_value=end_value;this.value_range=end_value-start_value};LinearRamp.prototype.map_value=function(value){value=Math.max(value,this.start_value);value=Math.min(value,this.end_value);value=(value-this.start_value)/this.value_range;return this.start_color.mix(this.end_color,1-value).toCSS()};var SplitRamp=function(start_color,middle_color,end_color,start_value,end_value){this.positive_ramp=new LinearRamp(middle_color,end_color,0,end_value);this.negative_ramp=new LinearRamp(middle_color,start_color,0,-start_value);this.start_value=start_value;this.end_value=end_value};SplitRamp.prototype.map_value=function(value){value=Math.max(value,this.start_value);value=Math.min(value,this.end_value);if(value>=0){return this.positive_ramp.map_value(value)}else{return this.negative_ramp.map_value(-value)}};var DiagonalHeatmapPainter=function(data,view_start,view_end,prefs,mode){Painter.call(this,data,view_start,view_end,prefs,mode);var i,len;if(this.prefs.min_value===undefined){var min_value=Infinity;for(i=0,len=this.data.length;isecond_end){overlap=AFTER}else{if(first_end<=second_end){overlap=CONTAINED_BY}else{overlap=OVERLAP_END}}}return overlap};var is_overlap=function(first_region,second_region){var overlap=compute_overlap(first_region,second_region);return(overlap!==BEFORE&&overlap!==AFTER)};var dashedLine=function(ctx,x1,y1,x2,y2,dashLen){if(dashLen===undefined){dashLen=4}var dX=x2-x1;var dY=y2-y1;var dashes=Math.floor(Math.sqrt(dX*dX+dY*dY)/dashLen);var dashX=dX/dashes;var dashY=dY/dashes;var q;for(q=0;q1){delta_x_px=Math.ceil((data[1][0]-data[0][0])*w_scale)}else{delta_x_px=10}var painter_color=this.prefs.block_color||this.prefs.color,pref_color=parseInt(painter_color.slice(1),16),pref_r=(pref_color&16711680)>>16,pref_g=(pref_color&65280)>>8,pref_b=pref_color&255;for(var i=0,len=data.length;imax_value){top_overflow=true;y=max_value}}if(mode==="Histogram"){y=Math.round(y/vertical_range*height_px);ctx.fillRect(x_scaled,y_zero,delta_x_px,-y)}else{if(mode==="Intensity"){var saturation=(y-min_value)/vertical_range,new_r=Math.round(pref_r+(255-pref_r)*(1-saturation)),new_g=Math.round(pref_g+(255-pref_g)*(1-saturation)),new_b=Math.round(pref_b+(255-pref_b)*(1-saturation));ctx.fillStyle="rgb("+new_r+","+new_g+","+new_b+")";ctx.fillRect(x_scaled,0,delta_x_px,height_px)}else{y=Math.round(height_px-(y-min_value)/vertical_range*height_px);if(in_path){ctx.lineTo(x_scaled,y)}else{in_path=true;if(mode==="Filled"){ctx.moveTo(x_scaled,height_px);ctx.lineTo(x_scaled,y)}else{ctx.moveTo(x_scaled,y)}}}}ctx.fillStyle=this.prefs.overflow_color;if(top_overflow||bot_overflow){var overflow_x;if(mode==="Histogram"||mode==="Intensity"){overflow_x=delta_x_px}else{x_scaled-=2;overflow_x=4}if(top_overflow){ctx.fillRect(x_scaled,0,overflow_x,3)}if(bot_overflow){ctx.fillRect(x_scaled,height_px-3,overflow_x,3)}}ctx.fillStyle=painter_color}if(mode==="Filled"){if(in_path){ctx.lineTo(x_scaled,y_zero);ctx.lineTo(0,y_zero)}ctx.fill()}else{ctx.stroke()}ctx.restore()};var FeaturePositionMapper=function(slot_height){this.feature_positions={};this.slot_height=slot_height;this.translation=0;this.y_translation=0};FeaturePositionMapper.prototype.map_feature_data=function(feature_data,slot,x_start,x_end){if(!this.feature_positions[slot]){this.feature_positions[slot]=[]}this.feature_positions[slot].push({data:feature_data,x_start:x_start,x_end:x_end})};FeaturePositionMapper.prototype.get_feature_data=function(x,y){var slot=Math.floor((y-this.y_translation)/this.slot_height),feature_dict;if(!this.feature_positions[slot]){return null}x+=this.translation;for(var i=0;i=feature_dict.x_start&&x<=feature_dict.x_end){return feature_dict.data}}};var FeaturePainter=function(data,view_start,view_end,prefs,mode,alpha_scaler,height_scaler){Painter.call(this,data,view_start,view_end,prefs,mode);this.alpha_scaler=(alpha_scaler?alpha_scaler:new Scaler());this.height_scaler=(height_scaler?height_scaler:new Scaler())};FeaturePainter.prototype.default_prefs={block_color:"#FFF",connector_color:"#FFF"};extend(FeaturePainter.prototype,{get_required_height:function(rows_required,width){var required_height=this.get_row_height(),y_scale=required_height,mode=this.mode;if(mode==="no_detail"||mode==="Squish"||mode==="Pack"){required_height=rows_required*y_scale}return required_height+this.get_top_padding(width)+this.get_bottom_padding(width)},get_top_padding:function(width){return 0},get_bottom_padding:function(width){return Math.max(Math.round(this.get_row_height()/2),5)},draw:function(ctx,width,height,w_scale,slots){var data=this.data,view_start=this.view_start,view_end=this.view_end;ctx.save();ctx.fillStyle=this.prefs.block_color;ctx.textAlign="right";var y_scale=this.get_row_height(),feature_mapper=new FeaturePositionMapper(y_scale),x_draw_coords;for(var i=0,len=data.length;iview_start)&&(this.mode==="Dense"||slot!==null)){x_draw_coords=this.draw_element(ctx,this.mode,feature,slot,view_start,view_end,w_scale,y_scale,width);feature_mapper.map_feature_data(feature,slot,x_draw_coords[0],x_draw_coords[1])}}ctx.restore();feature_mapper.y_translation=this.get_top_padding(width);return feature_mapper},draw_element:function(ctx,mode,feature,slot,tile_low,tile_high,w_scale,y_scale,width){console.log("WARNING: Unimplemented function.");return[0,0]}});var DENSE_TRACK_HEIGHT=10,NO_DETAIL_TRACK_HEIGHT=3,SQUISH_TRACK_HEIGHT=5,PACK_TRACK_HEIGHT=10,NO_DETAIL_FEATURE_HEIGHT=1,DENSE_FEATURE_HEIGHT=9,SQUISH_FEATURE_HEIGHT=3,PACK_FEATURE_HEIGHT=9,LABEL_SPACING=2,CONNECTOR_COLOR="#ccc";var LinkedFeaturePainter=function(data,view_start,view_end,prefs,mode,alpha_scaler,height_scaler){FeaturePainter.call(this,data,view_start,view_end,prefs,mode,alpha_scaler,height_scaler);this.draw_background_connector=true;this.draw_individual_connectors=false};extend(LinkedFeaturePainter.prototype,FeaturePainter.prototype,{get_row_height:function(){var mode=this.mode,height;if(mode==="Dense"){height=DENSE_TRACK_HEIGHT}else{if(mode==="no_detail"){height=NO_DETAIL_TRACK_HEIGHT}else{if(mode==="Squish"){height=SQUISH_TRACK_HEIGHT}else{height=PACK_TRACK_HEIGHT}}}return height},draw_element:function(ctx,mode,feature,slot,tile_low,tile_high,w_scale,y_scale,width){var feature_uid=feature[0],feature_start=feature[1],feature_end=feature[2],feature_name=feature[3],feature_strand=feature[4],f_start=Math.floor(Math.max(0,(feature_start-tile_low-0.5)*w_scale)),f_end=Math.ceil(Math.min(width,Math.max(0,(feature_end-tile_low-0.5)*w_scale))),draw_start=f_start,draw_end=f_end,y_center=(mode==="Dense"?0:(0+slot))*y_scale+this.get_top_padding(width),thickness,y_start,thick_start=null,thick_end=null,block_color=(!feature_strand||feature_strand==="+"||feature_strand==="."?this.prefs.block_color:this.prefs.reverse_strand_color);label_color=this.prefs.label_color;ctx.globalAlpha=this.alpha_scaler.gen_val(feature);if(mode==="Dense"){slot=1}if(mode==="no_detail"){ctx.fillStyle=block_color;ctx.fillRect(f_start,y_center+5,f_end-f_start,NO_DETAIL_FEATURE_HEIGHT)}else{var feature_ts=feature[5],feature_te=feature[6],feature_blocks=feature[7],full_height=true;if(feature_ts&&feature_te){thick_start=Math.floor(Math.max(0,(feature_ts-tile_low)*w_scale));thick_end=Math.ceil(Math.min(width,Math.max(0,(feature_te-tile_low)*w_scale)))}var thin_height,thick_height;if(mode==="Squish"){thin_height=1;thick_height=SQUISH_FEATURE_HEIGHT;full_height=false}else{if(mode==="Dense"){thin_height=5;thick_height=DENSE_FEATURE_HEIGHT}else{thin_height=5;thick_height=PACK_FEATURE_HEIGHT}}if(!feature_blocks){ctx.fillStyle=block_color;ctx.fillRect(f_start,y_center+1,f_end-f_start,thick_height);if(feature_strand&&full_height){if(feature_strand==="+"){ctx.fillStyle=ctx.canvas.manager.get_pattern("right_strand_inv")}else{if(feature_strand==="-"){ctx.fillStyle=ctx.canvas.manager.get_pattern("left_strand_inv")}}ctx.fillRect(f_start,y_center+1,f_end-f_start,thick_height)}}else{var cur_y_center,cur_height;if(mode==="Squish"||mode==="Dense"){cur_y_center=y_center+Math.floor(SQUISH_FEATURE_HEIGHT/2)+1;cur_height=1}else{if(feature_strand){cur_y_center=y_center;cur_height=thick_height}else{cur_y_center+=(SQUISH_FEATURE_HEIGHT/2)+1;cur_height=1}}if(this.draw_background_connector){if(mode==="Squish"||mode==="Dense"){ctx.fillStyle=CONNECTOR_COLOR}else{if(feature_strand){if(feature_strand==="+"){ctx.fillStyle=ctx.canvas.manager.get_pattern("right_strand")}else{if(feature_strand==="-"){ctx.fillStyle=ctx.canvas.manager.get_pattern("left_strand")}}}else{ctx.fillStyle=CONNECTOR_COLOR}}ctx.fillRect(f_start,cur_y_center,f_end-f_start,cur_height)}var start_and_height;for(var k=0,k_len=feature_blocks.length;kblock_end){continue}ctx.fillStyle=block_color;ctx.fillRect(block_start,y_center+(thick_height-thin_height)/2+1,block_end-block_start,thin_height);if(thick_start!==undefined&&feature_te>feature_ts&&!(block_start>thick_end||block_endtile_low){ctx.fillStyle=label_color;if(tile_low===0&&f_start-ctx.measureText(feature_name).width<0){ctx.textAlign="left";ctx.fillText(feature_name,f_end+LABEL_SPACING,y_center+8);draw_end+=ctx.measureText(feature_name).width+LABEL_SPACING}else{ctx.textAlign="right";ctx.fillText(feature_name,f_start-LABEL_SPACING,y_center+8);draw_start-=ctx.measureText(feature_name).width+LABEL_SPACING}}}ctx.globalAlpha=1;return[draw_start,draw_end]}});var ReadPainter=function(data,view_start,view_end,prefs,mode,alpha_scaler,height_scaler,ref_seq,base_color_fn){FeaturePainter.call(this,data,view_start,view_end,prefs,mode,alpha_scaler,height_scaler);this.ref_seq=(ref_seq?ref_seq.data:null);this.base_color_fn=base_color_fn};extend(ReadPainter.prototype,FeaturePainter.prototype,{get_row_height:function(){var height,mode=this.mode;if(mode==="Dense"){height=DENSE_TRACK_HEIGHT}else{if(mode==="Squish"){height=SQUISH_TRACK_HEIGHT}else{height=PACK_TRACK_HEIGHT;if(this.prefs.show_insertions){height*=2}}}return height},_parse_cigar:function(cigar_str){var cigar_ops="MIDNSHP=X";var blocks=[[0,0]],cur_block=blocks[0],base_pos=0,parsed_cigar=_.map(cigar_str.match(/[0-9]+[MIDNSHP=X]/g),function(op){var op_len=parseInt(op.slice(0,-1),10),op_char=op.slice(-1);if(op_char==="N"){if(cur_block[1]!==0){cur_block=[base_pos+op_len,base_pos+op_len];blocks.push(cur_block)}}else{if("ISHP".indexOf(op_char)===-1){cur_block[1]+=op_len;base_pos+=op_len}}return[cigar_ops.indexOf(op_char),op_len]});return{blocks:blocks,cigar:parsed_cigar}},draw_read:function(ctx,mode,w_scale,y_center,tile_low,tile_high,feature_start,cigar,strand,read_seq){ctx.textAlign="center";var tile_region=[tile_low,tile_high],base_offset=0,seq_offset=0,gap=Math.round(w_scale/2),char_width_px=ctx.canvas.manager.char_width_px,block_color=(strand==="+"?this.prefs.block_color:this.prefs.reverse_strand_color),pack_mode=(mode==="Pack");var draw_last=[];if(!cigar){cigar=[[0,read_seq.length]]}for(var cig_id=0,len=cigar.length;cig_id=tile_low&&seq_start+c<=tile_high){ref_char=(this.ref_seq?this.ref_seq[seq_start-tile_low+c]:null);read_char=seq[c];if((ref_char&&(!this.prefs.show_differences||(read_char.toLowerCase!=="n"&&(ref_char.toLowerCase()!==read_char.toLowerCase()))))||(!ref_char&&!this.prefs.show_differences)){var c_start=Math.floor(Math.max(0,(seq_start+c-tile_low)*w_scale));ctx.fillStyle=this.base_color_fn(seq[c]);if(pack_mode&&w_scale>char_width_px){ctx.fillText(seq[c],c_start,y_center+9)}else{if(w_scale>0.05){ctx.fillRect(c_start-gap,y_center+(pack_mode?1:4),Math.max(1,Math.round(w_scale)),(pack_mode?PACK_FEATURE_HEIGHT:SQUISH_FEATURE_HEIGHT))}}}}}}seq_offset+=cig_len;base_offset+=cig_len;break;case"N":ctx.fillStyle=CONNECTOR_COLOR;ctx.fillRect(s_start,y_center+5,s_end-s_start,1);base_offset+=cig_len;break;case"D":ctx.fillStyle="black";ctx.fillRect(s_start,y_center+4,s_end-s_start,3);base_offset+=cig_len;break;case"P":break;case"I":var insert_x_coord=s_start-gap;if(is_overlap([seq_start,seq_start+cig_len],tile_region)){var seq=read_seq.slice(seq_offset,seq_offset+cig_len);if(this.prefs.show_insertions){var x_center=s_start-(s_end-s_start)/2;if((mode==="Pack"||this.mode==="Auto")&&read_seq!==undefined&&w_scale>char_width_px){ctx.fillStyle="yellow";ctx.fillRect(x_center-gap,y_center-9,s_end-s_start,9);draw_last[draw_last.length]={type:"triangle",data:[insert_x_coord,y_center+4,5]};ctx.fillStyle=CONNECTOR_COLOR;switch(compute_overlap([seq_start,seq_start+cig_len],tile_region)){case (OVERLAP_START):seq=seq.slice(tile_low-seq_start);break;case (OVERLAP_END):seq=seq.slice(0,seq_start-tile_high);break;case (CONTAINED_BY):break;case (CONTAINS):seq=seq.slice(tile_low-seq_start,seq_start-tile_high);break}for(var c=0,str_len=seq.length;cchar_width_px){draw_last.push({type:"text",data:[seq.length,insert_x_coord,y_center+9]})}else{}}}seq_offset+=cig_len;break}}ctx.fillStyle="yellow";var item,type,data;for(var i=0;i=tile_low&&feature[4][0]<=tile_high&&feature[4][2]){this.draw_read(ctx,mode,w_scale,y_center,tile_low,tile_high,feature[4][0],feature[4][2],feature[4][3],feature[4][4])}else{connector=false}if(feature[5][1]>=tile_low&&feature[5][0]<=tile_high&&feature[5][2]){this.draw_read(ctx,mode,w_scale,y_center,tile_low,tile_high,feature[5][0],feature[5][2],feature[5][3],feature[5][4])}else{connector=false}if(connector&&b2_start>b1_end){ctx.fillStyle=CONNECTOR_COLOR;dashedLine(ctx,b1_end,y_center+5,b2_start,y_center+5)}}else{this.draw_read(ctx,mode,w_scale,y_center,tile_low,tile_high,feature_start,feature[4],feature[5],feature[6])}if(mode==="Pack"&&feature_start>=tile_low&&feature_name!=="."){ctx.fillStyle=this.prefs.label_color;if(tile_low===0&&f_start-ctx.measureText(feature_name).width<0){ctx.textAlign="left";ctx.fillText(feature_name,f_end+LABEL_SPACING,y_center+8)}else{ctx.textAlign="right";ctx.fillText(feature_name,f_start-LABEL_SPACING,y_center+8)}}return[0,0]}});var RefBasedReadPainter=function(data,view_start,view_end,prefs,mode,alpha_scaler,height_scaler,ref_seq,base_color_fn){ReadPainter.call(this,data,view_start,view_end,prefs,mode,alpha_scaler,height_scaler,ref_seq,base_color_fn)};extend(RefBasedReadPainter.prototype,ReadPainter.prototype,FeaturePainter,{draw_read:function(ctx,mode,w_scale,y_center,tile_low,tile_high,feature_start,cigar,strand,read_seq){ctx.textAlign="center";var tile_region=[tile_low,tile_high],base_offset=0,seq_offset=0,gap=Math.round(w_scale/2),char_width_px=ctx.canvas.manager.char_width_px,block_color=(strand==="+"?this.prefs.block_color:this.prefs.reverse_strand_color),pack_mode=(mode==="Pack"),drawing_blocks=[];var draw_last=[];var t=this._parse_cigar(cigar);cigar=t.cigar;drawing_blocks=t.blocks;for(var i=0;ichar_width_px){ctx.fillText(cur_seq[c],c_start,y_center+9)}else{if(w_scale>0.05){ctx.fillRect(c_start-gap,y_center+(pack_mode?1:4),Math.max(1,Math.round(w_scale)),(pack_mode?PACK_FEATURE_HEIGHT:SQUISH_FEATURE_HEIGHT))}}}}}if(cig_op==="X"){seq_offset+=cig_len}base_offset+=cig_len;break;case"N":ctx.fillStyle=CONNECTOR_COLOR;ctx.fillRect(s_start,y_center+5,s_end-s_start,1);base_offset+=cig_len;break;case"D":ctx.fillStyle="black";ctx.fillRect(s_start,y_center+4,s_end-s_start,3);base_offset+=cig_len;break;case"I":var insert_x_coord=s_start-gap;if(is_overlap([seq_start,seq_start+cig_len],tile_region)){var seq=read_seq.slice(seq_offset,seq_offset+cig_len);if(this.prefs.show_insertions){var x_center=s_start-(s_end-s_start)/2;if((mode==="Pack"||this.mode==="Auto")&&read_seq!==undefined&&w_scale>char_width_px){ctx.fillStyle="yellow";ctx.fillRect(x_center-gap,y_center-9,s_end-s_start,9);draw_last[draw_last.length]={type:"triangle",data:[insert_x_coord,y_center+4,5]};ctx.fillStyle=CONNECTOR_COLOR;switch(compute_overlap([seq_start,seq_start+cig_len],tile_region)){case (OVERLAP_START):seq=seq.slice(tile_low-seq_start);break;case (OVERLAP_END):seq=seq.slice(0,seq_start-tile_high);break;case (CONTAINED_BY):break;case (CONTAINS):seq=seq.slice(tile_low-seq_start,seq_start-tile_high);break}for(var c=0,str_len=seq.length;cchar_width_px){draw_last.push({type:"text",data:[seq.length,insert_x_coord,y_center+9]})}else{}}}seq_offset+=cig_len;break}}ctx.fillStyle="yellow";var item,type,data;for(var i=0;i0){ctx.beginPath();ctx.arc(x_center,y_center,block2_start-x_center,Math.PI,0);ctx.stroke()}}});var Color=function(rgb,a){if(Array.isArray(rgb)){this.rgb=rgb}else{if(rgb.length==6){this.rgb=rgb.match(/.{2}/g).map(function(c){return parseInt(c,16)})}else{if(rgb.length==7){this.rgb=rgb.substring(1,7).match(/.{2}/g).map(function(c){return parseInt(c,16)})}else{this.rgb=rgb.split("").map(function(c){return parseInt(c+c,16)})}}}this.alpha=typeof(a)==="number"?a:1};Color.prototype={eval:function(){return this},toCSS:function(){if(this.alpha<1){return"rgba("+this.rgb.map(function(c){return Math.round(c)}).concat(this.alpha).join(", ")+")"}else{return"#"+this.rgb.map(function(i){i=Math.round(i);i=(i>255?255:(i<0?0:i)).toString(16);return i.length===1?"0"+i:i}).join("")}},toHSL:function(){var r=this.rgb[0]/255,g=this.rgb[1]/255,b=this.rgb[2]/255,a=this.alpha;var max=Math.max(r,g,b),min=Math.min(r,g,b);var h,s,l=(max+min)/2,d=max-min;if(max===min){h=s=0}else{s=l>0.5?d/(2-max-min):d/(max+min);switch(max){case r:h=(g-b)/d+(g255?255:(i<0?0:i)).toString(16);return i.length===1?"0"+i:i}).join("")},mix:function(color2,weight){color1=this;var p=weight;var w=p*2-1;var a=color1.toHSL().a-color2.toHSL().a;var w1=(((w*a==-1)?w:(w+a)/(1+w*a))+1)/2;var w2=1-w1;var rgb=[color1.rgb[0]*w1+color2.rgb[0]*w2,color1.rgb[1]*w1+color2.rgb[1]*w2,color1.rgb[2]*w1+color2.rgb[2]*w2];var alpha=color1.alpha*p+color2.alpha*(1-p);return new Color(rgb,alpha)}};var LinearRamp=function(start_color,end_color,start_value,end_value){this.start_color=new Color(start_color);this.end_color=new Color(end_color);this.start_value=start_value;this.end_value=end_value;this.value_range=end_value-start_value};LinearRamp.prototype.map_value=function(value){value=Math.max(value,this.start_value);value=Math.min(value,this.end_value);value=(value-this.start_value)/this.value_range;return this.start_color.mix(this.end_color,1-value).toCSS()};var SplitRamp=function(start_color,middle_color,end_color,start_value,end_value){this.positive_ramp=new LinearRamp(middle_color,end_color,0,end_value);this.negative_ramp=new LinearRamp(middle_color,start_color,0,-start_value);this.start_value=start_value;this.end_value=end_value};SplitRamp.prototype.map_value=function(value){value=Math.max(value,this.start_value);value=Math.min(value,this.end_value);if(value>=0){return this.positive_ramp.map_value(value)}else{return this.negative_ramp.map_value(-value)}};var DiagonalHeatmapPainter=function(data,view_start,view_end,prefs,mode){Painter.call(this,data,view_start,view_end,prefs,mode);var i,len;if(this.prefs.min_value===undefined){var min_value=Infinity;for(i=0,len=this.data.length;i Date: Mon, 20 May 2013 16:50:32 -0400 Subject: [PATCH 467/523] Fix for uploading repository_dependencies.xml without a changeset revision specified when the target repository's latest downloadable changeset revision is followed by two or more non-downloadable changeset revisions. --- lib/tool_shed/util/shed_util_common.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/tool_shed/util/shed_util_common.py b/lib/tool_shed/util/shed_util_common.py index db5a938cab4..a392bf81150 100644 --- a/lib/tool_shed/util/shed_util_common.py +++ b/lib/tool_shed/util/shed_util_common.py @@ -497,7 +497,10 @@ def get_latest_downloadable_changeset_revision( trans, repository, repo ): repository_metadata = get_repository_metadata_by_changeset_revision( trans, trans.security.encode_id( repository.id ), repository_tip ) if repository_metadata and repository_metadata.downloadable: return repository_tip - return get_previous_metadata_changeset_revision( repository, repo, repository_tip, downloadable=True ) + changeset_revisions = get_ordered_metadata_changeset_revisions( repository, repo, downloadable=True ) + if changeset_revisions: + return changeset_revisions[ -1 ] + return INITIAL_CHANGELOG_HASH def get_named_tmpfile_from_ctx( ctx, filename, dir ): """Return a named temporary file created from a specified file with a given name included in a repository changeset revision.""" @@ -583,7 +586,7 @@ def get_ordered_metadata_changeset_revisions( repository, repo, downloadable=Tru rev = '-1' changeset_tups.append( ( rev, changeset_revision ) ) sorted_changeset_tups = sorted( changeset_tups ) - sorted_changeset_revisions = [ changeset_tup[ 1 ] for changeset_tup in sorted_changeset_tups ] + sorted_changeset_revisions = [ str( changeset_tup[ 1 ] ) for changeset_tup in sorted_changeset_tups ] return sorted_changeset_revisions def get_previous_metadata_changeset_revision( repository, repo, before_changeset_revision, downloadable=True ): From 7f61cabe0d49ae36d96b99a08d6285015e7ac362 Mon Sep 17 00:00:00 2001 From: Daniel Blankenberg Date: Tue, 21 May 2013 10:39:47 -0400 Subject: [PATCH 468/523] Fix for uploading toolshed repository via hg. --- lib/galaxy/webapps/tool_shed/controllers/upload.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/galaxy/webapps/tool_shed/controllers/upload.py b/lib/galaxy/webapps/tool_shed/controllers/upload.py index 55ed8838f1a..c273f2fcd3b 100644 --- a/lib/galaxy/webapps/tool_shed/controllers/upload.py +++ b/lib/galaxy/webapps/tool_shed/controllers/upload.py @@ -74,7 +74,7 @@ class UploadController( BaseUIController ): fd, uploaded_file_name = tempfile.mkstemp() uploaded_file = open( uploaded_file_name, 'wb' ) while 1: - chunk = stream.read( CHUNK_SIZE ) + chunk = stream.read( util.CHUNK_SIZE ) if not chunk: break uploaded_file.write( chunk ) From d98e75dcdede243b78e90c3390f4bc72a1d0b558 Mon Sep 17 00:00:00 2001 From: Carl Eberhard Date: Tue, 21 May 2013 10:58:08 -0400 Subject: [PATCH 469/523] Fix to scatterplot to use non-dictionary hda --- .../galaxy/controllers/visualization.py | 42 +------------------ 1 file changed, 2 insertions(+), 40 deletions(-) diff --git a/lib/galaxy/webapps/galaxy/controllers/visualization.py b/lib/galaxy/webapps/galaxy/controllers/visualization.py index 82d98f92758..6be1308bcb5 100644 --- a/lib/galaxy/webapps/galaxy/controllers/visualization.py +++ b/lib/galaxy/webapps/galaxy/controllers/visualization.py @@ -883,48 +883,10 @@ class VisualizationController( BaseUIController, SharableMixin, UsesAnnotations, """ Returns a page that controls and renders a scatteplot graph. """ - # Get HDA. hda = self.get_dataset( trans, dataset_id, check_ownership=False, check_accessible=True ) - hda_dict = hda.get_api_value() - hda_dict[ 'id' ] = dataset_id - - if( ( hda_dict[ 'metadata_column_names' ] == None ) - and ( hasattr( hda.datatype, 'column_names' ) ) ): - hda_dict[ 'metadata_column_names' ] = hda.datatype.column_names - - # try to get the first line (assuming it's a header) - #TODO: doesn't belong here - try: - num_comment_lines = 1 - if hda_dict[ 'metadata_comment_lines' ]: - num_comment_lines = hda_dict[ 'metadata_comment_lines' ] - - infile = open( hda.file_name ) - for index, line in enumerate( infile ): - if 'comment_lines' not in hda_dict: - hda_dict[ 'comment_lines' ] = [] - hda_dict[ 'comment_lines' ].append( line ) - if index >= num_comment_lines: - break - infile.close() - - except Exception, exc: - if infile: - infile.close() - log.error( str( exc ) ) - - history_id = trans.security.encode_id( hda.history.id ) - - #TODO: add column data - # Read data. - #data_provider = ColumnDataProvider( original_dataset=hda, **kwargs ) - #data = data_provider.get_data() - - # Return plot. return trans.fill_template_mako( "visualization/scatterplot.mako", - hda=hda_dict, - historyID=history_id, - kwargs=kwargs ) + hda=hda, + query_args=kwargs ) @web.expose def phyloviz( self, trans, dataset_id, tree_index=0, **kwargs ): From 730a15657f78899f0a67299bfe38a72765ca3c0a Mon Sep 17 00:00:00 2001 From: Carl Eberhard Date: Tue, 21 May 2013 11:15:10 -0400 Subject: [PATCH 470/523] Fixes to phyloviz --- .../data_providers/phyloviz/__init__.py | 2 +- .../data_providers/phyloviz/newickparser.py | 6 +- .../visualization/data_providers/registry.py | 2 +- static/scripts/packed/viz/phyloviz.js | 2 +- static/scripts/viz/phyloviz.js | 304 ++++++++++-------- .../galaxy/visualization/phyloviz.mako | 23 +- 6 files changed, 174 insertions(+), 165 deletions(-) diff --git a/lib/galaxy/visualization/data_providers/phyloviz/__init__.py b/lib/galaxy/visualization/data_providers/phyloviz/__init__.py index 71e5bbe05a9..85745da88ce 100644 --- a/lib/galaxy/visualization/data_providers/phyloviz/__init__.py +++ b/lib/galaxy/visualization/data_providers/phyloviz/__init__.py @@ -1,3 +1,4 @@ + """ Data providers code for PhyloViz """ from galaxy.visualization.data_providers.basic import BaseDataProvider @@ -40,4 +41,3 @@ class PhylovizDataProvider( BaseDataProvider ): rval[ "msg"] = parseMsg return rval - diff --git a/lib/galaxy/visualization/data_providers/phyloviz/newickparser.py b/lib/galaxy/visualization/data_providers/phyloviz/newickparser.py index 0d832830eea..fa6e272c2e9 100644 --- a/lib/galaxy/visualization/data_providers/phyloviz/newickparser.py +++ b/lib/galaxy/visualization/data_providers/phyloviz/newickparser.py @@ -77,8 +77,6 @@ class Newick_Parser(Base_Parser): childrenNodes += [node] return childrenNodes - - def _mapName(self, newickString, nameMap): """ Necessary to replace names of terms inside nexus representation @@ -108,9 +106,9 @@ class Newick_Parser(Base_Parser): newString += newickString[start:] return newString - def parseNode(self, string, depth): - """ Recursive method for parsing newick string, works by stripping down the string into substring + """ + Recursive method for parsing newick string, works by stripping down the string into substring of newick contained with brackers, which is used to call itself. Eg ... ( A, B, (D, E)C, F, G ) ... diff --git a/lib/galaxy/visualization/data_providers/registry.py b/lib/galaxy/visualization/data_providers/registry.py index 185ee3ecf03..ff30ec8ef4d 100644 --- a/lib/galaxy/visualization/data_providers/registry.py +++ b/lib/galaxy/visualization/data_providers/registry.py @@ -53,7 +53,7 @@ class DataProviderRegistry( object ): elif isinstance( original_dataset.datatype, Tabular ): data_provider_class = ColumnDataProvider elif isinstance( original_dataset.datatype, ( Nexus, Newick, Phyloxml ) ): - data_provider_class = genome.PhylovizDataProvider + data_provider_class = PhylovizDataProvider data_provider = data_provider_class( original_dataset=original_dataset ) diff --git a/static/scripts/packed/viz/phyloviz.js b/static/scripts/packed/viz/phyloviz.js index 99bb242b702..18c051899b8 100644 --- a/static/scripts/packed/viz/phyloviz.js +++ b/static/scripts/packed/viz/phyloviz.js @@ -1 +1 @@ -define(["libs/d3","viz/visualization","mvc/data"],function(m,f,g){var l=Backbone.View.extend({className:"UserMenuBase",isAcceptableValue:function(r,p,n){var o=this,s=r.val(),t=r.attr("displayLabel")||r.attr("id").replace("phyloViz","");function q(u){return !isNaN(parseFloat(u))&&isFinite(u)}if(!q(s)){alert(t+" is not a number!");return false}if(s>n){alert(t+" is too large.");return false}else{if(s360){return w}else{v=x;return w}};w.separation=function(x){if(typeof x==="undefined"){return s}else{s=x;return w}};w.links=function(x){return m.layout.tree().links(x)};w.nodes=function(A,y){var z=r.call(w,A,y),x=[],C=0,B=0;z.forEach(function(D){var E=D.data;E.depth=D.depth;C=E.depth>C?E.depth:C;x.push(E)});x.forEach(function(D){if(!D.children){B+=1;D.depth=C}});u=q==="Circular"?v/B:u;t=0;o(x[0],C,u,null);return x};function o(B,D,A,z){var y=B.children,x=0;var C=B.dist||p;C=C>1?1:C;B.dist=C;if(z!==null){B.y0=z.y0+C*s}else{B.y0=n}if(!y){B.x0=t++*A}else{y.forEach(function(E){E.parent=B;x+=o(E,D,A,B)});B.x0=x/y.length}B.x=B.x0;B.y=B.y0;return B.x0}return w}var b=f.Visualization.extend({defaults:{layout:"Linear",separation:250,leafHeight:18,type:"phyloviz",title:"Title",scaleFactor:1,translate:[0,0],fontSize:12,selectedNode:null,nodeAttrChangedTime:0},initialize:function(n){this.set("dataset",new g.Dataset({id:n.dataset_id}))},root:{},toggle:function(n){if(typeof n==="undefined"){return}if(n.children){n._children=n.children;n.children=null}else{n.children=n._children;n._children=null}},toggleAll:function(n){if(n.children&&n.children.length!==0){n.children.forEach(this.toggleAll);toggle(n)}},getData:function(){return this.root},save:function(){var n=this.root;o(n);this.set("root",n);function o(q){delete q.parent;if(q._selected){delete q._selected}if(q.children){q.children.forEach(o)}if(q._children){q._children.forEach(o)}}var p=jQuery.extend(true,{},this.attributes);p.selectedNode=null;show_message("Saving to Galaxy","progress");return $.ajax({url:this.url(),type:"POST",dataType:"json",data:{vis_json:JSON.stringify(p)},success:function(q){var r=q.url.split("id=")[1].split("&")[0],s="/visualization?id="+r;window.history.pushState({},"",s+window.location.hash);hide_modal()}})}});var d=Backbone.View.extend({defaults:{nodeRadius:4.5},stdInit:function(o){var n=this;n.model.on("change:separation change:leafHeight change:fontSize change:nodeAttrChangedTime",n.updateAndRender,n);n.vis=o.vis;n.i=0;n.maxDepth=-1;n.width=o.width;n.height=o.height},updateAndRender:function(p){var o=m.select(".vis"),n=this;p=p||n.model.root;n.renderNodes(p);n.renderLinks(p);n.addTooltips()},renderLinks:function(n){var w=this;var o=w.diagonal;var p=w.duration;var r=w.layoutMode;var t=w.vis.selectAll("g.completeLink").data(w.tree.links(w.nodes),function(x){return x.target.id});var v=function(x){x.pos0=x.source.y0+" "+x.source.x0;x.pos1=x.source.y0+" "+x.target.x0;x.pos2=x.target.y0+" "+x.target.x0};var u=t.enter().insert("svg:g","g.node").attr("class","completeLink");u.append("svg:path").attr("class","link").attr("d",function(x){v(x);return"M "+x.pos0+" L "+x.pos1});var s=t.transition().duration(500);s.select("path.link").attr("d",function(x){v(x);return"M "+x.pos0+" L "+x.pos1+" L "+x.pos2});var q=t.exit().remove()},selectNode:function(o){var n=this;m.selectAll("g.node").classed("selectedHighlight",function(p){if(o.id===p.id){if(o._selected){delete o._selected;return false}else{o._selected=true;return true}}return false});n.model.set("selectedNode",o);$("#phyloVizSelectedNodeName").val(o.name);$("#phyloVizSelectedNodeDist").val(o.dist);$("#phyloVizSelectedNodeAnnotation").val(o.annotation||"")},addTooltips:function(){$(".bs-tooltip").remove();$(".node").attr("data-original-title",function(){var o=this.__data__,n=o.annotation||"None";return o?(o.name?o.name+"
    ":"")+"Dist: "+o.dist+"
    Annotation: "+n:""}).tooltip({placement:"top",trigger:"hover"})}});var a=d.extend({initialize:function(o){var n=this;n.margins=o.margins;n.layoutMode="Linear";n.stdInit(o);n.layout();n.updateAndRender(n.model.root)},layout:function(){var n=this;n.tree=new h().layoutMode("Linear");n.diagonal=m.svg.diagonal().projection(function(o){return[o.y,o.x]})},renderNodes:function(n){var u=this,v=u.model.get("fontSize")+"px";u.tree.separation(u.model.get("separation")).leafHeight(u.model.get("leafHeight"));var q=500,o=u.tree.separation(u.model.get("separation")).nodes(u.model.root);var p=u.vis.selectAll("g.node").data(o,function(w){return w.name+w.id||(w.id=++u.i)});u.nodes=o;u.duration=q;var r=p.enter().append("svg:g").attr("class","node").on("dblclick",function(){m.event.stopPropagation()}).on("click",function(w){if(m.event.altKey){u.selectNode(w)}else{if(w.children&&w.children.length===0){return}u.model.toggle(w);u.updateAndRender(w)}});r.attr("transform",function(w){return"translate("+n.y0+","+n.x0+")"});r.append("svg:circle").attr("r",0.000001).style("fill",function(w){return w._children?"lightsteelblue":"#fff"});r.append("svg:text").attr("class","nodeLabel").attr("x",function(w){return w.children||w._children?-10:10}).attr("dy",".35em").attr("text-anchor",function(w){return w.children||w._children?"end":"start"}).style("fill-opacity",0.000001);var s=p.transition().duration(q);s.attr("transform",function(w){return"translate("+w.y+","+w.x+")"});s.select("circle").attr("r",u.defaults.nodeRadius).style("fill",function(w){return w._children?"lightsteelblue":"#fff"});s.select("text").style("fill-opacity",1).style("font-size",v).text(function(w){return w.name});var t=p.exit().transition().duration(q).remove();t.select("circle").attr("r",0.000001);t.select("text").style("fill-opacity",0.000001);o.forEach(function(w){w.x0=w.x;w.y0=w.y})}});var j=Backbone.View.extend({className:"phyloviz",initialize:function(o){var n=this;n.MIN_SCALE=0.05;n.MAX_SCALE=5;n.MAX_DISPLACEMENT=500;n.margins=[10,60,10,80];n.width=$("#PhyloViz").width();n.height=$("#PhyloViz").height();n.radius=n.width;n.data=o.data;$(window).resize(function(){n.width=$("#PhyloViz").width();n.height=$("#PhyloViz").height();n.render()});n.phyloTree=new b(o.config);n.phyloTree.root=n.data;n.zoomFunc=m.behavior.zoom().scaleExtent([n.MIN_SCALE,n.MAX_SCALE]);n.zoomFunc.translate(n.phyloTree.get("translate"));n.zoomFunc.scale(n.phyloTree.get("scaleFactor"));n.navMenu=new c(n);n.settingsMenu=new i({phyloTree:n.phyloTree});n.nodeSelectionView=new e({phyloTree:n.phyloTree});n.search=new k();setTimeout(function(){n.zoomAndPan()},1000)},render:function(){var o=this;$("#PhyloViz").empty();o.mainSVG=m.select("#PhyloViz").append("svg:svg").attr("width",o.width).attr("height",o.height).attr("pointer-events","all").call(o.zoomFunc.on("zoom",function(){o.zoomAndPan()}));o.boundingRect=o.mainSVG.append("svg:rect").attr("class","boundingRect").attr("width",o.width).attr("height",o.height).attr("stroke","black").attr("fill","white");o.vis=o.mainSVG.append("svg:g").attr("class","vis");o.layoutOptions={model:o.phyloTree,width:o.width,height:o.height,vis:o.vis,margins:o.margins};$("#title").text("Phylogenetic Tree from "+o.phyloTree.get("title")+":");var n=new a(o.layoutOptions)},zoomAndPan:function(n){var t,p;if(typeof n!=="undefined"){t=n.zoom;p=n.translate}var w=this,r=w.zoomFunc.scale(),v=w.zoomFunc.translate(),s="",u="";switch(t){case"reset":r=1;v=[0,0];break;case"+":r*=1.1;break;case"-":r*=0.9;break;default:if(typeof t==="number"){r=t}else{if(m.event!==null){r=m.event.scale}}}if(rw.MAX_SCALE){return}w.zoomFunc.scale(r);s="translate("+w.margins[3]+","+w.margins[0]+") scale("+r+")";if(m.event!==null){u="translate("+m.event.translate+")"}else{if(typeof p!=="undefined"){var q=p.split(",")[0];var o=p.split(",")[1];if(!isNaN(q)&&!isNaN(o)){v=[v[0]+parseFloat(q),v[1]+parseFloat(o)]}}w.zoomFunc.translate(v);u="translate("+v+")"}w.phyloTree.set("scaleFactor",r);w.phyloTree.set("translate",v);w.vis.attr("transform",u+s)},reloadViz:function(){var n=this,o=$("#phylovizNexSelector :selected").val();$.getJSON(n.phyloTree.get("dataset").url(),{tree_index:o,data_type:"raw_data"},function(p){n.data=p.data;n.config=p;n.render()})}});var c=Backbone.View.extend({initialize:function(o){var n=this;n.phylovizView=o;$("#panelHeaderRightBtns").empty();$("#phyloVizNavBtns").empty();$("#phylovizNexSelector").off();n.initNavBtns();n.initRightHeaderBtns();$("#phylovizNexSelector").off().on("change",function(){n.phylovizView.reloadViz()})},initRightHeaderBtns:function(){var n=this;rightMenu=create_icon_buttons_menu([{icon_class:"gear",title:"PhyloViz Settings",on_click:function(){$("#SettingsMenu").show();n.settingsMenu.updateUI()}},{icon_class:"disk",title:"Save visualization",on_click:function(){var o=$("#phylovizNexSelector option:selected").text();if(o){n.phylovizView.phyloTree.set("title",o)}n.phylovizView.phyloTree.save()}},{icon_class:"chevron-expand",title:"Search / Edit Nodes",on_click:function(){$("#nodeSelectionView").show()}},{icon_class:"information",title:"Phyloviz Help",on_click:function(){window.open("http://wiki.g2.bx.psu.edu/Learn/Visualization/PhylogeneticTree")}}],{tooltip_config:{placement:"bottom"}});$("#panelHeaderRightBtns").append(rightMenu.$el)},initNavBtns:function(){var n=this,o=create_icon_buttons_menu([{icon_class:"zoom-in",title:"Zoom in",on_click:function(){n.phylovizView.zoomAndPan({zoom:"+"})}},{icon_class:"zoom-out",title:"Zoom out",on_click:function(){n.phylovizView.zoomAndPan({zoom:"-"})}},{icon_class:"arrow-circle",title:"Reset Zoom/Pan",on_click:function(){n.phylovizView.zoomAndPan({zoom:"reset"})}}],{tooltip_config:{placement:"bottom"}});$("#phyloVizNavBtns").append(o.$el)}});var i=l.extend({className:"Settings",initialize:function(o){var n=this;n.phyloTree=o.phyloTree;n.el=$("#SettingsMenu");n.inputs={separation:$("#phyloVizTreeSeparation"),leafHeight:$("#phyloVizTreeLeafHeight"),fontSize:$("#phyloVizTreeFontSize")};$("#settingsCloseBtn").off().on("click",function(){n.el.hide()});$("#phylovizResetSettingsBtn").off().on("click",function(){n.resetToDefaults()});$("#phylovizApplySettingsBtn").off().on("click",function(){n.apply()})},apply:function(){var n=this;if(!n.isAcceptableValue(n.inputs.separation,50,2500)||!n.isAcceptableValue(n.inputs.leafHeight,5,30)||!n.isAcceptableValue(n.inputs.fontSize,5,20)){return}$.each(n.inputs,function(o,p){n.phyloTree.set(o,p.val())})},updateUI:function(){var n=this;$.each(n.inputs,function(o,p){p.val(n.phyloTree.get(o))})},resetToDefaults:function(){$(".bs-tooltip").remove();var n=this;$.each(n.phyloTree.defaults,function(o,p){n.phyloTree.set(o,p)});n.updateUI()},render:function(){}});var e=l.extend({className:"Settings",initialize:function(o){var n=this;n.el=$("#nodeSelectionView");n.phyloTree=o.phyloTree;n.UI={enableEdit:$("#phylovizEditNodesCheck"),saveChanges:$("#phylovizNodeSaveChanges"),cancelChanges:$("#phylovizNodeCancelChanges"),name:$("#phyloVizSelectedNodeName"),dist:$("#phyloVizSelectedNodeDist"),annotation:$("#phyloVizSelectedNodeAnnotation")};n.valuesOfConcern={name:null,dist:null,annotation:null};$("#nodeSelCloseBtn").off().on("click",function(){n.el.hide()});n.UI.saveChanges.off().on("click",function(){n.updateNodes()});n.UI.cancelChanges.off().on("click",function(){n.cancelChanges()});(function(p){p.fn.enable=function(q){return p(this).each(function(){if(q){p(this).removeAttr("disabled")}else{p(this).attr("disabled","disabled")}})}})(jQuery);n.UI.enableEdit.off().on("click",function(){n.toggleUI()})},toggleUI:function(){var n=this,o=n.UI.enableEdit.is(":checked");if(!o){n.cancelChanges()}$.each(n.valuesOfConcern,function(p,q){n.UI[p].enable(o)});if(o){n.UI.saveChanges.show();n.UI.cancelChanges.show()}else{n.UI.saveChanges.hide();n.UI.cancelChanges.hide()}},cancelChanges:function(){var n=this,o=n.phyloTree.get("selectedNode");if(o){$.each(n.valuesOfConcern,function(p,q){n.UI[p].val(o[p])})}},updateNodes:function(){var n=this,o=n.phyloTree.get("selectedNode");if(o){if(!n.isAcceptableValue(n.UI.dist,0,1)||n.hasIllegalJsonCharacters(n.UI.name)||n.hasIllegalJsonCharacters(n.UI.annotation)){return}$.each(n.valuesOfConcern,function(p,q){(o[p])=n.UI[p].val()});n.phyloTree.set("nodeAttrChangedTime",new Date())}else{alert("No node selected")}}});var k=l.extend({initialize:function(){var n=this;$("#phyloVizSearchBtn").on("click",function(){var p=$("#phyloVizSearchTerm"),q=$("#phyloVizSearchCondition").val().split("-"),o=q[0],r=q[1];n.hasIllegalJsonCharacters(p);if(o==="dist"){n.isAcceptableValue(p,0,1)}n.searchTree(o,r,p.val())})},searchTree:function(n,p,o){m.selectAll("g.node").classed("searchHighlight",function(r){var q=r[n];if(typeof q!=="undefined"&&q!==null){if(n==="dist"){switch(p){case"greaterEqual":return q>=+o;case"lesserEqual":return q<=+o;default:return}}else{if(n==="name"||n==="annotation"){return q.toLowerCase().indexOf(o.toLowerCase())!==-1}}}})}});return{PhylovizView:j}}); \ No newline at end of file +define(["libs/d3","viz/visualization","mvc/data"],function(m,f,g){var l=Backbone.View.extend({className:"UserMenuBase",isAcceptableValue:function(q,o,n){var r=q.val(),s=q.attr("displayLabel")||q.attr("id").replace("phyloViz","");function p(t){return !isNaN(parseFloat(t))&&isFinite(t)}if(!p(r)){alert(s+" is not a number!");return false}if(r>n){alert(s+" is too large.");return false}else{if(r360){return w}else{v=x;return w}};w.separation=function(x){if(typeof x==="undefined"){return s}else{s=x;return w}};w.links=function(x){return m.layout.tree().links(x)};w.nodes=function(A,y){if(toString.call(A)==="[object Array]"){A=A[0]}var z=r.call(w,A,y),x=[],C=0,B=0;window._d=A;window._nodes=z;z.forEach(function(D){C=D.depth>C?D.depth:C;x.push(D)});x.forEach(function(D){if(!D.children){B+=1;D.depth=C}});u=q==="Circular"?v/B:u;t=0;o(x[0],C,u,null);return x};function o(B,D,A,z){var y=B.children,x=0;var C=B.dist||p;C=C>1?1:C;B.dist=C;if(z!==null){B.y0=z.y0+C*s}else{B.y0=n}if(!y){B.x0=t*A;t+=1}else{y.forEach(function(E){E.parent=B;x+=o(E,D,A,B)});B.x0=x/y.length}B.x=B.x0;B.y=B.y0;return B.x0}return w}var b=f.Visualization.extend({defaults:{layout:"Linear",separation:250,leafHeight:18,type:"phyloviz",title:"Title",scaleFactor:1,translate:[0,0],fontSize:12,selectedNode:null,nodeAttrChangedTime:0},initialize:function(n){this.set("dataset",new g.Dataset({id:n.dataset_id}))},root:{},toggle:function(n){if(typeof n==="undefined"){return}if(n.children){n._children=n.children;n.children=null}else{n.children=n._children;n._children=null}},toggleAll:function(n){if(n.children&&n.children.length!==0){n.children.forEach(this.toggleAll);toggle(n)}},getData:function(){return this.root},save:function(){var n=this.root;o(n);this.set("root",n);function o(q){delete q.parent;if(q._selected){delete q._selected}if(q.children){q.children.forEach(o)}if(q._children){q._children.forEach(o)}}var p=jQuery.extend(true,{},this.attributes);p.selectedNode=null;show_message("Saving to Galaxy","progress");return $.ajax({url:this.url(),type:"POST",dataType:"json",data:{vis_json:JSON.stringify(p)},success:function(q){var r=q.url.split("id=")[1].split("&")[0],s="/visualization?id="+r;window.history.pushState({},"",s+window.location.hash);hide_modal()}})}});var d=Backbone.View.extend({defaults:{nodeRadius:4.5},stdInit:function(o){var n=this;n.model.on("change:separation change:leafHeight change:fontSize change:nodeAttrChangedTime",n.updateAndRender,n);n.vis=o.vis;n.i=0;n.maxDepth=-1;n.width=o.width;n.height=o.height},updateAndRender:function(p){var o=m.select(".vis"),n=this;p=p||n.model.root;n.renderNodes(p);n.renderLinks(p);n.addTooltips()},renderLinks:function(n){var w=this;var o=w.diagonal;var p=w.duration;var r=w.layoutMode;var t=w.vis.selectAll("g.completeLink").data(w.tree.links(w.nodes),function(x){return x.target.id});var v=function(x){x.pos0=x.source.y0+" "+x.source.x0;x.pos1=x.source.y0+" "+x.target.x0;x.pos2=x.target.y0+" "+x.target.x0};var u=t.enter().insert("svg:g","g.node").attr("class","completeLink");u.append("svg:path").attr("class","link").attr("d",function(x){v(x);return"M "+x.pos0+" L "+x.pos1});var s=t.transition().duration(500);s.select("path.link").attr("d",function(x){v(x);return"M "+x.pos0+" L "+x.pos1+" L "+x.pos2});var q=t.exit().remove()},selectNode:function(o){var n=this;m.selectAll("g.node").classed("selectedHighlight",function(p){if(o.id===p.id){if(o._selected){delete o._selected;return false}else{o._selected=true;return true}}return false});n.model.set("selectedNode",o);$("#phyloVizSelectedNodeName").val(o.name);$("#phyloVizSelectedNodeDist").val(o.dist);$("#phyloVizSelectedNodeAnnotation").val(o.annotation||"")},addTooltips:function(){$(".bs-tooltip").remove();$(".node").attr("data-original-title",function(){var o=this.__data__,n=o.annotation||"None";return o?(o.name?o.name+"
    ":"")+"Dist: "+o.dist+"
    Annotation: "+n:""}).tooltip({placement:"top",trigger:"hover"})}});var a=d.extend({initialize:function(o){var n=this;n.margins=o.margins;n.layoutMode="Linear";n.stdInit(o);n.layout();n.updateAndRender(n.model.root)},layout:function(){var n=this;n.tree=new h().layoutMode("Linear");n.diagonal=m.svg.diagonal().projection(function(o){return[o.y,o.x]})},renderNodes:function(n){var u=this,v=u.model.get("fontSize")+"px";u.tree.separation(u.model.get("separation")).leafHeight(u.model.get("leafHeight"));var q=500,o=u.tree.separation(u.model.get("separation")).nodes(u.model.root);var p=u.vis.selectAll("g.node").data(o,function(w){return w.name+w.id||(w.id=++u.i)});u.nodes=o;u.duration=q;var r=p.enter().append("svg:g").attr("class","node").on("dblclick",function(){m.event.stopPropagation()}).on("click",function(w){if(m.event.altKey){u.selectNode(w)}else{if(w.children&&w.children.length===0){return}u.model.toggle(w);u.updateAndRender(w)}});console.debug("source:",n);if(toString.call(n)==="[object Array]"){n=n[0]}r.attr("transform",function(w){return"translate("+n.y0+","+n.x0+")"});r.append("svg:circle").attr("r",0.000001).style("fill",function(w){return w._children?"lightsteelblue":"#fff"});r.append("svg:text").attr("class","nodeLabel").attr("x",function(w){return w.children||w._children?-10:10}).attr("dy",".35em").attr("text-anchor",function(w){return w.children||w._children?"end":"start"}).style("fill-opacity",0.000001);var s=p.transition().duration(q);s.attr("transform",function(w){return"translate("+w.y+","+w.x+")"});s.select("circle").attr("r",u.defaults.nodeRadius).style("fill",function(w){return w._children?"lightsteelblue":"#fff"});s.select("text").style("fill-opacity",1).style("font-size",v).text(function(w){return w.name});var t=p.exit().transition().duration(q).remove();t.select("circle").attr("r",0.000001);t.select("text").style("fill-opacity",0.000001);o.forEach(function(w){w.x0=w.x;w.y0=w.y})}});var j=Backbone.View.extend({className:"phyloviz",initialize:function(o){var n=this;n.MIN_SCALE=0.05;n.MAX_SCALE=5;n.MAX_DISPLACEMENT=500;n.margins=[10,60,10,80];n.width=$("#PhyloViz").width();n.height=$("#PhyloViz").height();n.radius=n.width;n.data=o.data;$(window).resize(function(){n.width=$("#PhyloViz").width();n.height=$("#PhyloViz").height();n.render()});n.phyloTree=new b(o.config);n.phyloTree.root=n.data;n.zoomFunc=m.behavior.zoom().scaleExtent([n.MIN_SCALE,n.MAX_SCALE]);n.zoomFunc.translate(n.phyloTree.get("translate"));n.zoomFunc.scale(n.phyloTree.get("scaleFactor"));n.navMenu=new c(n);n.settingsMenu=new i({phyloTree:n.phyloTree});n.nodeSelectionView=new e({phyloTree:n.phyloTree});n.search=new k();setTimeout(function(){n.zoomAndPan()},1000)},render:function(){var o=this;$("#PhyloViz").empty();o.mainSVG=m.select("#PhyloViz").append("svg:svg").attr("width",o.width).attr("height",o.height).attr("pointer-events","all").call(o.zoomFunc.on("zoom",function(){o.zoomAndPan()}));o.boundingRect=o.mainSVG.append("svg:rect").attr("class","boundingRect").attr("width",o.width).attr("height",o.height).attr("stroke","black").attr("fill","white");o.vis=o.mainSVG.append("svg:g").attr("class","vis");o.layoutOptions={model:o.phyloTree,width:o.width,height:o.height,vis:o.vis,margins:o.margins};$("#title").text("Phylogenetic Tree from "+o.phyloTree.get("title")+":");var n=new a(o.layoutOptions)},zoomAndPan:function(n){var t,p;if(typeof n!=="undefined"){t=n.zoom;p=n.translate}var w=this,r=w.zoomFunc.scale(),v=w.zoomFunc.translate(),s="",u="";switch(t){case"reset":r=1;v=[0,0];break;case"+":r*=1.1;break;case"-":r*=0.9;break;default:if(typeof t==="number"){r=t}else{if(m.event!==null){r=m.event.scale}}}if(rw.MAX_SCALE){return}w.zoomFunc.scale(r);s="translate("+w.margins[3]+","+w.margins[0]+") scale("+r+")";if(m.event!==null){u="translate("+m.event.translate+")"}else{if(typeof p!=="undefined"){var q=p.split(",")[0];var o=p.split(",")[1];if(!isNaN(q)&&!isNaN(o)){v=[v[0]+parseFloat(q),v[1]+parseFloat(o)]}}w.zoomFunc.translate(v);u="translate("+v+")"}w.phyloTree.set("scaleFactor",r);w.phyloTree.set("translate",v);w.vis.attr("transform",u+s)},reloadViz:function(){var n=this,o=$("#phylovizNexSelector :selected").val();$.getJSON(n.phyloTree.get("dataset").url(),{tree_index:o,data_type:"raw_data"},function(p){n.data=p.data;n.config=p;n.render()})}});var c=Backbone.View.extend({initialize:function(o){var n=this;n.phylovizView=o;$("#panelHeaderRightBtns").empty();$("#phyloVizNavBtns").empty();$("#phylovizNexSelector").off();n.initNavBtns();n.initRightHeaderBtns();$("#phylovizNexSelector").off().on("change",function(){n.phylovizView.reloadViz()})},initRightHeaderBtns:function(){var n=this;rightMenu=create_icon_buttons_menu([{icon_class:"gear",title:"PhyloViz Settings",on_click:function(){$("#SettingsMenu").show();n.settingsMenu.updateUI()}},{icon_class:"disk",title:"Save visualization",on_click:function(){var o=$("#phylovizNexSelector option:selected").text();if(o){n.phylovizView.phyloTree.set("title",o)}n.phylovizView.phyloTree.save()}},{icon_class:"chevron-expand",title:"Search / Edit Nodes",on_click:function(){$("#nodeSelectionView").show()}},{icon_class:"information",title:"Phyloviz Help",on_click:function(){window.open("http://wiki.g2.bx.psu.edu/Learn/Visualization/PhylogeneticTree")}}],{tooltip_config:{placement:"bottom"}});$("#panelHeaderRightBtns").append(rightMenu.$el)},initNavBtns:function(){var n=this,o=create_icon_buttons_menu([{icon_class:"zoom-in",title:"Zoom in",on_click:function(){n.phylovizView.zoomAndPan({zoom:"+"})}},{icon_class:"zoom-out",title:"Zoom out",on_click:function(){n.phylovizView.zoomAndPan({zoom:"-"})}},{icon_class:"arrow-circle",title:"Reset Zoom/Pan",on_click:function(){n.phylovizView.zoomAndPan({zoom:"reset"})}}],{tooltip_config:{placement:"bottom"}});$("#phyloVizNavBtns").append(o.$el)}});var i=l.extend({className:"Settings",initialize:function(o){var n=this;n.phyloTree=o.phyloTree;n.el=$("#SettingsMenu");n.inputs={separation:$("#phyloVizTreeSeparation"),leafHeight:$("#phyloVizTreeLeafHeight"),fontSize:$("#phyloVizTreeFontSize")};$("#settingsCloseBtn").off().on("click",function(){n.el.hide()});$("#phylovizResetSettingsBtn").off().on("click",function(){n.resetToDefaults()});$("#phylovizApplySettingsBtn").off().on("click",function(){n.apply()})},apply:function(){var n=this;if(!n.isAcceptableValue(n.inputs.separation,50,2500)||!n.isAcceptableValue(n.inputs.leafHeight,5,30)||!n.isAcceptableValue(n.inputs.fontSize,5,20)){return}$.each(n.inputs,function(o,p){n.phyloTree.set(o,p.val())})},updateUI:function(){var n=this;$.each(n.inputs,function(o,p){p.val(n.phyloTree.get(o))})},resetToDefaults:function(){$(".bs-tooltip").remove();var n=this;$.each(n.phyloTree.defaults,function(o,p){n.phyloTree.set(o,p)});n.updateUI()},render:function(){}});var e=l.extend({className:"Settings",initialize:function(o){var n=this;n.el=$("#nodeSelectionView");n.phyloTree=o.phyloTree;n.UI={enableEdit:$("#phylovizEditNodesCheck"),saveChanges:$("#phylovizNodeSaveChanges"),cancelChanges:$("#phylovizNodeCancelChanges"),name:$("#phyloVizSelectedNodeName"),dist:$("#phyloVizSelectedNodeDist"),annotation:$("#phyloVizSelectedNodeAnnotation")};n.valuesOfConcern={name:null,dist:null,annotation:null};$("#nodeSelCloseBtn").off().on("click",function(){n.el.hide()});n.UI.saveChanges.off().on("click",function(){n.updateNodes()});n.UI.cancelChanges.off().on("click",function(){n.cancelChanges()});(function(p){p.fn.enable=function(q){return p(this).each(function(){if(q){p(this).removeAttr("disabled")}else{p(this).attr("disabled","disabled")}})}})(jQuery);n.UI.enableEdit.off().on("click",function(){n.toggleUI()})},toggleUI:function(){var n=this,o=n.UI.enableEdit.is(":checked");if(!o){n.cancelChanges()}$.each(n.valuesOfConcern,function(p,q){n.UI[p].enable(o)});if(o){n.UI.saveChanges.show();n.UI.cancelChanges.show()}else{n.UI.saveChanges.hide();n.UI.cancelChanges.hide()}},cancelChanges:function(){var n=this,o=n.phyloTree.get("selectedNode");if(o){$.each(n.valuesOfConcern,function(p,q){n.UI[p].val(o[p])})}},updateNodes:function(){var n=this,o=n.phyloTree.get("selectedNode");if(o){if(!n.isAcceptableValue(n.UI.dist,0,1)||n.hasIllegalJsonCharacters(n.UI.name)||n.hasIllegalJsonCharacters(n.UI.annotation)){return}$.each(n.valuesOfConcern,function(p,q){(o[p])=n.UI[p].val()});n.phyloTree.set("nodeAttrChangedTime",new Date())}else{alert("No node selected")}}});var k=l.extend({initialize:function(){var n=this;$("#phyloVizSearchBtn").on("click",function(){var p=$("#phyloVizSearchTerm"),q=$("#phyloVizSearchCondition").val().split("-"),o=q[0],r=q[1];n.hasIllegalJsonCharacters(p);if(o==="dist"){n.isAcceptableValue(p,0,1)}n.searchTree(o,r,p.val())})},searchTree:function(n,p,o){m.selectAll("g.node").classed("searchHighlight",function(r){var q=r[n];if(typeof q!=="undefined"&&q!==null){if(n==="dist"){switch(p){case"greaterEqual":return q>=+o;case"lesserEqual":return q<=+o;default:return}}else{if(n==="name"||n==="annotation"){return q.toLowerCase().indexOf(o.toLowerCase())!==-1}}}})}});return{PhylovizView:j}}); \ No newline at end of file diff --git a/static/scripts/viz/phyloviz.js b/static/scripts/viz/phyloviz.js index 19e9ea61613..d9ea843ff29 100644 --- a/static/scripts/viz/phyloviz.js +++ b/static/scripts/viz/phyloviz.js @@ -1,18 +1,18 @@ define(['libs/d3', 'viz/visualization', 'mvc/data'], function(d3, visualization_mod, data_mod) { +/** + * Base class of any menus that takes in user interaction. Contains checking methods. + */ var UserMenuBase = Backbone.View.extend({ - /** - * Base class of any menus that takes in user interaction. Contains checking methods. - */ className: 'UserMenuBase', + /** + * Check if an input value is a number and falls within max min. + */ isAcceptableValue : function ($inputKey, min, max) { - /** - * Check if an input value is a number and falls within max min. - */ - var self = this, - value = $inputKey.val(), + //TODO: use better feedback than alert + var value = $inputKey.val(), fieldName = $inputKey.attr("displayLabel") || $inputKey.attr("id").replace("phyloViz", ""); function isNumeric(n) { @@ -34,12 +34,13 @@ var UserMenuBase = Backbone.View.extend({ return true; }, + /** + * Check if any user string inputs has illegal characters that json cannot accept + */ hasIllegalJsonCharacters : function($inputKey) { - /** - * Check if any user string inputs has illegal characters that json cannot accept - */ if ($inputKey.val().search(/"|'|\\/) !== -1){ - alert("Named fields cannot contain these illegal characters: double quote(\"), single guote(\'), or back slash(\\). "); + alert("Named fields cannot contain these illegal characters: " + + "double quote(\"), single guote(\'), or back slash(\\). "); return true; } return false; @@ -47,12 +48,12 @@ var UserMenuBase = Backbone.View.extend({ }); +/** + * -- Custom Layout call for phyloViz to suit the needs of a phylogenetic tree. + * -- Specifically: 1) Nodes have a display display of (= evo dist X depth separation) from their parent + * 2) Nodes must appear in other after they have expand and contracted + */ function PhyloTreeLayout() { - /** - * -- Custom Layout call for phyloViz to suit the needs of a phylogenetic tree. - * -- Specifically: 1) Nodes have a display display of (= evo dist X depth separation) from their parent - * 2) Nodes must appear in other after they have expand and contracted - */ var self = this, hierarchy = d3.layout.hierarchy().sort(null).value(null), @@ -75,9 +76,11 @@ function PhyloTreeLayout() { else { layoutMode = mode; return self;} }; - self.layoutAngle = function(angle) { // changes the layout angle of the display, which is really changing the height + // changes the layout angle of the display, which is really changing the height + self.layoutAngle = function(angle) { if (typeof angle === "undefined"){ return height; } - if (isNaN(angle) || angle < 0 || angle > 360) { return self; } // to use default if the user puts in strange values + // to use default if the user puts in strange values + if (isNaN(angle) || angle < 0 || angle > 360) { return self; } else { height = angle; return self;} }; @@ -92,19 +95,28 @@ function PhyloTreeLayout() { // -- Custom method for laying out phylogeny tree in a linear fashion self.nodes = function (d, i) { - var _nodes = hierarchy.call(self, d, i), // self is to find the depth of all the nodes, assumes root is passed in + //TODO: newick and phyloxml return arrays. where should this go (client (here, else), server)? + if( toString.call( d ) === '[object Array]' ){ + // if d is an array, replate with the first object (newick, phyloxml) + d = d[0]; + } + // self is to find the depth of all the nodes, assumes root is passed in + var _nodes = hierarchy.call(self, d, i), nodes = [], maxDepth = 0, numLeaves = 0; + //console.debug( JSON.stringify( _nodes, null, 2 ) ) + window._d = d; + window._nodes = _nodes; + //TODO: remove dbl-touch loop // changing from hierarchy's custom format for data to usable format - _nodes.forEach(function (_node){ - var node = _node.data; - node.depth = _node.depth; + _nodes.forEach(function (node){ maxDepth = node.depth > maxDepth ? node.depth : maxDepth; //finding max depth of tree nodes.push(node); }); - // counting the number of leaf nodes and assigning max depth to nodes that do not have children to flush all the leave nodes + // counting the number of leaf nodes and assigning max depth + // to nodes that do not have children to flush all the leave nodes nodes.forEach(function(node){ if ( !node.children ) { //&& !node._children numLeaves += 1; @@ -120,15 +132,16 @@ function PhyloTreeLayout() { }; + /** + * -- Function with side effect of adding x0, y0 to all child; take in the root as starting point + * assuming that the leave nodes would be sorted in presented order + * horizontal(y0) is calculated according to (= evo dist X depth separation) from their parent + * vertical (x0) - if leave node: find its order in all of the leave node === node.id, + * then multiply by verticalSeparation + * - if parent node: is place in the mid point all of its children nodes + * -- The layout will first calculate the y0 field going towards the leaves, and x0 when returning + */ function layout (node, maxDepth, vertSeparation, parent) { - /** - * -- Function with side effect of adding x0, y0 to all child; take in the root as starting point - * assuming that the leave nodes would be sorted in presented order - * horizontal(y0) is calculated according to (= evo dist X depth separation) from their parent - * vertical (x0) - if leave node: find its order in all of the leave node === node.id, then multiply by verticalSeparation - * - if parent node: is place in the mid point all of its children nodes - * -- The layout will first calculate the y0 field going towards the leaves, and x0 when returning - */ var children = node.children, sumChildVertSeparation = 0; @@ -145,7 +158,8 @@ function PhyloTreeLayout() { // if a node have no children, we will treat it as a leaf and start laying it out first if (!children) { - node.x0 = leafIndex++ * vertSeparation; + node.x0 = leafIndex * vertSeparation; + leafIndex += 1; } else { // if it has children, we will visit all its children and calculate its position from its children children.forEach( function (child) { @@ -189,12 +203,12 @@ var PhyloTree = visualization_mod.Visualization.extend({ root : {}, // Root has to be its own independent object because it is not part of the viz_config + /** + * Mechanism to expand or contract a single node. Expanded nodes have a children list, while for + * contracted nodes the list is stored in _children. Nodes with their children data stored in _children will not + * have their children rendered. + */ toggle : function (d) { - /** - * Mechanism to expand or contract a single node. Expanded nodes have a children list, while for - * contracted nodes the list is stored in _children. Nodes with their children data stored in _children will not have their - * children rendered. - */ if(typeof d === "undefined") {return ;} if (d.children ) { d._children = d.children; @@ -205,29 +219,29 @@ var PhyloTree = visualization_mod.Visualization.extend({ } }, + /** + * Contracts the phylotree to a single node by repeatedly calling itself to place all the list + * of children under _children. + */ toggleAll : function(d) { - /** - * Contracts the phylotree to a single node by repeatedly calling itself to place all the list - * of children under _children. - */ if (d.children && d.children.length !== 0) { d.children.forEach(this.toggleAll); toggle(d); } }, + /** + * Return the data of the tree. Used for preserving state. + */ getData : function (){ - /** - * Return the data of the tree. Used for preserving state. - */ return this.root; }, + /** + * Overriding the default save mechanism to do some clean of circular reference of the + * phyloTree and to include phyloTree in the saved json + */ save: function() { - /** - * Overriding the default save mechanism to do some clean of circular reference of the - * phyloTree and to include phyloTree in the saved json - */ var root = this.root; cleanTree(root); this.set("root", root); @@ -240,7 +254,7 @@ var PhyloTree = visualization_mod.Visualization.extend({ if (node._selected){ delete node._selected;} if (node.children) { - node.children.forEach(cleanTree); + node.children.forEach(cleanTree); } if (node._children) { node._children.forEach(cleanTree); @@ -270,26 +284,24 @@ var PhyloTree = visualization_mod.Visualization.extend({ }); - +// -- Views -- /** - * -- Views -- + * Stores the default variable for setting up the visualization */ var PhylovizLayoutBase = Backbone.View.extend({ - /** - * Stores the default variable for setting up the visualization - */ defaults : { nodeRadius : 4.5 // radius of each node in the diagram }, + /** + * Common initialization in layouts + */ stdInit : function (options) { - /** - * Common initialization in layouts - */ var self = this; - self.model.on("change:separation change:leafHeight change:fontSize change:nodeAttrChangedTime", self.updateAndRender, self); + self.model.on("change:separation change:leafHeight change:fontSize change:nodeAttrChangedTime", + self.updateAndRender, self); self.vis = options.vis; self.i = 0; @@ -300,11 +312,11 @@ var PhylovizLayoutBase = Backbone.View.extend({ }, + /** + * Updates the visualization whenever there are changes in the expansion and contraction of nodes + * AND possibly when the tree is edited. + */ updateAndRender : function(source) { - /** - * Updates the visualization whenever there are changes in the expansion and contraction of nodes - * AND possibly when the tree is edited. - */ var vis = d3.select(".vis"), self = this; source = source || self.model.root; @@ -315,10 +327,10 @@ var PhylovizLayoutBase = Backbone.View.extend({ }, + /** + * Renders the links for the visualization. + */ renderLinks : function(source) { - /** - * Renders the links for the visualization. - */ var self = this; var diagonal = self.diagonal; var duration = self.duration; @@ -327,15 +339,17 @@ var PhylovizLayoutBase = Backbone.View.extend({ .data(self.tree.links(self.nodes), function(d) { return d.target.id; }); var calcalateLinePos = function(d) { - d.pos0 = d.source.y0 + " " + d.source.x0; // position of the source node <=> starting location of the line drawn - d.pos1 = d.source.y0 + " " + d.target.x0; // position where the line makes a right angle bend - d.pos2 = d.target.y0 + " " + d.target.x0; // point where the horizontal line becomes a dotted line + // position of the source node <=> starting location of the line drawn + d.pos0 = d.source.y0 + " " + d.source.x0; + // position where the line makes a right angle bend + d.pos1 = d.source.y0 + " " + d.target.x0; + // point where the horizontal line becomes a dotted line + d.pos2 = d.target.y0 + " " + d.target.x0; }; var linkEnter = link.enter().insert("svg:g","g.node") .attr("class", "completeLink"); - linkEnter.append("svg:path") .attr("class", "link") .attr("d", function(d) { @@ -357,10 +371,10 @@ var PhylovizLayoutBase = Backbone.View.extend({ // User Interaction methods below + /** + * Displays the information for editing + */ selectNode : function(node){ - /** - * Displays the information for editting - */ var self = this; d3.selectAll("g.node") .classed("selectedHighlight", function(d){ @@ -382,11 +396,11 @@ var PhylovizLayoutBase = Backbone.View.extend({ $("#phyloVizSelectedNodeAnnotation").val(node.annotation || ""); }, + /** + * Creates bootstrap tooltip for the visualization. Has to be called repeatedly due to newly generated + * enterNodes + */ addTooltips : function (){ - /** - * Creates bootstrap tooltip for the visualization. Has to be called repeatedly due to newly generated - * enterNodes - */ $(".bs-tooltip").remove(); //clean up tooltip, just in case its listeners are removed by d3 $(".node") .attr("data-original-title", function(){ @@ -400,13 +414,11 @@ var PhylovizLayoutBase = Backbone.View.extend({ }); - - +/** + * Linea layout class of Phyloviz, is responsible for rendering the nodes + * calls PhyloTreeLayout to determine the positions of the nodes + */ var PhylovizLinearView = PhylovizLayoutBase.extend({ - /** - * Linea layout class of Phyloviz, is responsible for rendering the nodes - * calls PhyloTreeLayout to determine the positions of the nodes - */ initialize : function(options){ // Default values of linear layout var self = this; @@ -419,23 +431,21 @@ var PhylovizLinearView = PhylovizLayoutBase.extend({ self.updateAndRender(self.model.root); }, + /** + * Creates the basic layout of a linear tree by precalculating fixed values. + * One of calculations are also made here + */ layout : function() { - /** - * Creates the basic layout of a linear tree by precalculating fixed values. - * One of calculations are also made here - */ - var self = this; - self.tree = new PhyloTreeLayout().layoutMode("Linear"); self.diagonal = d3.svg.diagonal() .projection(function(d) { return [d.y, d.x ]; }); }, + /** + * Renders the nodes base on Linear layout. + */ renderNodes : function (source) { - /** - * Renders the nodes base on Linear layout. - */ var self = this, fontSize = self.model.get("fontSize") + "px"; @@ -446,7 +456,9 @@ var PhylovizLinearView = PhylovizLayoutBase.extend({ nodes = self.tree.separation(self.model.get("separation")).nodes(self.model.root); var node = self.vis.selectAll("g.node") - .data(nodes, function(d) { return d.name + d.id || (d.id = ++self.i); }); + .data(nodes, function(d) { + return d.name + d.id || (d.id = ++self.i); + }); // These variables has to be passed into update links which are in the base methods self.nodes = nodes; @@ -466,7 +478,12 @@ var PhylovizLinearView = PhylovizLayoutBase.extend({ self.updateAndRender(d); // re-render the tree } }); - + console.debug( 'source:', source ) + //TODO: newick and phyloxml return arrays. where should this go (client (here, else), server)? + if( toString.call( source ) === '[object Array]' ){ + // if d is an array, replate with the first object (newick, phyloxml) + source = source[0]; + } nodeEnter.attr("transform", function(d) { return "translate(" + source.y0 + "," + source.x0 + ")"; }); nodeEnter.append("svg:circle") @@ -557,8 +574,8 @@ var PhylovizView = Backbone.View.extend({ self.nodeSelectionView = new NodeSelectionView({phyloTree : self.phyloTree}); self.search = new PhyloVizSearch(); - - setTimeout(function(){ // using settimeout to call the zoomAndPan function according to the stored attributes in viz_config + // using settimeout to call the zoomAndPan function according to the stored attributes in viz_config + setTimeout(function(){ self.zoomAndPan(); }, 1000); }, @@ -603,11 +620,11 @@ var PhylovizView = Backbone.View.extend({ var linearView = new PhylovizLinearView(self.layoutOptions); }, + /** + * Function to zoom and pan the svg element which the entire tree is contained within + * Uses d3.zoom events, and extend them to allow manual updates and keeping states in model + */ zoomAndPan : function(event){ - /** - * Function to zoom and pan the svg element which the entire tree is contained within - * Uses d3.zoom events, and extend them to allow manual updates and keeping states in model - */ var zoomParams, translateParams; if (typeof event !== "undefined") { @@ -659,21 +676,26 @@ var PhylovizView = Backbone.View.extend({ self.phyloTree.set("scaleFactor", scaleFactor); self.phyloTree.set("translate", translationCoor); - self.vis.attr("transform", translateStatement + zoomStatement); //refers to the view that we are actually zooming + //refers to the view that we are actually zooming + self.vis.attr("transform", translateStatement + zoomStatement); }, + /** + * Primes the Ajax URL to load another Nexus tree + */ reloadViz : function() { - /** - * Primes the Ajax URL to load another Nexus tree - */ var self = this, treeIndex = $("#phylovizNexSelector :selected").val(); - $.getJSON(self.phyloTree.get("dataset").url(), { tree_index: treeIndex, data_type: 'raw_data' }, function(packedJson){ - self.data = packedJson.data; - self.config = packedJson; - self.render(); - }); + $.getJSON(self.phyloTree.get("dataset").url(), { + tree_index: treeIndex, + data_type: 'raw_data' + }, + function(packedJson){ + self.data = packedJson.data; + self.config = packedJson; + self.render(); + }); } }); @@ -768,10 +790,10 @@ var SettingsMenu = UserMenuBase.extend({ $("#phylovizApplySettingsBtn").off().on("click", function() { self.apply(); }); }, + /** + * Applying user values to phylotree model. + */ apply : function(){ - /** - * Applying user values to phylotree model. - */ var self = this; if (!self.isAcceptableValue(self.inputs.separation, 50, 2500) || !self.isAcceptableValue(self.inputs.leafHeight, 5, 30) || @@ -782,19 +804,19 @@ var SettingsMenu = UserMenuBase.extend({ self.phyloTree.set(key, $input.val()); }); }, + /** + * Called to update the values input to that stored in the model + */ updateUI : function(){ - /** - * Called to update the values input to that stored in the model - */ var self = this; $.each(self.inputs, function(key, $input){ $input.val(self.phyloTree.get(key)); }); }, + /** + * Resets the value of the phyloTree model to its default + */ resetToDefaults : function(){ - /** - * Resets the value of the phyloTree model to its default - */ $(".bs-tooltip").remove(); // just in case the tool tip was not removed var self = this; $.each(self.phyloTree.defaults, function(key, value) { @@ -810,10 +832,11 @@ var SettingsMenu = UserMenuBase.extend({ }); +/** + * View for inspecting node properties and editing them + */ var NodeSelectionView = UserMenuBase.extend({ - /** - * View for inspecting node properties and editing them - */ + className: 'Settings', initialize : function (options){ @@ -830,11 +853,12 @@ var NodeSelectionView = UserMenuBase.extend({ annotation : $("#phyloVizSelectedNodeAnnotation") }; + // temporarily stores the values in case user change their mind self.valuesOfConcern = { name : null, dist : null, annotation : null - }; // temporarily stores the values in case user change their mind + }; //init UI buttons $("#nodeSelCloseBtn").off().on("click", function() { self.el.hide(); }); @@ -859,10 +883,10 @@ var NodeSelectionView = UserMenuBase.extend({ }); }, + /** + * For turning on and off the child elements + */ toggleUI : function(){ - /** - * For turning on and off the child elements - */ var self = this, checked = self.UI.enableEdit.is(':checked'); @@ -881,10 +905,10 @@ var NodeSelectionView = UserMenuBase.extend({ }, + /** + * Reverting to previous values in case user change their minds + */ cancelChanges : function() { - /** - * Reverting to previous values in case user change their minds - */ var self = this, node = self.phyloTree.get("selectedNode"); if (node){ @@ -894,10 +918,10 @@ var NodeSelectionView = UserMenuBase.extend({ } }, + /** + * Changing the data in the underlying tree with user-specified values + */ updateNodes : function (){ - /** - * Changing the data in the underlying tree with user-specified values - */ var self = this, node = self.phyloTree.get("selectedNode"); if (node){ @@ -914,17 +938,15 @@ var NodeSelectionView = UserMenuBase.extend({ alert("No node selected"); } } - - }); +/** + * Initializes the search panel on phyloviz and handles its user interaction + * It allows user to search the entire free based on some qualifer, like dist <= val. + */ var PhyloVizSearch = UserMenuBase.extend({ - /** - * Initializes the search panel on phyloviz and handles its user interaction - * It allows user to search the entire free based on some qualifer, like dist <= val. - */ initialize : function () { var self = this; @@ -942,10 +964,10 @@ var PhyloVizSearch = UserMenuBase.extend({ }); }, + /** + * Searches the entire tree and will highlight the nodes that match the condition in green + */ searchTree : function (attr, condition, val){ - /** - * Searches the entire tree and will highlight the nodes that match the condition in green - */ d3.selectAll("g.node") .classed("searchHighlight", function(d){ var attrVal = d[attr]; @@ -972,4 +994,4 @@ return { PhylovizView: PhylovizView }; -}); \ No newline at end of file +}); diff --git a/templates/webapps/galaxy/visualization/phyloviz.mako b/templates/webapps/galaxy/visualization/phyloviz.mako index c4defff7b03..7a8f8d7d62a 100644 --- a/templates/webapps/galaxy/visualization/phyloviz.mako +++ b/templates/webapps/galaxy/visualization/phyloviz.mako @@ -81,7 +81,7 @@ position: fixed; ## Borrowed from galaxy modal_dialogues - background-color: white; + background-color: white; border: 1px solid #999; border: 1px solid rgba(0, 0, 0, 0.3); -webkit-border-radius: 6px; @@ -157,9 +157,9 @@ // -- Initialization code |--> phyloviz = new phyloviz_mod.PhylovizView({ - data: data, - layout : "Linear", - config : config + data : data, + layout : "Linear", + config : config }); // -- Render viz. -- @@ -169,7 +169,9 @@ $(function firstVizLoad(){ // calls when viz is loaded for the first time var config = JSON.parse( '${ h.to_json_string( config )}'); + window.config = config; var data = JSON.parse('${h.to_json_string(data)}'); + window.data = data; initPhyloViz(data, config); }); }); @@ -189,7 +191,6 @@
    -
    %if config["ext"] == "nex" and not config["saved_visualization"]: @@ -208,10 +209,7 @@ - -
    -
    ## Node Selection Menu @@ -284,11 +282,6 @@
    - - - - -
    Scaling & Rotation:
    -
    Translation:
    @@ -315,8 +306,6 @@ - -
    Others:
    From 4d781de114f021bf25068d03c5b58bd21f45f16d Mon Sep 17 00:00:00 2001 From: Greg Von Kuster Date: Tue, 21 May 2013 12:26:55 -0400 Subject: [PATCH 471/523] Fixes for complext repository dependency definitions that do not include a changeset_revision attribute. A fix for indenting the updates XML file is also included. --- .../webapps/tool_shed/controllers/upload.py | 12 ++++++------ lib/tool_shed/util/commit_util.py | 15 ++++++++++----- 2 files changed, 16 insertions(+), 11 deletions(-) diff --git a/lib/galaxy/webapps/tool_shed/controllers/upload.py b/lib/galaxy/webapps/tool_shed/controllers/upload.py index c273f2fcd3b..69823cf79b9 100644 --- a/lib/galaxy/webapps/tool_shed/controllers/upload.py +++ b/lib/galaxy/webapps/tool_shed/controllers/upload.py @@ -131,7 +131,7 @@ class UploadController( BaseUIController ): # Inspect the contents of the file to see if changeset_revision values are missing and if so, set them appropriately. altered, root = commit_util.handle_repository_dependencies_definition( trans, uploaded_file_name ) if altered: - tmp_filename = commit_util.create_and_write_tmp_file( util.xml_to_string( root, pretty=True ) ) + tmp_filename = commit_util.create_and_write_tmp_file( root ) shutil.move( tmp_filename, full_path ) else: shutil.move( uploaded_file_name, full_path ) @@ -140,7 +140,7 @@ class UploadController( BaseUIController ): # are missing and if so, set them appropriately. altered, root = commit_util.handle_tool_dependencies_definition( trans, uploaded_file_name ) if altered: - tmp_filename = commit_util.create_and_write_tmp_file( util.xml_to_string( root, pretty=True ) ) + tmp_filename = commit_util.create_and_write_tmp_file( root ) shutil.move( tmp_filename, full_path ) else: shutil.move( uploaded_file_name, full_path ) @@ -268,13 +268,13 @@ class UploadController( BaseUIController ): # Inspect the contents of the file to see if changeset_revision values are missing and if so, set them appropriately. altered, root = commit_util.handle_repository_dependencies_definition( trans, uploaded_file_name ) if altered: - tmp_filename = commit_util.create_and_write_tmp_file( util.xml_to_string( root, pretty=True ) ) + tmp_filename = commit_util.create_and_write_tmp_file( root ) shutil.move( tmp_filename, uploaded_file_name ) elif os.path.split( uploaded_file_name )[ -1 ] == 'tool_dependencies.xml': # Inspect the contents of the file to see if changeset_revision values are missing and if so, set them appropriately. altered, root = commit_util.handle_tool_dependencies_definition( trans, uploaded_file_name ) if altered: - tmp_filename = commit_util.create_and_write_tmp_file( util.xml_to_string( root, pretty=True ) ) + tmp_filename = commit_util.create_and_write_tmp_file( root ) shutil.move( tmp_filename, uploaded_file_name ) if ok: repo_path = os.path.join( full_path, relative_path ) @@ -330,13 +330,13 @@ class UploadController( BaseUIController ): # Inspect the contents of the file to see if changeset_revision values are missing and if so, set them appropriately. altered, root = commit_util.handle_repository_dependencies_definition( trans, uploaded_file_name ) if altered: - tmp_filename = commit_util.create_and_write_tmp_file( util.xml_to_string( root, pretty=True ) ) + tmp_filename = commit_util.create_and_write_tmp_file( root ) shutil.move( tmp_filename, uploaded_file_name ) elif os.path.split( uploaded_file_name )[ -1 ] == 'tool_dependencies.xml': # Inspect the contents of the file to see if changeset_revision values are missing and if so, set them appropriately. altered, root = commit_util.handle_tool_dependencies_definition( trans, uploaded_file_name ) if altered: - tmp_filename = commit_util.create_and_write_tmp_file( util.xml_to_string( root, pretty=True ) ) + tmp_filename = commit_util.create_and_write_tmp_file( root ) shutil.move( tmp_filename, uploaded_file_name ) return commit_util.handle_directory_changes( trans, repository, diff --git a/lib/tool_shed/util/commit_util.py b/lib/tool_shed/util/commit_util.py index 29bb3a33c7b..58ed1e40ad1 100644 --- a/lib/tool_shed/util/commit_util.py +++ b/lib/tool_shed/util/commit_util.py @@ -1,5 +1,6 @@ import logging import os +import pkg_resources import shutil import tempfile from galaxy import util @@ -9,11 +10,15 @@ import tool_shed.util.shed_util_common as suc from tool_shed.util import tool_util from galaxy import eggs + eggs.require( 'mercurial' ) from mercurial import commands from mercurial import hg from mercurial import ui +pkg_resources.require( 'elementtree' ) +from elementtree.ElementTree import tostring + log = logging.getLogger( __name__ ) UNDESIRABLE_DIRS = [ '.hg', '.svn', '.git', '.cvs' ] @@ -57,13 +62,13 @@ def check_file_content_for_html_and_images( file_path ): message = 'The file "%s" contains image content.\n' % str( file_path ) return message -def create_and_write_tmp_file( text ): +def create_and_write_tmp_file( root ): fh = tempfile.NamedTemporaryFile( 'wb' ) tmp_filename = fh.name fh.close() fh = open( tmp_filename, 'wb' ) fh.write( '\n' ) - fh.write( text ) + fh.write( tostring( root, 'utf-8' ) ) fh.close() return tmp_filename @@ -255,7 +260,6 @@ def handle_tool_dependencies_definition( trans, tool_dependencies_config ): package_altered = True if not altered: altered = True - elif package_elem.tag == 'install': # for actions_index, actions_elem in enumerate( package_elem ): @@ -277,8 +281,9 @@ def handle_tool_dependencies_definition( trans, tool_dependencies_config ): if package_altered: actions_elem[ action_index ] = action_elem if package_altered: - root_elem[ actions_index ] = actions_elem - + package_elem[ actions_index ] = actions_elem + if package_altered: + root_elem[ package_index ] = package_elem if package_altered: root[ root_index ] = root_elem return altered, root From a13d13d077941e92c4def262bb4e5cef932eefb0 Mon Sep 17 00:00:00 2001 From: Carl Eberhard Date: Tue, 21 May 2013 12:54:21 -0400 Subject: [PATCH 472/523] Scatterplot: use named data provider, fix encoding of ids; pack scripts --- lib/galaxy/datatypes/tabular.py | 1 - lib/galaxy/visualization/data_providers/basic.py | 12 +++++++----- .../mvc/visualizations/scatterplotControlForm.js | 1 + .../mvc/visualizations/scatterplotControlForm.js | 2 +- static/scripts/viz/scatterplot.js | 14 +++++++------- .../webapps/galaxy/visualization/scatterplot.mako | 3 +-- 6 files changed, 17 insertions(+), 16 deletions(-) diff --git a/lib/galaxy/datatypes/tabular.py b/lib/galaxy/datatypes/tabular.py index c8858c61472..9f7a4227fb3 100644 --- a/lib/galaxy/datatypes/tabular.py +++ b/lib/galaxy/datatypes/tabular.py @@ -328,7 +328,6 @@ class Tabular( data.Text ): """ Returns a list of visualizations for datatype. """ - # Can visualize tabular data as scatterplot if there are 2+ numerical # columns. num_numerical_cols = 0 diff --git a/lib/galaxy/visualization/data_providers/basic.py b/lib/galaxy/visualization/data_providers/basic.py index e6f6b878b61..a71efc2450c 100644 --- a/lib/galaxy/visualization/data_providers/basic.py +++ b/lib/galaxy/visualization/data_providers/basic.py @@ -67,11 +67,14 @@ class ColumnDataProvider( BaseDataProvider ): # allow throttling self.max_lines_returned = max_lines_returned - def get_data( self, columns, start_val=0, max_vals=None, skip_comments=True, **kwargs ): + def get_data( self, columns=None, start_val=0, max_vals=None, skip_comments=True, **kwargs ): """ Returns data from specified columns in dataset. Format is list of lists where each list is a line of data. """ + if not columns: + raise TypeError( 'parameter required: columns' ) + #TODO: validate kwargs try: max_vals = int( max_vals ) @@ -88,11 +91,10 @@ class ColumnDataProvider( BaseDataProvider ): # skip comment lines (if any/avail) # pre: should have original_dataset and if( skip_comments - and start_val == 0 - and self.original_dataset.metadata.comment_lines ): - start_val = int( self.original_dataset.metadata.comment_lines ) + 1 + and self.original_dataset.metadata.comment_lines + and start_val < self.original_dataset.metadata.comment_lines ): + start_val = int( self.original_dataset.metadata.comment_lines ) - #TODO bail if columns None, not parsable, not within meta.columns # columns is an array of ints for now (should handle column names later) columns = from_json_string( columns ) for column in columns: diff --git a/static/scripts/mvc/visualizations/scatterplotControlForm.js b/static/scripts/mvc/visualizations/scatterplotControlForm.js index 76c12db9a69..ce2eb3c156e 100644 --- a/static/scripts/mvc/visualizations/scatterplotControlForm.js +++ b/static/scripts/mvc/visualizations/scatterplotControlForm.js @@ -562,6 +562,7 @@ var ScatterplotControlForm = BaseView.extend( LoggableMixin ).extend({ var params = { data_type : 'raw_data', + provider : 'column', columns : '[' + columns + ']' }; this.log( '\t data settings (url params):', params ); diff --git a/static/scripts/packed/mvc/visualizations/scatterplotControlForm.js b/static/scripts/packed/mvc/visualizations/scatterplotControlForm.js index c15ed5a8033..2c113a3780e 100644 --- a/static/scripts/packed/mvc/visualizations/scatterplotControlForm.js +++ b/static/scripts/packed/mvc/visualizations/scatterplotControlForm.js @@ -1 +1 @@ -var ScatterplotControlForm=BaseView.extend(LoggableMixin).extend({className:"scatterplot-control-form",dataLoadDelay:4000,dataLoadSize:5000,loadingIndicatorImage:"loading_small_white_bg.gif",fetchMsg:"Fetching data...",renderMsg:"Rendering...",initialize:function(a){this.log(this+".initialize, attributes:",a);this.dataset=null;this.chartConfig=null;this.chart=null;this.loader=null;this.$dataControl=null;this.$chartControl=null;this.$statsDisplay=null;this.$chartDisplay=null;this.dataFetch=null;this.initializeFromAttributes(a);this.initializeChart(a);this.initializeDataLoader(a)},initializeFromAttributes:function(a){if(!a||!a.dataset){throw ("ScatterplotView requires a dataset")}else{this.dataset=a.dataset}if(jQuery.type(this.dataset.metadata_column_types)==="string"){this.dataset.metadata_column_types=this.dataset.metadata_column_types.split(", ")}this.log("\t dataset:",this.dataset);if(this.dataset.comment_lines&&this.dataset.comment_lines.length){var b=this.dataset.comment_lines[0],c=b.split("\t");if(c.length===this.dataset.metadata_column_types.length){this.possibleHeaders=c}}if(!a.apiDatasetsURL){throw ("ScatterplotView requires a apiDatasetsURL")}else{this.dataURL=a.apiDatasetsURL+"/"+this.dataset.id+"?"}this.log("\t dataURL:",this.dataURL)},initializeChart:function(a){this.chartConfig=a.chartConfig||{};this.log("\t initial chartConfig:",this.chartConfig);this.chart=new TwoVarScatterplot(this.chartConfig);this.chartConfig=this.chart.config},initializeDataLoader:function(b){var a=this;this.loader=new LazyDataLoader({url:null,start:b.start||0,total:b.total||this.dataset.metadata_data_lines,delay:this.dataLoadDelay,size:this.dataLoadSize,buildUrl:function(d,c){return this.url+"&"+jQuery.param({start_val:d,max_vals:c})}});$(this.loader).bind("error",function(e,c,d){a.log("ERROR:",c,d);alert("ERROR fetching data:\n"+c+"\n"+d);a.hideLoadingIndicator()})},render:function(){this.log(this+".render");this.$el.append(ScatterplotControlForm.templates.mainLayout({loadingIndicatorImagePath:galaxy_paths.get("image_path")+"/"+this.loadingIndicatorImage,message:""}));this.$dataControl=this._render_dataControl();this.$chartControl=this._render_chartControl();this.$statsDisplay=this.$el.find(".tab-pane#stats-display");this.$chartDisplay=this._render_chartDisplay();if(this.chartConfig.xColumn&&this.chartConfig.yColumn){this.renderChart()}this.$el.find(".tooltip").tooltip();return this},_render_dataControl:function(){var b=this,a=[],e=[],c=(this.possibleHeaders&&this.$dataControl)?(this.$dataControl.find("#first-line-header-checkbox").is(":checked")):(false);_.each(this.dataset.metadata_column_types,function(i,g){var h=g+1,f="column "+h;if(b.dataset.metadata_column_names){f=b.dataset.metadata_column_names[g]}else{if(c){f=b.possibleHeaders[g]}}a.push({index:h,name:f});if(i==="int"||i==="float"){e.push({index:h,name:f})}});var d=this.$el.find(".tab-pane#data-control");d.html(ScatterplotControlForm.templates.dataControl({allColumns:a,numericColumns:e,possibleHeaders:(this.possibleHeaders)?(this.possibleHeaders.join(", ")):(""),usePossibleHeaders:c}));if(!this.dataset.metadata_column_names&&this.possibleHeaders){d.find("#first-line-header").show()}d.find("#X-select").val(this.chartConfig.xColumn);d.find("#Y-select").val(this.chartConfig.yColumn);if(this.chartConfig.idColumn!==undefined){d.find("#include-id-checkbox").attr("checked",true).trigger("change");d.find("#ID-select").val(this.chartConfig.idColumn)}return d},_render_chartControl:function(){var a=this,b=this.$el.find(".tab-pane#chart-control"),c={datapointSize:{min:2,max:10,step:1},width:{min:200,max:800,step:20},height:{min:200,max:800,step:20}};b.append(ScatterplotControlForm.templates.chartControl(this.chartConfig));b.find(".numeric-slider-input").each(function(){var f=$(this),e=f.find(".slider-output"),g=f.find(".slider"),h=f.attr("id");function d(){var j=$(this),i=j.slider("value");e.text(i)}g.slider(_.extend(c[h],{value:a.chartConfig[h],change:d,slide:d}))});return b},_render_chartDisplay:function(){var a=this.$el.find(".tab-pane#chart-display");a.append(ScatterplotControlForm.templates.chartDisplay(this.chartConfig));return a},events:{"change #include-id-checkbox":"toggleThirdColumnSelector","change #first-line-header-checkbox":"rerenderDataControl","click #data-control #render-button":"renderChart","click #chart-control #render-button":"changeChartSettings"},toggleThirdColumnSelector:function(){this.$el.find('select[name="ID"]').parent().toggle()},rerenderDataControl:function(){this.$dataControl=this._render_dataControl()},showLoadingIndicator:function(b,c){b=b||"";var a=this.$el.find("div#loading-indicator");messageBox=a.find(".loading-message");if(a.is(":visible")){if(b){messageBox.fadeOut("fast",function(){messageBox.text(b);messageBox.fadeIn("fast",c)})}else{c()}}else{if(b){messageBox.text(b)}a.fadeIn("fast",c)}},hideLoadingIndicator:function(a){this.$el.find("div#loading-indicator").fadeOut("fast",a)},renderChart:function(){this.log(this+".renderChart");this.data=null;this.meta=null;_.extend(this.chartConfig,this.getChartSettings());this.log("\t chartConfig:",this.chartConfig);this.chart.updateConfig(this.chartConfig,false);this.loader.url=this.dataURL+"&"+jQuery.param(this.getDataSettings());this.log("\t loader: total lines:",this.loader.total," url:",this.loader.url);var a=this;$(this.loader).bind("loaded.new",function(c,b){a.log(a+" loaded.new",b);a.postProcessDataFetchResponse(b);a.log("\t postprocessed data:",a.data);a.log("\t postprocessed meta:",a.meta);a.showLoadingIndicator(a.renderMsg,function(){a.chart.render(a.data,a.meta);a.renderStats(a.data,a.meta);a.hideLoadingIndicator()})});$(this.loader).bind("complete",function(b,c){a.log(a+" complete",c);$(a.loader).unbind()});a.showLoadingIndicator(a.fetchMsg,function(){a.$el.find("ul.nav").find('a[href="#chart-display"]').tab("show");a.loader.load()})},renderStats:function(){this.log(this+".renderStats");this.$statsDisplay.html(ScatterplotControlForm.templates.statsDisplay({stats:[{name:"Count",xval:this.meta[0].count,yval:this.meta[1].count},{name:"Min",xval:this.meta[0].min,yval:this.meta[1].min},{name:"Max",xval:this.meta[0].max,yval:this.meta[1].max},{name:"Sum",xval:this.meta[0].sum,yval:this.meta[1].sum},{name:"Mean",xval:this.meta[0].mean,yval:this.meta[1].mean},{name:"Median",xval:this.meta[0].median,yval:this.meta[1].median}]}))},changeChartSettings:function(){var a=this;newChartSettings=this.getChartSettings();_.extend(this.chartConfig,newChartSettings);this.log("this.chartConfig:",this.chartConfig);this.chart.updateConfig(this.chartConfig,false);if(a.data&&a.meta){a.showLoadingIndicator(a.renderMsg,function(){a.$el.find("ul.nav").find('a[href="#chart-display"]').tab("show");a.chart.render(a.data,a.meta);a.hideLoadingIndicator()})}else{this.renderChart()}},postProcessDataFetchResponse:function(a){this.postProcessData(a.data);this.postProcessMeta(a.meta)},postProcessData:function(b){var a=this;if(a.data){_.each(b,function(d,c){a.data[c]=a.data[c].concat(d)})}else{a.data=b}},postProcessMeta:function(c){var a=this,b=this.dataset.metadata_column_types;if(a.meta){_.each(c,function(e,d){var i=a.meta[d],g=b[d];i.count+=(e.count)?(e.count):(0);if((g==="int")||(g==="float")){i.min=Math.min(e.min,i.min);i.max=Math.max(e.max,i.max);i.sum=e.sum+i.sum;i.mean=(i.count)?(i.sum/i.count):(null);var f=a.data[d].slice().sort(),h=Math.floor(f.length/2);if(f.length%2===0){i.median=((f[h]+f[(h+1)])/2)}else{i.median=f[h]}}})}else{a.meta=c}},getDataSettings:function(){var b=this.getColumnSelections(),a=[];this.log("\t columnSelections:",b);a=[b.X.colIndex-1,b.Y.colIndex-1];if(this.$dataControl.find("#include-id-checkbox").attr("checked")){a.push(b.ID.colIndex-1)}var c={data_type:"raw_data",columns:"["+a+"]"};this.log("\t data settings (url params):",c);return c},getColumnSelections:function(){var a={};this.$dataControl.find("div.column-select select").each(function(){var b=$(this),c=b.val();a[b.attr("name")]={colIndex:c,colName:b.children('[value="'+c+'"]').text()}});return a},getChartSettings:function(){var c={},d=this.getColumnSelections();c.datapointSize=this.$chartControl.find("#datapointSize.numeric-slider-input").find(".slider").slider("value");c.width=this.$chartControl.find("#width.numeric-slider-input").find(".slider").slider("value");c.height=this.$chartControl.find("#height.numeric-slider-input").find(".slider").slider("value");var b=this.$chartControl.find("input#X-axis-label").val(),a=this.$chartControl.find("input#Y-axis-label").val();c.xLabel=(b==="X")?(d.X.colName):(b);c.yLabel=(a==="Y")?(d.Y.colName):(a);c.animDuration=(this.$chartControl.find("#animate-chart").is(":checked"))?(this.chart.defaults.animDuration):(0);this.log("\t chartSettings:",c);return c},toString:function(){return"ScatterplotControlForm("+((this.dataset)?(this.dataset.id):(""))+")"}});ScatterplotControlForm.templates={mainLayout:Handlebars.templates["template-visualization-scatterplotControlForm"],dataControl:Handlebars.templates["template-visualization-dataControl"],chartControl:Handlebars.templates["template-visualization-chartControl"],statsDisplay:Handlebars.templates["template-visualization-statsDisplay"],chartDisplay:Handlebars.templates["template-visualization-chartDisplay"]}; \ No newline at end of file +var ScatterplotControlForm=BaseView.extend(LoggableMixin).extend({className:"scatterplot-control-form",dataLoadDelay:4000,dataLoadSize:5000,loadingIndicatorImage:"loading_small_white_bg.gif",fetchMsg:"Fetching data...",renderMsg:"Rendering...",initialize:function(a){this.log(this+".initialize, attributes:",a);this.dataset=null;this.chartConfig=null;this.chart=null;this.loader=null;this.$dataControl=null;this.$chartControl=null;this.$statsDisplay=null;this.$chartDisplay=null;this.dataFetch=null;this.initializeFromAttributes(a);this.initializeChart(a);this.initializeDataLoader(a)},initializeFromAttributes:function(a){if(!a||!a.dataset){throw ("ScatterplotView requires a dataset")}else{this.dataset=a.dataset}if(jQuery.type(this.dataset.metadata_column_types)==="string"){this.dataset.metadata_column_types=this.dataset.metadata_column_types.split(", ")}this.log("\t dataset:",this.dataset);if(this.dataset.comment_lines&&this.dataset.comment_lines.length){var b=this.dataset.comment_lines[0],c=b.split("\t");if(c.length===this.dataset.metadata_column_types.length){this.possibleHeaders=c}}if(!a.apiDatasetsURL){throw ("ScatterplotView requires a apiDatasetsURL")}else{this.dataURL=a.apiDatasetsURL+"/"+this.dataset.id+"?"}this.log("\t dataURL:",this.dataURL)},initializeChart:function(a){this.chartConfig=a.chartConfig||{};this.log("\t initial chartConfig:",this.chartConfig);this.chart=new TwoVarScatterplot(this.chartConfig);this.chartConfig=this.chart.config},initializeDataLoader:function(b){var a=this;this.loader=new LazyDataLoader({url:null,start:b.start||0,total:b.total||this.dataset.metadata_data_lines,delay:this.dataLoadDelay,size:this.dataLoadSize,buildUrl:function(d,c){return this.url+"&"+jQuery.param({start_val:d,max_vals:c})}});$(this.loader).bind("error",function(e,c,d){a.log("ERROR:",c,d);alert("ERROR fetching data:\n"+c+"\n"+d);a.hideLoadingIndicator()})},render:function(){this.log(this+".render");this.$el.append(ScatterplotControlForm.templates.mainLayout({loadingIndicatorImagePath:galaxy_paths.get("image_path")+"/"+this.loadingIndicatorImage,message:""}));this.$dataControl=this._render_dataControl();this.$chartControl=this._render_chartControl();this.$statsDisplay=this.$el.find(".tab-pane#stats-display");this.$chartDisplay=this._render_chartDisplay();if(this.chartConfig.xColumn&&this.chartConfig.yColumn){this.renderChart()}this.$el.find(".tooltip").tooltip();return this},_render_dataControl:function(){var b=this,a=[],e=[],c=(this.possibleHeaders&&this.$dataControl)?(this.$dataControl.find("#first-line-header-checkbox").is(":checked")):(false);_.each(this.dataset.metadata_column_types,function(i,g){var h=g+1,f="column "+h;if(b.dataset.metadata_column_names){f=b.dataset.metadata_column_names[g]}else{if(c){f=b.possibleHeaders[g]}}a.push({index:h,name:f});if(i==="int"||i==="float"){e.push({index:h,name:f})}});var d=this.$el.find(".tab-pane#data-control");d.html(ScatterplotControlForm.templates.dataControl({allColumns:a,numericColumns:e,possibleHeaders:(this.possibleHeaders)?(this.possibleHeaders.join(", ")):(""),usePossibleHeaders:c}));if(!this.dataset.metadata_column_names&&this.possibleHeaders){d.find("#first-line-header").show()}d.find("#X-select").val(this.chartConfig.xColumn);d.find("#Y-select").val(this.chartConfig.yColumn);if(this.chartConfig.idColumn!==undefined){d.find("#include-id-checkbox").attr("checked",true).trigger("change");d.find("#ID-select").val(this.chartConfig.idColumn)}return d},_render_chartControl:function(){var a=this,b=this.$el.find(".tab-pane#chart-control"),c={datapointSize:{min:2,max:10,step:1},width:{min:200,max:800,step:20},height:{min:200,max:800,step:20}};b.append(ScatterplotControlForm.templates.chartControl(this.chartConfig));b.find(".numeric-slider-input").each(function(){var f=$(this),e=f.find(".slider-output"),g=f.find(".slider"),h=f.attr("id");function d(){var j=$(this),i=j.slider("value");e.text(i)}g.slider(_.extend(c[h],{value:a.chartConfig[h],change:d,slide:d}))});return b},_render_chartDisplay:function(){var a=this.$el.find(".tab-pane#chart-display");a.append(ScatterplotControlForm.templates.chartDisplay(this.chartConfig));return a},events:{"change #include-id-checkbox":"toggleThirdColumnSelector","change #first-line-header-checkbox":"rerenderDataControl","click #data-control #render-button":"renderChart","click #chart-control #render-button":"changeChartSettings"},toggleThirdColumnSelector:function(){this.$el.find('select[name="ID"]').parent().toggle()},rerenderDataControl:function(){this.$dataControl=this._render_dataControl()},showLoadingIndicator:function(b,c){b=b||"";var a=this.$el.find("div#loading-indicator");messageBox=a.find(".loading-message");if(a.is(":visible")){if(b){messageBox.fadeOut("fast",function(){messageBox.text(b);messageBox.fadeIn("fast",c)})}else{c()}}else{if(b){messageBox.text(b)}a.fadeIn("fast",c)}},hideLoadingIndicator:function(a){this.$el.find("div#loading-indicator").fadeOut("fast",a)},renderChart:function(){this.log(this+".renderChart");this.data=null;this.meta=null;_.extend(this.chartConfig,this.getChartSettings());this.log("\t chartConfig:",this.chartConfig);this.chart.updateConfig(this.chartConfig,false);this.loader.url=this.dataURL+"&"+jQuery.param(this.getDataSettings());this.log("\t loader: total lines:",this.loader.total," url:",this.loader.url);var a=this;$(this.loader).bind("loaded.new",function(c,b){a.log(a+" loaded.new",b);a.postProcessDataFetchResponse(b);a.log("\t postprocessed data:",a.data);a.log("\t postprocessed meta:",a.meta);a.showLoadingIndicator(a.renderMsg,function(){a.chart.render(a.data,a.meta);a.renderStats(a.data,a.meta);a.hideLoadingIndicator()})});$(this.loader).bind("complete",function(b,c){a.log(a+" complete",c);$(a.loader).unbind()});a.showLoadingIndicator(a.fetchMsg,function(){a.$el.find("ul.nav").find('a[href="#chart-display"]').tab("show");a.loader.load()})},renderStats:function(){this.log(this+".renderStats");this.$statsDisplay.html(ScatterplotControlForm.templates.statsDisplay({stats:[{name:"Count",xval:this.meta[0].count,yval:this.meta[1].count},{name:"Min",xval:this.meta[0].min,yval:this.meta[1].min},{name:"Max",xval:this.meta[0].max,yval:this.meta[1].max},{name:"Sum",xval:this.meta[0].sum,yval:this.meta[1].sum},{name:"Mean",xval:this.meta[0].mean,yval:this.meta[1].mean},{name:"Median",xval:this.meta[0].median,yval:this.meta[1].median}]}))},changeChartSettings:function(){var a=this;newChartSettings=this.getChartSettings();_.extend(this.chartConfig,newChartSettings);this.log("this.chartConfig:",this.chartConfig);this.chart.updateConfig(this.chartConfig,false);if(a.data&&a.meta){a.showLoadingIndicator(a.renderMsg,function(){a.$el.find("ul.nav").find('a[href="#chart-display"]').tab("show");a.chart.render(a.data,a.meta);a.hideLoadingIndicator()})}else{this.renderChart()}},postProcessDataFetchResponse:function(a){this.postProcessData(a.data);this.postProcessMeta(a.meta)},postProcessData:function(b){var a=this;if(a.data){_.each(b,function(d,c){a.data[c]=a.data[c].concat(d)})}else{a.data=b}},postProcessMeta:function(c){var a=this,b=this.dataset.metadata_column_types;if(a.meta){_.each(c,function(e,d){var i=a.meta[d],g=b[d];i.count+=(e.count)?(e.count):(0);if((g==="int")||(g==="float")){i.min=Math.min(e.min,i.min);i.max=Math.max(e.max,i.max);i.sum=e.sum+i.sum;i.mean=(i.count)?(i.sum/i.count):(null);var f=a.data[d].slice().sort(),h=Math.floor(f.length/2);if(f.length%2===0){i.median=((f[h]+f[(h+1)])/2)}else{i.median=f[h]}}})}else{a.meta=c}},getDataSettings:function(){var b=this.getColumnSelections(),a=[];this.log("\t columnSelections:",b);a=[b.X.colIndex-1,b.Y.colIndex-1];if(this.$dataControl.find("#include-id-checkbox").attr("checked")){a.push(b.ID.colIndex-1)}var c={data_type:"raw_data",provider:"column",columns:"["+a+"]"};this.log("\t data settings (url params):",c);return c},getColumnSelections:function(){var a={};this.$dataControl.find("div.column-select select").each(function(){var b=$(this),c=b.val();a[b.attr("name")]={colIndex:c,colName:b.children('[value="'+c+'"]').text()}});return a},getChartSettings:function(){var c={},d=this.getColumnSelections();c.datapointSize=this.$chartControl.find("#datapointSize.numeric-slider-input").find(".slider").slider("value");c.width=this.$chartControl.find("#width.numeric-slider-input").find(".slider").slider("value");c.height=this.$chartControl.find("#height.numeric-slider-input").find(".slider").slider("value");var b=this.$chartControl.find("input#X-axis-label").val(),a=this.$chartControl.find("input#Y-axis-label").val();c.xLabel=(b==="X")?(d.X.colName):(b);c.yLabel=(a==="Y")?(d.Y.colName):(a);c.animDuration=(this.$chartControl.find("#animate-chart").is(":checked"))?(this.chart.defaults.animDuration):(0);this.log("\t chartSettings:",c);return c},toString:function(){return"ScatterplotControlForm("+((this.dataset)?(this.dataset.id):(""))+")"}});ScatterplotControlForm.templates={mainLayout:Handlebars.templates["template-visualization-scatterplotControlForm"],dataControl:Handlebars.templates["template-visualization-dataControl"],chartControl:Handlebars.templates["template-visualization-chartControl"],statsDisplay:Handlebars.templates["template-visualization-statsDisplay"],chartDisplay:Handlebars.templates["template-visualization-chartDisplay"]}; \ No newline at end of file diff --git a/static/scripts/viz/scatterplot.js b/static/scripts/viz/scatterplot.js index 3ae8de3698f..d6527d301d8 100644 --- a/static/scripts/viz/scatterplot.js +++ b/static/scripts/viz/scatterplot.js @@ -138,16 +138,16 @@ function TwoVarScatterplot( config ){ bottom = bottom || 0; left = left || 0; this.svg - .attr( "width", this.config.width + ( this.config.marginRight + right ) + - ( this.config.marginLeft + left ) ) - .attr( "height", this.config.height + ( this.config.marginTop + top ) + - ( this.config.marginBottom + bottom ) ) + .attr( "width", this.config.width + ( this.config.marginRight + right ) + + ( this.config.marginLeft + left ) ) + .attr( "height", this.config.height + ( this.config.marginTop + top ) + + ( this.config.marginBottom + bottom ) ) // initial is hidden - show it .style( 'display', 'block' ); // move content group away from margins //TODO: allow top, right axis - this.content = this.svg.select( "g.content" ) + this.content = this.svg.select( "g.content" ) .attr( "transform", this.translateStr( this.config.marginLeft + left, this.config.marginTop + top ) ); }; @@ -260,7 +260,7 @@ function TwoVarScatterplot( config ){ .attr( 'text-anchor', 'middle' ) .attr( 'transform', this.rotateStr( -90, this.config.yAxisLabelBumpX, this.config.height / 2 ) ) .text( this.config.yLabel ); - //this.log( 'yAxisLabel:', this.yAxisLabel ); + //this.log( 'yAxisLabel:', this.yAxisLabel ); }; // ........................................................ grid lines @@ -284,7 +284,7 @@ function TwoVarScatterplot( config ){ // remove unneeded (less ticks) this.vGridLines.exit().remove(); - //this.log( 'vGridLines:', this.vGridLines ); + //this.log( 'vGridLines:', this.vGridLines ); // HORIZONTAL this.hGridLines = this.content.selectAll( 'line.h-grid-line' ) diff --git a/templates/webapps/galaxy/visualization/scatterplot.mako b/templates/webapps/galaxy/visualization/scatterplot.mako index 0d844accacd..840b1dcdea1 100644 --- a/templates/webapps/galaxy/visualization/scatterplot.mako +++ b/templates/webapps/galaxy/visualization/scatterplot.mako @@ -222,8 +222,7 @@ ${h.js(