Commits

Carl Eberhard committed 5027e04

initial, un-wired prototype for scatterplot visualization

Comments (0)

Files changed (6)

lib/galaxy/visualization/data_providers/basic.py

                 break
             
             fields = line.split()
+            #pre: column indeces should be avail in fields
+            for col_index in cols:
+                assert col_index < len( fields ), (
+                    "column index (%d) must be less than fields length: %d" % ( col_index, len( fields ) ) )
+                
             data.append( [ cast_val( fields[c], self.original_dataset.metadata.column_types[c] ) for c in cols ] )
             
         f.close()

lib/galaxy/web/api/datasets.py

         # Return data.
         data = None
         data_provider = trans.app.data_provider_registry.get_data_provider( raw=True, original_dataset=dataset )
+        
         if data_provider == ColumnDataProvider:
+            #pre: should have column kwargs
+            #print 'kwargs:', kwargs
+            assert 'cols' in kwargs, (
+                "ColumnDataProvider needs a 'cols' parameter in the query string" )
             data = data_provider( original_dataset=dataset ).get_data( **kwargs )
+            
         else:
             # Default to genomic data.
             # FIXME: need better way to set dataset_type.

lib/galaxy/web/controllers/visualization.py

 from galaxy.web.controllers.library import LibraryListGrid
 from galaxy.visualization.genomes import decode_dbkey
 from galaxy.visualization.genome.visual_analytics import get_dataset_job
+from galaxy.visualization.data_providers.basic import ColumnDataProvider
 
 #
 # -- Grids --
     def get_item( self, trans, id ):
         return self.get_visualization( trans, id )
 
+    @web.expose    
+    def scatterplot( self, trans, dataset_id, cols ):
+        # Get HDA.
+        hda = self.get_dataset( trans, dataset_id, check_ownership=False, check_accessible=True )
+        
+        # get some metadata for the page
+        hda_dict = hda.get_api_value()
+        #title = "Scatter plot of {name}:".format( **hda_dict )
+        #subtitle = "{misc_info}".format( **hda_dict )
+        
+        #TODO: add column data
+        # Read data.
+        data_provider = ColumnDataProvider( original_dataset=hda )
+        data = data_provider.get_data( cols )
+        
+        # Return plot.
+        return trans.fill_template_mako( "visualization/scatterplot.mako",
+                                         title=hda.name, subtitle=hda.info,
+                                         hda=hda,
+                                         data=data )
+        
+
     @web.json
     def bookmarks_from_dataset( self, trans, hda_id=None, ldda_id=None ):
         if hda_id:

templates/root/history.mako

 <meta http-equiv="Pragma" content="no-cache">
 
 ${h.css( "base", "history", "autocomplete_tagging" )}
-${h.js( "libs/jquery/jquery", "libs/bootstrap", "galaxy.base", "libs/json2", "libs/jquery/jstorage", "libs/jquery/jquery.autocomplete", "galaxy.autocom_tagging" )}
+${h.js(
+    "libs/jquery/jquery",
+    "libs/bootstrap",
+    "galaxy.base",
+    "libs/json2",
+    "libs/jquery/jstorage",
+    "libs/jquery/jquery.autocomplete",
+    "galaxy.autocom_tagging",
+    "libs/underscore"
+)}
 
 <script type="text/javascript">
 
         });
     }
     
-    init_trackster_links();
+    /**
+     * Create popup menu for visualization icon.
+     */
+    function init_viz_icon(icon) {
+        var icon_link = $(icon);
+        make_popupmenu( icon_link , {
+            "${_("Trackster")}": function() {
+                $.ajax({
+                    url: icon_link.attr("data-url"),
+                    dataType: "html",
+                    error: function() { alert( "Could not add this dataset to browser." ); },
+                    success: function(table_html) {
+                        var parent = window.parent;
 
+                        parent.show_modal("View Data in a New or Saved Visualization", "", {
+                            "Cancel": function() {
+                                parent.hide_modal();
+                            },
+                            "View in saved visualization": function() {
+                                // Show new modal with saved visualizations.
+                                parent.hide_modal();
+                                parent.show_modal("Add Data to Saved Visualization", table_html, {
+                                    "Cancel": function() {
+                                        parent.hide_modal();
+                                    },
+                                    "Add to visualization": function() {
+                                        $(parent.document).find('input[name=id]:checked').each(function() {
+                                            var vis_id = $(this).val();
+                                            parent.location = icon_link.attr("action-url") + "&id=" + vis_id;
+                                        });
+                                    }, 
+                                });
+                            },
+                            "View in new visualization": function() {
+                                parent.location = icon_link.attr("new-url");
+                            }
+                        });
+                    }
+                });
+            },
+            //"${_("Scatterplot")}": function() {
+            //    var data_id = icon_link.parents(".historyItemContainer").attr("id").split("-")[1];
+            //    parent.galaxy_main.location =
+            //        "${h.url_for( controller='visualization', action='scatterplot', col1=9, col2=13 )}"
+            //        + "&dataset_id=" + data_id;
+            //}
+        } );
+        return icon;        
+    };
+    
+    _.each( $(".visualize-icon"), function(icon) {
+        console.debug( 'Init visualization icons, icon:', icon );
+        init_viz_icon(icon);
+    });
+        
     function init_phyloviz_links() {
         // PhyloViz links
         // Add to trackster browser functionality

templates/root/history_common.mako

                             else:
                                 data_url = h.url_for( controller='visualization', action='list_tracks' )
                             %>
-                            <a href="javascript:void(0)" data-url="${data_url}" class="icon-button chart_curve tooltip trackster-add"
-                                action-url="${h.url_for( controller='visualization', action='trackster', dataset_id=dataset_id)}"
-                                new-url="${h.url_for( controller='visualization', action='trackster', dataset_id=dataset_id, default_dbkey=data.dbkey)}" title="View in Trackster"></a>
+                            <!--<a href="javascript:void(0)" data-url="${data_url}" class="icon-button chart_curve tooltip trackster-add"-->
+                            <!--    action-url="${h.url_for( controller='visualization', action='trackster', dataset_id=dataset_id)}"-->
+                            <!--    new-url="${h.url_for( controller='visualization', action='trackster', dataset_id=dataset_id, default_dbkey=data.dbkey)}" title="View in Trackster"></a>-->
+                            <a href="javascript:void(0)" data-url="${data_url}" class="icon-button chart_curve tooltip visualize-icon"
+                                action-url="${h.url_for( controller='tracks', action='browser', dataset_id=dataset_id)}"
+                                new-url="${h.url_for( controller='tracks', action='index', dataset_id=dataset_id, default_dbkey=data.dbkey)}" title="Visualize"></a>
                         %endif
                         <%
                             isPhylogenyData = isinstance(data.datatype,  (Phyloxml, Nexus, Newick))

templates/visualization/scatterplot.mako

+<%inherit file="/base.mako"/>
+
+<%def name="stylesheets()">
+${parent.stylesheets()}
+${h.css( "history", "autocomplete_tagging", "trackster", "overcast/jquery-ui-1.8.5.custom", "library" )}
+
+<style type="text/css">
+* { margin: 0px, padding: 0px; }
+
+.subtitle {
+	margin-left: 1em;
+	margin-top: -1em;
+	color: grey;
+	font-size: small;
+}
+
+.chart {
+	/*shape-rendering: crispEdges;*/
+}
+
+.grid-line {
+	fill: none;
+	stroke: lightgrey;
+	stroke-opacity: 0.5;
+	shape-rendering: crispEdges;
+	stroke-dasharray: 3, 3;
+}
+
+.axis path, .axis line {
+	fill: none;
+	stroke: black;
+	shape-rendering: crispEdges;
+}
+.axis text {
+	font-family: sans-serif;
+	font-size: 12px;
+}
+
+
+circle.bubble {
+	stroke: none;
+	fill: black;
+	fill-opacity: 0.2;
+}
+	
+</style>
+	
+</%def>
+
+<%def name="javascripts()">
+${parent.javascripts()}
+${h.js( "libs/d3" )}
+
+<script type="text/javascript">
+/* =============================================================================
+todo:
+	validate columns (here or server)
+	send: type, column title/name in JSON
+	
+
+	move to obj, possibly view?
+	fetch (new?) data
+	config changes to the graph
+	download svg (png?)
+ 
+============================================================================= */
+function translateStr( x, y ){
+	return 'translate(' + x + ',' + y + ')';
+}
+function rotateStr( d, x, y ){
+	return 'rotate(' + d + ',' + x + ',' + y + ')';
+}
+
+$(function() {
+	// Constants
+	var data = ${data},
+		MAX_DATA_POINTS = 30000,
+		BUBBLE_RADIUS = 5,
+		ENTRY_ANIM_DURATION = 500,
+		X_TICKS = 10, Y_TICKS = 10,
+		X_AXIS_LABEL_BUMP_Y = 40,
+		Y_AXIS_LABEL_BUMP_X = -35,
+		WIDTH = 300,
+		HEIGHT = 300,
+		MARGIN= 50,
+		xLabel = "Magnitude",
+		yLabel = "Depth";
+		
+	// set a cap on the data, limit to first n points
+	data = data.slice( 0, MAX_DATA_POINTS );
+	
+	// split the data into columns
+	//TODO: compute min, max on server.
+	var col1_data = data.map( function(e) { return e[0] }),
+		col2_data = data.map( function(e) { return e[1] }),
+		xMin = d3.min( col1_data ),
+		xMax = d3.max( col1_data ),
+		yMin = d3.min( col2_data ),
+		yMax = d3.max( col2_data );
+	console.log( 'col1_data:', col1_data );
+	console.log( 'col2_data:', col2_data );
+	console.log( 'xMin, xMax, yMin, yMax:', xMin, xMax, yMin, yMax );
+
+	// Set up.
+	d3.select( "body" ).append( "svg:svg" )
+	  .attr( "width",  WIDTH +  ( MARGIN * 2 ) )
+	  .attr( "height", HEIGHT + ( MARGIN * 2 ) )
+	  .attr( "class", "chart" );
+
+	// Scale for x, y based on data domains
+	// origin: bottom, left
+	var x_scale = d3.scale.linear()
+			.domain([ xMin, xMax ])
+			.range([ 0, WIDTH ]),
+		y_scale = d3.scale.linear()
+			.domain([ yMin, yMax ])
+			.range([ HEIGHT, 0 ]);
+	
+	//	Selection of SVG, append group (will group our entire chart), give attributes
+	// apply a group and transform all coords away from margins
+	var chart = d3.select( ".chart" ).append( "svg:g" )		
+		.attr( "class", "content" )
+		.attr( "transform", translateStr( MARGIN, MARGIN ) );
+
+	// axes
+	var xAxisFn = d3.svg.axis()
+		.scale( x_scale )
+		.ticks( X_TICKS )
+		.orient( 'bottom' );
+	var xAxis = chart.append( 'g' ).attr( 'class', 'axis' ).attr( 'id', 'x-axis' )
+		.attr( 'transform', translateStr( 0, HEIGHT ) )
+		.call( xAxisFn )
+	console.debug( 'xAxis:', xAxis ); window.xAxis = xAxis, window.xAxisFn = xAxisFn;
+	
+	var xAxisLabel = xAxis.append( 'text' ).attr( 'class', 'axis-label' ).attr( 'id', 'x-axis-label' )
+		.attr( 'x', WIDTH / 2 )
+		.attr( 'y', X_AXIS_LABEL_BUMP_Y )
+		.attr( 'text-anchor', 'middle' )
+		.text( xLabel );
+	console.debug( 'xAxisLabel:', xAxisLabel ); window.xAxisLabel = xAxisLabel;
+
+	var yAxisFn = d3.svg.axis()
+		.scale( y_scale )
+		.ticks( Y_TICKS )
+		.orient( 'left' );
+	var yAxis = chart.append( 'g' ).attr( 'class', 'axis' ).attr( 'id', 'y-axis' )
+		.call( yAxisFn );
+	console.debug( 'yAxis:', yAxis ); window.yAxis = yAxis, window.yAxisFn = yAxisFn;
+
+	var yAxisLabel = yAxis.append( 'text' ).attr( 'class', 'axis-label' ).attr( 'id', 'y-axis-label' )
+		.attr( 'x', Y_AXIS_LABEL_BUMP_X )
+		.attr( 'y', HEIGHT / 2 )
+		.attr( 'text-anchor', 'middle' )
+		.attr( 'transform', rotateStr( -90, Y_AXIS_LABEL_BUMP_X, HEIGHT / 2 ) )
+		.text( yLabel );
+	console.debug( 'yAxisLabel:', yAxisLabel ); window.yAxisLabel = yAxisLabel;
+
+	// grid lines
+	var hGridLines = chart.selectAll( '.h-grid-line' )
+			.data( x_scale.ticks( xAxisFn.ticks()[0] ) )
+		.enter().append( 'svg:line' )
+			.classed( 'grid-line h-grid-line', true )
+			.attr( 'x1', x_scale ).attr( 'y1', 0 )
+			.attr( 'x2', x_scale ).attr( 'y2', HEIGHT )
+	console.debug( 'hGridLines:', hGridLines ); window.hGridLines = hGridLines;
+	
+	var vGridLines = chart.selectAll( '.v-grid-line' )
+			.data( y_scale.ticks( yAxisFn.ticks()[0] ) )
+		.enter().append( 'svg:line' )
+			.classed( 'grid-line v-grid-line', true )
+			.attr( 'x1', 0 )    .attr( 'y1', y_scale )
+			.attr( 'x2', WIDTH ).attr( 'y2', y_scale )
+	console.debug( 'vGridLines:', vGridLines ); window.vGridLines = vGridLines;
+	
+	// Functions used to render plot.
+	var xPosFn = function( d, i ){
+		return x_scale( col1_data[ i ] );
+	};
+	var yPosFn = function( d, i ){
+		return y_scale( col2_data[ i ] );
+	};
+
+	// Create bubbles for each data point.
+	chart.selectAll( "circle.bubble" )
+		.data(data).enter()
+		.append( "svg:circle" ).attr( "class", "bubble" )
+			// start all bubbles at corner...
+			.attr( "r", 0 )			
+			.attr( "fill", "white" )
+			// ...animate to final position
+			.transition().duration( ENTRY_ANIM_DURATION )
+				.attr("cx", xPosFn )
+				.attr("cy", yPosFn )			
+				.attr("r", BUBBLE_RADIUS);
+				
+	//TODO: on hover red line to axes, display values
+});
+</script>
+</%def>
+
+<%def name="body()">
+    <h1 class="title">Scatterplot of '${title}':</h1>
+    <h2 class="subtitle">${subtitle}</h2>
+	
+</%def>