From 22074dc16e5497404f4d98a7ec5fee12e7e46f16 Mon Sep 17 00:00:00 2001 From: JacobsonMT Date: Wed, 28 Mar 2018 18:00:42 -0700 Subject: [PATCH] Redesign Gene View - Loads HighCharts from CDN - General finishing touches. - Removes tabs in favour of compact view. - Uses area/streamgraph instead of xrange for annotation history chart. - More and polished contextual help. Related to Issue #37. --- .../ubc/pavlab/gotrack/beans/GeneView.java | 181 +++++---- gotrack/src/main/webapp/genes.xhtml | 353 +++++++++++------- .../composites/annotationTable.xhtml | 88 +++-- gotrack/src/main/webapp/resources/js/genes.js | 290 ++------------ .../src/main/webapp/resources/js/plotting.js | 140 ++++++- 5 files changed, 540 insertions(+), 512 deletions(-) diff --git a/gotrack/src/main/java/ubc/pavlab/gotrack/beans/GeneView.java b/gotrack/src/main/java/ubc/pavlab/gotrack/beans/GeneView.java index 5898ace..abe90ee 100644 --- a/gotrack/src/main/java/ubc/pavlab/gotrack/beans/GeneView.java +++ b/gotrack/src/main/java/ubc/pavlab/gotrack/beans/GeneView.java @@ -49,10 +49,11 @@ import java.io.Serializable; import java.util.*; import java.util.Map.Entry; +import java.util.stream.Collectors; /** * Backing bean for the gene tracking functionality. - * + * * @author mjacobson */ @Named @@ -108,7 +109,7 @@ public class GeneView implements Serializable { public GeneView() { log.info( "GeneView created" ); - log.info( "Used Memory: " + ( Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory() ) / 1000000 + log.info( "Used Memory: " + (Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory()) / 1000000 + " MB" ); } @@ -118,7 +119,7 @@ public void postConstruct() { /** * pre-render view - * + *

* This is kept lightweight so that the page loads quickly and lazy loads the data using remote commands */ public String init() throws GeneNotFoundException, IOException { @@ -164,7 +165,7 @@ public String init() throws GeneNotFoundException, IOException { * Attempt to get data from cache, if not in cache get from DB. */ private Map>> retrieveData() { - return retrieveData( Sets. newHashSet() ); + return retrieveData( Sets.newHashSet() ); } /** @@ -185,7 +186,7 @@ private Map>> retrieveData( Set filterTerms ) { - boolean bypassFilter = ( filterTerms == null || filterTerms.size() == 0 ); + boolean bypassFilter = (filterTerms == null || filterTerms.size() == 0); Map>> rawData = new HashMap<>(); @@ -259,27 +260,27 @@ public int compare( Edition o1, Edition o2 ) { } - private List fetchRightPanelRows(Edition edition) { + private List fetchRightPanelRows( Edition edition ) { return fetchRightPanelRows( edition, rawData.get( AnnotationType.I ).row( edition ).keySet() ); } - private List fetchRightPanelRows(Edition edition, Collection terms) { + private List fetchRightPanelRows( Edition edition, Collection terms ) { List results = Lists.newArrayList(); ImmutableMap> directs = rawData.get( AnnotationType.D ).row( edition ); ImmutableMap> inferred = rawData.get( AnnotationType.I ).row( edition ); for ( GeneOntologyTerm term : terms ) { Set annotations = inferred.get( term ); - if (annotations != null ) { + if ( annotations != null ) { results.add( new GeneViewRightPanelRow( term, directs.containsKey( term ) ? AnnotationType.D : AnnotationType.I, annotations ) ); } } - Collections.sort(results); + Collections.sort( results ); return results; } - private List fetchRightPanelRowsComparison(Edition editionA, Edition editionB ) { + private List fetchRightPanelRowsComparison( Edition editionA, Edition editionB ) { List results = Lists.newArrayList(); ImmutableMap> inferredA = rawData.get( AnnotationType.I ).row( editionA ); @@ -312,18 +313,18 @@ private List fetchRightPanelRowsComparison(Edition editio results.add( new GeneViewRightPanelRow( term, directsB.containsKey( term ) ? AnnotationType.D : AnnotationType.I, - annotations,inSet ) ); + annotations, inSet ) ); } comparisons = Lists.newArrayList( editionB ); - Collections.sort(results); + Collections.sort( results ); return results; } - private List addRightPanelRowsComparison(Edition newComparison ) { + private List addRightPanelRowsComparison( Edition newComparison ) { // if (rightPanelTerms == null || rightPanelTerms.isEmpty()) { // return fetchRightPanelRowsComparison( rightPanelEdition, newComparison ); @@ -337,11 +338,10 @@ private List addRightPanelRowsComparison(Edition newCompa for ( GeneViewRightPanelRow rightPanelTerm : rightPanelTerms ) { BitSet inSet = rightPanelTerm.getInSet(); - inSet.set( nextBitIndex , newInferred.containsKey( rightPanelTerm.getTerm() ) ); + inSet.set( nextBitIndex, newInferred.containsKey( rightPanelTerm.getTerm() ) ); } - // Not too fast but better than alternatives... probably Set termsInRightPanel = Sets.newHashSet( Collections2.transform( rightPanelTerms, new Function() { @Override @@ -364,7 +364,7 @@ public GeneOntologyTerm apply( GeneViewRightPanelRow row ) { comparisons.add( newComparison ); - Collections.sort(rightPanelTerms); + Collections.sort( rightPanelTerms ); return rightPanelTerms; } @@ -423,7 +423,7 @@ private Map createHCCallbackParamMap( ChartValues chart ) { /** * Create chart showing counts of unique terms annotated to this gene over time (both directly and through * propagation) - * + * * @param rawData data */ private void fetchAnnotationChart( @@ -433,8 +433,8 @@ private void fetchAnnotationChart( // Collect data from cache about species aggregates // Map aggregates = cache.getAggregates( species.getId() ); - ChartValues chart = new ChartValues("Terms Annotated to " + gene.getSymbol() + " vs Time", - "Annotations Count", "Date"); + ChartValues chart = new ChartValues( "Terms Annotated to " + gene.getSymbol(), + "Annotations Count", "Date" ); chart.setMin( 0 ); // // Create series for species aggregates @@ -445,10 +445,10 @@ private void fetchAnnotationChart( // chart.addSeries( aggregateSeries ); //Create series for direct annotations count - Series directCountSeries = new Series( "Direct Annotation Count" ); - SeriesExtra aggregateSeries = new SeriesExtra( "Species Direct Mean" ); + Series directCountSeries = new Series( "Direct" ); + SeriesExtra aggregateSeries = new SeriesExtra( "Direct Species Mean" ); aggregateSeries.putExtra( "color", "#939393" ); - SeriesExtra aggregateInferredSeries = new SeriesExtra( "Species Inferred Mean" ); + SeriesExtra aggregateInferredSeries = new SeriesExtra( "Inferred Species Mean" ); aggregateInferredSeries.putExtra( "color", "#939393" ); for ( Entry>> entry : rawData.get( AnnotationType.D ) .rowMap().entrySet() ) { @@ -459,7 +459,7 @@ private void fetchAnnotationChart( } // Create series for inferred annotations count - Series inferredCountSeries = new Series( "Inferred Annotation Count" ); + Series inferredCountSeries = new Series( "Inferred" ); for ( Entry>> entry : rawData.get( AnnotationType.I ) .rowMap().entrySet() ) { @@ -496,7 +496,7 @@ public void fetchAnnotationChart() { /** * Create chart showing similarity of terms annotated to this gene in an edition compared to the most current * edition (both directly and through propagation) - * + * * @param rawData data */ private void fetchJaccardChart( @@ -507,8 +507,8 @@ private void fetchJaccardChart( ImmutableTable> inferredData = rawData .get( AnnotationType.I ); - ChartValues chart = new ChartValues("Similarity of " + gene.getSymbol() + " vs Time", - "Jaccard Similarity Index", "Date"); + ChartValues chart = new ChartValues( "Similarity of " + gene.getSymbol(), + "Jaccard Index", "Date" ); chart.setMin( 0 ); chart.setMax( 1 ); @@ -516,7 +516,7 @@ private void fetchJaccardChart( Edition currentEdition = Collections.max( directData.rowKeySet() ); // For direct annotations - Series directSeries = new Series( "Direct Similarity" ); + Series directSeries = new Series( "Direct" ); SeriesExtra averageDirectSeries = new SeriesExtra( "Direct Species Mean" ); averageDirectSeries.putExtra( "color", "#939393" ); @@ -528,8 +528,8 @@ private void fetchJaccardChart( } // For Inferred annotations - Series inferredSeries = new Series( "Inferred Similarity" ); - SeriesExtra averageInferredSeries = new SeriesExtra( "Inferred Species Average" ); + Series inferredSeries = new Series( "Inferred" ); + SeriesExtra averageInferredSeries = new SeriesExtra( "Inferred Species Mean" ); averageInferredSeries.putExtra( "color", "#939393" ); currentGOSet = inferredData.row( currentEdition ).keySet(); @@ -568,20 +568,20 @@ public void fetchJaccardChart() { * Create chart showing multifunctionality of this gene over time (Gillis J, Pavlidis P (2011) The Impact of * Multifunctional Genes on "Guilt by Association" Analysis. PLoS ONE 6(2): e17258. doi: * 10.1371/journal.pone.0017258) - * + * * @param rawData data */ private void fetchMultifunctionalityChart( Map>> rawData ) { log.debug( "fetchMultifunctionalityChart" ); - ChartValues chart = new ChartValues("Multifunctionality of " + gene.getSymbol() + " vs Time", "Multifunctionality [10^-5]", - "Date"); - chart.setMin(0); + ChartValues chart = new ChartValues( "Multifunctionality of " + gene.getSymbol(), "Multifunctionality [10^-5]", + "Date" ); + chart.setMin( 0 ); // Calculate multifunctionality of the gene in each edition Series multiSeries = new Series( "Multifunctionality" ); - SeriesExtra averageSeries = new SeriesExtra( "Multifunctionality Species Mean" ); + SeriesExtra averageSeries = new SeriesExtra( "Species Mean" ); averageSeries.putExtra( "color", "#939393" ); for ( Entry>> entry : rawData.get( AnnotationType.I ) .rowMap().entrySet() ) { @@ -620,77 +620,66 @@ public void fetchMultifunctionalityChart() { */ public void fetchTimeline() { log.debug( "fetchTimeline" ); + ImmutableTable> data; + String subtitle; if ( rightPanelSelectedTerms == null || rightPanelSelectedTerms.size() == 0 ) { - // No Terms - RequestContext.getCurrentInstance().addCallbackParam( "HC", - new Gson().toJson( createHCCallbackParamFail( "No Terms Selected." ) ) ); - return; + data = rawData.get( AnnotationType.I ); + subtitle = "All Terms"; + } else { + data = retrieveData( rightPanelSelectedTerms ).get( AnnotationType.I ); + if (rightPanelSelectedTerms.size() > 3) { + subtitle = rightPanelSelectedTerms.size() + " Terms"; + } else if (rightPanelSelectedTerms.size() == 1) { + GeneOntologyTerm t = rightPanelSelectedTerms.iterator().next().getTerm(); + subtitle = t.getGoId() + " - " + t.getName(); + } else { + subtitle = rightPanelSelectedTerms.stream().map( t -> t.getTerm().getGoId() ).collect( Collectors.joining( ", " ) ); + } } - if ( rightPanelSelectedTerms.size() > 20 ) { - // Too many terms - RequestContext.getCurrentInstance().addCallbackParam( "HC", - new Gson().toJson( createHCCallbackParamFail( "Too Many Terms Selected. Maximum 20." ) ) ); - return; - } + ChartValues chart = new ChartValues( "Annotation Categories of Terms in " + gene.getSymbol(), + "Counts", "Date" ); + chart.setSubtitle( subtitle ); + Map seriesMap = Maps.newHashMap(); - HashSet filterTerms = new HashSet<>( rightPanelSelectedTerms ); + List allEditions = new ArrayList<>( cache.getAllEditions( this.gene.getSpecies() ) ); + Collections.sort( allEditions ); - ImmutableTable> data = retrieveData( filterTerms ) - .get( AnnotationType.I ); + for ( Edition ed : allEditions ) { + + ImmutableMap> editionData = data.row( ed ); - // Create an ordering for the categories - int i = 0; - Map categoryPositions = new HashMap<>(); - for ( String cat : cache.getEvidenceCategories() ) { - categoryPositions.put( cat, i++ ); + // Group by annotation.evidence.category + Map categoryCounts = editionData.entrySet().stream() + .flatMap( e -> e.getValue().stream() ) + .collect( Collectors.groupingBy( o -> o.getEvidence().getCategory(), Collectors.counting() ) ); + for (String category : cache.getEvidenceCategories() ) { + seriesMap.computeIfAbsent( category, Series::new ).addDataPoint( ed.getDate(), categoryCounts.getOrDefault( category, 0L ) ); + } } - ChartValues chart = new ChartValues("Annotation Categories of " + gene.getSymbol() + " vs Time", - "", "Date"); + for ( Entry>> entry : data.rowMap().entrySet() ) { + Edition ed = entry.getKey(); - List allEditions = new ArrayList<>( cache.getAllEditions( this.gene.getSpecies() ) ); - Collections.sort( allEditions ); - Map termNames = new HashMap<>(); - - for ( GeneViewRightPanelRow tv : filterTerms ) { - GeneOntologyTerm t = tv.getTerm(); - termNames.put( t.getGoId(), t.getName() ); - Series s = new Series( t.getGoId() ); - ImmutableMap> termData = data.column( t ); - // We iterate over this collection to insure that every term has all of the editions in its data - // otherwise we get wonky date ranges - for ( Edition ed : allEditions ) { - byte existenceByte = 0; - Set annotationSet = termData.get( ed ); - if ( annotationSet != null ) { - for ( Annotation annotation : annotationSet ) { - int pos = categoryPositions.get( annotation.getEvidence().getCategory() ); - // Set bit in this position to 1 - existenceByte |= ( 1 << pos ); - } - } + } - s.addDataPoint( ed.getDate(), existenceByte ); - } - chart.addSeries( s ); + for ( Series series : seriesMap.values().stream().sorted( Comparator.comparing( Series::getName ) ).collect( Collectors.toList() ) ) { + chart.addSeries( series ); } Map hcGsonMap = createHCCallbackParamMap( chart ); - hcGsonMap.put( "category_positions", categoryPositions ); - hcGsonMap.put( "term_names", termNames ); + hcGsonMap.put( "categories", cache.getEvidenceCategories().stream().sorted().collect( Collectors.toList() ) ); RequestContext.getCurrentInstance().addCallbackParam( "HC", new Gson().toJson( hcGsonMap ) ); } - public Collection fetchTermAnnotations(GeneOntologyTerm term) { + public Collection fetchTermAnnotations( GeneOntologyTerm term ) { log.info( "fetchTermAnnotations" ); - return rawData.get( AnnotationType.I ).row( rightPanelEdition).get( term ); + return rawData.get( AnnotationType.I ).row( rightPanelEdition ).get( term ); } - /** * Click event functionality for annotation chart */ @@ -701,7 +690,7 @@ public void fetchAnnotationPointData() { editionId = Integer.valueOf( FacesContext.getCurrentInstance().getExternalContext().getRequestParameterMap().get( "edition" ) ); - } catch ( NumberFormatException e ) { + } catch (NumberFormatException e) { log.error( e ); return; } @@ -721,7 +710,7 @@ public void fetchAnnotationPointData() { // Reset comparison fields comparisons = Lists.newArrayList(); - } catch ( NullPointerException e ) { + } catch (NullPointerException e) { log.error( e ); return; } @@ -737,7 +726,7 @@ public void fetchAnnotationComparisonData() { try { compareEditionId = Integer.valueOf( FacesContext.getCurrentInstance().getExternalContext().getRequestParameterMap().get( "compareEdition" ) ); - } catch ( NumberFormatException e ) { + } catch (NumberFormatException e) { log.error( e ); return; } @@ -753,7 +742,7 @@ public void fetchAnnotationComparisonData() { rightPanelTerms = fetchRightPanelRowsComparison( rightPanelEdition, compareEdition ); rightPanelFilteredTerms = null; rightPanelSelectedTerms = null; - } catch ( NullPointerException e ) { + } catch (NullPointerException e) { log.error( e ); return; } @@ -769,7 +758,7 @@ public void addAnnotationComparisonData() { try { compareEditionId = Integer.valueOf( FacesContext.getCurrentInstance().getExternalContext().getRequestParameterMap().get( "compareEdition" ) ); - } catch ( NumberFormatException e ) { + } catch (NumberFormatException e) { log.error( e ); return; } @@ -785,7 +774,7 @@ public void addAnnotationComparisonData() { addRightPanelRowsComparison( compareEdition ); rightPanelFilteredTerms = null; rightPanelSelectedTerms = null; - } catch ( NullPointerException e ) { + } catch (NullPointerException e) { log.error( e ); return; } @@ -801,7 +790,7 @@ public void fetchTimelinePointData() { editionId = Integer.valueOf( FacesContext.getCurrentInstance().getExternalContext().getRequestParameterMap().get( "edition" ) ); - } catch ( NumberFormatException e ) { + } catch (NumberFormatException e) { log.error( e ); return; } @@ -811,11 +800,11 @@ public void fetchTimelinePointData() { viewTerm = cache.getTerm( clickEdition, goId ); - viewAnnotations = rawData.get( AnnotationType.I).get( clickEdition, viewTerm ); + viewAnnotations = rawData.get( AnnotationType.I ).get( clickEdition, viewTerm ); filteredViewAnnotations = null; } - public void fetchTermGraph( GeneOntologyTerm term) { + public void fetchTermGraph( GeneOntologyTerm term ) { Graph graph = Graph.fromGO( term ); RequestContext.getCurrentInstance().addCallbackParam( "graph_data", graph.getJsonString() ); } @@ -844,8 +833,8 @@ public GeneOntologyTerm apply( GeneViewRightPanelRow row ) { for ( Entry>> editionMapEntry : inferred.rowMap().entrySet() ) { Edition edition = editionMapEntry.getKey(); - Boolean disjoint = Collections.disjoint( terms, editionMapEntry.getValue().keySet()); - if (disjoint) { + Boolean disjoint = Collections.disjoint( terms, editionMapEntry.getValue().keySet() ); + if ( disjoint ) { missingEditionDates.add( edition.getDate().getTime() ); } } @@ -856,7 +845,7 @@ public GeneOntologyTerm apply( GeneViewRightPanelRow row ) { * custom filter function for primefaces data table column, filters by multiple booleans */ public boolean filterByBitSet( Object value, Object filter, Locale locale ) { - Set filterIndices = ( filter == null ) ? null : Sets.newHashSet((String[]) filter); + Set filterIndices = (filter == null) ? null : Sets.newHashSet( (String[]) filter ); if ( filterIndices == null || filterIndices.isEmpty() ) { return true; } @@ -867,7 +856,7 @@ public boolean filterByBitSet( Object value, Object filter, Locale locale ) { BitSet enabledBits = (BitSet) value; - BitSet filterBits = new BitSet(enabledBits.length()); + BitSet filterBits = new BitSet( enabledBits.length() ); for ( String i : filterIndices ) { filterBits.set( Integer.valueOf( i ) ); } diff --git a/gotrack/src/main/webapp/genes.xhtml b/gotrack/src/main/webapp/genes.xhtml index 46b5159..c1d765d 100644 --- a/gotrack/src/main/webapp/genes.xhtml +++ b/gotrack/src/main/webapp/genes.xhtml @@ -27,7 +27,9 @@ - + + + @@ -53,6 +55,12 @@ style="text-align:center" collapsed="#{geneView.gene == null}"> + +

+

Description:

+

The following table shows all GO Terms that are annotated in the selected edition(s).

+
+ @@ -99,10 +107,35 @@ + + +
+

<Click> on to view the annotations associated with this term.

+ +
+
+ +
+ + +
+

A tag of the appropriate colour will display for each compared edition this term was present in.

+ +
+
+ + +
- + + +
+

+ Gene Ontology Id. +

+

+ <Click> on to view the term's QuickGO entry. +

+

+ <Click> on to view the term's ancestry chart, NOTE: we do not propagate across aspects whereas QuickGo does. +

+
+
+ + +
+ + + + + + + + +
+ + +
+

+ One of Biological Process, Cellular Component, or Molecular Function. +

+
+
+ + +
+ + +
+

+ Short description of the given term. +

+
+
+ + +
- + + +
+

<Click> or <Ctrl/Command/Shift> + <Click> to select rows.

+ +

<Click> to view the annotation history of the selected GO terms (or all terms if none are selected). Shows annotations counts to this gene split by evidence code category.

+

<Click> to view the combined ancestors graph of the selected GO terms.

+
+
+ - - - - +
+
+
+ +
+

Description:

+

The + annotation plot shows distinct counts of both direct and inferred GO annotations associated with this gene. + A distinct annotation is defined as being both unique in GO Term and Evidence.

+

Legend Descriptions:

+

Direct Annotation Count: Distinct count of directly applied annotations.

+

+ Inferred Annotation Count: Distinct count of directly applied annotations as well as their GO ancestors. + Example: A gene directly annotated with directional locomotion is also implicitly annotated with its parents (locomotion).

+

Controls:

+

<Click> any edition to view the annotations at that point in time in the right panel.

+

<Ctrl/Command> + <Click> any edition to compare the annotations at that point in time to the currently selected edition. + This is done through coloured tags on each GO annotation. If a tag is present it means the annotation existed in some form in the edition that colour represents.

+

<Ctrl/Command> + <Shift> + <Click> will let you compare up to four editions at once.

+

<Click> a legend item to toggle that series.

+

<Ctrl/Command> + <Click> a legend item to show only that series.

+ +
+
- -