commit/galaxy-central: carlfeberhard: History structure: documentation and clean up
1 new commit in galaxy-central: https://bitbucket.org/galaxy/galaxy-central/commits/8f8341ac47cd/ Changeset: 8f8341ac47cd User: carlfeberhard Date: 2014-10-15 18:05:18+00:00 Summary: History structure: documentation and clean up Affected #: 13 files diff -r eacfa5a1a3f2e9fc516bea159c16ddb36067a220 -r 8f8341ac47cd276cf0a2aa3d1c1098f7d3d58c75 client/galaxy/scripts/mvc/history/history-structure-view.js --- a/client/galaxy/scripts/mvc/history/history-structure-view.js +++ b/client/galaxy/scripts/mvc/history/history-structure-view.js @@ -13,6 +13,9 @@ */ // ============================================================================ +/** + * + */ var HistoryStructureComponent = Backbone.View.extend( BASE_MVC.LoggableMixin ).extend({ //logger : console, @@ -151,7 +154,6 @@ //TODO:? liMap needed - can't we attach to vertex? var li = view._jobLiMap[ v.name ], position = view.layout.nodeMap[ li.model.id ]; - console.debug( li.$el.is( ':visible' ) ); //this.debug( position ); li.$el.css({ top: position.y, left: position.x }); }); @@ -249,6 +251,9 @@ // ============================================================================ +/** + * + */ var VerticalHistoryStructureComponent = HistoryStructureComponent.extend({ logger : console, @@ -288,7 +293,6 @@ node.y = y; var li = view._jobLiMap[ jobId ]; if( li.$el.is( ':visible' ) ){ - console.debug( 'li is visible, adj. by:', li.$el.height() ); y += li.$el.height() + layout.jobSpacing; } else { y += layout.initialSpacing + layout.jobSpacing; @@ -331,6 +335,9 @@ // ============================================================================ +/** + * + */ var HistoryStructureView = Backbone.View.extend( BASE_MVC.LoggableMixin ).extend({ //logger : console, @@ -351,7 +358,7 @@ excludeSetMetadata : true, excludeErroredJobs : true }); -window.dag = this.dag; +//window.dag = this.dag; this.log( this + '.dag:', this.dag ); this._createComponents(); @@ -360,7 +367,7 @@ _createComponents : function(){ this.log( this + '._createComponents' ); var structure = this; -window.structure = structure; +//window.structure = structure; structure.componentViews = structure.dag.weakComponentGraphArray().map( function( componentGraph ){ return structure._createComponent( componentGraph ); @@ -402,8 +409,5 @@ // ============================================================================ - //return { - // HistoryStructureView : HistoryStructureView - //}; return HistoryStructureView; }); diff -r eacfa5a1a3f2e9fc516bea159c16ddb36067a220 -r 8f8341ac47cd276cf0a2aa3d1c1098f7d3d58c75 client/galaxy/scripts/mvc/history/job-dag.js --- a/client/galaxy/scripts/mvc/history/job-dag.js +++ b/client/galaxy/scripts/mvc/history/job-dag.js @@ -4,6 +4,10 @@ ],function( GRAPH, addLogging ){ // ============================================================================ var _super = GRAPH.Graph; +/** A Directed acyclic Graph built from a history's job data. + * Reads in job json, filters and process that json, and builds a graph + * using the connections between job inputs and outputs. + */ var JobDAG = function( options ){ var self = this; //this.logger = console; @@ -25,9 +29,12 @@ }; JobDAG.prototype = new GRAPH.Graph(); JobDAG.prototype.constructor = JobDAG; + +// add logging ability - turn off/on using the this.logger statement above addLogging( JobDAG ); // ---------------------------------------------------------------------------- +/** process jobs, options, filters, and any history data, then create the graph */ JobDAG.prototype.init = function _init( options ){ options = options || {}; _super.prototype.init.call( this, options ); @@ -52,6 +59,7 @@ return self; }; +/** add job filters based on options */ JobDAG.prototype._initFilters = function __initFilters(){ var self = this, filters = []; @@ -84,6 +92,7 @@ return filters; }; +/** sort the jobs and cache any that pass all filters into _idMap by job.id */ JobDAG.prototype.preprocessJobs = function _preprocessJobs( jobs ){ this.info( 'processing jobs' ); @@ -98,6 +107,7 @@ return self; }; +/** sort the jobs based on update time */ JobDAG.prototype.sort = function _sort( jobs ){ function cmpCreate( a, b ){ if( a.update_time > b.update_time ){ return 1; } @@ -107,6 +117,7 @@ return jobs.sort( cmpCreate ); }; +/** proces input/output, filter based on job data returning data if passing, null if not */ JobDAG.prototype.preprocessJob = function _preprocessJob( job, index ){ //this.info( 'preprocessJob', job, index ); var self = this, @@ -138,6 +149,9 @@ return jobData; }; +/** make verbose input/output lists more concise, sanity checking along the way + * processFn is called on each input/output and passed the dataset obj (id,src) and the input/output name. + */ JobDAG.prototype.datasetMapToIdArray = function _datasetMapToIdArray( datasetMap, processFn ){ var self = this; return _.map( datasetMap, function( dataset, nameInJob ){ @@ -152,6 +166,9 @@ }); }; +/** Walk all the jobs (vertices), attempting to find connections + * between datasets used as both inputs and outputs (edges) + */ JobDAG.prototype.createGraph = function _createGraph(){ var self = this; self.debug( 'connections:' ); @@ -183,6 +200,7 @@ return self; }; +/** Override to re-sort (ugh) jobs in each component by update time */ Graph.prototype.weakComponentGraphArray = function(){ return this.weakComponents().map( function( component ){ //TODO: this seems to belong above (in sort) - why isn't it preserved? @@ -198,7 +216,4 @@ // ============================================================================ return JobDAG; - //return { - // jobDAG : jobDAG - //}; }); diff -r eacfa5a1a3f2e9fc516bea159c16ddb36067a220 -r 8f8341ac47cd276cf0a2aa3d1c1098f7d3d58c75 client/galaxy/scripts/mvc/job/job-li.js --- a/client/galaxy/scripts/mvc/job/job-li.js +++ b/client/galaxy/scripts/mvc/job/job-li.js @@ -6,7 +6,8 @@ ], function( LIST_ITEM, DATASET_LIST, BASE_MVC, _l ){ //============================================================================== var _super = LIST_ITEM.FoldoutListItemView; -/** @class +/** @class A job view used from within a larger list of jobs. + * Each job itself is a foldout panel of history contents displaying the outputs of this job. */ var JobListItemView = _super.extend(/** @lends JobListItemView.prototype */{ diff -r eacfa5a1a3f2e9fc516bea159c16ddb36067a220 -r 8f8341ac47cd276cf0a2aa3d1c1098f7d3d58c75 client/galaxy/scripts/mvc/job/job-model.js --- a/client/galaxy/scripts/mvc/job/job-model.js +++ b/client/galaxy/scripts/mvc/job/job-model.js @@ -7,7 +7,7 @@ ], function( HISTORY_CONTENTS, STATES, AJAX_QUEUE, BASE_MVC, _l ){ //============================================================================== var searchableMixin = BASE_MVC.SearchableModelMixin; -/** @class +/** @class Represents a job running or ran on the server job handlers. */ var Job = Backbone.Model.extend( BASE_MVC.LoggableMixin ).extend( BASE_MVC.mixin( searchableMixin, /** @lends Job.prototype */{ @@ -126,6 +126,7 @@ }, // ........................................................................ ajax + /** fetches all details for each job in the collection using a queue */ queueDetailFetching : function(){ var collection = this, queue = new AJAX_QUEUE.AjaxQueue( this.map( function( job ){ @@ -179,7 +180,7 @@ //----------------------------------------------------------------------------- class vars }, { - + /** class level fn for fetching the job details for all jobs in a history */ fromHistory : function( historyId ){ console.debug( this ); var Collection = this, diff -r eacfa5a1a3f2e9fc516bea159c16ddb36067a220 -r 8f8341ac47cd276cf0a2aa3d1c1098f7d3d58c75 client/galaxy/scripts/utils/graph.js --- a/client/galaxy/scripts/utils/graph.js +++ b/client/galaxy/scripts/utils/graph.js @@ -2,10 +2,10 @@ ],function(){ /* ============================================================================ TODO: - edges can't retain data ============================================================================ */ //TODO: go ahead and move to underscore... +/** call fn on each key/value in d */ function each( d, fn ){ for( var k in d ){ if( d.hasOwnProperty( k ) ){ @@ -14,6 +14,7 @@ } } +/** copy key/values from d2 to d overwriting if present */ function extend( d, d2 ){ for( var k in d2 ){ if( d2.hasOwnProperty( k ) ){ @@ -23,6 +24,7 @@ return d; } +/** deep equal of two dictionaries */ function matches( d, d2 ){ for( var k in d2 ){ if( d2.hasOwnProperty( k ) ){ @@ -34,6 +36,10 @@ return true; } +/** map key/values in obj + * if propsOrFn is an object, return only those k/v that match the object + * if propsOrFn is function, call the fn and returned the mapped values from it + */ function iterate( obj, propsOrFn ){ var fn = typeof propsOrFn === 'function'? propsOrFn : undefined, props = typeof propsOrFn === 'object'? propsOrFn : undefined, @@ -60,6 +66,8 @@ // ============================================================================ +/** A graph edge containing the name/id of both source and target and optional data + */ function Edge( source, target, data ){ var self = this; self.source = source !== undefined? source : null; @@ -70,10 +78,12 @@ //} return self; } +/** String representation */ Edge.prototype.toString = function(){ return this.source + '->' + this.target; }; +/** Return a plain object representing this edge */ Edge.prototype.toJSON = function(){ //TODO: this is safe in most browsers (fns will be stripped) - alter tests to incorporate this in order to pass data //return this; @@ -88,6 +98,9 @@ }; // ============================================================================ +/** A graph vertex with a (unique) name/id and optional data. + * A vertex contains a list of Edges (whose sources are this vertex) and maintains the degree. + */ function Vertex( name, data ){ var self = this; self.name = name !== undefined? name : '(unnamed)'; @@ -96,16 +109,19 @@ self.degree = 0; return self; } -window.Vertex = Vertex; + +/** String representation */ Vertex.prototype.toString = function(){ return 'Vertex(' + this.name + ')'; }; //TODO: better name w no collision for either this.eachEdge or this.edges +/** Iterate over each edge from this vertex */ Vertex.prototype.eachEdge = function( propsOrFn ){ return iterate( this.edges, propsOrFn ); }; +/** Return a plain object representing this vertex */ Vertex.prototype.toJSON = function(){ //return this; return { @@ -116,6 +132,10 @@ // ============================================================================ +/** Base (abstract) class for Graph search algorithms. + * Pass in the graph to search + * and an optional dictionary containing the 3 vertex/edge processing fns listed below. + */ var GraphSearch = function( graph, processFns ){ var self = this; self.graph = graph; @@ -136,6 +156,9 @@ return self; }; +/** Search interface where start is the vertex (or the name/id of the vertex) to begin the search at + * This public interface caches searches and returns the cached version if it's already been done. + */ GraphSearch.prototype.search = function _search( start ){ var self = this; if( start in self._cache ){ return self._cache[ start ]; } @@ -143,6 +166,22 @@ return ( self._cache[ start.name ] = self._search( start ) ); }; +/** Actual search (private) function (abstract here) */ +GraphSearch.prototype._search = function __search( start, search ){ + search = search || { + discovered : {}, + //parents : {}, + edges : [] + }; + return search; +}; + +/** Searches graph from start and returns a search tree of the results */ +GraphSearch.prototype.searchTree = function _searchTree( start ){ + return this._searchTree( this.search( start ) ); +}; + +/** Helper fn that returns a graph (a search tree) based on the search object passed in (does not actually search) */ GraphSearch.prototype._searchTree = function __searchTree( search ){ var self = this; return new Graph( true, { @@ -153,11 +192,10 @@ }); }; -GraphSearch.prototype.searchTree = function _searchTree( start ){ - return this._searchTree( this.search( start ) ); -}; // ============================================================================ +/** Breadth first search algo. + */ var BreadthFirstSearch = function( graph, processFns ){ var self = this; GraphSearch.call( this, graph, processFns ); @@ -166,6 +204,7 @@ BreadthFirstSearch.prototype = new GraphSearch(); BreadthFirstSearch.prototype.constructor = BreadthFirstSearch; +/** (Private) implementation of BFS */ BreadthFirstSearch.prototype._search = function __search( start, search ){ search = search || { discovered : {}, @@ -205,6 +244,8 @@ // ============================================================================ +/** Depth first search algorithm. + */ var DepthFirstSearch = function( graph, processFns ){ var self = this; GraphSearch.call( this, graph, processFns ); @@ -213,6 +254,7 @@ DepthFirstSearch.prototype = new GraphSearch(); DepthFirstSearch.prototype.constructor = DepthFirstSearch; +/** (Private) implementation of DFS */ DepthFirstSearch.prototype._search = function( start, search ){ //console.debug( 'depthFirstSearch:', start ); search = search || { @@ -258,6 +300,8 @@ // ============================================================================ +/** A directed/non-directed graph object. + */ function Graph( directed, data, options ){ //TODO: move directed to options this.directed = directed || false; @@ -265,7 +309,7 @@ } window.Graph = Graph; - +/** Set up options and instance variables */ Graph.prototype.init = function( options ){ options = options || {}; var self = this; @@ -277,6 +321,7 @@ return self; }; +/** Read data from the plain object data - both in d3 form (nodes and links) or vertices and edges */ Graph.prototype.read = function( data ){ if( !data ){ return this; } var self = this; @@ -286,6 +331,7 @@ }; //TODO: the next two could be combined +/** Create the graph using a list of nodes and a list of edges (where source and target are indeces into nodes) */ Graph.prototype.readNodesAndLinks = function( data ){ if( !( data && data.hasOwnProperty( 'nodes' ) ) ){ return this; } //console.debug( 'readNodesAndLinks:', data ); @@ -306,6 +352,7 @@ return self; }; +/** Create the graph using a list of nodes and a list of edges (where source and target are names of nodes) */ Graph.prototype.readVerticesAndEdges = function( data ){ if( !( data && data.hasOwnProperty( 'vertices' ) ) ){ return this; } //console.debug( 'readVerticesAndEdges:', data ); @@ -324,12 +371,16 @@ return self; }; +/** Return the vertex with name, creating it if necessary */ Graph.prototype.createVertex = function( name, data ){ //console.debug( 'createVertex:', name, data ); if( this.vertices[ name ] ){ return this.vertices[ name ]; } return ( this.vertices[ name ] = new Vertex( name, data ) ); }; +/** Create an edge in vertex named sourceName to targetName (optionally adding data to it) + * If directed is false, create a second edge from targetName to sourceName. + */ Graph.prototype.createEdge = function( sourceName, targetName, directed, data ){ //note: allows multiple 'equivalent' edges (to/from same source/target) //console.debug( 'createEdge:', source, target, directed ); @@ -359,16 +410,19 @@ return edge; }; +/** Walk over all the edges of the graph using the vertex.eachEdge iterator */ Graph.prototype.edges = function( propsOrFn ){ return Array.prototype.concat.apply( [], this.eachVertex( function( vertex ){ return vertex.eachEdge( propsOrFn ); })); }; +/** Iterate over all the vertices in the graph */ Graph.prototype.eachVertex = function( propsOrFn ){ return iterate( this.vertices, propsOrFn ); }; +/** Return a list of the vertices adjacent to vertex */ Graph.prototype.adjacent = function( vertex ){ var self = this; return iterate( vertex.edges, function( edge ){ @@ -376,6 +430,7 @@ }); }; +/** Call fn on each vertex adjacent to vertex */ Graph.prototype.eachAdjacent = function( vertex, fn ){ var self = this; return iterate( vertex.edges, function( edge ){ @@ -384,6 +439,7 @@ }); }; +/** Print the graph to the console (debugging) */ Graph.prototype.print = function(){ var self = this; console.log( 'Graph has ' + Object.keys( self.vertices ).length + ' vertices' ); @@ -396,6 +452,7 @@ return self; }; +/** Return a DOT format string of this graph */ Graph.prototype.toDOT = function(){ var self = this, strings = []; @@ -407,6 +464,7 @@ return strings.join( '\n' ); }; +/** Return vertices and edges of this graph in d3 node/link format */ Graph.prototype.toNodesAndLinks = function(){ var self = this, indeces = {}; @@ -424,6 +482,7 @@ }; }; +/** Return vertices and edges of this graph where edges use the name/id as source and target */ Graph.prototype.toVerticesAndEdges = function(){ var self = this; return { @@ -436,18 +495,22 @@ }; }; +/** Search this graph using BFS */ Graph.prototype.breadthFirstSearch = function( start, processFns ){ return new BreadthFirstSearch( this ).search( start ); }; +/** Return a searchtree of this graph using BFS */ Graph.prototype.breadthFirstSearchTree = function( start, processFns ){ return new BreadthFirstSearch( this ).searchTree( start ); }; +/** Search this graph using DFS */ Graph.prototype.depthFirstSearch = function( start, processFns ){ return new DepthFirstSearch( this ).search( start ); }; +/** Return a searchtree of this graph using DFS */ Graph.prototype.depthFirstSearchTree = function( start, processFns ){ return new DepthFirstSearch( this ).searchTree( start ); }; @@ -465,6 +528,7 @@ //Graph.prototype.isBipartite = function(){ //}; +/** Return an array of weakly connected (no edges between) sub-graphs in this graph */ Graph.prototype.weakComponents = function(){ //TODO: alternately, instead of returning graph-like objects: // - could simply decorate the vertices (vertex.component = componentIndex), or clone the graph and do that @@ -515,6 +579,7 @@ return components; }; +/** Return a single graph containing the weakly connected components in this graph */ Graph.prototype.weakComponentGraph = function(){ //note: although this can often look like the original graph - edges can be lost var components = this.weakComponents(); @@ -528,6 +593,7 @@ }); }; +/** Return an array of graphs of the weakly connected components in this graph */ Graph.prototype.weakComponentGraphArray = function(){ //note: although this can often look like the original graph - edges can be lost return this.weakComponents().map( function( component ){ @@ -537,10 +603,8 @@ // ============================================================================ - - - -// ============================================================================ +/** Create a random graph with numVerts vertices and numEdges edges (for testing) + */ function randGraph( directed, numVerts, numEdges ){ //console.debug( 'randGraph', directed, numVerts, numEdges ); var data = { nodes : [], links : [] }; diff -r eacfa5a1a3f2e9fc516bea159c16ddb36067a220 -r 8f8341ac47cd276cf0a2aa3d1c1098f7d3d58c75 static/scripts/mvc/history/history-structure-view.js --- a/static/scripts/mvc/history/history-structure-view.js +++ b/static/scripts/mvc/history/history-structure-view.js @@ -13,6 +13,9 @@ */ // ============================================================================ +/** + * + */ var HistoryStructureComponent = Backbone.View.extend( BASE_MVC.LoggableMixin ).extend({ //logger : console, @@ -151,7 +154,6 @@ //TODO:? liMap needed - can't we attach to vertex? var li = view._jobLiMap[ v.name ], position = view.layout.nodeMap[ li.model.id ]; - console.debug( li.$el.is( ':visible' ) ); //this.debug( position ); li.$el.css({ top: position.y, left: position.x }); }); @@ -249,6 +251,9 @@ // ============================================================================ +/** + * + */ var VerticalHistoryStructureComponent = HistoryStructureComponent.extend({ logger : console, @@ -288,7 +293,6 @@ node.y = y; var li = view._jobLiMap[ jobId ]; if( li.$el.is( ':visible' ) ){ - console.debug( 'li is visible, adj. by:', li.$el.height() ); y += li.$el.height() + layout.jobSpacing; } else { y += layout.initialSpacing + layout.jobSpacing; @@ -331,6 +335,9 @@ // ============================================================================ +/** + * + */ var HistoryStructureView = Backbone.View.extend( BASE_MVC.LoggableMixin ).extend({ //logger : console, @@ -351,7 +358,7 @@ excludeSetMetadata : true, excludeErroredJobs : true }); -window.dag = this.dag; +//window.dag = this.dag; this.log( this + '.dag:', this.dag ); this._createComponents(); @@ -360,7 +367,7 @@ _createComponents : function(){ this.log( this + '._createComponents' ); var structure = this; -window.structure = structure; +//window.structure = structure; structure.componentViews = structure.dag.weakComponentGraphArray().map( function( componentGraph ){ return structure._createComponent( componentGraph ); @@ -402,8 +409,5 @@ // ============================================================================ - //return { - // HistoryStructureView : HistoryStructureView - //}; return HistoryStructureView; }); diff -r eacfa5a1a3f2e9fc516bea159c16ddb36067a220 -r 8f8341ac47cd276cf0a2aa3d1c1098f7d3d58c75 static/scripts/mvc/history/job-dag.js --- a/static/scripts/mvc/history/job-dag.js +++ b/static/scripts/mvc/history/job-dag.js @@ -4,6 +4,10 @@ ],function( GRAPH, addLogging ){ // ============================================================================ var _super = GRAPH.Graph; +/** A Directed acyclic Graph built from a history's job data. + * Reads in job json, filters and process that json, and builds a graph + * using the connections between job inputs and outputs. + */ var JobDAG = function( options ){ var self = this; //this.logger = console; @@ -25,9 +29,12 @@ }; JobDAG.prototype = new GRAPH.Graph(); JobDAG.prototype.constructor = JobDAG; + +// add logging ability - turn off/on using the this.logger statement above addLogging( JobDAG ); // ---------------------------------------------------------------------------- +/** process jobs, options, filters, and any history data, then create the graph */ JobDAG.prototype.init = function _init( options ){ options = options || {}; _super.prototype.init.call( this, options ); @@ -52,6 +59,7 @@ return self; }; +/** add job filters based on options */ JobDAG.prototype._initFilters = function __initFilters(){ var self = this, filters = []; @@ -84,6 +92,7 @@ return filters; }; +/** sort the jobs and cache any that pass all filters into _idMap by job.id */ JobDAG.prototype.preprocessJobs = function _preprocessJobs( jobs ){ this.info( 'processing jobs' ); @@ -98,6 +107,7 @@ return self; }; +/** sort the jobs based on update time */ JobDAG.prototype.sort = function _sort( jobs ){ function cmpCreate( a, b ){ if( a.update_time > b.update_time ){ return 1; } @@ -107,6 +117,7 @@ return jobs.sort( cmpCreate ); }; +/** proces input/output, filter based on job data returning data if passing, null if not */ JobDAG.prototype.preprocessJob = function _preprocessJob( job, index ){ //this.info( 'preprocessJob', job, index ); var self = this, @@ -138,6 +149,9 @@ return jobData; }; +/** make verbose input/output lists more concise, sanity checking along the way + * processFn is called on each input/output and passed the dataset obj (id,src) and the input/output name. + */ JobDAG.prototype.datasetMapToIdArray = function _datasetMapToIdArray( datasetMap, processFn ){ var self = this; return _.map( datasetMap, function( dataset, nameInJob ){ @@ -152,6 +166,9 @@ }); }; +/** Walk all the jobs (vertices), attempting to find connections + * between datasets used as both inputs and outputs (edges) + */ JobDAG.prototype.createGraph = function _createGraph(){ var self = this; self.debug( 'connections:' ); @@ -183,6 +200,7 @@ return self; }; +/** Override to re-sort (ugh) jobs in each component by update time */ Graph.prototype.weakComponentGraphArray = function(){ return this.weakComponents().map( function( component ){ //TODO: this seems to belong above (in sort) - why isn't it preserved? @@ -198,7 +216,4 @@ // ============================================================================ return JobDAG; - //return { - // jobDAG : jobDAG - //}; }); diff -r eacfa5a1a3f2e9fc516bea159c16ddb36067a220 -r 8f8341ac47cd276cf0a2aa3d1c1098f7d3d58c75 static/scripts/mvc/job/job-li.js --- a/static/scripts/mvc/job/job-li.js +++ b/static/scripts/mvc/job/job-li.js @@ -6,7 +6,8 @@ ], function( LIST_ITEM, DATASET_LIST, BASE_MVC, _l ){ //============================================================================== var _super = LIST_ITEM.FoldoutListItemView; -/** @class +/** @class A job view used from within a larger list of jobs. + * Each job itself is a foldout panel of history contents displaying the outputs of this job. */ var JobListItemView = _super.extend(/** @lends JobListItemView.prototype */{ diff -r eacfa5a1a3f2e9fc516bea159c16ddb36067a220 -r 8f8341ac47cd276cf0a2aa3d1c1098f7d3d58c75 static/scripts/mvc/job/job-model.js --- a/static/scripts/mvc/job/job-model.js +++ b/static/scripts/mvc/job/job-model.js @@ -7,7 +7,7 @@ ], function( HISTORY_CONTENTS, STATES, AJAX_QUEUE, BASE_MVC, _l ){ //============================================================================== var searchableMixin = BASE_MVC.SearchableModelMixin; -/** @class +/** @class Represents a job running or ran on the server job handlers. */ var Job = Backbone.Model.extend( BASE_MVC.LoggableMixin ).extend( BASE_MVC.mixin( searchableMixin, /** @lends Job.prototype */{ @@ -126,6 +126,7 @@ }, // ........................................................................ ajax + /** fetches all details for each job in the collection using a queue */ queueDetailFetching : function(){ var collection = this, queue = new AJAX_QUEUE.AjaxQueue( this.map( function( job ){ @@ -179,7 +180,7 @@ //----------------------------------------------------------------------------- class vars }, { - + /** class level fn for fetching the job details for all jobs in a history */ fromHistory : function( historyId ){ console.debug( this ); var Collection = this, diff -r eacfa5a1a3f2e9fc516bea159c16ddb36067a220 -r 8f8341ac47cd276cf0a2aa3d1c1098f7d3d58c75 static/scripts/packed/mvc/history/history-structure-view.js --- a/static/scripts/packed/mvc/history/history-structure-view.js +++ b/static/scripts/packed/mvc/history/history-structure-view.js @@ -1,1 +1,1 @@ -define(["mvc/job/job-model","mvc/job/job-li","mvc/history/history-content-model","mvc/history/job-dag","mvc/base-mvc","utils/localization","libs/d3"],function(e,f,i,c,h,a){var b=Backbone.View.extend(h.LoggableMixin).extend({className:"history-structure-component",initialize:function(j){this.log(this+"(HistoryStructureComponent).initialize:",j);this.component=j.component;this._jobLiMap={};this._createJobModels();this.layout=this._createLayout(j.layoutOptions)},_createJobModels:function(){var j=this;j.component.eachVertex(function(o){var n=o.data.job,m=new e.Job(n);var l=_.map(m.get("outputs"),function(p){var r=p.src==="hda"?"dataset":"dataset_collection",q=i.typeIdStr(r,p.id);return j.model.contents.get(q)});m.outputCollection.reset(l);m.outputCollection.historyId=structure.model.id;var k=new f.JobListItemView({model:m,expanded:true});k.$el.appendTo(j.$el);j._jobLiMap[m.id]=k});return j.jobs},layoutDefaults:{paddingTop:8,paddingLeft:20,linkSpacing:16,jobHeight:308,jobWidthSpacing:320,linkAdjX:4,linkAdjY:0},_createLayout:function(l){l=_.defaults(_.clone(l||{}),this.layoutDefaults);var j=this,k=_.values(j.component.vertices),m=_.extend(l,{nodeMap:{},links:[],el:{width:0,height:0},svg:{width:0,height:0,top:0,left:0}});k.forEach(function(n,o){var p={name:n.name,x:0,y:0};m.nodeMap[n.name]=p});j.component.edges(function(o){var n={source:o.source,target:o.target};m.links.push(n)});return m},_updateLayout:function(){var k=this,l=k.layout;l.svg.height=l.paddingTop+(l.linkSpacing*_.size(l.nodeMap));l.el.height=l.svg.height+l.jobHeight;var j=l.paddingLeft,m=l.svg.height;_.each(l.nodeMap,function(o,n){o.x=j;o.y=m;j+=l.jobWidthSpacing});l.el.width=l.svg.width=Math.max(l.el.width,j);l.links.forEach(function(n){var o=l.nodeMap[n.source],p=l.nodeMap[n.target];n.x1=o.x+l.linkAdjX;n.y1=o.y+l.linkAdjY;n.x2=p.x+l.linkAdjX;n.y2=p.y+l.linkAdjY});return this.layout},render:function(k){this.log(this+".renderComponent:",k);var j=this;j.component.eachVertex(function(m){var l=j._jobLiMap[m.name];if(!l.$el.is(":visible")){l.render(0)}});j._updateLayout();j.$el.width(j.layout.el.width).height(j.layout.el.height);this.renderSVG();j.component.eachVertex(function(n){var m=j._jobLiMap[n.name],l=j.layout.nodeMap[m.model.id];console.debug(m.$el.is(":visible"));m.$el.css({top:l.y,left:l.x})});return this},renderSVG:function(){var j=this,o=j.layout;var k=d3.select(this.el).select("svg");if(k.empty()){k=d3.select(this.el).append("svg")}k.attr("width",o.svg.width).attr("height",o.svg.height);function m(p){d3.select(this).classed("highlighted",true);j._jobLiMap[p.source].$el.addClass("highlighted");j._jobLiMap[p.target].$el.addClass("highlighted")}function l(p){d3.select(this).classed("highlighted",false);j._jobLiMap[p.source].$el.removeClass("highlighted");j._jobLiMap[p.target].$el.removeClass("highlighted")}var n=k.selectAll(".connection").data(o.links);n.enter().append("path").attr("class","connection").attr("id",function(p){return p.source+"-"+p.target}).on("mouseover",m).on("mouseout",l);n.transition().attr("d",function(p){return j._connectionPath(p)});return k.node()},_connectionPath:function(k){var l=0,j=((k.x2-k.x1)/this.layout.svg.width)*this.layout.svg.height;return["M",k.x1,",",k.y1," ","C",k.x1+l,",",k.y1-j," ",k.x2-l,",",k.y2-j," ",k.x2,",",k.y2].join("")},events:{"mouseover .job.list-item":function(j){this.highlightConnected(j.currentTarget,true)},"mouseout .job.list-item":function(j){this.highlightConnected(j.currentTarget,false)}},highlightConnected:function(p,k){k=k!==undefined?k:true;var j=this,l=j.component,n=k?jQuery.prototype.addClass:jQuery.prototype.removeClass,m=k?"connection highlighted":"connection";var o=n.call($(p),"highlighted"),q=o.attr("id").replace("job-","");l.edges({target:q}).forEach(function(r){n.call(j.$("#job-"+r.source),"highlighted");j.$("#"+r.source+"-"+q).attr("class",m)});l.vertices[q].eachEdge(function(r){n.call(j.$("#job-"+r.target),"highlighted");j.$("#"+q+"-"+r.target).attr("class",m)})},toString:function(){return"HistoryStructureComponent("+this.model.id+")"}});var g=b.extend({logger:console,className:b.prototype.className+" vertical",layoutDefaults:{paddingTop:8,paddingLeft:20,linkSpacing:16,jobWidth:308,jobHeight:308,initialSpacing:64,jobSpacing:16,linkAdjX:0,linkAdjY:4},_updateLayout:function(){var k=this,l=k.layout;l.svg.width=l.paddingLeft+(l.linkSpacing*_.size(l.nodeMap));l.el.width=l.svg.width+l.jobWidth;l.el.height=0;var j=l.svg.width,m=l.paddingTop;_.each(l.nodeMap,function(p,o){p.x=j;p.y=m;var n=k._jobLiMap[o];if(n.$el.is(":visible")){console.debug("li is visible, adj. by:",n.$el.height());m+=n.$el.height()+l.jobSpacing}else{m+=l.initialSpacing+l.jobSpacing}});l.el.height=l.svg.height=Math.max(l.el.height,m);l.links.forEach(function(n){var o=l.nodeMap[n.source],p=l.nodeMap[n.target];n.x1=o.x+l.linkAdjX;n.y1=o.y+l.linkAdjY;n.x2=p.x+l.linkAdjX;n.y2=p.y+l.linkAdjY;k.debug("link:",n.x1,n.y1,n.x2,n.y2,n)});k.debug("el:",l.el);k.debug("svg:",l.svg);return l},_connectionPath:function(l){var k=0,j=((l.y2-l.y1)/this.layout.svg.height)*this.layout.svg.width;return["M",l.x1,",",l.y1," ","C",l.x1-j,",",l.y1+k," ",l.x2-j,",",l.y2-k," ",l.x2,",",l.y2].join("")},toString:function(){return"VerticalHistoryStructureComponent("+this.model.id+")"}});var d=Backbone.View.extend(h.LoggableMixin).extend({className:"history-structure",initialize:function(j){this.log(this+"(HistoryStructureView).initialize:",j,this.model);this.jobs=j.jobs;this._createDAG()},_createDAG:function(){this.dag=new c({historyContents:this.model.contents.toJSON(),jobs:this.jobs,excludeSetMetadata:true,excludeErroredJobs:true});window.dag=this.dag;this.log(this+".dag:",this.dag);this._createComponents()},_createComponents:function(){this.log(this+"._createComponents");var j=this;window.structure=j;j.componentViews=j.dag.weakComponentGraphArray().map(function(k){return j._createComponent(k)})},_createComponent:function(j){this.log(this+"._createComponent:",j);return new b({model:this.model,component:j})},render:function(k){this.log(this+".render:",k);var j=this;j.$el.html(['<div class="controls"></div>','<div class="components"></div>'].join(""));j.componentViews.forEach(function(l){l.render().$el.appendTo(j.$components())});return j},$components:function(){return this.$(".components")},toString:function(){return"HistoryStructureView()"}});return d}); \ No newline at end of file +define(["mvc/job/job-model","mvc/job/job-li","mvc/history/history-content-model","mvc/history/job-dag","mvc/base-mvc","utils/localization","libs/d3"],function(e,f,i,c,h,a){var b=Backbone.View.extend(h.LoggableMixin).extend({className:"history-structure-component",initialize:function(j){this.log(this+"(HistoryStructureComponent).initialize:",j);this.component=j.component;this._jobLiMap={};this._createJobModels();this.layout=this._createLayout(j.layoutOptions)},_createJobModels:function(){var j=this;j.component.eachVertex(function(o){var n=o.data.job,m=new e.Job(n);var l=_.map(m.get("outputs"),function(p){var r=p.src==="hda"?"dataset":"dataset_collection",q=i.typeIdStr(r,p.id);return j.model.contents.get(q)});m.outputCollection.reset(l);m.outputCollection.historyId=structure.model.id;var k=new f.JobListItemView({model:m,expanded:true});k.$el.appendTo(j.$el);j._jobLiMap[m.id]=k});return j.jobs},layoutDefaults:{paddingTop:8,paddingLeft:20,linkSpacing:16,jobHeight:308,jobWidthSpacing:320,linkAdjX:4,linkAdjY:0},_createLayout:function(l){l=_.defaults(_.clone(l||{}),this.layoutDefaults);var j=this,k=_.values(j.component.vertices),m=_.extend(l,{nodeMap:{},links:[],el:{width:0,height:0},svg:{width:0,height:0,top:0,left:0}});k.forEach(function(n,o){var p={name:n.name,x:0,y:0};m.nodeMap[n.name]=p});j.component.edges(function(o){var n={source:o.source,target:o.target};m.links.push(n)});return m},_updateLayout:function(){var k=this,l=k.layout;l.svg.height=l.paddingTop+(l.linkSpacing*_.size(l.nodeMap));l.el.height=l.svg.height+l.jobHeight;var j=l.paddingLeft,m=l.svg.height;_.each(l.nodeMap,function(o,n){o.x=j;o.y=m;j+=l.jobWidthSpacing});l.el.width=l.svg.width=Math.max(l.el.width,j);l.links.forEach(function(n){var o=l.nodeMap[n.source],p=l.nodeMap[n.target];n.x1=o.x+l.linkAdjX;n.y1=o.y+l.linkAdjY;n.x2=p.x+l.linkAdjX;n.y2=p.y+l.linkAdjY});return this.layout},render:function(k){this.log(this+".renderComponent:",k);var j=this;j.component.eachVertex(function(m){var l=j._jobLiMap[m.name];if(!l.$el.is(":visible")){l.render(0)}});j._updateLayout();j.$el.width(j.layout.el.width).height(j.layout.el.height);this.renderSVG();j.component.eachVertex(function(n){var m=j._jobLiMap[n.name],l=j.layout.nodeMap[m.model.id];m.$el.css({top:l.y,left:l.x})});return this},renderSVG:function(){var j=this,o=j.layout;var k=d3.select(this.el).select("svg");if(k.empty()){k=d3.select(this.el).append("svg")}k.attr("width",o.svg.width).attr("height",o.svg.height);function m(p){d3.select(this).classed("highlighted",true);j._jobLiMap[p.source].$el.addClass("highlighted");j._jobLiMap[p.target].$el.addClass("highlighted")}function l(p){d3.select(this).classed("highlighted",false);j._jobLiMap[p.source].$el.removeClass("highlighted");j._jobLiMap[p.target].$el.removeClass("highlighted")}var n=k.selectAll(".connection").data(o.links);n.enter().append("path").attr("class","connection").attr("id",function(p){return p.source+"-"+p.target}).on("mouseover",m).on("mouseout",l);n.transition().attr("d",function(p){return j._connectionPath(p)});return k.node()},_connectionPath:function(k){var l=0,j=((k.x2-k.x1)/this.layout.svg.width)*this.layout.svg.height;return["M",k.x1,",",k.y1," ","C",k.x1+l,",",k.y1-j," ",k.x2-l,",",k.y2-j," ",k.x2,",",k.y2].join("")},events:{"mouseover .job.list-item":function(j){this.highlightConnected(j.currentTarget,true)},"mouseout .job.list-item":function(j){this.highlightConnected(j.currentTarget,false)}},highlightConnected:function(p,k){k=k!==undefined?k:true;var j=this,l=j.component,n=k?jQuery.prototype.addClass:jQuery.prototype.removeClass,m=k?"connection highlighted":"connection";var o=n.call($(p),"highlighted"),q=o.attr("id").replace("job-","");l.edges({target:q}).forEach(function(r){n.call(j.$("#job-"+r.source),"highlighted");j.$("#"+r.source+"-"+q).attr("class",m)});l.vertices[q].eachEdge(function(r){n.call(j.$("#job-"+r.target),"highlighted");j.$("#"+q+"-"+r.target).attr("class",m)})},toString:function(){return"HistoryStructureComponent("+this.model.id+")"}});var g=b.extend({logger:console,className:b.prototype.className+" vertical",layoutDefaults:{paddingTop:8,paddingLeft:20,linkSpacing:16,jobWidth:308,jobHeight:308,initialSpacing:64,jobSpacing:16,linkAdjX:0,linkAdjY:4},_updateLayout:function(){var k=this,l=k.layout;l.svg.width=l.paddingLeft+(l.linkSpacing*_.size(l.nodeMap));l.el.width=l.svg.width+l.jobWidth;l.el.height=0;var j=l.svg.width,m=l.paddingTop;_.each(l.nodeMap,function(p,o){p.x=j;p.y=m;var n=k._jobLiMap[o];if(n.$el.is(":visible")){m+=n.$el.height()+l.jobSpacing}else{m+=l.initialSpacing+l.jobSpacing}});l.el.height=l.svg.height=Math.max(l.el.height,m);l.links.forEach(function(n){var o=l.nodeMap[n.source],p=l.nodeMap[n.target];n.x1=o.x+l.linkAdjX;n.y1=o.y+l.linkAdjY;n.x2=p.x+l.linkAdjX;n.y2=p.y+l.linkAdjY;k.debug("link:",n.x1,n.y1,n.x2,n.y2,n)});k.debug("el:",l.el);k.debug("svg:",l.svg);return l},_connectionPath:function(l){var k=0,j=((l.y2-l.y1)/this.layout.svg.height)*this.layout.svg.width;return["M",l.x1,",",l.y1," ","C",l.x1-j,",",l.y1+k," ",l.x2-j,",",l.y2-k," ",l.x2,",",l.y2].join("")},toString:function(){return"VerticalHistoryStructureComponent("+this.model.id+")"}});var d=Backbone.View.extend(h.LoggableMixin).extend({className:"history-structure",initialize:function(j){this.log(this+"(HistoryStructureView).initialize:",j,this.model);this.jobs=j.jobs;this._createDAG()},_createDAG:function(){this.dag=new c({historyContents:this.model.contents.toJSON(),jobs:this.jobs,excludeSetMetadata:true,excludeErroredJobs:true});this.log(this+".dag:",this.dag);this._createComponents()},_createComponents:function(){this.log(this+"._createComponents");var j=this;j.componentViews=j.dag.weakComponentGraphArray().map(function(k){return j._createComponent(k)})},_createComponent:function(j){this.log(this+"._createComponent:",j);return new b({model:this.model,component:j})},render:function(k){this.log(this+".render:",k);var j=this;j.$el.html(['<div class="controls"></div>','<div class="components"></div>'].join(""));j.componentViews.forEach(function(l){l.render().$el.appendTo(j.$components())});return j},$components:function(){return this.$(".components")},toString:function(){return"HistoryStructureView()"}});return d}); \ No newline at end of file diff -r eacfa5a1a3f2e9fc516bea159c16ddb36067a220 -r 8f8341ac47cd276cf0a2aa3d1c1098f7d3d58c75 static/scripts/packed/utils/graph.js --- a/static/scripts/packed/utils/graph.js +++ b/static/scripts/packed/utils/graph.js @@ -1,1 +1,1 @@ -define([],function(){function l(r,q){for(var p in r){if(r.hasOwnProperty(p)){q(r[p],p,r)}}}function i(r,q){for(var p in q){if(q.hasOwnProperty(p)){r[p]=q[p]}}return r}function f(r,q){for(var p in q){if(q.hasOwnProperty(p)){if(!r.hasOwnProperty(p)||r[p]!==q[p]){return false}}}return true}function c(w,p){var t=typeof p==="function"?p:undefined,s=typeof p==="object"?p:undefined,u=[],q=0;for(var r in w){if(w.hasOwnProperty(r)){var v=w[r];if(t){u.push(t.call(v,v,r,q))}else{if(s){if(typeof v==="object"&&f(v,s)){u.push(v)}}else{u.push(v)}}q+=1}}return u}function g(r,s,q){var p=this;p.source=r!==undefined?r:null;p.target=s!==undefined?s:null;p.data=q||null;return p}g.prototype.toString=function(){return this.source+"->"+this.target};g.prototype.toJSON=function(){var p={source:this.source,target:this.target};if(this.data){p.data=this.data}return p};function m(q,r){var p=this;p.name=q!==undefined?q:"(unnamed)";p.data=r||null;p.edges={};p.degree=0;return p}window.Vertex=m;m.prototype.toString=function(){return"Vertex("+this.name+")"};m.prototype.eachEdge=function(p){return c(this.edges,p)};m.prototype.toJSON=function(){return{name:this.name,data:this.data}};var k=function(r,q){var p=this;p.graph=r;p.processFns=q||{vertexEarly:function(t,s){},edge:function(u,t,s){},vertexLate:function(t,s){}};p._cache={};return p};k.prototype.search=function e(q){var p=this;if(q in p._cache){return p._cache[q]}if(!(q instanceof m)){q=p.graph.vertices[q]}return(p._cache[q.name]=p._search(q))};k.prototype._searchTree=function a(q){var p=this;return new j(true,{edges:q.edges,vertices:Object.keys(q.discovered).map(function(r){return p.graph.vertices[r].toJSON()})})};k.prototype.searchTree=function b(p){return this._searchTree(this.search(p))};var n=function(r,q){var p=this;k.call(this,r,q);return p};n.prototype=new k();n.prototype.constructor=n;n.prototype._search=function d(u,s){s=s||{discovered:{},edges:[]};var r=this,p=[];function q(v,w){var x=this;if(r.processFns.edge){r.processFns.edge.call(r,x,w,s)}if(!s.discovered[v.name]){s.discovered[v.name]=true;s.edges.push({source:x.name,target:v.name});p.push(v)}}s.discovered[u.name]=true;p.push(u);while(p.length){var t=p.shift();if(r.processFns.vertexEarly){r.processFns.vertexEarly.call(r,t,s)}r.graph.eachAdjacent(t,q);if(r.processFns.vertexLate){r.processFns.vertexLate.call(r,t,s)}}return s};var h=function(r,q){var p=this;k.call(this,r,q);return p};h.prototype=new k();h.prototype.constructor=h;h.prototype._search=function(u,q){q=q||{discovered:{},edges:[],entryTimes:{},exitTimes:{}};var p=this,t=0;function s(w,x){var v=this;if(p.processFns.edge){p.processFns.edge.call(p,v,x,q)}if(!q.discovered[w.name]){q.edges.push({source:v.name,target:w.name});r(w)}}function r(v){q.discovered[v.name]=true;if(p.processFns.vertexEarly){p.processFns.vertexEarly.call(p,v,q)}q.entryTimes[v.name]=t++;p.graph.eachAdjacent(v,s);if(p.processFns.vertexLate){p.processFns.vertexLate.call(p,v,q)}q.exitTimes[v.name]=t++}r(u);return q};function j(q,r,p){this.directed=q||false;return this.init(p).read(r)}window.Graph=j;j.prototype.init=function(q){q=q||{};var p=this;p.allowReflexiveEdges=q.allowReflexiveEdges||false;p.vertices={};p.numEdges=0;return p};j.prototype.read=function(q){if(!q){return this}var p=this;if(q.hasOwnProperty("nodes")){return p.readNodesAndLinks(q)}if(q.hasOwnProperty("vertices")){return p.readVerticesAndEdges(q)}return p};j.prototype.readNodesAndLinks=function(q){if(!(q&&q.hasOwnProperty("nodes"))){return this}var p=this;q.nodes.forEach(function(r){p.createVertex(r.name,r.data)});(q.links||[]).forEach(function(u,s){var r=q.nodes[u.source].name,t=q.nodes[u.target].name;p.createEdge(r,t,p.directed)});return p};j.prototype.readVerticesAndEdges=function(q){if(!(q&&q.hasOwnProperty("vertices"))){return this}var p=this;q.vertices.forEach(function(r){p.createVertex(r.name,r.data)});(q.edges||[]).forEach(function(s,r){p.createEdge(s.source,s.target,p.directed)});return p};j.prototype.createVertex=function(p,q){if(this.vertices[p]){return this.vertices[p]}return(this.vertices[p]=new m(p,q))};j.prototype.createEdge=function(q,r,t,u){var v=q===r;if(!this.allowReflexiveEdges&&v){return null}sourceVertex=this.vertices[q];targetVertex=this.vertices[r];if(!(sourceVertex&&targetVertex)){return null}var p=this,s=new g(q,r,u);sourceVertex.edges[r]=s;sourceVertex.degree+=1;p.numEdges+=1;if(!v&&!t){p.createEdge(r,q,true)}return s};j.prototype.edges=function(p){return Array.prototype.concat.apply([],this.eachVertex(function(q){return q.eachEdge(p)}))};j.prototype.eachVertex=function(p){return c(this.vertices,p)};j.prototype.adjacent=function(q){var p=this;return c(q.edges,function(r){return p.vertices[r.target]})};j.prototype.eachAdjacent=function(r,q){var p=this;return c(r.edges,function(t){var s=p.vertices[t.target];return q.call(r,s,t)})};j.prototype.print=function(){var p=this;console.log("Graph has "+Object.keys(p.vertices).length+" vertices");p.eachVertex(function(q){console.log(q.toString());q.eachEdge(function(r){console.log("\t "+r)})});return p};j.prototype.toDOT=function(){var q=this,p=[];p.push("graph bler {");q.edges(function(r){p.push("\t"+r.from+" -- "+r.to+";")});p.push("}");return p.join("\n")};j.prototype.toNodesAndLinks=function(){var p=this,q={};return{nodes:p.eachVertex(function(t,s,r){q[t.name]=r;return t.toJSON()}),links:p.edges(function(s){var r=s.toJSON();r.source=q[s.source];r.target=q[s.target];return r})}};j.prototype.toVerticesAndEdges=function(){var p=this;return{vertices:p.eachVertex(function(r,q){return r.toJSON()}),edges:p.edges(function(q){return q.toJSON()})}};j.prototype.breadthFirstSearch=function(q,p){return new n(this).search(q)};j.prototype.breadthFirstSearchTree=function(q,p){return new n(this).searchTree(q)};j.prototype.depthFirstSearch=function(q,p){return new h(this).search(q)};j.prototype.depthFirstSearchTree=function(q,p){return new h(this).searchTree(q)};j.prototype.weakComponents=function(){var r=this,u=this,q,t=[];function p(v){var w=new h(u)._search(v);q=q.filter(function(x){return !(x in w.discovered)});return{vertices:Object.keys(w.discovered).map(function(x){return r.vertices[x].toJSON()}),edges:w.edges.map(function(y){var x=r.vertices[y.target].edges[y.source]!==undefined;if(r.directed&&x){var z=y.source;y.source=y.target;y.target=z}return y})}}if(r.directed){u=new j(false,r.toNodesAndLinks())}q=Object.keys(u.vertices);while(q.length){var s=u.vertices[q.shift()];t.push(p(s))}return t};j.prototype.weakComponentGraph=function(){var p=this.weakComponents();return new j(this.directed,{vertices:p.reduce(function(r,q){return r.concat(q.vertices)},[]),edges:p.reduce(function(r,q){return r.concat(q.edges)},[])})};j.prototype.weakComponentGraphArray=function(){return this.weakComponents().map(function(p){return new j(this.directed,p)})};function o(s,p,r){var u={nodes:[],links:[]};function t(v){return Math.floor(Math.random()*v)}for(var q=0;q<p;q++){u.nodes.push({name:q})}for(q=0;q<r;q++){u.links.push({source:t(p),target:t(p)})}return new j(s,u)}return{Vertex:m,Edge:g,BreadthFirstSearch:n,DepthFirstSearch:h,Graph:j,randGraph:o}}); \ No newline at end of file +define([],function(){function l(r,q){for(var p in r){if(r.hasOwnProperty(p)){q(r[p],p,r)}}}function i(r,q){for(var p in q){if(q.hasOwnProperty(p)){r[p]=q[p]}}return r}function f(r,q){for(var p in q){if(q.hasOwnProperty(p)){if(!r.hasOwnProperty(p)||r[p]!==q[p]){return false}}}return true}function c(w,p){var t=typeof p==="function"?p:undefined,s=typeof p==="object"?p:undefined,u=[],q=0;for(var r in w){if(w.hasOwnProperty(r)){var v=w[r];if(t){u.push(t.call(v,v,r,q))}else{if(s){if(typeof v==="object"&&f(v,s)){u.push(v)}}else{u.push(v)}}q+=1}}return u}function g(r,s,q){var p=this;p.source=r!==undefined?r:null;p.target=s!==undefined?s:null;p.data=q||null;return p}g.prototype.toString=function(){return this.source+"->"+this.target};g.prototype.toJSON=function(){var p={source:this.source,target:this.target};if(this.data){p.data=this.data}return p};function m(q,r){var p=this;p.name=q!==undefined?q:"(unnamed)";p.data=r||null;p.edges={};p.degree=0;return p}m.prototype.toString=function(){return"Vertex("+this.name+")"};m.prototype.eachEdge=function(p){return c(this.edges,p)};m.prototype.toJSON=function(){return{name:this.name,data:this.data}};var k=function(r,q){var p=this;p.graph=r;p.processFns=q||{vertexEarly:function(t,s){},edge:function(u,t,s){},vertexLate:function(t,s){}};p._cache={};return p};k.prototype.search=function e(q){var p=this;if(q in p._cache){return p._cache[q]}if(!(q instanceof m)){q=p.graph.vertices[q]}return(p._cache[q.name]=p._search(q))};k.prototype._search=function d(q,p){p=p||{discovered:{},edges:[]};return p};k.prototype.searchTree=function b(p){return this._searchTree(this.search(p))};k.prototype._searchTree=function a(q){var p=this;return new j(true,{edges:q.edges,vertices:Object.keys(q.discovered).map(function(r){return p.graph.vertices[r].toJSON()})})};var n=function(r,q){var p=this;k.call(this,r,q);return p};n.prototype=new k();n.prototype.constructor=n;n.prototype._search=function d(u,s){s=s||{discovered:{},edges:[]};var r=this,p=[];function q(v,w){var x=this;if(r.processFns.edge){r.processFns.edge.call(r,x,w,s)}if(!s.discovered[v.name]){s.discovered[v.name]=true;s.edges.push({source:x.name,target:v.name});p.push(v)}}s.discovered[u.name]=true;p.push(u);while(p.length){var t=p.shift();if(r.processFns.vertexEarly){r.processFns.vertexEarly.call(r,t,s)}r.graph.eachAdjacent(t,q);if(r.processFns.vertexLate){r.processFns.vertexLate.call(r,t,s)}}return s};var h=function(r,q){var p=this;k.call(this,r,q);return p};h.prototype=new k();h.prototype.constructor=h;h.prototype._search=function(u,q){q=q||{discovered:{},edges:[],entryTimes:{},exitTimes:{}};var p=this,t=0;function s(w,x){var v=this;if(p.processFns.edge){p.processFns.edge.call(p,v,x,q)}if(!q.discovered[w.name]){q.edges.push({source:v.name,target:w.name});r(w)}}function r(v){q.discovered[v.name]=true;if(p.processFns.vertexEarly){p.processFns.vertexEarly.call(p,v,q)}q.entryTimes[v.name]=t++;p.graph.eachAdjacent(v,s);if(p.processFns.vertexLate){p.processFns.vertexLate.call(p,v,q)}q.exitTimes[v.name]=t++}r(u);return q};function j(q,r,p){this.directed=q||false;return this.init(p).read(r)}window.Graph=j;j.prototype.init=function(q){q=q||{};var p=this;p.allowReflexiveEdges=q.allowReflexiveEdges||false;p.vertices={};p.numEdges=0;return p};j.prototype.read=function(q){if(!q){return this}var p=this;if(q.hasOwnProperty("nodes")){return p.readNodesAndLinks(q)}if(q.hasOwnProperty("vertices")){return p.readVerticesAndEdges(q)}return p};j.prototype.readNodesAndLinks=function(q){if(!(q&&q.hasOwnProperty("nodes"))){return this}var p=this;q.nodes.forEach(function(r){p.createVertex(r.name,r.data)});(q.links||[]).forEach(function(u,s){var r=q.nodes[u.source].name,t=q.nodes[u.target].name;p.createEdge(r,t,p.directed)});return p};j.prototype.readVerticesAndEdges=function(q){if(!(q&&q.hasOwnProperty("vertices"))){return this}var p=this;q.vertices.forEach(function(r){p.createVertex(r.name,r.data)});(q.edges||[]).forEach(function(s,r){p.createEdge(s.source,s.target,p.directed)});return p};j.prototype.createVertex=function(p,q){if(this.vertices[p]){return this.vertices[p]}return(this.vertices[p]=new m(p,q))};j.prototype.createEdge=function(q,r,t,u){var v=q===r;if(!this.allowReflexiveEdges&&v){return null}sourceVertex=this.vertices[q];targetVertex=this.vertices[r];if(!(sourceVertex&&targetVertex)){return null}var p=this,s=new g(q,r,u);sourceVertex.edges[r]=s;sourceVertex.degree+=1;p.numEdges+=1;if(!v&&!t){p.createEdge(r,q,true)}return s};j.prototype.edges=function(p){return Array.prototype.concat.apply([],this.eachVertex(function(q){return q.eachEdge(p)}))};j.prototype.eachVertex=function(p){return c(this.vertices,p)};j.prototype.adjacent=function(q){var p=this;return c(q.edges,function(r){return p.vertices[r.target]})};j.prototype.eachAdjacent=function(r,q){var p=this;return c(r.edges,function(t){var s=p.vertices[t.target];return q.call(r,s,t)})};j.prototype.print=function(){var p=this;console.log("Graph has "+Object.keys(p.vertices).length+" vertices");p.eachVertex(function(q){console.log(q.toString());q.eachEdge(function(r){console.log("\t "+r)})});return p};j.prototype.toDOT=function(){var q=this,p=[];p.push("graph bler {");q.edges(function(r){p.push("\t"+r.from+" -- "+r.to+";")});p.push("}");return p.join("\n")};j.prototype.toNodesAndLinks=function(){var p=this,q={};return{nodes:p.eachVertex(function(t,s,r){q[t.name]=r;return t.toJSON()}),links:p.edges(function(s){var r=s.toJSON();r.source=q[s.source];r.target=q[s.target];return r})}};j.prototype.toVerticesAndEdges=function(){var p=this;return{vertices:p.eachVertex(function(r,q){return r.toJSON()}),edges:p.edges(function(q){return q.toJSON()})}};j.prototype.breadthFirstSearch=function(q,p){return new n(this).search(q)};j.prototype.breadthFirstSearchTree=function(q,p){return new n(this).searchTree(q)};j.prototype.depthFirstSearch=function(q,p){return new h(this).search(q)};j.prototype.depthFirstSearchTree=function(q,p){return new h(this).searchTree(q)};j.prototype.weakComponents=function(){var r=this,u=this,q,t=[];function p(v){var w=new h(u)._search(v);q=q.filter(function(x){return !(x in w.discovered)});return{vertices:Object.keys(w.discovered).map(function(x){return r.vertices[x].toJSON()}),edges:w.edges.map(function(y){var x=r.vertices[y.target].edges[y.source]!==undefined;if(r.directed&&x){var z=y.source;y.source=y.target;y.target=z}return y})}}if(r.directed){u=new j(false,r.toNodesAndLinks())}q=Object.keys(u.vertices);while(q.length){var s=u.vertices[q.shift()];t.push(p(s))}return t};j.prototype.weakComponentGraph=function(){var p=this.weakComponents();return new j(this.directed,{vertices:p.reduce(function(r,q){return r.concat(q.vertices)},[]),edges:p.reduce(function(r,q){return r.concat(q.edges)},[])})};j.prototype.weakComponentGraphArray=function(){return this.weakComponents().map(function(p){return new j(this.directed,p)})};function o(s,p,r){var u={nodes:[],links:[]};function t(v){return Math.floor(Math.random()*v)}for(var q=0;q<p;q++){u.nodes.push({name:q})}for(q=0;q<r;q++){u.links.push({source:t(p),target:t(p)})}return new j(s,u)}return{Vertex:m,Edge:g,BreadthFirstSearch:n,DepthFirstSearch:h,Graph:j,randGraph:o}}); \ No newline at end of file diff -r eacfa5a1a3f2e9fc516bea159c16ddb36067a220 -r 8f8341ac47cd276cf0a2aa3d1c1098f7d3d58c75 static/scripts/utils/graph.js --- a/static/scripts/utils/graph.js +++ b/static/scripts/utils/graph.js @@ -2,10 +2,10 @@ ],function(){ /* ============================================================================ TODO: - edges can't retain data ============================================================================ */ //TODO: go ahead and move to underscore... +/** call fn on each key/value in d */ function each( d, fn ){ for( var k in d ){ if( d.hasOwnProperty( k ) ){ @@ -14,6 +14,7 @@ } } +/** copy key/values from d2 to d overwriting if present */ function extend( d, d2 ){ for( var k in d2 ){ if( d2.hasOwnProperty( k ) ){ @@ -23,6 +24,7 @@ return d; } +/** deep equal of two dictionaries */ function matches( d, d2 ){ for( var k in d2 ){ if( d2.hasOwnProperty( k ) ){ @@ -34,6 +36,10 @@ return true; } +/** map key/values in obj + * if propsOrFn is an object, return only those k/v that match the object + * if propsOrFn is function, call the fn and returned the mapped values from it + */ function iterate( obj, propsOrFn ){ var fn = typeof propsOrFn === 'function'? propsOrFn : undefined, props = typeof propsOrFn === 'object'? propsOrFn : undefined, @@ -60,6 +66,8 @@ // ============================================================================ +/** A graph edge containing the name/id of both source and target and optional data + */ function Edge( source, target, data ){ var self = this; self.source = source !== undefined? source : null; @@ -70,10 +78,12 @@ //} return self; } +/** String representation */ Edge.prototype.toString = function(){ return this.source + '->' + this.target; }; +/** Return a plain object representing this edge */ Edge.prototype.toJSON = function(){ //TODO: this is safe in most browsers (fns will be stripped) - alter tests to incorporate this in order to pass data //return this; @@ -88,6 +98,9 @@ }; // ============================================================================ +/** A graph vertex with a (unique) name/id and optional data. + * A vertex contains a list of Edges (whose sources are this vertex) and maintains the degree. + */ function Vertex( name, data ){ var self = this; self.name = name !== undefined? name : '(unnamed)'; @@ -96,16 +109,19 @@ self.degree = 0; return self; } -window.Vertex = Vertex; + +/** String representation */ Vertex.prototype.toString = function(){ return 'Vertex(' + this.name + ')'; }; //TODO: better name w no collision for either this.eachEdge or this.edges +/** Iterate over each edge from this vertex */ Vertex.prototype.eachEdge = function( propsOrFn ){ return iterate( this.edges, propsOrFn ); }; +/** Return a plain object representing this vertex */ Vertex.prototype.toJSON = function(){ //return this; return { @@ -116,6 +132,10 @@ // ============================================================================ +/** Base (abstract) class for Graph search algorithms. + * Pass in the graph to search + * and an optional dictionary containing the 3 vertex/edge processing fns listed below. + */ var GraphSearch = function( graph, processFns ){ var self = this; self.graph = graph; @@ -136,6 +156,9 @@ return self; }; +/** Search interface where start is the vertex (or the name/id of the vertex) to begin the search at + * This public interface caches searches and returns the cached version if it's already been done. + */ GraphSearch.prototype.search = function _search( start ){ var self = this; if( start in self._cache ){ return self._cache[ start ]; } @@ -143,6 +166,22 @@ return ( self._cache[ start.name ] = self._search( start ) ); }; +/** Actual search (private) function (abstract here) */ +GraphSearch.prototype._search = function __search( start, search ){ + search = search || { + discovered : {}, + //parents : {}, + edges : [] + }; + return search; +}; + +/** Searches graph from start and returns a search tree of the results */ +GraphSearch.prototype.searchTree = function _searchTree( start ){ + return this._searchTree( this.search( start ) ); +}; + +/** Helper fn that returns a graph (a search tree) based on the search object passed in (does not actually search) */ GraphSearch.prototype._searchTree = function __searchTree( search ){ var self = this; return new Graph( true, { @@ -153,11 +192,10 @@ }); }; -GraphSearch.prototype.searchTree = function _searchTree( start ){ - return this._searchTree( this.search( start ) ); -}; // ============================================================================ +/** Breadth first search algo. + */ var BreadthFirstSearch = function( graph, processFns ){ var self = this; GraphSearch.call( this, graph, processFns ); @@ -166,6 +204,7 @@ BreadthFirstSearch.prototype = new GraphSearch(); BreadthFirstSearch.prototype.constructor = BreadthFirstSearch; +/** (Private) implementation of BFS */ BreadthFirstSearch.prototype._search = function __search( start, search ){ search = search || { discovered : {}, @@ -205,6 +244,8 @@ // ============================================================================ +/** Depth first search algorithm. + */ var DepthFirstSearch = function( graph, processFns ){ var self = this; GraphSearch.call( this, graph, processFns ); @@ -213,6 +254,7 @@ DepthFirstSearch.prototype = new GraphSearch(); DepthFirstSearch.prototype.constructor = DepthFirstSearch; +/** (Private) implementation of DFS */ DepthFirstSearch.prototype._search = function( start, search ){ //console.debug( 'depthFirstSearch:', start ); search = search || { @@ -258,6 +300,8 @@ // ============================================================================ +/** A directed/non-directed graph object. + */ function Graph( directed, data, options ){ //TODO: move directed to options this.directed = directed || false; @@ -265,7 +309,7 @@ } window.Graph = Graph; - +/** Set up options and instance variables */ Graph.prototype.init = function( options ){ options = options || {}; var self = this; @@ -277,6 +321,7 @@ return self; }; +/** Read data from the plain object data - both in d3 form (nodes and links) or vertices and edges */ Graph.prototype.read = function( data ){ if( !data ){ return this; } var self = this; @@ -286,6 +331,7 @@ }; //TODO: the next two could be combined +/** Create the graph using a list of nodes and a list of edges (where source and target are indeces into nodes) */ Graph.prototype.readNodesAndLinks = function( data ){ if( !( data && data.hasOwnProperty( 'nodes' ) ) ){ return this; } //console.debug( 'readNodesAndLinks:', data ); @@ -306,6 +352,7 @@ return self; }; +/** Create the graph using a list of nodes and a list of edges (where source and target are names of nodes) */ Graph.prototype.readVerticesAndEdges = function( data ){ if( !( data && data.hasOwnProperty( 'vertices' ) ) ){ return this; } //console.debug( 'readVerticesAndEdges:', data ); @@ -324,12 +371,16 @@ return self; }; +/** Return the vertex with name, creating it if necessary */ Graph.prototype.createVertex = function( name, data ){ //console.debug( 'createVertex:', name, data ); if( this.vertices[ name ] ){ return this.vertices[ name ]; } return ( this.vertices[ name ] = new Vertex( name, data ) ); }; +/** Create an edge in vertex named sourceName to targetName (optionally adding data to it) + * If directed is false, create a second edge from targetName to sourceName. + */ Graph.prototype.createEdge = function( sourceName, targetName, directed, data ){ //note: allows multiple 'equivalent' edges (to/from same source/target) //console.debug( 'createEdge:', source, target, directed ); @@ -359,16 +410,19 @@ return edge; }; +/** Walk over all the edges of the graph using the vertex.eachEdge iterator */ Graph.prototype.edges = function( propsOrFn ){ return Array.prototype.concat.apply( [], this.eachVertex( function( vertex ){ return vertex.eachEdge( propsOrFn ); })); }; +/** Iterate over all the vertices in the graph */ Graph.prototype.eachVertex = function( propsOrFn ){ return iterate( this.vertices, propsOrFn ); }; +/** Return a list of the vertices adjacent to vertex */ Graph.prototype.adjacent = function( vertex ){ var self = this; return iterate( vertex.edges, function( edge ){ @@ -376,6 +430,7 @@ }); }; +/** Call fn on each vertex adjacent to vertex */ Graph.prototype.eachAdjacent = function( vertex, fn ){ var self = this; return iterate( vertex.edges, function( edge ){ @@ -384,6 +439,7 @@ }); }; +/** Print the graph to the console (debugging) */ Graph.prototype.print = function(){ var self = this; console.log( 'Graph has ' + Object.keys( self.vertices ).length + ' vertices' ); @@ -396,6 +452,7 @@ return self; }; +/** Return a DOT format string of this graph */ Graph.prototype.toDOT = function(){ var self = this, strings = []; @@ -407,6 +464,7 @@ return strings.join( '\n' ); }; +/** Return vertices and edges of this graph in d3 node/link format */ Graph.prototype.toNodesAndLinks = function(){ var self = this, indeces = {}; @@ -424,6 +482,7 @@ }; }; +/** Return vertices and edges of this graph where edges use the name/id as source and target */ Graph.prototype.toVerticesAndEdges = function(){ var self = this; return { @@ -436,18 +495,22 @@ }; }; +/** Search this graph using BFS */ Graph.prototype.breadthFirstSearch = function( start, processFns ){ return new BreadthFirstSearch( this ).search( start ); }; +/** Return a searchtree of this graph using BFS */ Graph.prototype.breadthFirstSearchTree = function( start, processFns ){ return new BreadthFirstSearch( this ).searchTree( start ); }; +/** Search this graph using DFS */ Graph.prototype.depthFirstSearch = function( start, processFns ){ return new DepthFirstSearch( this ).search( start ); }; +/** Return a searchtree of this graph using DFS */ Graph.prototype.depthFirstSearchTree = function( start, processFns ){ return new DepthFirstSearch( this ).searchTree( start ); }; @@ -465,6 +528,7 @@ //Graph.prototype.isBipartite = function(){ //}; +/** Return an array of weakly connected (no edges between) sub-graphs in this graph */ Graph.prototype.weakComponents = function(){ //TODO: alternately, instead of returning graph-like objects: // - could simply decorate the vertices (vertex.component = componentIndex), or clone the graph and do that @@ -515,6 +579,7 @@ return components; }; +/** Return a single graph containing the weakly connected components in this graph */ Graph.prototype.weakComponentGraph = function(){ //note: although this can often look like the original graph - edges can be lost var components = this.weakComponents(); @@ -528,6 +593,7 @@ }); }; +/** Return an array of graphs of the weakly connected components in this graph */ Graph.prototype.weakComponentGraphArray = function(){ //note: although this can often look like the original graph - edges can be lost return this.weakComponents().map( function( component ){ @@ -537,10 +603,8 @@ // ============================================================================ - - - -// ============================================================================ +/** Create a random graph with numVerts vertices and numEdges edges (for testing) + */ function randGraph( directed, numVerts, numEdges ){ //console.debug( 'randGraph', directed, numVerts, numEdges ); var data = { nodes : [], links : [] }; diff -r eacfa5a1a3f2e9fc516bea159c16ddb36067a220 -r 8f8341ac47cd276cf0a2aa3d1c1098f7d3d58c75 templates/webapps/galaxy/history/structure.mako --- a/templates/webapps/galaxy/history/structure.mako +++ b/templates/webapps/galaxy/history/structure.mako @@ -95,9 +95,6 @@ <script type="text/javascript"> /* TODO: - components should be graphs? - as it is they're verts+edges - in-panel view of anc desc show datasets when job not expanded @@ -119,8 +116,8 @@ controls: (optionally) filter all deleted (optionally) filter all hidden - (optionally) filter __SET_METADATA__ - (optionally) filter error'd jobs + //(optionally) filter __SET_METADATA__ + //(optionally) filter error'd jobs vertical layout will not allow expansion of jobs/datasets easily @@ -141,8 +138,6 @@ would need to update as jquery was expanding foreignObject/dom */ - - define( 'app', function(){ require([ 'mvc/job/job-model', @@ -151,18 +146,14 @@ ], function( JOB, HISTORY, StructureView ){ var historyModel = new HISTORY.History( bootstrapped.history, bootstrapped.hdas ); -window.historymodel = history; -window.jobs = bootstrapped.jobs; +//window.historymodel = history; +//window.jobs = bootstrapped.jobs; var structure = new StructureView({ model : historyModel, jobs : bootstrapped.jobs }); - structure.$el.appendTo( 'body' ); - console.debug( 'structure visible?', structure.$el.is( ':visible' ) ); - structure.render(); -window.structure = structure; - + structure.render().$el.appendTo( 'body' ); }); }); </script> Repository URL: https://bitbucket.org/galaxy/galaxy-central/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email.
participants (1)
-
commits-noreply@bitbucket.org