File

projects/web-mev/src/app/features/workspace-detail/components/metadata/metadata.component.ts

Description

Metadata Component

Used to add, edit, remove custom observations sets and edit, remove custom feature sets. User's custom sets are stored in local storage only

Implements

OnInit

Metadata

changeDetection ChangeDetectionStrategy.Default
selector mev-metadata
styleUrls ./metadata.component.scss
templateUrl ./metadata.component.html

Index

Properties
Methods
Inputs

Constructor

constructor(service: WorkspaceDetailService, metadataService: MetadataService, storage: LclStorageService, route: ActivatedRoute, cd: ChangeDetectorRef, dialog: MatDialog)
Parameters :
Name Type Optional
service WorkspaceDetailService No
metadataService MetadataService No
storage LclStorageService No
route ActivatedRoute No
cd ChangeDetectorRef No
dialog MatDialog No

Inputs

workspaceResources
Type : WorkspaceResource[]

Methods

generateMetadataColumns
generateMetadataColumns(currentObsSet)

Make the list of columns for the Mat Table with current annotation

Parameters :
Name Optional
currentObsSet No
Returns : void
generateObservationSetsVisualization
generateObservationSetsVisualization()

Method to display the list of custom observation sets in the Visualization mode

Returns : void
getGlobalObservationSets
getGlobalObservationSets()

Get the list of observations used in all files/resources included in the current workspace

Returns : any
ngAfterViewInit
ngAfterViewInit()
Returns : void
ngOnDestroy
ngOnDestroy()
Returns : void
ngOnInit
ngOnInit()
Returns : void
onChooseAnnotation
onChooseAnnotation()

Method is triggered when the user clicks button 'Incorporate annotation'

Returns : void
onCreateObservationSet
onCreateObservationSet()

Method is triggered when the user clicks button 'Create a custom observation set' Display the list of all available samples from all files in the workspace

Returns : void
onDeleteCustomSet
onDeleteCustomSet(setId: string)

Method is triggered when the user clicks icon 'Delete' Delete a custom observation or feature set from the list

Parameters :
Name Type Optional
setId string No
Returns : void
onEditCustomSet
onEditCustomSet(set)

Method is triggered when the user clicks icon 'Edit'

Edit a custom observation or feature set. For custom observation sets the user can update name, color, list of samples. For feature sets only names can be updated

Parameters :
Name Optional
set No
Returns : void
onViewCustomSet
onViewCustomSet(set)

Method is triggered when the user clicks icon 'View' View a custom observation or feature set.

Parameters :
Name Optional
set No
Returns : void
viewCustomSetInfo
viewCustomSetInfo()

Method is triggered when the user clicks a link in the Metadata tab description

Returns : void

Properties

accordion
Type : MatAccordion
Decorators :
@ViewChild(MatAccordion)
customSetDS
customSetsDisplayedColumns
Type : string[]
Default value : ['name', 'type', 'actions']
datasource
Public dialog
Type : MatDialog
displayedColumns
Type : []
Default value : ['name', 'symbol']
displayedColumnsAttributesOnly
Type : []
Default value : []
globalObservationSets
Type : []
Default value : []
isWait
Default value : false
metadataObsDisplayedColumns
Type : string[]
metadataObsDisplayedColumnsAttributesOnly
Type : string[]
observationSetDS
Private Readonly onDestroy
Default value : new Subject<void>()
paginator
Type : MatPaginator
Decorators :
@ViewChild(MatPaginator)
selection
Default value : new SelectionModel(true, [])
storageSubscription
Type : Subscription
visObsDisplayedColumns
Type : string[]
visObsDisplayedColumnsSetsOnly
Type : string[]
visObservationSetDS
workspaceId
Type : string
import {
  Component,
  OnInit,
  ChangeDetectionStrategy,
  Input,
  ViewChild,
  ChangeDetectorRef
} from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { Subject, Subscription } from 'rxjs';
import { takeUntil, switchMap, delay } from 'rxjs/operators';
import { MatTableDataSource } from '@angular/material/table';
import { MatAccordion } from '@angular/material/expansion';
import { MatDialog } from '@angular/material/dialog';
import { MatPaginator } from '@angular/material/paginator';
import { SelectionModel } from '@angular/cdk/collections';

import { AddAnnotationDialogComponent } from './dialogs/add-annotation-dialog/add-annotation-dialog.component';
import { WorkspaceResource } from '../../models/workspace-resource';
import { WorkspaceDetailService } from '../../services/workspace-detail.service';
import { AddObservationSetDialogComponent } from './dialogs/add-observation-set-dialog/add-observation-set-dialog.component';
import { DeleteSetDialogComponent } from './dialogs/delete-set-dialog/delete-set-dialog.component';
import { ViewSetDialogComponent } from './dialogs/view-set-dialog/view-set-dialog.component';
import { LclStorageService } from '@app/core/local-storage/lcl-storage.service';
import { MetadataService } from '@app/core/metadata/metadata.service';
import { EditFeatureSetDialogComponent } from './dialogs/edit-feature-set-dialog/edit-feature-set-dialog.component';
import { ViewInfoDialogComponent } from './dialogs/view-info-dialog/view-info-dialog.component';

/**
 * Metadata Component
 *
 * Used to add, edit, remove custom observations sets and edit, remove custom feature sets.
 * User's custom sets are stored in local storage only
 */
@Component({
  selector: 'mev-metadata',
  templateUrl: './metadata.component.html',
  styleUrls: ['./metadata.component.scss'],
  changeDetection: ChangeDetectionStrategy.Default
})
export class MetadataComponent implements OnInit {
  displayedColumns = ['name', 'symbol'];
  displayedColumnsAttributesOnly = [];

  @ViewChild(MatAccordion) accordion: MatAccordion;
  @Input() workspaceResources: WorkspaceResource[];
  workspaceId: string;

  observationSetDS; // use in MatDataTable to display the current annotation
  metadataObsDisplayedColumns: string[]; // columns for the Current Annotation table
  metadataObsDisplayedColumnsAttributesOnly: string[];

  customSetDS; // use in MatDataTable to display the list of custom observation/feature sets created by user
  customSetsDisplayedColumns: string[] = ['name', 'type', 'actions']; // the list of columns for the Custom Sets table

  visObservationSetDS; // use in MatDataTable to display visualisation for custom observation sets
  visObsDisplayedColumns: string[];
  visObsDisplayedColumnsSetsOnly: string[];

  globalObservationSets = []; // all samples from all resources

  datasource;
  selection = new SelectionModel(true, []);
  isWait = false;
  private readonly onDestroy = new Subject<void>();
  @ViewChild(MatPaginator) paginator: MatPaginator;

  storageSubscription: Subscription;

  constructor(
    private service: WorkspaceDetailService,
    private metadataService: MetadataService,
    private storage: LclStorageService,
    private route: ActivatedRoute,
    private cd: ChangeDetectorRef,
    public dialog: MatDialog
  ) {}

  ngOnInit(): void {
    this.workspaceId = this.route.snapshot.paramMap.get('workspaceId');

    // check if there is a current annotation saved locally to display
    const currentObsSet =
      JSON.parse(
        localStorage.getItem(this.workspaceId + '_current_observation_set')
      ) || [];
    this.generateMetadataColumns(currentObsSet);
    this.observationSetDS = new MatTableDataSource(currentObsSet);

    // retrieve custom observation/feature sets
    const customSet = this.metadataService.getCustomSets();
    this.customSetDS = new MatTableDataSource(customSet);

    // watch value changes
    this.storageSubscription = this.storage
      .watch(this.workspaceId + '_custom_sets')
      .subscribe(response => {
        this.customSetDS = new MatTableDataSource(
          this.metadataService.getCustomSets()
        );
        this.generateObservationSetsVisualization(); // generate custom observation visualization
        this.cd.markForCheck();
      });
  }

  ngAfterViewInit() {
    this.observationSetDS.paginator = this.paginator;
  }

  ngOnDestroy() {
    this.onDestroy.next();
    this.onDestroy.complete();
    this.storageSubscription.unsubscribe();
  }

  /**
   * Method is triggered when the user clicks button 'Incorporate annotation'
   *
   */
  onChooseAnnotation() {
    const dialogRef = this.dialog.open(AddAnnotationDialogComponent, {
      data: { workspaceResources: this.workspaceResources }
    });
    dialogRef.afterClosed().subscribe(newCustomSets => {
      if (newCustomSets) {
        newCustomSets.forEach(newCustomSet =>
          this.metadataService.addCustomSet(newCustomSet)
        );
      }
    });
  }

  /**
   * Method is triggered when the user clicks button 'Create a custom observation set'
   * Display the list of all available samples from all files in the workspace
   */
  onCreateObservationSet() {
    this.isWait = true;
    this.globalObservationSets = [];
    this.service
      .getWorkspaceMetadataObservations(this.workspaceId)
      .pipe(
        delay(500), // delay for spinner
        switchMap(metadata => {
          if (metadata?.observation_set?.elements) {
            this.globalObservationSets = metadata.observation_set.elements;
          }
          const globalObservationSetsDS = new MatTableDataSource(
            this.globalObservationSets
          );

          // the list of columns for pop-up table to select samples for custom observation sets
          const observationSetsDisplayedColumns = ['select', 'id'];
          const observationSetsDisplayedColumnsAttributesOnly = [];

          const obsSetsWithAttr = this.globalObservationSets.filter(
            set => 'attributes' in set
          );
          const attributes = obsSetsWithAttr.length
            ? obsSetsWithAttr[0].attributes
            : {};

          for (const attribute in attributes) {
            if (attributes.hasOwnProperty(attribute)) {
              observationSetsDisplayedColumns.push(attribute);
              observationSetsDisplayedColumnsAttributesOnly.push(attribute);
            }
          }

          this.isWait = false;
          const dialogRef = this.dialog.open(AddObservationSetDialogComponent, {
            data: {
              observationSetDS: globalObservationSetsDS,
              observationSetsDisplayedColumns: observationSetsDisplayedColumns,
              observationSetsDisplayedColumnsAttributesOnly: observationSetsDisplayedColumnsAttributesOnly
            }
          });
          return dialogRef.afterClosed();
        }),

        takeUntil(this.onDestroy)
      )
      .subscribe(newObservationSet => {
        if (newObservationSet) {
          this.metadataService.addCustomSet(newObservationSet);
        }
      });
  }

  /**
   * Method to display the list of custom observation sets in the Visualization mode
   */
  generateObservationSetsVisualization() {
    const visTable = [];
    const customObservationSets = this.metadataService.getCustomObservationSets();

    this.visObsDisplayedColumns = [
      'id',
      ...customObservationSets.map(customSet => customSet.name)
    ];
    this.visObsDisplayedColumnsSetsOnly = customObservationSets.map(
      customSet => customSet.name
    );

    if (this.visObsDisplayedColumnsSetsOnly.length > 0) {
      this.getGlobalObservationSets().subscribe(data => {
        this.globalObservationSets = data;
        this.globalObservationSets.forEach(sample => {
          const elem = { sampleName: sample.id };
          customObservationSets.forEach(customSet => {
            if (customSet.elements.filter(e => e.id === sample.id).length > 0) {
              elem[customSet.name] = customSet.color;
            } else {
              elem[customSet.name] = 'transparent';
            }
          });

          visTable.push(elem);
        });
        this.visObservationSetDS = new MatTableDataSource(visTable);
        this.cd.markForCheck();
      });
    }
  }

  /**
   * Method is triggered when the user clicks icon 'Delete'
   * Delete a custom observation or feature set from the list
   */
  onDeleteCustomSet(setId: string) {
    const dialogRef = this.dialog.open(DeleteSetDialogComponent, {
      data: { setId: setId }
    });

    dialogRef.afterClosed().subscribe(result => {
      if (result === 1) {
        this.metadataService.deleteCustomSet(setId);
      }
    });
  }

  /**
   * Method is triggered when the user clicks icon 'Edit'
   *
   * Edit a custom observation or feature set.
   * For custom observation sets the user can update name, color, list of samples.
   * For feature sets only names can be updated
   */
  onEditCustomSet(set) {
    this.isWait = true;
    this.globalObservationSets = [];
    this.service
      .getWorkspaceMetadataObservations(this.workspaceId)
      .pipe(
        delay(500), // delay for spinner
        switchMap(metadata => {
          if (metadata?.observation_set?.elements) {
            this.globalObservationSets = metadata.observation_set.elements;
          }
          const globalObservationSetsDS = new MatTableDataSource(
            this.globalObservationSets
          );

          // the list of columns for pop-up table to select samples for custom observation sets
          const observationSetsDisplayedColumns = ['select', 'id'];
          const observationSetsDisplayedColumnsAttributesOnly = [];

          const obsSetsWithAttr = this.globalObservationSets.filter(
            set => 'attributes' in set
          );
          const attributes = obsSetsWithAttr.length
            ? obsSetsWithAttr[0].attributes
            : {};

          for (const attribute in attributes) {
            if (attributes.hasOwnProperty(attribute)) {
              observationSetsDisplayedColumns.push(attribute);
              observationSetsDisplayedColumnsAttributesOnly.push(attribute);
            }
          }

          this.isWait = false;
          const dialogRef = this.dialog.open(EditFeatureSetDialogComponent, {
            data: {
              name: set.name,
              color: set.color,
              type: set.type,
              selectedElements: set.elements,
              observationSetDS: globalObservationSetsDS,
              observationSetsDisplayedColumns: observationSetsDisplayedColumns,
              observationSetsDisplayedColumnsAttributesOnly: observationSetsDisplayedColumnsAttributesOnly
            }
          });
          return dialogRef.afterClosed();
        }),
        takeUntil(this.onDestroy)
      )
      .subscribe(updatedObservationSet => {
        if (updatedObservationSet) {
          this.metadataService.updateCustomSet(updatedObservationSet, set.name);
        }
      });
  }

  /**
   * Method is triggered when the user clicks icon 'View'
   * View a custom observation or feature set.
   */

  onViewCustomSet(set) {
    const dialogRef = this.dialog.open(ViewSetDialogComponent, { data: set });
    dialogRef.afterClosed().subscribe();
  }

  /**
   * Make the list of columns for the Mat Table with current annotation
   */
  generateMetadataColumns(currentObsSet) {
    if (currentObsSet && currentObsSet.length) {
      this.metadataObsDisplayedColumns = ['id'];
      this.metadataObsDisplayedColumnsAttributesOnly = [];
      let attributes = {};
      currentObsSet.forEach(
        sample => (attributes = { ...attributes, ...sample.attributes })
      );
      for (const attribute in attributes) {
        if (attributes.hasOwnProperty(attribute)) {
          this.metadataObsDisplayedColumns.push(attribute);
          this.metadataObsDisplayedColumnsAttributesOnly.push(attribute);
        }
      }
    }
  }

  /**
   * Get the list of observations used in all files/resources included in the current workspace
   */
  getGlobalObservationSets() {
    let globalObservationSets = [];
    const subject = new Subject<any>();
    this.service
      .getWorkspaceMetadataObservations(this.workspaceId)
      .subscribe(metadata => {
        if (metadata?.observation_set?.elements) {
          globalObservationSets = metadata.observation_set.elements;
        }
        subject.next(globalObservationSets);
      });
    return subject.asObservable();
  }

  /**
   * Method is triggered when the user clicks a link in the Metadata tab description
   */
  viewCustomSetInfo() {
    this.dialog.open(ViewInfoDialogComponent);
  }
}
<mev-spinner-overlay *ngIf="isWait"></mev-spinner-overlay>

<section class="metadata-container">
  <mat-card class="metadata-card">
      <mat-card-content class="metadata-card__main">
          <div class="metadata-card__instruction">
            <p>
              Here you can create <a (click)="viewCustomSetInfo()">custom observation and feature sets</a> to filter your data during
              the analysis. You also can incorporate an annotation file as attributes to your custom observation set. 
            </p>
            <p *ngIf="(workspaceResources| annotationFilesPipe)?.length > 0; else noAnnotationFiles">
              Click the Incorporate annotations button to use your annotation file.
            </p>
           <ng-template #noAnnotationFiles>
             <p>Please upload an annotation file 
               <mat-icon class="icon" matTooltipClass="tooltip" fontSet="material-icons-outlined"
              matTooltip=" An Annotation Table is a special type of table that is be responsible for annotating observations/samples (e.g. adding sample names and associated attributes like experimental group or other covariates). The first column gives the sample names and the remaining columns  each individually represent different covariates associated with that sample." aria-label="Info tooltip about the field">info
            </mat-icon>and add it to your workspace to. </p>
           </ng-template>
          </div>
          <mat-divider [inset]="true"></mat-divider>
          <div class="metadata-card__content">
            <div class="control-group">
              <div class="btn-group">
                <button mat-raised-button color="accent" (click)="onCreateObservationSet()">
                  <mat-icon aria-label="Create an observation set">add</mat-icon>
                  Create an observation set
                </button>
      
                <button mat-raised-button color="accent" (click)="onChooseAnnotation()"
                [disabled]="(workspaceResources| annotationFilesPipe)?.length === 0">
                  <mat-icon aria-label="Incorporate annotations">add</mat-icon>
                  Incorporate annotations
                  </button>
      
              </div>

              <mat-button-toggle-group #customSetsViewMode="matButtonToggleGroup" value="tableMode">
                <mat-button-toggle value="tableMode" aria-label="Text align left">
                  Table view
                </mat-button-toggle>
                <mat-button-toggle value="visMode" aria-label="Text align center">
                  Visualization
                </mat-button-toggle>
              </mat-button-toggle-group>
            </div>
            <div *ngIf="!customSetDS || !customSetDS.data || customSetDS.data.length === 0">
              No custom observation and feature sets available.
            </div>
            <div *ngIf="customSetDS?.data?.length > 0">
              <table *ngIf="customSetsViewMode.value === 'tableMode'" mat-table [dataSource]="customSetDS"
                class="mat-elevation-z8">
                <ng-container matColumnDef="name">
                  <th mat-header-cell *matHeaderCellDef> Name </th>
                  <td mat-cell *matCellDef="let element">
                     <span class="sample-color" [style.background-color]="element.color"></span>
                     {{element.name}} 
                  </td>
                </ng-container>
      
                <ng-container matColumnDef="type">
                  <th mat-header-cell *matHeaderCellDef> Type (Observation/Feature set) </th>
                  <td mat-cell *matCellDef="let element"> {{element.type}} </td>
                </ng-container>
      
                <ng-container matColumnDef="actions">
                  <th mat-header-cell *matHeaderCellDef>Actions</th>
                  <td mat-cell *matCellDef="let element; let i=index;">
                    <button mat-icon-button color="accent" title="View" (click)="onViewCustomSet(element)">
                      <mat-icon aria-label="View">visibility</mat-icon>
                    </button>
                    <button mat-icon-button color="accent" title="Delete" (click)="onDeleteCustomSet(element.name)">
                      <mat-icon aria-label="Delete">delete</mat-icon>
                    </button>
                    <button mat-icon-button color="accent" title="Edit" (click)="onEditCustomSet(element)">
                      <mat-icon aria-label="Edit">edit</mat-icon>
                    </button>
                  </td>
                </ng-container>
      
                <tr mat-header-row *matHeaderRowDef="customSetsDisplayedColumns"></tr>
                <tr mat-row *matRowDef="let row; columns: customSetsDisplayedColumns;">
      
              </table>
      
      
              <table *ngIf="customSetsViewMode.value === 'visMode'" mat-table [dataSource]="visObservationSetDS"
                class="visualization-table">
                <ng-container matColumnDef="id">
                  <th mat-header-cell *matHeaderCellDef> Sample </th>
                  <td mat-cell *matCellDef="let element"> {{ element.sampleName }} </td>
                </ng-container>
      
                <ng-container *ngFor="let setCol of visObsDisplayedColumnsSetsOnly" matColumnDef="{{setCol}}">
                  <th mat-header-cell *matHeaderCellDef>{{ setCol }}</th>
                  <td mat-cell *matCellDef="let element">
                    <div class="highlighted-cell" [style.background-color]="element[setCol]"
                      [ngClass]="{'highlighted-cell2': element[setCol] === true}"></div>
                  </td>
                </ng-container>
      
      
                <tr mat-header-row *matHeaderRowDef="visObsDisplayedColumns"></tr>
                <tr mat-row *matRowDef="let row; columns: visObsDisplayedColumns;">
                </tr>
              </table>
            </div>
          </div>
      </mat-card-content>
  </mat-card>
</section>

./metadata.component.scss

a:hover {
  cursor: pointer;
}

.metadata-container {
  margin: 20px;
  width: 98%;
}

.mat-card {
  overflow-x: scroll;
}

table {
  width: 100%;
}

.mat-table-container {
  width: 80%;
  align-self: start;
}

.mat-header-cell {
  text-transform: capitalize;
}

::ng-deep .mat-figure {
  overflow: scroll;
}

.control-group {
  display: flex;
  justify-content: space-between;
  padding: 16px;
}

::ng-deep .mat-button-toggle-label-content {
  line-height: 36px !important;
}

button {
  margin-right: 10px;
}

.visualization-table {
  width: auto;

  .mat-cell {
    width: 150px;
  }

  .mat-header-cell:nth-child(n + 2) {
    text-align: center;
  }

  .highlighted-cell {
    width: 20px;
    height: 20px;
    margin: auto;
  }
}

.mat-button-toggle-group {
  float: right;
}

.mat-button-toggle-label-content,
.mat-button-toggle-group button div {
  line-height: 35px;
}

.mat-cell.mat-column-comment {
  cursor: pointer;
}

.annotation-text {
  padding: 10px;
}

.hint-text {
  opacity: 0.5;
}

.annotation-table-wrapper {
  display: flex;
}

.add-column-btn {
  position: relative;
  left: -50px;
  top: 15px;
  cursor: pointer;
}

.icon {
  font-size: 14px;
}

.sample-color {
  width: 15px;
  height: 15px;
  display: inline-block;
  vertical-align: middle;
  margin-right: 5px;
}

::ng-deep .mat-tooltip {
  font-size: 12px;
}
Legend
Html element
Component
Html element with directive

result-matching ""

    No results matching ""