1 new commit in galaxy-central: https://bitbucket.org/galaxy/galaxy-central/changeset/27f54f28e9f1/ changeset: 27f54f28e9f1 user: jgoecks date: 2012-06-27 16:51:20 summary: (a) Paramamonster implemention; (b) bug fixes and small additions to style sheets; and (c) bug fixes for rerunning tools on complete datasets. affected #: 10 files diff -r 21ec1290ad02beb9c33516dcc4d5d4a732559414 -r 27f54f28e9f1542e9681b8a4c238d856be7b9a34 lib/galaxy/web/api/tools.py --- a/lib/galaxy/web/api/tools.py +++ b/lib/galaxy/web/api/tools.py @@ -133,8 +133,9 @@ # Run tool on region if region is specificied. run_on_regions = False - regions = from_json_string( payload.get( 'regions', None ) ) + regions = payload.get( 'regions', None ) if regions: + regions = from_json_string( regions ) if isinstance( regions, dict ): # Regions is a single region. regions = [ GenomeRegion.from_dict( regions ) ] @@ -256,7 +257,8 @@ # Set input datasets for tool. If running on regions, extract and use subset # when possible. # - regions_str = ",".join( [ str( r ) for r in regions ] ) + if run_on_regions: + regions_str = ",".join( [ str( r ) for r in regions ] ) for jida in original_job.input_datasets: # If param set previously by config actions, do nothing. if jida.name in params_set: diff -r 21ec1290ad02beb9c33516dcc4d5d4a732559414 -r 27f54f28e9f1542e9681b8a4c238d856be7b9a34 static/june_2007_style/base.less --- a/static/june_2007_style/base.less +++ b/static/june_2007_style/base.less @@ -650,7 +650,7 @@ } div.form-row-input { - width: 300px; + width: 90%; float: left; } @@ -696,7 +696,7 @@ select, textarea, input[type="text"], input[type="file"], input[type="password"] { // -webkit-box-sizing: border-box; - max-width: 300px; + max-width: 90%; } textarea, input[type="text"], input[type="password"] { diff -r 21ec1290ad02beb9c33516dcc4d5d4a732559414 -r 27f54f28e9f1542e9681b8a4c238d856be7b9a34 static/june_2007_style/base_sprites.less.tmpl --- a/static/june_2007_style/base_sprites.less.tmpl +++ b/static/june_2007_style/base_sprites.less.tmpl @@ -52,6 +52,10 @@ } .icon-button.toggle { -sprite-group: fugue; + -sprite-image: fugue/toggle-bw.png; +} +.icon-button.toggle:hover { + -sprite-group: fugue; -sprite-image: fugue/toggle.png; } .icon-button.arrow-circle { diff -r 21ec1290ad02beb9c33516dcc4d5d4a732559414 -r 27f54f28e9f1542e9681b8a4c238d856be7b9a34 static/june_2007_style/blue/base.css --- a/static/june_2007_style/blue/base.css +++ b/static/june_2007_style/blue/base.css @@ -742,23 +742,24 @@ .icon-button.tag--plus{background:url(fugue.png) no-repeat 0px -52px;} .icon-button.toggle-expand{background:url(fugue.png) no-repeat 0px -78px;} .icon-button.toggle{background:url(fugue.png) no-repeat 0px -104px;} -.icon-button.arrow-circle{background:url(fugue.png) no-repeat 0px -130px;} -.icon-button.chevron{background:url(fugue.png) no-repeat 0px -156px;} -.icon-button.bug{background:url(fugue.png) no-repeat 0px -182px;} -.icon-button.disk{background:url(fugue.png) no-repeat 0px -208px;} -.icon-button.information{background:url(fugue.png) no-repeat 0px -234px;} -.icon-button.annotate{background:url(fugue.png) no-repeat 0px -260px;} -.icon-button.go-to-full-screen{background:url(fugue.png) no-repeat 0px -286px;} -.icon-button.import{background:url(fugue.png) no-repeat 0px -312px;} -.icon-button.plus-button{background:url(fugue.png) no-repeat 0px -338px;} -.icon-button.plus-button:hover{background:url(fugue.png) no-repeat 0px -364px;} -.icon-button.gear{background:url(fugue.png) no-repeat 0px -390px;} -.icon-button.chart_curve{background:url(fugue.png) no-repeat 0px -416px;} -.icon-button.disk--arrow{background:url(fugue.png) no-repeat 0px -442px;} -.icon-button.disk--arrow:hover{background:url(fugue.png) no-repeat 0px -468px;} -.icon-button.cross-circle{background:url(fugue.png) no-repeat 0px -494px;} -.icon-button.cross-circle:hover{background:url(fugue.png) no-repeat 0px -520px;} -.text-and-autocomplete-select{background:url(fugue.png) no-repeat right -546px;} +.icon-button.toggle:hover{background:url(fugue.png) no-repeat 0px -130px;} +.icon-button.arrow-circle{background:url(fugue.png) no-repeat 0px -156px;} +.icon-button.chevron{background:url(fugue.png) no-repeat 0px -182px;} +.icon-button.bug{background:url(fugue.png) no-repeat 0px -208px;} +.icon-button.disk{background:url(fugue.png) no-repeat 0px -234px;} +.icon-button.information{background:url(fugue.png) no-repeat 0px -260px;} +.icon-button.annotate{background:url(fugue.png) no-repeat 0px -286px;} +.icon-button.go-to-full-screen{background:url(fugue.png) no-repeat 0px -312px;} +.icon-button.import{background:url(fugue.png) no-repeat 0px -338px;} +.icon-button.plus-button{background:url(fugue.png) no-repeat 0px -364px;} +.icon-button.plus-button:hover{background:url(fugue.png) no-repeat 0px -390px;} +.icon-button.gear{background:url(fugue.png) no-repeat 0px -416px;} +.icon-button.chart_curve{background:url(fugue.png) no-repeat 0px -442px;} +.icon-button.disk--arrow{background:url(fugue.png) no-repeat 0px -468px;} +.icon-button.disk--arrow:hover{background:url(fugue.png) no-repeat 0px -494px;} +.icon-button.cross-circle{background:url(fugue.png) no-repeat 0px -520px;} +.icon-button.cross-circle:hover{background:url(fugue.png) no-repeat 0px -546px;} +.text-and-autocomplete-select{background:url(fugue.png) no-repeat right -572px;} div.historyItem-error .state-icon{background:url(history-states.png) no-repeat 0px 0px;} div.historyItem-empty .state-icon{background:url(history-states.png) no-repeat 0px -25px;} div.historyItem-queued .state-icon{background:url(history-states.png) no-repeat 0px -50px;} diff -r 21ec1290ad02beb9c33516dcc4d5d4a732559414 -r 27f54f28e9f1542e9681b8a4c238d856be7b9a34 static/june_2007_style/blue/fugue.png Binary file static/june_2007_style/blue/fugue.png has changed diff -r 21ec1290ad02beb9c33516dcc4d5d4a732559414 -r 27f54f28e9f1542e9681b8a4c238d856be7b9a34 static/scripts/mvc/tools.js --- a/static/scripts/mvc/tools.js +++ b/static/scripts/mvc/tools.js @@ -52,6 +52,26 @@ ], urlRoot: galaxy_paths.get('tool_url'), + + /** + * Returns object copy, optionally including only inputs that can be sampled. + */ + copy: function(only_samplable_inputs) { + var copy = new Tool(this.toJSON()); + + // Return only samplable inputs if flag is set. + if (only_samplable_inputs) { + var valid_inputs = new Backbone.Collection(); + copy.get('inputs').each(function(input) { + if (input.get_samples()) { + valid_inputs.push(input); + } + }); + copy.set('inputs', valid_inputs); + } + + return copy; + }, apply_search_results: function(results) { ( _.indexOf(results, this.attributes.id) !== -1 ? this.show() : this.hide() ); @@ -81,7 +101,7 @@ * Run tool; returns a Deferred that resolves to the tool's output(s). */ run: function() { - return this._run() + return this._run(); }, /** @@ -94,6 +114,17 @@ regions: JSON.stringify(regions) }); }, + + /** + * Returns input dict for tool's inputs. + */ + get_inputs_dict: function() { + var input_dict = {}; + this.get('inputs').each(function(input) { + input_dict[input.get('name')] = input.get('value'); + }); + return input_dict; + }, /** * Run tool; returns a Deferred that resolves to the tool's output(s). @@ -102,13 +133,9 @@ _run: function(additional_params) { // Create payload. var payload = _.extend({ - tool_id: this.id - }, additional_params), - input_dict = {}; - this.get('inputs').each(function(input) { - input_dict[input.get('name')] = input.get('value'); - }); - payload.inputs = input_dict; + tool_id: this.id, + inputs: this.get_inputs_dict() + }, additional_params); // Because job may require indexing datasets, use server-side // deferred to ensure that job is run. Also use deferred that @@ -145,21 +172,27 @@ label: null, type: null, value: null, + num_samples: 5 }, initialize: function() { this.attributes.html = unescape(this.attributes.html); }, + + copy: function() { + return new ToolInput(this.toJSON()); + }, /** * Returns samples from a tool input. */ - get_samples: function(num_samples) { - var - type = this.get('type'), - samples = [] + get_samples: function() { + var type = this.get('type'), + samples = null; if (type === 'number') { - samples = d3.scale.linear().domain([this.get('min'), this.get('max')]).ticks(num_samples); + samples = d3.scale.linear() + .domain([this.get('min'), this.get('max')]) + .ticks(this.get('num_samples')); } else if (type === 'select') { samples = _.map(this.get('options'), function(option) { @@ -167,24 +200,11 @@ }); } - return new ParamSamples({ - param: this, - samples: samples - }); + return samples; } }); /** - * A tool and parameter samples. - */ -var ParamSamples = Backbone.Model.extend({ - defaults: { - param: null, - samples: null - } -}) - -/** * Wrap collection of tools for fast access/manipulation. */ var ToolCollection = Backbone.Collection.extend({ diff -r 21ec1290ad02beb9c33516dcc4d5d4a732559414 -r 27f54f28e9f1542e9681b8a4c238d856be7b9a34 static/scripts/mvc/ui.js --- a/static/scripts/mvc/ui.js +++ b/static/scripts/mvc/ui.js @@ -51,6 +51,10 @@ */ var IconButtonMenuView = Backbone.View.extend({ tagName: 'div', + + initialize: function() { + this.render(); + }, render: function() { var self = this; diff -r 21ec1290ad02beb9c33516dcc4d5d4a732559414 -r 27f54f28e9f1542e9681b8a4c238d856be7b9a34 static/scripts/viz/paramamonster.js --- a/static/scripts/viz/paramamonster.js +++ b/static/scripts/viz/paramamonster.js @@ -4,95 +4,230 @@ */ /** - * --- Models --- + * Tree for a tool's parameters. */ - var ToolParameterTree = Backbone.Model.extend({ defaults: { tool: null, - params: null, - num_samples: 4 - }, - - - initialize: function(options) { - // - // -- Create tree data from tool. -- - // - - // Valid inputs for tree are number, select parameters. - var tool = this.get('tool'), - num_samples = this.get('num_samples'), - params_samples = tool.get('inputs').map(function(input) { - return input.get_samples(num_samples); - }), - filtered_params_samples = _.filter(params_samples, function(param_sample) { - return param_sample.get('samples').length !== 0; - }); - - /** - * Returns tree data. Params_sampling is an array of - * - */ - var create_tree_data = function(params_samples, index) { - var - param_samples = params_samples[index], - param = param_samples.get('param'), - param_label = param.get('label'), - settings = param_samples.get('samples'); - - // Create leaves when last parameter setting is reached. - if (params_samples.length - 1 === index) { - return _.map(settings, function(setting) { - return { - name: param_label + '=' + setting, - param: param, - value: setting - } - }); - } - - // Recurse to handle other parameters. - return _.map(settings, function(setting) { - return { - name: param_label + '=' + setting, - param: param, - value: setting, - children: create_tree_data(filtered_params_samples, index + 1) - } - }); - }; - - var tree_data = { - name: 'Parameter Tree for ' + tool.get('name'), - children: create_tree_data(filtered_params_samples, 0) - }; - - // Set valid inputs, tree data for later use. - this.set('params', _.map(params_samples, function(s) { return s.get('param') })); - this.set('tree_data', tree_data); - } - -}); - -/** - * Tile of rendered genomic data. - */ -var Tile = Backbone.Model.extend({ - defaults: { - track: null, - index: null, - region: null, - resolution: null, - data: null, - stale: null, - html_elt: null + tree_data: null }, initialize: function(options) { + // Set up tool parameters to work with tree. + var self = this; + this.get('tool').get('inputs').each(function(input) { + if (!input.get_samples()) { return; } + + // All inputs are in tree to start. + self.add_param(input); + + // Listen for changes to input's attributes. + input.on('change:min change:max change:num_samples', function(input) { + if (input.get('in_ptree')) { + self.set_tree_data(); + } + }, self); + input.on('change:in_ptree', function(input) { + if (input.get('in_ptree')) { + self.add_param(input); + } + else { + self.remove_param(input); + } + self.set_tree_data(); + }, self); + }); + + self.set_tree_data(); + }, + + add_param: function(param) { + // If parameter already present, do not add it. + if (param.get('ptree_index')) { return; } + + param.set('in_ptree', true); + param.set('ptree_index', this.get_tree_params().length); + }, + + remove_param: function(param) { + // Remove param from tree. + param.set('in_ptree', false); + param.set('ptree_index', null); + + // Update ptree indices for remaining params. + _(this.get_tree_params()).each(function(input, index) { + // +1 to use 1-based indexing. + input.set('ptree_index', index + 1); + }); + }, + + /** + * Sets tree data using tool's inputs. + */ + set_tree_data: function() { + // Get samples for each parameter. + var params_samples = _.map(this.get_tree_params(), function(param) { + return { + param: param, + samples: param.get_samples() + }; + }); + var node_id = 0, + // Creates tree data recursively. + create_tree_data = function(params_samples, index) { + var param_samples = params_samples[index], + param = param_samples.param, + param_label = param.get('label'), + settings = param_samples.samples; + + // Create leaves when last parameter setting is reached. + if (params_samples.length - 1 === index) { + return _.map(settings, function(setting) { + return { + id: node_id++, + name: setting, + param: param, + value: setting + }; + }); + } + + // Recurse to handle other parameters. + return _.map(settings, function(setting) { + return { + id: node_id++, + name: setting, + param: param, + value: setting, + children: create_tree_data(params_samples, index + 1) + }; + }); + }; + + this.set('tree_data', { + name: 'Root', + children: (params_samples.length !== 0 ? create_tree_data(params_samples, 0) : null) + }); + }, + + get_tree_params: function() { + // Filter and sort parameters to get list in tree. + return _(this.get('tool').get('inputs').where( {in_ptree: true} )) + .sortBy( function(input) { return input.get('ptree_index'); } ); + }, + + /** + * Returns number of leaves in tree. + */ + get_num_leaves: function() { + return this.get_tree_params().reduce(function(memo, param) { return memo * param.get_samples().length; }, 1); + }, + + /** + * Returns array of settings based on a node and its subtree. + */ + get_node_settings: function(target_node) { + // -- Get fixed settings from tool and parent nodes. + + // Start with tool's settings. + var fixed_settings = this.get('tool').get_inputs_dict(); + + // Get fixed settings using node's parents. + var cur_node = target_node.parent; + if (cur_node) { + while(cur_node.depth !== 0) { + fixed_settings[cur_node.param.get('name')] = cur_node.value; + cur_node = cur_node.parent; + } + } + // Walk subtree starting at clicked node to get full list of settings. + var get_settings = function(node, settings) { + // Add setting for this node. + settings[node.param.get('name')] = node.value; + + if (!node.children) { + // At leaf node: add param setting and return. + return settings; + } + else { + // At interior node: return list of subtree settings. + return _.flatten( _.map(node.children, function(c) { return get_settings(c, _.clone(settings)); }) ); + } + }, + all_settings = get_settings(target_node, fixed_settings); + + // If user clicked on leaf, settings is a single dict. Convert to array for simplicity. + if (!_.isArray(all_settings)) { all_settings = [ all_settings ]; } + + return all_settings; + }, + + /** + * Returns all nodes connected a particular node; this includes parents and children of the node. + */ + get_connected_nodes: function(node) { + var get_subtree_nodes = function(a_node) { + if (!a_node.children) { + return a_node; + } + else { + // At interior node: return subtree nodes. + return _.flatten( [a_node, _.map(a_node.children, function(c) { return get_subtree_nodes(c); })] ); + } + }; + + // Get node's parents. + var parents = [], + cur_parent = node.parent; + while(cur_parent) { + parents.push(cur_parent); + cur_parent = cur_parent.parent; + } + + return _.flatten([parents, get_subtree_nodes(node)]); + }, + + /** + * Returns the leaf that corresponds to a settings collection. + */ + get_leaf: function(settings) { + var cur_node = this.get('tree_data'), + find_child = function(children) { + return _.find(children, function(child) { + return settings[child.param.get('name')] === child.value; + }); + }; + + while (cur_node.children) { + cur_node = find_child(cur_node.children); + } + return cur_node; } - +}); + +var ParamaMonsterTrack = Backbone.Model.extend({ + defaults: { + track: null, + settings: null, + regions: null + }, + + same_settings: function(a_track) { + var this_settings = this.get('settings'), + other_settings = a_track.get('settings'); + for (var prop in this_settings) { + if (!other_settings[prop] || + this_settings[prop] !== other_settings[prop]) { + return false; + } + } + return true; + } +}); + +var TrackCollection = Backbone.Collection.extend({ + model: ParamaMonsterTrack }); /** @@ -102,11 +237,24 @@ defaults: _.extend({}, Visualization.prototype.defaults, { tool: null, parameter_tree: null, - regions: null + regions: null, + tracks: null }), initialize: function(options) { - this.set('parameter_tree', new ToolParameterTree({ tool: this.get('tool') })); + var tool_with_samplable_inputs = this.get('tool').copy(true); + this.set('tool_with_samplable_inputs', tool_with_samplable_inputs); + + this.set('parameter_tree', new ToolParameterTree({ tool: tool_with_samplable_inputs })); + this.set('tracks', new TrackCollection()); + }, + + add_placeholder: function(settings) { + this.get('tracks').add(new PlaceholderTrack(settings)); + }, + + add_track: function(track) { + this.get('tracks').add(track); } }); @@ -114,87 +262,388 @@ * --- Views --- */ -var TileView = Backbone.View.extend({ - +/** + * ParamaMonster track view. + */ +var ParamaMonsterTrackView = Backbone.View.extend({ + tagName: 'tr', + + initialize: function(options) { + this.canvas_manager = options.canvas_manager; + this.render(); + this.model.on('change:track', this.draw_tiles, this); + }, + + render: function() { + // Render settings icon and popup. + // TODO: use template. + var settings = this.model.get('settings'), + settings_td = $('<td/>').addClass('settings').appendTo(this.$el), + settings_div = $('<div/>').addClass('track-info').hide().appendTo(settings_td); + settings_div.append( $('<div/>').css('font-weight', 'bold').text('Track Settings') ); + _.each(_.keys(settings), function(name) { + settings_div.append( name + ': ' + settings[name] + '<br/>'); + }); + var self = this, + run_on_dataset_button = $('<button/>').appendTo(settings_div).text('Run on complete dataset').click(function() { + self.trigger('run_on_dataset', settings); + }); + var icon_menu = create_icon_buttons_menu([ + { + title: 'Settings', + icon_class: 'gear track-settings', + on_click: function () { + settings_div.toggle(); + } + } + ]); + settings_td.prepend(icon_menu.$el); + + // Render tile placeholders. + _.each(this.model.get('regions'), function() { + self.$el.append($('<td/>').addClass('tile').html( + $('<img/>').attr('src', galaxy_paths.get('image_path') + '/loading_large_white_bg.gif') + )); + }); + + if (this.model.get('track')) { + this.draw_tiles(); + } + }, + + draw_tiles: function() { + // Display tiles for regions of interest. + var self = this, + track = this.model.get('track'), + regions = this.model.get('regions'), + tile_containers = this.$el.find('td.tile'); + + // When data is ready, draw tiles. + $.when(track.data_manager.data_is_ready()).then(function(data_ok) { + // Draw tile for each region. + _.each(regions, function(region, index) { + var resolution = region.length() / 300, + w_scale = 1/resolution, + mode = 'Pack'; + $.when(track.data_manager.get_data(region, mode, resolution, {})).then(function(tile_data) { + var canvas = self.canvas_manager.new_canvas(); + canvas.width = 300; + canvas.height = track.get_canvas_height(tile_data, mode, w_scale, canvas.width); + track.draw_tile(tile_data, canvas.getContext('2d'), mode, resolution, region, w_scale); + $(tile_containers[index]).empty().append(canvas); + }); + }); + }); + } }); +/** + * Tool input (parameter) that enables both value and sweeping inputs. View is unusual as + * it augments an existing input form row rather than creates a completely new HTML element. + */ +var ToolInputValOrSweepView = Backbone.View.extend({ + + // Template for rendering sweep inputs: + number_input_template: '<div class="form-row-input sweep">' + + '<input class="min" type="text" size="6" value="<%= min %>"> - ' + + '<input class="max" type="text" size="6" value="<%= max %>">' + + ' samples: <input class="num_samples" type="text" size="1" value="<%= num_samples %>">' + + '</div>', + + select_input_template: '<div class="form-row-input sweep"><%= options %></div>', + + initialize: function(options) { + this.$el = options.tool_row; + this.render(); + }, + + render: function() { + var input = this.model, + type = input.get('type'), + single_input_row = this.$el.find('.form-row-input'), + sweep_inputs_row = null; + + // Update tool inputs as single input changes. + single_input_row.find('input').change(function() { + input.set('value', $(this).val()); + }); + + // Add row for parameter sweep inputs. + if (type === 'number') { + sweep_inputs_row = $(_.template(this.number_input_template, this.model.toJSON())); + } + else if (type === 'select') { + var options = _.map(this.$el.find('select option'), function(option) { + return $(option).val(); + }), + options_text = options.join(', '); + sweep_inputs_row = $(_.template(this.select_input_template, { + options: options_text + })); + } + + // Fow now, assume parameter is included in tree to start. + sweep_inputs_row.insertAfter(single_input_row); + single_input_row.hide(); + + // Add buttons for adding/removing parameter. + var self = this, + menu = create_icon_buttons_menu([ + { + title: 'Add', + icon_class: 'plus-button', + on_click: function () { + input.set('in_ptree', true); + single_input_row.hide(); + sweep_inputs_row.show(); + } + + }, + { + title: 'Remove', + icon_class: 'toggle', + on_click: function() { + // Remove parameter from tree params where name matches clicked paramter. + input.set('in_ptree', false); + sweep_inputs_row.hide(); + single_input_row.show(); + } + } + ]); + this.$el.prepend(menu.$el); + + // Update input's min, max, number of samples as values change. + _.each(['min', 'max', 'num_samples'], function(attr) { + sweep_inputs_row.find('.' + attr).change(function() { + input.set(attr, parseFloat( $(this).val() )); + }); + }); + } +}); + +var ToolParameterTreeDesignView = Backbone.View.extend({ + className: 'tree-design', + + initialize: function(options) { + this.render(); + }, + + render: function() { + // Start with tool form view. + var tool_form_view = new ToolFormView({ + model: this.model.get('tool') + }); + tool_form_view.render(); + this.$el.append(tool_form_view.$el); + + // Set up views for each tool input. + var self = this, + inputs = self.model.get('tool').get('inputs'); + this.$el.find('.form-row').not('.form-actions').each(function(i) { + var input_view = new ToolInputValOrSweepView({ + model: inputs.at(i), + tool_row: $(this) + }); + }); + } +}); + +/** + * Displays and updates parameter tree. + */ var ToolParameterTreeView = Backbone.View.extend({ className: 'tool-parameter-tree', initialize: function(options) { + // When tree data changes, re-render. + this.model.on('change:tree_data', this.render, this); }, render: function() { - var width = 960, - height = 2000; + var tree_params = this.model.get_tree_params(); + // Start fresh. + this.$el.children().remove(); + + // Set width, height based on params and samples. + this.width = 100 * (2 + tree_params.length); + this.height = 15 * this.model.get_num_leaves(); + + var self = this; // Layout tree. var cluster = d3.layout.cluster() - .size([height, width - 160]); + .size([this.height - 10, this.width - 160]); var diagonal = d3.svg.diagonal() .projection(function(d) { return [d.y, d.x]; }); + // Layout nodes. + var nodes = cluster.nodes(this.model.get('tree_data')); + + // Setup and add labels for tree levels. + var param_depths = _.uniq(_.pluck(nodes, "y")); + _.each(tree_params, function(param, index) { + var x = param_depths[index+1]; + self.$el.append( $('<div>').addClass('label') + .text(param.get('label')) + // HACK: add 250 b/c in center panel. + .css('left', x + 250) ); + }); + // Set up vis element. var vis = d3.select(this.$el[0]) .append("svg") - .attr("width", width) - .attr("height", height) + .attr("width", this.width) + .attr("height", this.height) .append("g") - .attr("transform", "translate(80, 0)"); + .attr("transform", "translate(40, 10)"); - // Set up nodes, links. - var nodes = cluster.nodes(this.model.get('tree_data')); - + // Draw links. var link = vis.selectAll("path.link") .data(cluster.links(nodes)) .enter().append("path") .attr("class", "link") .attr("d", diagonal); + // Draw nodes. var node = vis.selectAll("g.node") .data(nodes) .enter().append("g") .attr("class", "node") - .attr("transform", function(d) { return "translate(" + d.y + "," + d.x + ")"; }); + .attr("transform", function(d) { return "translate(" + d.y + "," + d.x + ")"; }) + .on('mouseover', function(a_node) { + var connected_node_ids = _.pluck(self.model.get_connected_nodes(a_node), 'id'); + // TODO: probably can use enter() to do this more easily. + node.filter(function(d) { + return _.find(connected_node_ids, function(id) { return id === d.id; }) !== undefined; + }).style('fill', '#f00'); + }) + .on('mouseout', function() { + node.style('fill', '#000'); + }); node.append("circle") - .attr("r", 4.5); + .attr("r", 9); node.append("text") - .attr("dx", function(d) { return d.children ? -8 : 8; }) + .attr("dx", function(d) { return d.children ? -12 : 12; }) .attr("dy", 3) .attr("text-anchor", function(d) { return d.children ? "end" : "start"; }) .text(function(d) { return d.name; }); } }); +/** + * ParamaMonster visualization view. View requires rendering in 3-panel setup for now. + */ var ParamaMonsterVisualizationView = Backbone.View.extend({ className: 'paramamonster', initialize: function(options) { - + this.canvas_manager = new CanvasManager(this.$el.parents('body')); + this.tool_param_tree_view = new ToolParameterTreeView({ model: this.model.get('parameter_tree') }); + this.track_collection_container = $('<table/>').addClass('tracks'); + + // Handle node clicks for tree data. + this.model.get('parameter_tree').on('change:tree_data', this.handle_node_clicks, this); }, render: function() { - // Set up tool parameter tree. - var tool_param_tree_view = new ToolParameterTreeView({ model: this.model.get('parameter_tree') }); - tool_param_tree_view.render(); - this.$el.append(tool_param_tree_view.$el); - - // When node clicked in tree, run tool and show tiles. - var node = d3.select(tool_param_tree_view.$el[0]).selectAll("g.node") + // Render tree design view in left panel. + var tree_design_view = new ToolParameterTreeDesignView({ + model: this.model.get('parameter_tree') + }); + + $('#left').append(tree_design_view.$el); + + // Render track collection container/view in right panel. + var self = this, + regions = self.model.get('regions'), + tr = $('<tr/>').appendTo(this.track_collection_container); + + _.each(regions, function(region) { + tr.append( $('<th>').text(region.toString()) ); + }); + tr.children().first().attr('colspan', 2); + + $('#right').append(this.track_collection_container); + + // Render tool parameter tree in center panel. + this.tool_param_tree_view.render(); + $('#center').append(this.tool_param_tree_view.$el); + + this.handle_node_clicks(); + }, + + run_tool_on_dataset: function(settings) { + var tool = this.model.get('tool'), + dataset = this.model.get('dataset'); + tool.set_input_values(settings); + $.when(tool.rerun(dataset)).then(function(outputs) { + // TODO: show modal with information about how to get to datasets. + }); + }, + + handle_node_clicks: function() { + // When node clicked in tree, run tool and add tracks to model. + var self = this, + param_tree = this.model.get('parameter_tree'), + regions = this.model.get('regions'), + node = d3.select(this.tool_param_tree_view.$el[0]).selectAll("g.node"); node.on("click", function(d, i) { - console.log(d, i); + // TODO: Show popup menu. - // Gather: (a) dataset of interest; (b) region(s) of interest and (c) sets of parameters based on node clicked. - - // Run job by submitting parameters + dataset as job inputs; get dataset ids as result. - - // Create tracks for all resulting dataset ids. - - // Display tiles for region(s) of interest. + // Get all settings corresponding to node. + var tool = self.model.get('tool'), + dataset = self.model.get('dataset'), + all_settings = param_tree.get_node_settings(d); + + // Create and add tracks for each settings group. + var tracks = _.map(all_settings, function(settings) { + var pm_track = new ParamaMonsterTrack({ + settings: settings, + regions: regions + }); + self.model.add_track(pm_track); + var track_view = new ParamaMonsterTrackView({ + model: pm_track, + canvas_manager: self.canvas_manager + }); + track_view.on('run_on_dataset', self.run_tool_on_dataset, self); + self.track_collection_container.append(track_view.$el); + track_view.$el.hover(function() { + var settings_leaf = param_tree.get_leaf(settings); + var connected_node_ids = _.pluck(param_tree.get_connected_nodes(settings_leaf), 'id'); + + // TODO: can do faster with enter? + d3.select(self.tool_param_tree_view.$el[0]).selectAll("g.node") + .filter(function(d) { + return _.find(connected_node_ids, function(id) { return id === d.id; }) !== undefined; + }).style('fill', '#f00'); + }, + function() { + d3.select(self.tool_param_tree_view.$el[0]).selectAll("g.node").style('fill', '#000'); + }); + return pm_track; + }); + + // For each track, run tool using track's settings and update track. + _.each(tracks, function(pm_track, index) { + setTimeout(function() { + // Set inputs and run tool. + //console.log('running with settings', pm_track.get('settings')); + tool.set_input_values(pm_track.get('settings')); + $.when(tool.rerun(dataset, regions)).then(function(output) { + // Create and add track for output dataset. + var track_config = _.extend({ + data_url: galaxy_paths.get('raw_data_url'), + converted_datasets_state_url: galaxy_paths.get('dataset_state_url') + }, output.first().get('track_config')), + track_obj = object_from_template(track_config, self, null); + pm_track.set('track', track_obj); + }); + }, index * 10000); + }); }); - - }, + } }); \ No newline at end of file diff -r 21ec1290ad02beb9c33516dcc4d5d4a732559414 -r 27f54f28e9f1542e9681b8a4c238d856be7b9a34 templates/tracks/browser.mako --- a/templates/tracks/browser.mako +++ b/templates/tracks/browser.mako @@ -179,7 +179,6 @@ } } ]); - menu.render(); menu.$el.attr("style", "float: right"); $("#center .unified-panel-header-inner").append(menu.$el); diff -r 21ec1290ad02beb9c33516dcc4d5d4a732559414 -r 27f54f28e9f1542e9681b8a4c238d856be7b9a34 templates/visualization/paramamonster.mako --- a/templates/visualization/paramamonster.mako +++ b/templates/visualization/paramamonster.mako @@ -2,8 +2,8 @@ <%def name="init()"><% - self.has_left_panel=False - self.has_right_panel=False + self.has_left_panel=True + self.has_right_panel=True self.active_view="visualization" self.message_box_visible=False %> @@ -12,7 +12,7 @@ <%def name="stylesheets()"> ${parent.stylesheets()} <style> - .unified-panel-body { + div#center { overflow: auto; } .link { @@ -27,6 +27,56 @@ fill: #fff; stroke: steelblue; stroke-width: 1.5px; + cursor: pointer; + } + .node:hover { + fill: #f00; + } + .node:hover circle { + fill: #ccc; + stroke: #f00; + } + table.tracks { + border-collapse: separate; + border-spacing: 5px; + } + .tile { + border: solid 1px #DDD; + margin: 2px; + border-radius: 10px; + margin: 3px; + } + .label { + position: fixed; + font: 10px sans-serif; + font-weight: bold; + background-color: #DDD; + border-radius: 5px; + padding: 1px; + } + th,td { + text-align: center; + } + td.settings { + vertical-align: top; + } + .icon-button.track-settings { + float: none; + } + .track-info { + text-align: left; + font: 10px sans-serif; + position: fixed; + background-color: #CCC; + border: solid 1px #AAA; + border-radius: 2px; + padding: 2px; + } + .btn-primary, .btn-primary:hover { + color: #EEE; + background-color: #DDD; + background-image: none; + border-radius: 12px; } </style></%def> @@ -34,28 +84,16 @@ <%def name="javascripts()"> ${parent.javascripts()} - ${h.templates( "tool_link", "panel_section", "tool_search" )} - ${h.js( "libs/d3", "mvc/data", "mvc/tools", "viz/visualization", "viz/paramamonster" )} + ${h.templates( "tool_link", "panel_section", "tool_search", "tool_form" )} + ${h.js( "libs/d3", "mvc/data", "mvc/tools", "viz/visualization", "viz/paramamonster", "viz/trackster", "viz/trackster_ui", "jquery.ui.sortable.slider" )} <script type="text/javascript"> - var tool; + var viz; $(function() { // -- Viz set up. -- - tool = new Tool(JSON.parse('${ h.to_json_string( tool ) }')); - // HACK: need to replace \ with \\ due to simplejson bug. - var dataset = new Dataset(JSON.parse('${ h.to_json_string( dataset.get_api_value() ).replace('\\', '\\\\' ) }')), - paramamonster_viz = new ParamaMonsterVisualization({ - tool: tool, - dataset: dataset - }); - viz_view = new ParamaMonsterVisualizationView({ model: paramamonster_viz }); - - viz_view.render(); - $('.unified-panel-body').append(viz_view.$el); - - // Tool testing. - var regions = [ + var tool = new Tool(JSON.parse('${ h.to_json_string( tool ) }')), + regions = [ new GenomeRegion({ chrom: 'chr19', start: '10000', @@ -63,25 +101,32 @@ }), new GenomeRegion({ chrom: 'chr19', - start: '30000', - end: '36000' + start: '150000', + end: '175000' }) - ]; + ], + // HACK: need to replace \ with \\ due to simplejson bug. + dataset = new Dataset(JSON.parse('${ h.to_json_string( dataset.get_api_value() ).replace('\\', '\\\\' ) }')); - $.when(tool.rerun(dataset, regions)).then(function(outputs) { - console.log(outputs); + + viz = new ParamaMonsterVisualization({ + tool: tool, + dataset: dataset, + regions: regions }); + var viz_view = new ParamaMonsterVisualizationView({ model: viz }); + + viz_view.render(); + $('.unified-panel-body').append(viz_view.$el); }); </script></%def><%def name="center_panel()"> - <div class="unified-panel-header" unselectable="on"> - <div class="unified-panel-header-inner"> - <div style="float:left;" id="title"></div> - </div> - <div style="clear: both"></div> - </div> - <div class="unified-panel-body"> - </div></%def> + +<%def name="left_panel()"> +</%def> + +<%def name="right_panel()"> +</%def> \ No newline at end of file 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.