// ============================================================================= /** * 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 ScatterplotDisplay = Backbone.View.extend({ initialize : function( attributes ){ this.data = null, this.dataset = attributes.dataset; this.lineCount = this.dataset.metadata_data_lines || null; }, fetchData : function(){ this.showLoadingIndicator(); //console.debug( 'currPage', this.config.pagination.currPage ); 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 : config.pagination.perPage, offset : ( config.pagination.currPage * config.pagination.perPage ) }); xhr.done( function( data ){ // no need to hide loading indicator, line info will write over that view.data = data.data; view.trigger( 'data:fetched', view ); view.renderData(); }); xhr.fail( function( xhr, status, message ){ console.error( xhr, status, message ); view.trigger( 'data:error', view ); alert( 'Error loading data:\n' + xhr.responseText ); }); return xhr; }, 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( '' ); return html; }, render : function(){ this.$el.addClass( 'scatterplot-display' ).html( this.template() ); if( this.data ){ this.renderData(); } return this; }, renderData : function(){ this.renderLeftControls(); this.renderRightControls(); this.renderPlot( this.data ); this.getStats(); }, renderLeftControls : function(){ var display = this, config = this.model.get( 'config' ); this.$el.find( '.controls .left .page-control' ).pagination({ startingPage : config.pagination.currPage, perPage : config.pagination.perPage, totalDataSize: this.lineCount, currDataSize : this.data.length //TODO: move to named function and remove only named }).off().on( 'pagination.page-change', function( event, page ){ //console.debug( 'pagination:page-change', page ); config.pagination.currPage = page; display.model.set( 'config', { pagination: config.pagination }); //console.debug( pagination, display.model.get( 'config' ).pagination ); display.resetZoom(); display.fetchData(); }); return this; }, renderRightControls : function(){ var view = this; 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 ); }); }, /** render and show the d3 plot into the svg node of the view */ renderPlot : function(){ var view = this, $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 ); }, setLineInfo : function( data, contents ){ if( data ){ var config = this.model.get( 'config' ), totalLines = this.lineCount || 'an unknown total', lineStart = config.pagination.currPage * config.pagination.perPage, lineEnd = lineStart + data.length; this.$el.find( '.controls p.scatterplot-data-info' ) .text([ lineStart + 1, 'to', lineEnd, 'of', totalLines ].join( ' ' )); } else { this.$el.find( '.controls p.scatterplot-data-info' ).html( contents || '' ); } return this; }, resetZoom : function( scale, translate ){ scale = ( scale !== undefined )?( scale ):( 1 ); translate = ( translate !== undefined )?( translate ):( [ 0, 0 ] ); this.model.set( 'config', { scale: scale, translate: translate } ); return this; }, // ------------------------------------------------------------------------ statistics display /** create a webworker to calc stats for data given */ getStats : function(){ if( !this.data ){ return; } var view = this, config = this.model.get( 'config' ), meanWorker = new Worker( '/plugins/visualizations/scatterplot/static/worker-stats.js' ); meanWorker.postMessage({ data : this.data, keys : [ config.xColumn, config.yColumn ] }); meanWorker.onerror = function( event ){ meanWorker.terminate(); }; meanWorker.onmessage = function( event ){ view.renderStats( event.data ); }; }, renderStats : function( stats, error ){ //console.debug( 'renderStats:', stats, error ); //console.debug( JSON.stringify( stats, null, ' ' ) ); var config = this.model.get( 'config' ), $statsTable = this.$el.find( '.stats-display' ), xLabel = config.xLabel, yLabel = config.yLabel, $table = $( '' ).addClass( 'table' ) .append([ '' ].join( '' )) .append( _.map( stats, function( stat, key ){ return $([ '' ].join( '' )); })); $statsTable.empty().append( $table ); }, toggleStats : function( showStats ){ var $statsDisplay = this.$el.find( '.stats-display' ); showStats = ( showStats === undefined )?( $statsDisplay.is( ':hidden' ) ):( showStats ); if( showStats ){ this.$el.find( 'svg' ).hide(); $statsDisplay.show(); this.$el.find( '.controls .stats-toggle-btn' ).text( 'Plot' ); } else { $statsDisplay.hide(); this.$el.find( 'svg' ).show(); this.$el.find( '.controls .stats-toggle-btn' ).text( 'Stats' ); } }, toString : function(){ return 'ScatterplotView()'; } });
', xLabel, '', yLabel, '
', key, '', stat[0], '', stat[1], '