diff --git a/config/plugins/visualizations/charts/config/charts.xml b/config/plugins/visualizations/charts/config/charts.xml index d18b3a4e5d3..98623288dc3 100644 --- a/config/plugins/visualizations/charts/config/charts.xml +++ b/config/plugins/visualizations/charts/config/charts.xml @@ -7,15 +7,9 @@ tabular.Tabular dataset_id - - Visualization - charts - visualization_id - dataset_id - visualization_id diff --git a/config/plugins/visualizations/common/templates/config_utils.mako b/config/plugins/visualizations/common/templates/config_utils.mako new file mode 100644 index 00000000000..49f5e188604 --- /dev/null +++ b/config/plugins/visualizations/common/templates/config_utils.mako @@ -0,0 +1,46 @@ +<%def name="add_config_defaults( defaults )"> +## overwrite default_config_dict with config (if any) then assign to config +<% + for key, default in defaults.items(): + if key not in config or config[ key ] is None: + config[ key ] = default +%> + + +<%def name="config_form( config_dict )"> +## render form for everything in possible config + + +<%def name="link_to_change_config( link_contents, new_settings, target='' )"> +<% + # assumes there's a config var + url_for_args = { + 'controller' : 'visualization', + 'action' : 'render', + 'visualization_name' : visualization_name, + 'title' : title + } + url_for_args.update( config ) + url_for_args.update( new_settings ) + if visualization_id: + url_for_args[ 'id' ] = visualization_id +%> + ${link_contents} + + +<%def name="save_button( text='Save' )"> +<% + # still a GET + url_for_args = { + 'controller' : 'visualization', + 'action' : 'saved', + 'type' : visualization_name, + 'title' : title, + 'config' : h.to_json_string( config ) + } + # save to existing visualization + if visualization_id: + url_for_args[ 'id' ] = visualization_id +%> +
+ diff --git a/config/plugins/visualizations/visualization_base.mako b/config/plugins/visualizations/common/templates/visualization_base.mako similarity index 100% rename from config/plugins/visualizations/visualization_base.mako rename to config/plugins/visualizations/common/templates/visualization_base.mako diff --git a/config/plugins/visualizations/scatterplot/config/scatterplot.xml b/config/plugins/visualizations/scatterplot/config/scatterplot.xml index cfecc5f3847..642b528dd0f 100644 --- a/config/plugins/visualizations/scatterplot/config/scatterplot.xml +++ b/config/plugins/visualizations/scatterplot/config/scatterplot.xml @@ -7,15 +7,9 @@ tabular.Tabular dataset_id - - Visualization - scatterplot - visualization_id - dataset_id - visualization_id diff --git a/config/plugins/visualizations/scatterplot/src/handlebars/editor.handlebars b/config/plugins/visualizations/scatterplot/src/handlebars/editor.handlebars index 3d6b256c883..5b0859bd07b 100644 --- a/config/plugins/visualizations/scatterplot/src/handlebars/editor.handlebars +++ b/config/plugins/visualizations/scatterplot/src/handlebars/editor.handlebars @@ -10,14 +10,20 @@ Chart Controls - {{! both stats and chart start as disabled since there's no info yet }} + {{! chart starts as disabled since there's no info yet }}
  • Chart
  • + {{! ... }} +
  • + + +
  • - {{! data form, chart config form, stats, and chart all get their own tab }} + {{! data form, chart config form, chart all get their own tab }}
    {{! ---------------------------- tab for data settings form }}
    diff --git a/config/plugins/visualizations/scatterplot/src/scatterplot-config-editor.js b/config/plugins/visualizations/scatterplot/src/scatterplot-config-editor.js index 204deefeb90..1089f511a57 100644 --- a/config/plugins/visualizations/scatterplot/src/scatterplot-config-editor.js +++ b/config/plugins/visualizations/scatterplot/src/scatterplot-config-editor.js @@ -1,24 +1,22 @@ /* ============================================================================= todo: - Remove 'chart' names - Make this (the config control/editor) and the ScatterplotView (in scatterplot.js) both - views onto a visualization/revision model + import button(display), func(model) - when user doesn't match 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 setting perPage in config Allow option to auto set width/height based on screen real estate avail. Handle large number of pages better (Known genes hg19) 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) + Remove 'chart' names + Somehow link out from info box? Subclass on specific datatypes? (vcf, cuffdiff, etc.) What can be common/useful to other visualizations? @@ -40,138 +38,190 @@ var ScatterplotConfigEditor = Backbone.View.extend( LoggableMixin ).extend({ if( !this.model ){ this.model = new Visualization({ type: 'scatterplot' }); } - console.log( this + '.initialize, attributes:', attributes ); + this.log( this + '.initialize, attributes:', attributes ); if( !attributes || !attributes.dataset ){ throw new Error( "ScatterplotConfigEditor requires a dataset" ); } this.dataset = attributes.dataset; - console.log( 'dataset:', this.dataset ); + this.log( 'dataset:', this.dataset ); -//TODO: ScatterplotView -> ScatterplotDisplay, this.plotView -> this.display - this.plotView = new ScatterplotView({ + this.display = new ScatterplotDisplay({ dataset : attributes.dataset, model : this.model -//TODO: if data }); }, // ------------------------------------------------------------------------- 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.empty().append( ScatterplotConfigEditor.templates.mainLayout({})); + if( this.model.id ){ + this.$el.find( '.copy-btn' ).show(); + this.$el.find( '.save-btn' ).text( 'Update saved' ); + } 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' ); + // render the tab content + this._render_dataControl(); + this._render_chartControls(); + this._render_chartDisplay(); + + // set up behaviours + + // auto render if given both x, y column choices + var config = this.model.get( 'config' ); + if( this.model.id && _.isFinite( config.xColumn ) && _.isFinite( config.yColumn ) ){ + this.renderChart(); + } 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; + /** controls for which columns are used to plot datapoints (and ids/additional info to attach if desired) */ + _render_dataControl : function( $where ){ + //TODO: better handling of missing column names, column types + $where = $where || this.$el; + var editor = this, + dataset = this.dataset, + column_names = dataset.metadata_column_names || [], + config = this.model.get( 'config' ); //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 ){ +//TODO: to peek based control + var numericColumns = [], + allColumns = _.map( dataset.metadata_column_types, function( type, i ){ + // save column data for select rendering, adding metadata name if available in dataset + var column = { index: i, type: type, name: ( column_names[ i ] || ( 'column ' + ( i + 1 )) ) }; + // also add column to numerics if numeric type + if( ( column.type === 'int' ) || ( column.type === 'float' ) ){ + numericColumns.push( column ); + } + return column; + }); + if( numericColumns.length < 2 ){ numericColumns = allColumns; } //console.log( 'allColumns:', allColumns ); //console.log( 'numericColumns:', numericColumns ); // render the html - var $dataControl = this.$el.find( '.tab-pane#data-control' ); + var $dataControl = $where.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 ){ +//TODO: column selection boilerplate + // preset to column selectors if they were passed in the config in the query string; set up events + var newConfig = { + xColumn : ( _.isFinite( config.xColumn ) )? ( config.xColumn ): ( numericColumns[0].index ), + yColumn : ( _.isFinite( config.yColumn ) )? ( config.yColumn ): ( numericColumns[1].index ), + idColumn : allColumns[0].index + }; + // use an idColumn from the config or attempt to get one different from the numeric + if( _.isFinite( config.idColumn ) ){ + newConfig.idColumn = config.idColumn; + } else { + if( allColumns.length > 2 ){ + var uniqueCol = _.find( allColumns, function( column, i ){ + return i !== newConfig.xColumn && i !== newConfig.yColumn; + }); + newConfig.idColumn = uniqueCol.index; + } + } + config = this.model.set( 'config', newConfig, { silent: true }).get( 'config' ); + + $dataControl.find( '[name="xColumn"]' ).val( config.xColumn ).on( 'change', function(){ + editor.model.set( 'config', { xColumn: Number( $( this ).val() ) }); + }); + $dataControl.find( '[name="yColumn"]' ).val( config.yColumn ).on( 'change', function(){ + editor.model.set( 'config', { yColumn: Number( $( this ).val() ) }); + }); + $dataControl.find( 'select[name="idColumn"]' ).val( config.idColumn ).on( 'change', function(){ + editor.model.set( 'config', { idColumn: Number( $( this ).val() ) }); + }); + if( config.idColumn !== undefined ){ $dataControl.find( '#include-id-checkbox' ).prop( 'checked', true ).trigger( 'change' ); - $dataControl.find( 'select[name="idColumn"]' ).val( this.plotView.config.idColumn ); } + $dataControl.find( '[title]' ).tooltip(); 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 ) ); + /** tab content to control how the chart is rendered (data glyph size, chart size, etc.) */ + _render_chartControls : function( $where ){ + $where = $where || this.$el; + var editor = this, + config = this.model.get( 'config' ), + $chartControls = $where.find( '#chart-control' ); + + // ---- skeleton/form for controls + $chartControls.html( ScatterplotConfigEditor.templates.chartControl( 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 = { + // ---- slider controls + // limits for controls (by control/chartConfig id) + //TODO: as class attribute + var 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' ) ); + // set the model config when changed and update the slider output text + var $this = $( this ), + //note: returns a number nicely enough + newVal = $this.slider( 'value' ); + // parent of slide event target has html5 attr data-config-key + editor.model.set( 'config', _.object([[ $this.parent().data( 'config-key' ), newVal ]]) ); + $this.siblings( '.slider-output' ).text( newVal ); } + + //console.debug( 'numeric sliders:', $chartControls.find( '.numeric-slider-input' ) ); $chartControls.find( '.numeric-slider-input' ).each( function(){ + // set up the slider with control ranges, change event; set output text to initial value var $this = $( this ), configKey = $this.attr( 'data-config-key' ), sliderSettings = _.extend( controlRanges[ configKey ], { - value : view.plotView.config[ configKey ], + value : config[ configKey ], change : onSliderChange, slide : onSliderChange }); //console.debug( configKey + ' slider settings:', sliderSettings ); $this.find( '.slider' ).slider( sliderSettings ); + $this.children( '.slider-output' ).text( config[ configKey ] ); }); -//TODO: to more common area (like render)? + + // ---- axes labels + var columnNames = this.dataset.metadata_column_names || []; + var xLabel = config.xLabel || columnNames[ config.xColumn ] || 'X'; + var yLabel = config.yLabel || columnNames[ config.yColumn ] || 'Y'; // 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 - } + $chartControls.find( 'input[name="X-axis-label"]' ).val( xLabel ) + .on( 'change', function(){ + editor.model.set( 'config', { xLabel: $( this ).val() }); + }); + $chartControls.find( 'input[name="Y-axis-label"]' ).val( yLabel ) + .on( 'change', function(){ + editor.model.set( 'config', { yLabel: $( this ).val() }); + }); //console.debug( '$chartControls:', $chartControls ); + $chartControls.find( '[title]' ).tooltip(); 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(); + /** render the tab content where the chart is displayed (but not the chart itself) */ + _render_chartDisplay : function( $where ){ + $where = $where || this.$el; + var $chartDisplay = $where.find( '.tab-pane#chart-display' ); + this.display.setElement( $chartDisplay ); + this.display.render(); + + $chartDisplay.find( '[title]' ).tooltip(); return $chartDisplay; }, @@ -179,7 +229,22 @@ var ScatterplotConfigEditor = Backbone.View.extend( LoggableMixin ).extend({ events : { 'change #include-id-checkbox' : 'toggleThirdColumnSelector', 'click #data-control .render-button' : 'renderChart', - 'click #chart-control .render-button' : 'renderChart' + 'click #chart-control .render-button' : 'renderChart', + 'click .save-btn' : 'saveVisualization', + //'click .copy-btn' : function(e){ this.model.save(); } + }, + + saveVisualization : function(){ + var editor = this; + this.model.save() + .fail( function( xhr, status, message ){ + console.error( xhr, status, message ); + editor.trigger( 'save:error', view ); + alert( 'Error loading data:\n' + xhr.responseText ); + }) + .then( function(){ + editor.render(); + }); }, toggleThirdColumnSelector : function(){ @@ -192,42 +257,9 @@ var ScatterplotConfigEditor = Backbone.View.extend( LoggableMixin ).extend({ //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 : Number( $dataControls.find( '[name="xColumn"]' ).val() ), - yColumn : Number( $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; + this.display.fetchData(); + //console.debug( this.display.$el ); }, toString : function(){ diff --git a/config/plugins/visualizations/scatterplot/src/scatterplot-display.js b/config/plugins/visualizations/scatterplot/src/scatterplot-display.js index 3f6c1ab353e..ea9927430d4 100644 --- a/config/plugins/visualizations/scatterplot/src/scatterplot-display.js +++ b/config/plugins/visualizations/scatterplot/src/scatterplot-display.js @@ -6,242 +6,242 @@ * 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 : { - metadata : { - dataLines : undefined - }, - - 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 - }, +var ScatterplotDisplay = Backbone.View.extend({ initialize : function( attributes ){ - this.config = _.extend( _.clone( this.defaults ), attributes.config || {}); + this.data = null, this.dataset = attributes.dataset; - //console.debug( this + '.config:', this.config ); + this.calcNumPages(); }, - updateConfig : function( newConfig ){ - //console.log( this + '.updateConfig:', newConfig ); - this.config = this.config || {}; - //TODO: validate here - _.extend( this.config, newConfig ); - //TODO: implement rerender flag + calcNumPages : function(){ + var config = this.model.get( 'config' ); + this.lineCount = this.dataset.metadata_data_lines, + this.numPages = ( this.lineCount )?( Math.ceil( this.lineCount / config.pagination.perPage ) ):( undefined ); + if( !this.lineCount || this.numPages === undefined ){ + console.warn( 'no data total found' ); + } }, 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; + var view = this, + config = this.model.get( 'config' ), //TODO: very tied to datasets - should be generalized eventually xhr = jQuery.getJSON( '/api/datasets/' + this.dataset.id, { data_type : 'raw_data', provider : 'dataset-column', - limit : this.config.pagination.perPage, - offset : ( this.config.pagination.currPage * this.config.pagination.perPage ) + limit : config.pagination.perPage, + offset : ( config.pagination.currPage * config.pagination.perPage ) }); xhr.done( function( data ){ - view.renderData( data.data ); + view.data = data.data; + view.trigger( 'data:fetched', view ); + view.renderData(); }); xhr.fail( function( xhr, status, message ){ - alert( 'Error loading data:\n' + xhr.responseText ); console.error( xhr, status, message ); - }); - xhr.always( function(){ - view.hideLoadingIndicator(); + view.trigger( 'data:error', view ); + alert( 'Error loading data:\n' + xhr.responseText ); }); return xhr; }, - render : function( data ){ - this.$el.addClass( 'scatterplot-display' ).html([ - '
    ', + showLoadingIndicator : function(){ + // display the loading indicator over the tab panels if hidden, update message (if passed) + this.$el.find( '.scatterplot-data-info' ).html([ '
    ', '', - '', + 'loading...', + '
    ' + ].join( '' )); + }, + + template : function(){ + var html = [ + '
    ', + '
    ', + '
    ', + '
    ', + '

    ', + '', + '', + '
    ', '
    ', '', //TODO: id '
    ' - ].join( '' )); - this.$el.children().hide(); + ].join( '' ); + return html; + }, - if( data ){ - this.renderData( data ); + render : function(){ + this.$el.addClass( 'scatterplot-display' ).html( this.template() ); + if( this.data ){ + this.renderData(); } 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' ); + renderData : function(){ + this.renderLeftControls(); + this.renderRightControls(); + this.renderPlot( this.data ); + this.getStats(); + }, - 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(); + renderLeftControls : function(){ + if( this.lineCount ){ + this.$el.find( '.controls .left' ).empty().append( this.renderPagination() ); + } else { + this.$el.find( '.controls .left' ).empty().append( this.renderPrevNext() ); } + return this; }, - 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 ){ + renderRightControls : function(){ 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 ]; + this.setLineInfo( this.data ); + // clear prev. handlers due to closure around data + this.$el.find( '.stats-toggle-btn' ) + .off().click( function(){ + view.toggleStats(); + }); + this.$el.find( '.rerender-btn' ) + .off().click( function(){ + view.resetZoom(); + view.renderPlot( this.data ); + }); }, - renderLineInfo : function( data ){ - var totalLines = this.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 + /** render and show the d3 plot into the svg node of the view */ + renderPlot : function(){ var view = this, - dataLines = this.dataset.metadata_data_lines, - numPages = ( dataLines )?( Math.ceil( dataLines / this.config.pagination.perPage ) ):( undefined ); - //console.debug( 'data:', this.dataset.metadata_data_lines, 'numPages:', numPages ); + $svg = this.$el.find( 'svg' ); + // turn off stats, clear previous svg, and make it visible + this.toggleStats( false ); + $svg.off().empty().show() + // set up listeners for events from plot + .on( 'zoom.scatterplot', function( ev, zoom ){ + //TODO: possibly throttle this + //console.debug( 'zoom.scatterplot', zoom.scale, zoom.translate ); + view.model.set( 'config', zoom ); + }); + //TODO: may not be necessary to off/on this more than the initial on + // call the sep. d3 function to generate the plot + scatterplot( $svg.get( 0 ), this.model.get( 'config' ), this.data ); + }, - // 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 = $( '