1 new commit in galaxy-central: https://bitbucket.org/galaxy/galaxy-central/changeset/cd739bd3dca4/ changeset: cd739bd3dca4 user: jgoecks date: 2012-09-21 17:14:56 summary: Place track filters in their own file. affected #: 2 files diff -r 040555fcc6b5341e2878c0d4ec33f54c712273c2 -r cd739bd3dca4859fecfe499fc976b0e0b6478238 static/scripts/viz/trackster/filters.js --- /dev/null +++ b/static/scripts/viz/trackster/filters.js @@ -0,0 +1,623 @@ +define( ["libs/underscore"], function(_) { + +var extend = _.extend; + +/** + * Filters that enable users to show/hide data points dynamically. + */ +var Filter = function(obj_dict) { + this.manager = null; + this.name = obj_dict.name; + // Index into payload to filter. + this.index = obj_dict.index; + this.tool_id = obj_dict.tool_id; + // Name to use for filter when building expression for tool. + this.tool_exp_name = obj_dict.tool_exp_name; +}; + +extend(Filter.prototype, { + /** + * Convert filter to dictionary. + */ + to_dict: function() { + return { + name: this.name, + index: this.index, + tool_id: this.tool_id, + tool_exp_name: this.tool_exp_name + }; + } +}); + +/** + * Creates an action icon. + */ +var create_action_icon = function(title, css_class, on_click_fn) { + return $("<a/>").attr("href", "javascript:void(0);").attr("title", title) + .addClass("icon-button").addClass(css_class).tooltip() + .click(on_click_fn); +}; + +/** + * Number filters have a min, max as well as a low, high; low and high are used + */ +var NumberFilter = function(obj_dict) { + // + // Attribute init. + // + Filter.call(this, obj_dict); + // Filter low/high. These values are used to filter elements. + this.low = ('low' in obj_dict ? obj_dict.low : -Number.MAX_VALUE); + this.high = ('high' in obj_dict ? obj_dict.high : Number.MAX_VALUE); + // Slide min/max. These values are used to set/update slider. + this.min = ('min' in obj_dict ? obj_dict.min : Number.MAX_VALUE); + this.max = ('max' in obj_dict ? obj_dict.max : -Number.MAX_VALUE); + // UI elements associated with filter. + this.container = null; + this.slider = null; + this.slider_label = null; + + // + // Create HTML. + // + + // Function that supports inline text editing of slider values. + // Enable users to edit parameter's value via a text box. + var edit_slider_values = function(container, span, slider) { + container.click(function() { + var cur_value = span.text(), + max = parseFloat(slider.slider("option", "max")), + input_size = (max <= 1 ? 4 : max <= 1000000 ? max.toString().length : 6), + multi_value = false, + slider_row = $(this).parents(".slider-row"); + + // Row now has input. + slider_row.addClass("input"); + + // Increase input size if there are two values. + if (slider.slider("option", "values")) { + input_size = 2*input_size + 1; + multi_value = true; + } + span.text(""); + // Temporary input for changing value. + $("<input type='text'/>").attr("size", input_size).attr("maxlength", input_size) + .attr("value", cur_value).appendTo(span).focus().select() + .click(function(e) { + // Don't want click to propogate up to values_span and restart everything. + e.stopPropagation(); + }).blur(function() { + $(this).remove(); + span.text(cur_value); + slider_row.removeClass("input"); + }).keyup(function(e) { + if (e.keyCode === 27) { + // Escape key. + $(this).trigger("blur"); + } else if (e.keyCode === 13) { + // + // Enter/return key initiates callback. If new value(s) are in slider range, + // change value (which calls slider's change() function). + // + var slider_min = slider.slider("option", "min"), + slider_max = slider.slider("option", "max"), + invalid = function(a_val) { + return (isNaN(a_val) || a_val > slider_max || a_val < slider_min); + }, + new_value = $(this).val(); + if (!multi_value) { + new_value = parseFloat(new_value); + if (invalid(new_value)) { + alert("Parameter value must be in the range [" + slider_min + "-" + slider_max + "]"); + return $(this); + } + } + else { // Multi value. + new_value = new_value.split("-"); + new_value = [parseFloat(new_value[0]), parseFloat(new_value[1])]; + if (invalid(new_value[0]) || invalid(new_value[1])) { + alert("Parameter value must be in the range [" + slider_min + "-" + slider_max + "]"); + return $(this); + } + } + + // Updating the slider also updates slider values and removes input. + slider.slider((multi_value ? "values" : "value"), new_value); + slider_row.removeClass("input"); + } + }); + }); + }; + + var filter = this; + + filter.parent_div = $("<div/>").addClass("filter-row slider-row"); + + // Set up filter label (name, values). + var filter_label = $("<div/>").addClass("elt-label").appendTo(filter.parent_div), + name_span = $("<span/>").addClass("slider-name").text(filter.name + " ").appendTo(filter_label), + values_span = $("<span/>").text(this.low + "-" + this.high), + values_span_container = $("<span/>").addClass("slider-value").appendTo(filter_label).append("[").append(values_span).append("]"); + filter.values_span = values_span; + + // Set up slider for filter. + var slider_div = $("<div/>").addClass("slider").appendTo(filter.parent_div); + filter.control_element = $("<div/>").attr("id", filter.name + "-filter-control").appendTo(slider_div); + filter.control_element.slider({ + range: true, + min: this.min, + max: this.max, + step: this.get_slider_step(this.min, this.max), + values: [this.low, this.high], + slide: function(event, ui) { + filter.slide(event, ui); + }, + change: function(event, ui) { + filter.control_element.slider("option", "slide").call(filter.control_element, event, ui); + } + }); + filter.slider = filter.control_element; + filter.slider_label = values_span; + + // Enable users to edit slider values via text box. + edit_slider_values(values_span_container, values_span, filter.control_element); + + // Set up filter display controls. + var display_controls_div = $("<div/>").addClass("display-controls").appendTo(filter.parent_div); + this.transparency_icon = create_action_icon("Use filter for data transparency", "layer-transparent", + function() { + if (filter.manager.alpha_filter !== filter) { + // Setting this filter as the alpha filter. + filter.manager.alpha_filter = filter; + // Update UI for new filter. + filter.manager.parent_div.find(".layer-transparent").removeClass("active").hide(); + filter.transparency_icon.addClass("active").show(); + } + else { + // Clearing filter as alpha filter. + filter.manager.alpha_filter = null; + filter.transparency_icon.removeClass("active"); + } + filter.manager.track.request_draw(true, true); + } ) + .appendTo(display_controls_div).hide(); + this.height_icon = create_action_icon("Use filter for data height", "arrow-resize-090", + function() { + if (filter.manager.height_filter !== filter) { + // Setting this filter as the height filter. + filter.manager.height_filter = filter; + // Update UI for new filter. + filter.manager.parent_div.find(".arrow-resize-090").removeClass("active").hide(); + filter.height_icon.addClass("active").show(); + } + else { + // Clearing filter as alpha filter. + filter.manager.height_filter = null; + filter.height_icon.removeClass("active"); + } + filter.manager.track.request_draw(true, true); + } ) + .appendTo(display_controls_div).hide(); + filter.parent_div.hover( function() { + filter.transparency_icon.show(); + filter.height_icon.show(); + }, + function() { + if (filter.manager.alpha_filter !== filter) { + filter.transparency_icon.hide(); + } + if (filter.manager.height_filter !== filter) { + filter.height_icon.hide(); + } + } ); + + // Add to clear floating layout. + $("<div style='clear: both;'/>").appendTo(filter.parent_div); +}; +extend(NumberFilter.prototype, { + /** + * Convert filter to dictionary. + */ + to_dict: function() { + var obj_dict = Filter.prototype.to_dict.call(this); + return extend(obj_dict, { + type: 'number', + min: this.min, + max: this.max, + low: this.low, + high: this.high + }); + }, + /** + * Return a copy of filter. + */ + copy: function() { + return new NumberFilter( + { + name: this.name, + index: this.index, + tool_id: this.tool_id, + tool_exp_name: this.tool_exp_name + }); + }, + /** + * Get step for slider. + */ + // FIXME: make this a "static" function. + get_slider_step: function(min, max) { + var range = max - min; + return (range <= 2 ? 0.01 : 1); + }, + /** + * Handle slide events. + */ + slide: function(event, ui) { + var values = ui.values; + + // Set new values in UI. + this.values_span.text(values[0] + "-" + values[1]); + + // Set new values in filter. + this.low = values[0]; + this.high = values[1]; + + // Set timeout to update if filter low, high are stable. + var self = this; + setTimeout(function() { + if (values[0] === self.low && values[1] === self.high) { + self.manager.track.request_draw(true, true); + } + }, 25); + + }, + /** + * Returns true if filter can be applied to element. + */ + applies_to: function(element) { + if (element.length > this.index) { + return true; + } + return false; + }, + /** + * Helper function: returns true if value in in filter's [low, high] range. + */ + _keep_val: function(val) { + return (isNaN(val) || (val >= this.low && val <= this.high)); + }, + /** + * Returns true if (a) element's value(s) is in [low, high] (range is inclusive) + * or (b) if value is non-numeric and hence unfilterable. + */ + keep: function(element) { + if ( !this.applies_to( element ) ) { + // No element to filter on. + return true; + } + + // Keep value function. + var filter = this; + + // Do filtering. + var to_filter = element[this.index]; + if (to_filter instanceof Array) { + var returnVal = true; + for (var i = 0; i < to_filter.length; i++) { + if (!this._keep_val(to_filter[i])) { + // Exclude element. + returnVal = false; + break; + } + } + return returnVal; + } + else { + return this._keep_val(element[this.index]); + } + }, + /** + * Update filter's min and max values based on element's values. + */ + update_attrs: function(element) { + var updated = false; + if (!this.applies_to(element) ) { + return updated; + } + + // + // Update filter's min, max based on element values. + // + + // Make value(s) into an Array. + var values = element[this.index]; + if (!(values instanceof Array)) { + values = [values]; + } + + // Loop through values and update min, max. + for (var i = 0; i < values.length; i++) { + var value = values[i]; + if (value < this.min) { + this.min = Math.floor(value); + updated = true; + } + if (value > this.max) { + this.max = Math.ceil(value); + updated = true; + } + } + return updated; + }, + /** + * Update filter's slider. + */ + update_ui_elt: function () { + // Only show filter if min < max because filter is not useful otherwise. This + // covers all corner cases, such as when min, max have not been defined and + // when min == max. + if (this.min < this.max) { + this.parent_div.show(); + } + else { + this.parent_div.hide(); + } + + var + slider_min = this.slider.slider("option", "min"), + slider_max = this.slider.slider("option", "max"); + if (this.min < slider_min || this.max > slider_max) { + // Update slider min, max, step. + this.slider.slider("option", "min", this.min); + this.slider.slider("option", "max", this.max); + this.slider.slider("option", "step", this.get_slider_step(this.min, this.max)); + // Refresh slider: + // TODO: do we want to keep current values or reset to min/max? + // Currently we reset values: + this.slider.slider("option", "values", [this.min, this.max]); + // To use the current values. + //var values = this.slider.slider( "option", "values" ); + //this.slider.slider( "option", "values", values ); + } + } +}); + +/** + * Manages a set of filters. + */ +var FiltersManager = function(track, obj_dict) { + this.track = track; + this.alpha_filter = null; + this.height_filter = null; + this.filters = []; + + // + // Create HTML. + // + + // + // Create parent div. + // + this.parent_div = $("<div/>").addClass("filters").hide(); + // Disable dragging, double clicking, keys on div so that actions on slider do not impact viz. + this.parent_div.bind("drag", function(e) { + e.stopPropagation(); + }).click(function(e) { + e.stopPropagation(); + }).bind("dblclick", function(e) { + e.stopPropagation(); + }).bind("keydown", function(e) { + e.stopPropagation(); + }); + + // + // Restore state from dict. + // + if (obj_dict && 'filters' in obj_dict) { // Second condition needed for backward compatibility. + var + alpha_filter_name = ('alpha_filter' in obj_dict ? obj_dict.alpha_filter : null), + height_filter_name = ('height_filter' in obj_dict ? obj_dict.height_filter : null), + filters_dict = obj_dict.filters, + filter; + for (var i = 0; i < filters_dict.length; i++) { + if (filters_dict[i].type === 'number') { + filter = new NumberFilter(filters_dict[i]); + this.add_filter(filter); + if (filter.name === alpha_filter_name) { + this.alpha_filter = filter; + filter.transparency_icon.addClass("active").show(); + } + if (filter.name === height_filter_name) { + this.height_filter = filter; + filter.height_icon.addClass("active").show(); + } + } + else { + console.log("ERROR: unsupported filter: ", name, type); + } + } + + + if ('visible' in obj_dict && obj_dict.visible) { + this.parent_div.show(); + } + } + + // Add button to filter complete dataset. + if (this.filters.length !== 0) { + var run_buttons_row = $("<div/>").addClass("param-row").appendTo(this.parent_div); + var run_on_dataset_button = $("<input type='submit'/>").attr("value", "Run on complete dataset").appendTo(run_buttons_row); + var filter_manager = this; + run_on_dataset_button.click( function() { + filter_manager.run_on_dataset(); + }); + } + +}; + +extend(FiltersManager.prototype, { + // HTML manipulation and inspection. + show: function() { this.parent_div.show(); }, + hide: function() { this.parent_div.hide(); }, + toggle: function() { this.parent_div.toggle(); }, + visible: function() { return this.parent_div.is(":visible"); }, + /** + * Returns dictionary for manager. + */ + to_dict: function() { + var obj_dict = {}, + filter_dicts = [], + filter; + + // Include individual filter states. + for (var i = 0; i < this.filters.length; i++) { + filter = this.filters[i]; + filter_dicts.push(filter.to_dict()); + } + obj_dict.filters = filter_dicts; + + // Include transparency, height filters. + obj_dict.alpha_filter = (this.alpha_filter ? this.alpha_filter.name : null); + obj_dict.height_filter = (this.height_filter ? this.height_filter.name : null); + + // Include visibility. + obj_dict.visible = this.parent_div.is(":visible"); + + return obj_dict; + }, + /** + * Return a copy of the manager. + */ + copy: function(new_track) { + var copy = new FiltersManager(new_track); + for (var i = 0; i < this.filters.length; i++) { + copy.add_filter(this.filters[i].copy()); + } + return copy; + }, + /** + * Add a filter to the manager. + */ + add_filter: function(filter) { + filter.manager = this; + this.parent_div.append(filter.parent_div); + this.filters.push(filter); + }, + /** + * Remove all filters from manager. + */ + remove_all: function() { + this.filters = []; + this.parent_div.children().remove(); + }, + /** + * Initialize filters. + */ + init_filters: function() { + for (var i = 0; i < this.filters.length; i++) { + var filter = this.filters[i]; + filter.update_ui_elt(); + } + }, + /** + * Clear filters so that they do not impact track display. + */ + clear_filters: function() { + for (var i = 0; i < this.filters.length; i++) { + var filter = this.filters[i]; + filter.slider.slider("option", "values", [filter.min, filter.max]); + } + this.alpha_filter = null; + this.height_filter = null; + + // Hide icons for setting filters. + this.parent_div.find(".icon-button").hide(); + }, + run_on_dataset: function() { + // Get or create dictionary item. + var get_or_create_dict_item = function(dict, key, new_item) { + // Add new item to dict if + if (!(key in dict)) { + dict[key] = new_item; + } + return dict[key]; + }; + + // + // Find and group active filters. Active filters are those being used to hide data. + // Filters with the same tool id are grouped. + // + var active_filters = {}, + filter, + tool_filter_conditions; + for (var i = 0; i < this.filters.length; i++) { + filter = this.filters[i]; + if (filter.tool_id) { + // Add filtering conditions if filter low/high are set. + if (filter.min !== filter.low) { + tool_filter_conditions = get_or_create_dict_item(active_filters, filter.tool_id, []); + tool_filter_conditions[tool_filter_conditions.length] = filter.tool_exp_name + " >= " + filter.low; + } + if (filter.max !== filter.high) { + tool_filter_conditions = get_or_create_dict_item(active_filters, filter.tool_id, []); + tool_filter_conditions[tool_filter_conditions.length] = filter.tool_exp_name + " <= " + filter.high; + } + } + } + + // + // Use tools to run filters. + // + + // Create list of (tool_id, tool_filters) tuples. + var active_filters_list = []; + for (var tool_id in active_filters) { + active_filters_list[active_filters_list.length] = [tool_id, active_filters[tool_id]]; + } + + // Invoke recursive function to run filters; this enables chaining of filters via + // iteratively application. + (function run_filter(input_dataset_id, filters) { + var + // Set up filtering info and params. + filter_tuple = filters[0], + tool_id = filter_tuple[0], + tool_filters = filter_tuple[1], + tool_filter_str = "(" + tool_filters.join(") and (") + ")", + url_params = { + cond: tool_filter_str, + input: input_dataset_id, + target_dataset_id: input_dataset_id, + tool_id: tool_id + }; + + // Remove current filter. + filters = filters.slice(1); + + $.getJSON(run_tool_url, url_params, function(response) { + if (response.error) { + // General error. + show_modal("Filter Dataset", + "Error running tool " + tool_id, + { "Close" : hide_modal } ); + } + else if (filters.length === 0) { + // No more filters to run. + show_modal("Filtering Dataset", + "Filter(s) are running on the complete dataset. Outputs are in dataset's history.", + { "Close" : hide_modal } ); + } + else { + // More filters to run. + run_filter(response.dataset_id, filters); + } + }); + + })(this.track.dataset_id, active_filters_list); + } +}); + +return { + FiltersManager: FiltersManager +}; + +}); diff -r 040555fcc6b5341e2878c0d4ec33f54c712273c2 -r cd739bd3dca4859fecfe499fc976b0e0b6478238 static/scripts/viz/trackster/tracks.js --- a/static/scripts/viz/trackster/tracks.js +++ b/static/scripts/viz/trackster/tracks.js @@ -1,6 +1,7 @@ define( ["libs/underscore", "viz/visualization", "viz/trackster/util", - "viz/trackster/slotting", "viz/trackster/painters", "mvc/data" ], - function( _, visualization, util, slotting, painters, data ) { + "viz/trackster/slotting", "viz/trackster/painters", "mvc/data", + "viz/trackster/filters" ], + function( _, visualization, util, slotting, painters, data, filters ) { var extend = _.extend; var get_random_color = util.get_random_color; @@ -586,7 +587,7 @@ moveable(this.container_div, this.drag_handle_class, ".group", this); // Set up filters. - this.filters_manager = new FiltersManager(this); + this.filters_manager = new filters.FiltersManager(this); this.header_div.after(this.filters_manager.parent_div); // For saving drawables' filter managers when group-level filtering is done: this.saved_filters_managers = []; @@ -600,7 +601,7 @@ if ('filters' in obj_dict) { // FIXME: Pass collection_dict to DrawableCollection/Drawable will make this easier. var old_manager = this.filters_manager; - this.filters_manager = new FiltersManager(this, obj_dict.filters); + this.filters_manager = new filters.FiltersManager(this, obj_dict.filters); old_manager.parent_div.replaceWith(this.filters_manager.parent_div); if (obj_dict.filters.visible) { @@ -1831,619 +1832,6 @@ }); /** - * Filters that enable users to show/hide data points dynamically. - */ -var Filter = function(obj_dict) { - this.manager = null; - this.name = obj_dict.name; - // Index into payload to filter. - this.index = obj_dict.index; - this.tool_id = obj_dict.tool_id; - // Name to use for filter when building expression for tool. - this.tool_exp_name = obj_dict.tool_exp_name; -}; - -extend(Filter.prototype, { - /** - * Convert filter to dictionary. - */ - to_dict: function() { - return { - name: this.name, - index: this.index, - tool_id: this.tool_id, - tool_exp_name: this.tool_exp_name - }; - } -}); - -/** - * Creates an action icon. - */ -var create_action_icon = function(title, css_class, on_click_fn) { - return $("<a/>").attr("href", "javascript:void(0);").attr("title", title) - .addClass("icon-button").addClass(css_class).tooltip() - .click(on_click_fn); -}; - -/** - * Number filters have a min, max as well as a low, high; low and high are used - */ -var NumberFilter = function(obj_dict) { - // - // Attribute init. - // - Filter.call(this, obj_dict); - // Filter low/high. These values are used to filter elements. - this.low = ('low' in obj_dict ? obj_dict.low : -Number.MAX_VALUE); - this.high = ('high' in obj_dict ? obj_dict.high : Number.MAX_VALUE); - // Slide min/max. These values are used to set/update slider. - this.min = ('min' in obj_dict ? obj_dict.min : Number.MAX_VALUE); - this.max = ('max' in obj_dict ? obj_dict.max : -Number.MAX_VALUE); - // UI elements associated with filter. - this.container = null; - this.slider = null; - this.slider_label = null; - - // - // Create HTML. - // - - // Function that supports inline text editing of slider values. - // Enable users to edit parameter's value via a text box. - var edit_slider_values = function(container, span, slider) { - container.click(function() { - var cur_value = span.text(), - max = parseFloat(slider.slider("option", "max")), - input_size = (max <= 1 ? 4 : max <= 1000000 ? max.toString().length : 6), - multi_value = false, - slider_row = $(this).parents(".slider-row"); - - // Row now has input. - slider_row.addClass("input"); - - // Increase input size if there are two values. - if (slider.slider("option", "values")) { - input_size = 2*input_size + 1; - multi_value = true; - } - span.text(""); - // Temporary input for changing value. - $("<input type='text'/>").attr("size", input_size).attr("maxlength", input_size) - .attr("value", cur_value).appendTo(span).focus().select() - .click(function(e) { - // Don't want click to propogate up to values_span and restart everything. - e.stopPropagation(); - }).blur(function() { - $(this).remove(); - span.text(cur_value); - slider_row.removeClass("input"); - }).keyup(function(e) { - if (e.keyCode === 27) { - // Escape key. - $(this).trigger("blur"); - } else if (e.keyCode === 13) { - // - // Enter/return key initiates callback. If new value(s) are in slider range, - // change value (which calls slider's change() function). - // - var slider_min = slider.slider("option", "min"), - slider_max = slider.slider("option", "max"), - invalid = function(a_val) { - return (isNaN(a_val) || a_val > slider_max || a_val < slider_min); - }, - new_value = $(this).val(); - if (!multi_value) { - new_value = parseFloat(new_value); - if (invalid(new_value)) { - alert("Parameter value must be in the range [" + slider_min + "-" + slider_max + "]"); - return $(this); - } - } - else { // Multi value. - new_value = new_value.split("-"); - new_value = [parseFloat(new_value[0]), parseFloat(new_value[1])]; - if (invalid(new_value[0]) || invalid(new_value[1])) { - alert("Parameter value must be in the range [" + slider_min + "-" + slider_max + "]"); - return $(this); - } - } - - // Updating the slider also updates slider values and removes input. - slider.slider((multi_value ? "values" : "value"), new_value); - slider_row.removeClass("input"); - } - }); - }); - }; - - var filter = this; - - filter.parent_div = $("<div/>").addClass("filter-row slider-row"); - - // Set up filter label (name, values). - var filter_label = $("<div/>").addClass("elt-label").appendTo(filter.parent_div), - name_span = $("<span/>").addClass("slider-name").text(filter.name + " ").appendTo(filter_label), - values_span = $("<span/>").text(this.low + "-" + this.high), - values_span_container = $("<span/>").addClass("slider-value").appendTo(filter_label).append("[").append(values_span).append("]"); - filter.values_span = values_span; - - // Set up slider for filter. - var slider_div = $("<div/>").addClass("slider").appendTo(filter.parent_div); - filter.control_element = $("<div/>").attr("id", filter.name + "-filter-control").appendTo(slider_div); - filter.control_element.slider({ - range: true, - min: this.min, - max: this.max, - step: this.get_slider_step(this.min, this.max), - values: [this.low, this.high], - slide: function(event, ui) { - filter.slide(event, ui); - }, - change: function(event, ui) { - filter.control_element.slider("option", "slide").call(filter.control_element, event, ui); - } - }); - filter.slider = filter.control_element; - filter.slider_label = values_span; - - // Enable users to edit slider values via text box. - edit_slider_values(values_span_container, values_span, filter.control_element); - - // Set up filter display controls. - var display_controls_div = $("<div/>").addClass("display-controls").appendTo(filter.parent_div); - this.transparency_icon = create_action_icon("Use filter for data transparency", "layer-transparent", - function() { - if (filter.manager.alpha_filter !== filter) { - // Setting this filter as the alpha filter. - filter.manager.alpha_filter = filter; - // Update UI for new filter. - filter.manager.parent_div.find(".layer-transparent").removeClass("active").hide(); - filter.transparency_icon.addClass("active").show(); - } - else { - // Clearing filter as alpha filter. - filter.manager.alpha_filter = null; - filter.transparency_icon.removeClass("active"); - } - filter.manager.track.request_draw(true, true); - } ) - .appendTo(display_controls_div).hide(); - this.height_icon = create_action_icon("Use filter for data height", "arrow-resize-090", - function() { - if (filter.manager.height_filter !== filter) { - // Setting this filter as the height filter. - filter.manager.height_filter = filter; - // Update UI for new filter. - filter.manager.parent_div.find(".arrow-resize-090").removeClass("active").hide(); - filter.height_icon.addClass("active").show(); - } - else { - // Clearing filter as alpha filter. - filter.manager.height_filter = null; - filter.height_icon.removeClass("active"); - } - filter.manager.track.request_draw(true, true); - } ) - .appendTo(display_controls_div).hide(); - filter.parent_div.hover( function() { - filter.transparency_icon.show(); - filter.height_icon.show(); - }, - function() { - if (filter.manager.alpha_filter !== filter) { - filter.transparency_icon.hide(); - } - if (filter.manager.height_filter !== filter) { - filter.height_icon.hide(); - } - } ); - - // Add to clear floating layout. - $("<div style='clear: both;'/>").appendTo(filter.parent_div); -}; -extend(NumberFilter.prototype, { - /** - * Convert filter to dictionary. - */ - to_dict: function() { - var obj_dict = Filter.prototype.to_dict.call(this); - return extend(obj_dict, { - type: 'number', - min: this.min, - max: this.max, - low: this.low, - high: this.high - }); - }, - /** - * Return a copy of filter. - */ - copy: function() { - return new NumberFilter( - { - name: this.name, - index: this.index, - tool_id: this.tool_id, - tool_exp_name: this.tool_exp_name - }); - }, - /** - * Get step for slider. - */ - // FIXME: make this a "static" function. - get_slider_step: function(min, max) { - var range = max - min; - return (range <= 2 ? 0.01 : 1); - }, - /** - * Handle slide events. - */ - slide: function(event, ui) { - var values = ui.values; - - // Set new values in UI. - this.values_span.text(values[0] + "-" + values[1]); - - // Set new values in filter. - this.low = values[0]; - this.high = values[1]; - - // Set timeout to update if filter low, high are stable. - var self = this; - setTimeout(function() { - if (values[0] === self.low && values[1] === self.high) { - self.manager.track.request_draw(true, true); - } - }, 25); - - }, - /** - * Returns true if filter can be applied to element. - */ - applies_to: function(element) { - if (element.length > this.index) { - return true; - } - return false; - }, - /** - * Helper function: returns true if value in in filter's [low, high] range. - */ - _keep_val: function(val) { - return (isNaN(val) || (val >= this.low && val <= this.high)); - }, - /** - * Returns true if (a) element's value(s) is in [low, high] (range is inclusive) - * or (b) if value is non-numeric and hence unfilterable. - */ - keep: function(element) { - if ( !this.applies_to( element ) ) { - // No element to filter on. - return true; - } - - // Keep value function. - var filter = this; - - // Do filtering. - var to_filter = element[this.index]; - if (to_filter instanceof Array) { - var returnVal = true; - for (var i = 0; i < to_filter.length; i++) { - if (!this._keep_val(to_filter[i])) { - // Exclude element. - returnVal = false; - break; - } - } - return returnVal; - } - else { - return this._keep_val(element[this.index]); - } - }, - /** - * Update filter's min and max values based on element's values. - */ - update_attrs: function(element) { - var updated = false; - if (!this.applies_to(element) ) { - return updated; - } - - // - // Update filter's min, max based on element values. - // - - // Make value(s) into an Array. - var values = element[this.index]; - if (!(values instanceof Array)) { - values = [values]; - } - - // Loop through values and update min, max. - for (var i = 0; i < values.length; i++) { - var value = values[i]; - if (value < this.min) { - this.min = Math.floor(value); - updated = true; - } - if (value > this.max) { - this.max = Math.ceil(value); - updated = true; - } - } - return updated; - }, - /** - * Update filter's slider. - */ - update_ui_elt: function () { - // Only show filter if min < max because filter is not useful otherwise. This - // covers all corner cases, such as when min, max have not been defined and - // when min == max. - if (this.min < this.max) { - this.parent_div.show(); - } - else { - this.parent_div.hide(); - } - - var - slider_min = this.slider.slider("option", "min"), - slider_max = this.slider.slider("option", "max"); - if (this.min < slider_min || this.max > slider_max) { - // Update slider min, max, step. - this.slider.slider("option", "min", this.min); - this.slider.slider("option", "max", this.max); - this.slider.slider("option", "step", this.get_slider_step(this.min, this.max)); - // Refresh slider: - // TODO: do we want to keep current values or reset to min/max? - // Currently we reset values: - this.slider.slider("option", "values", [this.min, this.max]); - // To use the current values. - //var values = this.slider.slider( "option", "values" ); - //this.slider.slider( "option", "values", values ); - } - } -}); - -/** - * Manages a set of filters. - */ -var FiltersManager = function(track, obj_dict) { - this.track = track; - this.alpha_filter = null; - this.height_filter = null; - this.filters = []; - - // - // Create HTML. - // - - // - // Create parent div. - // - this.parent_div = $("<div/>").addClass("filters").hide(); - // Disable dragging, double clicking, keys on div so that actions on slider do not impact viz. - this.parent_div.bind("drag", function(e) { - e.stopPropagation(); - }).click(function(e) { - e.stopPropagation(); - }).bind("dblclick", function(e) { - e.stopPropagation(); - }).bind("keydown", function(e) { - e.stopPropagation(); - }); - - // - // Restore state from dict. - // - if (obj_dict && 'filters' in obj_dict) { // Second condition needed for backward compatibility. - var - alpha_filter_name = ('alpha_filter' in obj_dict ? obj_dict.alpha_filter : null), - height_filter_name = ('height_filter' in obj_dict ? obj_dict.height_filter : null), - filters_dict = obj_dict.filters, - filter; - for (var i = 0; i < filters_dict.length; i++) { - if (filters_dict[i].type === 'number') { - filter = new NumberFilter(filters_dict[i]); - this.add_filter(filter); - if (filter.name === alpha_filter_name) { - this.alpha_filter = filter; - filter.transparency_icon.addClass("active").show(); - } - if (filter.name === height_filter_name) { - this.height_filter = filter; - filter.height_icon.addClass("active").show(); - } - } - else { - console.log("ERROR: unsupported filter: ", name, type); - } - } - - - if ('visible' in obj_dict && obj_dict.visible) { - this.parent_div.show(); - } - } - - // Add button to filter complete dataset. - if (this.filters.length !== 0) { - var run_buttons_row = $("<div/>").addClass("param-row").appendTo(this.parent_div); - var run_on_dataset_button = $("<input type='submit'/>").attr("value", "Run on complete dataset").appendTo(run_buttons_row); - var filter_manager = this; - run_on_dataset_button.click( function() { - filter_manager.run_on_dataset(); - }); - } - -}; - -extend(FiltersManager.prototype, { - // HTML manipulation and inspection. - show: function() { this.parent_div.show(); }, - hide: function() { this.parent_div.hide(); }, - toggle: function() { this.parent_div.toggle(); }, - visible: function() { return this.parent_div.is(":visible"); }, - /** - * Returns dictionary for manager. - */ - to_dict: function() { - var obj_dict = {}, - filter_dicts = [], - filter; - - // Include individual filter states. - for (var i = 0; i < this.filters.length; i++) { - filter = this.filters[i]; - filter_dicts.push(filter.to_dict()); - } - obj_dict.filters = filter_dicts; - - // Include transparency, height filters. - obj_dict.alpha_filter = (this.alpha_filter ? this.alpha_filter.name : null); - obj_dict.height_filter = (this.height_filter ? this.height_filter.name : null); - - // Include visibility. - obj_dict.visible = this.parent_div.is(":visible"); - - return obj_dict; - }, - /** - * Return a copy of the manager. - */ - copy: function(new_track) { - var copy = new FiltersManager(new_track); - for (var i = 0; i < this.filters.length; i++) { - copy.add_filter(this.filters[i].copy()); - } - return copy; - }, - /** - * Add a filter to the manager. - */ - add_filter: function(filter) { - filter.manager = this; - this.parent_div.append(filter.parent_div); - this.filters.push(filter); - }, - /** - * Remove all filters from manager. - */ - remove_all: function() { - this.filters = []; - this.parent_div.children().remove(); - }, - /** - * Initialize filters. - */ - init_filters: function() { - for (var i = 0; i < this.filters.length; i++) { - var filter = this.filters[i]; - filter.update_ui_elt(); - } - }, - /** - * Clear filters so that they do not impact track display. - */ - clear_filters: function() { - for (var i = 0; i < this.filters.length; i++) { - var filter = this.filters[i]; - filter.slider.slider("option", "values", [filter.min, filter.max]); - } - this.alpha_filter = null; - this.height_filter = null; - - // Hide icons for setting filters. - this.parent_div.find(".icon-button").hide(); - }, - run_on_dataset: function() { - // Get or create dictionary item. - var get_or_create_dict_item = function(dict, key, new_item) { - // Add new item to dict if - if (!(key in dict)) { - dict[key] = new_item; - } - return dict[key]; - }; - - // - // Find and group active filters. Active filters are those being used to hide data. - // Filters with the same tool id are grouped. - // - var active_filters = {}, - filter, - tool_filter_conditions; - for (var i = 0; i < this.filters.length; i++) { - filter = this.filters[i]; - if (filter.tool_id) { - // Add filtering conditions if filter low/high are set. - if (filter.min !== filter.low) { - tool_filter_conditions = get_or_create_dict_item(active_filters, filter.tool_id, []); - tool_filter_conditions[tool_filter_conditions.length] = filter.tool_exp_name + " >= " + filter.low; - } - if (filter.max !== filter.high) { - tool_filter_conditions = get_or_create_dict_item(active_filters, filter.tool_id, []); - tool_filter_conditions[tool_filter_conditions.length] = filter.tool_exp_name + " <= " + filter.high; - } - } - } - - // - // Use tools to run filters. - // - - // Create list of (tool_id, tool_filters) tuples. - var active_filters_list = []; - for (var tool_id in active_filters) { - active_filters_list[active_filters_list.length] = [tool_id, active_filters[tool_id]]; - } - - // Invoke recursive function to run filters; this enables chaining of filters via - // iteratively application. - (function run_filter(input_dataset_id, filters) { - var - // Set up filtering info and params. - filter_tuple = filters[0], - tool_id = filter_tuple[0], - tool_filters = filter_tuple[1], - tool_filter_str = "(" + tool_filters.join(") and (") + ")", - url_params = { - cond: tool_filter_str, - input: input_dataset_id, - target_dataset_id: input_dataset_id, - tool_id: tool_id - }, - // Remove current filter. - filters = filters.slice(1); - - $.getJSON(run_tool_url, url_params, function(response) { - if (response.error) { - // General error. - show_modal("Filter Dataset", - "Error running tool " + tool_id, - { "Close" : hide_modal } ); - } - else if (filters.length === 0) { - // No more filters to run. - show_modal("Filtering Dataset", - "Filter(s) are running on the complete dataset. Outputs are in dataset's history.", - { "Close" : hide_modal } ); - } - else { - // More filters to run. - run_filter(response.dataset_id, filters); - } - }); - - })(this.track.dataset_id, active_filters_list); - } -}); - -/** * Generates scale values based on filter and feature's value for filter. */ var FilterScaler = function(filter, default_val) { @@ -3224,7 +2612,7 @@ moveable(track.container_div, track.drag_handle_class, ".group", track); // Attribute init. - this.filters_manager = new FiltersManager(this, ('filters' in obj_dict ? obj_dict.filters : null)); + this.filters_manager = new filters.FiltersManager(this, ('filters' in obj_dict ? obj_dict.filters : null)); // HACK: set filters manager for data manager. // FIXME: prolly need function to set filters and update data_manager reference. this.data_manager.set('filters_manager', this.filters_manager); 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.