diff --git a/config/plugins/visualizations/scatterplot/Gruntfile.js b/config/plugins/visualizations/scatterplot/Gruntfile.js index 55b9a3b7a8d..019b676499f 100644 --- a/config/plugins/visualizations/scatterplot/Gruntfile.js +++ b/config/plugins/visualizations/scatterplot/Gruntfile.js @@ -6,6 +6,7 @@ module.exports = function(grunt) { pkg: grunt.file.readJSON( 'package.json' ), handlebars: { + // compile all hb templates into a single file in the build dir compile: { options: { namespace: 'Templates', @@ -20,6 +21,7 @@ module.exports = function(grunt) { }, concat: { + // concat the template file and any js files in the src dir into a single file in the build dir options: { separator: ';\n' }, @@ -31,16 +33,19 @@ module.exports = function(grunt) { }, uglify: { + // uglify the concat single file directly into the static dir options: { + //mangle : false, + //beautify : true }, dist: { src : 'build/scatterplot-concat.js', - // uglify directly into static dir - dest: 'static/scatterplot.js' + dest: 'static/scatterplot-edit.js' } }, watch: { + // watch for changes in the src dir files: [ 'src/**.js', 'src/handlebars/*.handlebars' ], tasks: [ 'default' ] } @@ -52,5 +57,6 @@ module.exports = function(grunt) { grunt.loadNpmTasks( 'grunt-contrib-watch' ); grunt.registerTask( 'default', [ 'handlebars', 'concat', 'uglify' ]); - grunt.registerTask( 'watch', [ 'handlebars', 'concat', 'uglify', 'watch' ]); + // you can run grunt watch directly: + // grunt watch }; diff --git a/config/plugins/visualizations/scatterplot/package.json b/config/plugins/visualizations/scatterplot/package.json index d0c81baafb2..84909ef83bf 100644 --- a/config/plugins/visualizations/scatterplot/package.json +++ b/config/plugins/visualizations/scatterplot/package.json @@ -19,6 +19,6 @@ "grunt-contrib-handlebars": "~0.5.10", "grunt-contrib-concat": "~0.3.0", "grunt-contrib-uglify": "~0.2.2", - "grunt-contrib-watch": "~0.5.1" + "grunt-contrib-watch": "~0.5.3" } } diff --git a/config/plugins/visualizations/scatterplot/src/handlebars/chartControl.handlebars b/config/plugins/visualizations/scatterplot/src/handlebars/chartControl.handlebars deleted file mode 100644 index b08521fbc8d..00000000000 --- a/config/plugins/visualizations/scatterplot/src/handlebars/chartControl.handlebars +++ /dev/null @@ -1,56 +0,0 @@ -

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

- -
- -
{{datapointSize}}
-
-

- Size of the graphic representation of each data point -

-
- -
- - -

- Uncheck this to disable the animations used on the chart -

-
- -
- -
{{width}}
-
-

- (not including chart margins and axes) -

-
- -
- -
{{height}}
-
-

- (not including chart margins and axes) -

-
- -
- - -

-
- -
- - -

-
- - \ No newline at end of file diff --git a/config/plugins/visualizations/scatterplot/src/handlebars/chartDisplay.handlebars b/config/plugins/visualizations/scatterplot/src/handlebars/chartDisplay.handlebars deleted file mode 100644 index f1c54ebc2f0..00000000000 --- a/config/plugins/visualizations/scatterplot/src/handlebars/chartDisplay.handlebars +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/config/plugins/visualizations/scatterplot/src/handlebars/chartcontrol.handlebars b/config/plugins/visualizations/scatterplot/src/handlebars/chartcontrol.handlebars new file mode 100644 index 00000000000..8ac966fa7d0 --- /dev/null +++ b/config/plugins/visualizations/scatterplot/src/handlebars/chartcontrol.handlebars @@ -0,0 +1,47 @@ +

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

+ +
+ +
{{datapointSize}}
+
+

+ Size of the graphic representation of each data point +

+
+ +
+ +
{{width}}
+
+

+ (not including chart margins and axes) +

+
+ +
+ +
{{height}}
+
+

+ (not including chart margins and axes) +

+
+ +
+ + +

+
+ +
+ + +

+
+ + diff --git a/config/plugins/visualizations/scatterplot/src/handlebars/dataControl.handlebars b/config/plugins/visualizations/scatterplot/src/handlebars/dataControl.handlebars deleted file mode 100644 index 3da6dc4b946..00000000000 --- a/config/plugins/visualizations/scatterplot/src/handlebars/dataControl.handlebars +++ /dev/null @@ -1,56 +0,0 @@ -

- Use the following controls to change the data used by the chart. - Use the 'Draw' button to render (or re-render) the chart with the current settings. -

- - {{! column selector containers }} -
- - -
-
- - -
- - {{! optional id column }} -
- - -

- These will be displayed (along with the x and y values) when you hover over - a data point. -

-
- - - {{! if we're using generic column selection names ('column 1') - allow the user to use the first line }} - - - -
\ No newline at end of file diff --git a/config/plugins/visualizations/scatterplot/src/handlebars/datacontrol.handlebars b/config/plugins/visualizations/scatterplot/src/handlebars/datacontrol.handlebars new file mode 100644 index 00000000000..2e7eb35dc8f --- /dev/null +++ b/config/plugins/visualizations/scatterplot/src/handlebars/datacontrol.handlebars @@ -0,0 +1,55 @@ +

+ Use the following controls to change the data used by the chart. + Use the 'Draw' button to render (or re-render) the chart with the current settings. +

+ +{{! column selector containers }} +
+ + +
+
+ + +
+ +{{! optional id column }} +
+ + +

+ These will be displayed (along with the x and y values) when you hover over + a data point. +

+
+ + +{{! if we're using generic column selection names ('column 1') - allow the user to use the first line }} + + + diff --git a/config/plugins/visualizations/scatterplot/src/handlebars/editor.handlebars b/config/plugins/visualizations/scatterplot/src/handlebars/editor.handlebars new file mode 100644 index 00000000000..3d6b256c883 --- /dev/null +++ b/config/plugins/visualizations/scatterplot/src/handlebars/editor.handlebars @@ -0,0 +1,36 @@ +
+ {{! tab buttons/headers using Bootstrap }} + + + {{! data form, chart config form, stats, and chart all get their own tab }} +
+ {{! ---------------------------- tab for data settings form }} +
+ {{! rendered separately }} +
+ + {{! ---------------------------- tab for chart graphics control form }} +
+ {{! rendered separately }} +
+ + {{! ---------------------------- tab for actual chart }} +
+ +
{{! end .tab-content }} +
{{! end .chart-control }} diff --git a/config/plugins/visualizations/scatterplot/src/handlebars/scatterplotControlForm.handlebars b/config/plugins/visualizations/scatterplot/src/handlebars/scatterplotControlForm.handlebars deleted file mode 100644 index f203450f49e..00000000000 --- a/config/plugins/visualizations/scatterplot/src/handlebars/scatterplotControlForm.handlebars +++ /dev/null @@ -1,46 +0,0 @@ -{{! main layout }} - -
- {{! tab buttons/headers using Bootstrap }} - - - {{! data form, chart config form, stats, and chart all get their own tab }} -
- {{! ---------------------------- tab for data settings form }} -
- {{! rendered separately }} -
- - {{! ---------------------------- tab for chart graphics control form }} -
- {{! rendered separately }} -
- - {{! ---------------------------- tab for data statistics }} -
- {{! rendered separately }} -
- - {{! ---------------------------- tab for actual chart }} -
- {{! chart rendered separately }} -
- -
{{! end .tab-content }} -
{{! end .chart-control }} \ No newline at end of file diff --git a/config/plugins/visualizations/scatterplot/src/handlebars/statsDisplay.handlebars b/config/plugins/visualizations/scatterplot/src/handlebars/statsDisplay.handlebars deleted file mode 100644 index 038b4ea7b71..00000000000 --- a/config/plugins/visualizations/scatterplot/src/handlebars/statsDisplay.handlebars +++ /dev/null @@ -1,8 +0,0 @@ -

By column:

- - - {{#each stats}} - - - {{/each}} -
XY
{{name}}{{xval}}{{yval}}
\ No newline at end of file diff --git a/config/plugins/visualizations/scatterplot/src/scatterplot-config-editor.js b/config/plugins/visualizations/scatterplot/src/scatterplot-config-editor.js new file mode 100644 index 00000000000..5dc81d982b4 --- /dev/null +++ b/config/plugins/visualizations/scatterplot/src/scatterplot-config-editor.js @@ -0,0 +1,237 @@ +/* ============================================================================= +todo: + Remove 'chart' names + Make this (the config control/editor) and the ScatterplotView (in scatterplot.js) both + views onto a visualization/revision model + Move margins into wid/hi calcs (so final svg dims are w/h) + Better separation of AJAX in scatterplot.js (maybe pass in function?) + Labels should auto fill in chart control when dataset has column_names + Allow column selection/config using the peek output as a base for UI + Allow setting perPage of config + Auto render if given data and/or config + Allow option to auto set width/height based on screen real estate avail. + Use d3.nest to allow grouping, pagination/filtration by group (e.g. chromCol) + Semantic HTML (figure, caption) + Save as visualization, load from visualization + Save as SVG/png + Does it work w/ Galaxy.Frame? + Embedding + Small multiples + Drag & Drop other splots onto current (redraw with new axis and differentiate the datasets) + + Subclass on specific datatypes? (vcf, cuffdiff, etc.) + What can be common/useful to other visualizations? + +============================================================================= */ +/** + * Scatterplot config control UI as a backbone view + * handles: + * configuring which data will be used + * configuring the plot display + */ +var ScatterplotConfigEditor = BaseView.extend( LoggableMixin ).extend({ + //TODO: !should be a view on a visualization model + //logger : console, + className : 'scatterplot-control-form', + + /** initialize requires a configuration Object containing a dataset Object */ + initialize : function( attributes ){ + //console.log( this + '.initialize, attributes:', attributes ); + if( !attributes || !attributes.config || !attributes.config.dataset ){ + throw new Error( "ScatterplotView requires a configuration and dataset" ); + } + this.dataset = attributes.config.dataset; + //console.log( 'dataset:', this.dataset ); + + this.plotView = new ScatterplotView({ + config : attributes.config + }); + }, + + // ------------------------------------------------------------------------- CONTROLS RENDERING + render : function(){ + //console.log( this + '.render' ); + + // render the tab controls, areas and loading indicator + this.$el.append( ScatterplotConfigEditor.templates.mainLayout({ + })); + + // render the tab content + this.$el.find( '#data-control' ).append( this._render_dataControl() ); + this._render_chartControls( this.$el.find( '#chart-control' ) ); + //this.$statsDisplay = this.$el.find( '.tab-pane#stats-display' ); + this._render_chartDisplay(); + + //TODO: auto render if given both x, y column choices in query for page + + // set up behaviours + this.$el.find( '[title]' ).tooltip(); + + // uncomment any of the following to have that tab show on initial load (for testing) + //this.$el.find( 'ul.nav' ).find( 'a[href="#data-control"]' ).tab( 'show' ); + //this.$el.find( 'ul.nav' ).find( 'a[href="#chart-control"]' ).tab( 'show' ); + //this.$el.find( 'ul.nav' ).find( 'a[href="#stats-display"]' ).tab( 'show' ); + //this.$el.find( 'ul.nav' ).find( 'a[href="#chart-display"]' ).tab( 'show' ); + return this; + }, + + _render_dataControl : function(){ + // controls for which columns are used to plot datapoints (and ids/additional info to attach if desired) + var dataset = this.dataset; + //console.log( 'metadata_column_types:', this.dataset.metadata_column_types ); + //console.log( 'metadata_column_names:', this.dataset.metadata_column_names ); + + var allColumns = _.map( dataset.metadata_column_types, function( type, i ){ + var column = { index: i, type: type, name: ( 'column ' + ( i + 1 ) ) }; + if( dataset.metadata_column_names && dataset.metadata_column_names[ i ] ){ + column.name = dataset.metadata_column_names[ i ]; + } + return column; + }); + var numericColumns = _.filter( allColumns, function( column, i ){ + return ( ( column.type === 'int' ) || ( column.type === 'float' ) ); + }); + if( numericColumns < 2 ){ + numericColumns = allColumns; + } + //console.log( 'allColumns:', allColumns ); + //console.log( 'numericColumns:', numericColumns ); + + // render the html + var $dataControl = this.$el.find( '.tab-pane#data-control' ); + $dataControl.html( ScatterplotConfigEditor.templates.dataControl({ + allColumns : allColumns, + numericColumns : numericColumns + })); + + // preset to column selectors if they were passed in the config in the query string + $dataControl.find( '[name="xColumn"]' ).val( this.plotView.config.xColumn || numericColumns[0].index ); + $dataControl.find( '[name="yColumn"]' ).val( this.plotView.config.yColumn || numericColumns[1].index ); + if( this.plotView.config.idColumn !== undefined ){ + $dataControl.find( '#include-id-checkbox' ).prop( 'checked', true ).trigger( 'change' ); + $dataControl.find( 'select[name="idColumn"]' ).val( this.plotView.config.idColumn ); + } + + return $dataControl; + }, + + _render_chartControls : function( $chartControls ){ + // tab content to control how the chart is rendered (data glyph size, chart size, etc.) + $chartControls.html( ScatterplotConfigEditor.templates.chartControl( this.plotView.config ) ); + //console.debug( '$chartControl:', $chartControls ); + + // set up behaviours, js on sliders + //console.debug( 'numeric sliders:', $chartControls.find( '.numeric-slider-input' ) ); + // what to do when the slider changes: update display and update chartConfig + var view = this, + // limits for controls (by control/chartConfig id) + //TODO: move into TwoVarScatterplot + controlRanges = { + 'datapointSize' : { min: 2, max: 10, step: 1 }, + 'width' : { min: 200, max: 800, step: 20 }, + 'height' : { min: 200, max: 800, step: 20 } + }; + + function onSliderChange(){ + var $this = $( this ); + $this.siblings( '.slider-output' ).text( $this.slider( 'value' ) ); + } + $chartControls.find( '.numeric-slider-input' ).each( function(){ + var $this = $( this ), + configKey = $this.attr( 'data-config-key' ), + sliderSettings = _.extend( controlRanges[ configKey ], { + value : view.plotView.config[ configKey ], + change : onSliderChange, + slide : onSliderChange + }); + //console.debug( configKey + ' slider settings:', sliderSettings ); + $this.find( '.slider' ).slider( sliderSettings ); + }); +//TODO: to more common area (like render)? + // set label inputs to current x, y metadata_column_names (if any) + if( this.dataset.metadata_column_names ){ + //var colNames = this.dataset.metadata_column_names; + //$chartControls.find( 'input[name="X-axis-label"]' ).val( colNames ); + //$chartControls.find( 'input[name="Y-axis-label"]' ).val( colNames ); +//TODO: on change of x, y data controls + } + + //console.debug( '$chartControls:', $chartControls ); + return $chartControls; + }, + + _render_chartDisplay : function(){ + // render the tab content where the chart is displayed (but not the chart itself) + var $chartDisplay = this.$el.find( '.tab-pane#chart-display' ); + this.plotView.setElement( $chartDisplay ); + this.plotView.render(); + return $chartDisplay; + }, + + // ------------------------------------------------------------------------- EVENTS + events : { + 'change #include-id-checkbox' : 'toggleThirdColumnSelector', + 'click #data-control .render-button' : 'renderChart', + 'click #chart-control .render-button' : 'renderChart' + }, + + toggleThirdColumnSelector : function(){ + // show/hide the id selector on the data settings panel + this.$el.find( 'select[name="idColumn"]' ).parent().toggle(); + }, + + // ------------------------------------------------------------------------- CHART/STATS RENDERING + renderChart : function(){ + //console.log( this + '.renderChart' ); + // fetch the data, (re-)render the chart + this.$el.find( '.nav li.disabled' ).removeClass( 'disabled' ); + this.updateConfigWithDataSettings(); + this.updateConfigWithChartSettings(); + this.$el.find( 'ul.nav' ).find( 'a[href="#chart-display"]' ).tab( 'show' ); + this.plotView.fetchData(); + //console.debug( this.plotView.$el ); + }, + + // ------------------------------------------------------------------------- GET DATA/CHART SETTINGS + updateConfigWithDataSettings : function(){ + // parse the column values for both indeces (for the data fetch) and names (for the chart) + var $dataControls = this.$el.find( '#data-control' ); + var settings = { + xColumn : $dataControls.find( '[name="xColumn"]' ).val(), + yColumn : $dataControls.find( '[name="yColumn"]' ).val() + }; + if( $dataControls.find( '#include-id-checkbox' ).prop( 'checked' ) ){ + settings.idColumn = $dataControls.find( '[name="idColumn"]' ).val(); + } + //console.log( '\t data settings:', settings ); + return _.extend( this.plotView.config, settings ); + }, + + updateConfigWithChartSettings : function(){ + // gets the user-selected chartConfig from the chart settings panel + var plotView = this.plotView, + $chartControls = this.$el.find( '#chart-control' ); + // use a loop of config keys to get the form values for these sliders + [ 'datapointSize', 'width', 'height' ].forEach( function( v, i ){ + plotView.config[ v ] = $chartControls.find( '.numeric-slider-input[data-config-key="' + v + '"]' ) + .find( '.slider' ).slider( 'value' ); + }); + // update axes labels using chartSettings inputs (if not at defaults), otherwise the selects' colName + plotView.config.x.label = $chartControls.find( 'input[name="X-axis-label"]' ).val(); + plotView.config.y.label = $chartControls.find( 'input[name="Y-axis-label"]' ).val(); + //console.log( '\t chartSettings:', settings ); + return plotView.config; + }, + + toString : function(){ + return 'ScatterplotConfigEditor(' + (( this.dataset )?( this.dataset.id ):( '' )) + ')'; + } +}); + +ScatterplotConfigEditor.templates = { + mainLayout : Templates.editor, + dataControl : Templates.datacontrol, + chartControl : Templates.chartcontrol +}; + +//============================================================================== diff --git a/config/plugins/visualizations/scatterplot/src/scatterplot-display.js b/config/plugins/visualizations/scatterplot/src/scatterplot-display.js new file mode 100644 index 00000000000..dd0258369b8 --- /dev/null +++ b/config/plugins/visualizations/scatterplot/src/scatterplot-display.js @@ -0,0 +1,288 @@ +// ============================================================================= +/** + * Scatterplot display control UI as a backbone view + * handles: + * fetching the data (if needed) + * computing and displaying data stats + * controls for pagination of data (if needed) + */ +var ScatterplotView = Backbone.View.extend({ + //TODO: should be a view on visualization(revision) model + + defaults : { + dataset : { + }, + metadata : { + dataLines : undefined + }, + + ajaxFn : null, + + pagination : { + currPage : 0, + perPage : 3000 + }, + + width : 400, + height : 400, + + margin : { + top : 16, + right : 16, + bottom : 40, + left : 54 + }, + + x : { + ticks : 10, + label : 'X' + }, + y : { + ticks : 10, + label : 'Y' + }, + + datapointSize : 4, + animDuration : 500 + }, + + initialize : function( attributes ){ + this.config = _.extend( _.clone( this.defaults ), attributes.config || {}); + //console.debug( this + '.config:', this.config ); + }, + + updateConfig : function( newConfig ){ + //console.log( this + '.updateConfig:', newConfig ); + this.config = this.config || {}; + //TODO: validate here + _.extend( this.config, newConfig ); + //TODO: implement rerender flag + }, + + fetchData : function(){ +//TODO: doesn't work bc it's rendered in render()... + this.showLoadingIndicator( 'getting data' ); + //console.debug( 'currPage', this.config.pagination.currPage ); + var view = this; +//TODO: very tied to datasets - should be generalized eventually + xhr = jQuery.getJSON( '/api/datasets/' + this.config.dataset.id, { + data_type : 'raw_data', + provider : 'dataset-column', + limit : this.config.pagination.perPage, + offset : ( this.config.pagination.currPage * this.config.pagination.perPage ) + }); + xhr.done( function( data ){ + view.renderData( data.data ); + }); + xhr.fail( function( xhr, status, message ){ + alert( 'Error loading data:\n' + xhr.responseText ); + console.error( xhr, status, message ); + }); + xhr.always( function(){ + view.hideLoadingIndicator(); + }); + return xhr; + }, + + render : function( data ){ + this.$el.addClass( 'scatterplot-display' ).html([ + '
', + '
', + '', + '', + '
', + '', //TODO: id + '
' + ].join( '' )); + this.$el.children().hide(); + + if( data ){ + this.renderData( data ); + } + return this; + }, + + showLoadingIndicator : function( message, speed ){ + // display the loading indicator over the tab panels if hidden, update message (if passed) +//TODO: move loading indicator into data-info-text + message = message || ''; + speed = speed || 'fast'; + var $indicator = this.$el.find( '.loading-indicator' ); + + if( message ){ $indicator.find( '.loading-indicator-message' ).text( message ); } + if( !$indicator.is( ':visible' ) ){ + this.toggleStats( false ); + $indicator.css({ left: ( this.config.width / 2 ), top: this.config.height / 2 }).show(); + } + }, + + hideLoadingIndicator : function( speed ){ + speed = speed || 'fast'; + this.$el.find( '.loading-indicator' ).hide(); + }, + + renderData : function( data ){ + this.$el.find( '.controls' ).empty().append( this.renderControls( data ) ).show(); + this.renderPlot( data ); + this.getStats( data ); + }, + + renderControls : function( data ){ + var view = this; + var $left = $( '
' ), + $right = $( '
' ); + + $left.append([ + this.renderPrevNext( data ), + this.renderPagination( data ) + ]); + $right.append([ + this.renderLineInfo( data ), + $( '' ).addClass( 'stats-toggle-btn' ) + .click( function(){ + view.toggleStats(); + }), + $( '' ).addClass( 'rerender-btn' ) + .click( function(){ + view.renderPlot( data ); + }) + ]); + return [ $left, $right ]; + }, + + renderLineInfo : function( data ){ + var totalLines = this.config.dataset.metadata_data_lines || 'an unknown number of', + lineStart = ( this.config.pagination.currPage * this.config.pagination.perPage ), + lineEnd = lineStart + data.length; + return $( '

' ).addClass( 'scatterplot-data-info' ) + .text([ 'Displaying lines', lineStart + 1, 'to', lineEnd, 'of', totalLines, 'lines' ].join( ' ' )); + }, + + renderPrevNext : function( data ){ + // this is cra-zazy + if( !data + || ( this.config.pagination.currPage === 0 && data.length < this.config.pagination.perPage ) ){ return null; } + + function makePage$Li( text ){ + return $([ '

  • ', text, '
  • ' ].join( '' )); + } +//TODO: cache numPages/numLines in config + var view = this, + dataLines = this.config.dataset.metadata_data_lines, + numPages = ( dataLines )?( Math.ceil( dataLines / this.config.pagination.perPage ) ):( undefined ); + //console.debug( 'data:', this.config.dataset.metadata_data_lines, 'numPages:', numPages ); + + // prev next buttons + var $prev = makePage$Li( 'Prev' ).click( function(){ + if( view.config.pagination.currPage > 0 ){ + view.config.pagination.currPage -= 1; + view.fetchData(); + } + }), + $next = makePage$Li( 'Next' ).click( function(){ + if( !numPages || view.config.pagination.currPage < ( numPages - 1 ) ){ + view.config.pagination.currPage += 1; + view.fetchData(); + } + }), + $prevNextList = $( '