projects/web-mev/src/app/features/workspace-detail/components/metadata/metadata.component.ts
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
| changeDetection | ChangeDetectionStrategy.Default |
| selector | mev-metadata |
| styleUrls | ./metadata.component.scss |
| templateUrl | ./metadata.component.html |
constructor(service: WorkspaceDetailService, metadataService: MetadataService, storage: LclStorageService, route: ActivatedRoute, cd: ChangeDetectorRef, dialog: MatDialog)
|
|||||||||||||||||||||
|
Parameters :
|
| workspaceResources | |
Type : WorkspaceResource[]
|
|
| generateMetadataColumns | ||||
generateMetadataColumns(currentObsSet)
|
||||
|
Make the list of columns for the Mat Table with current annotation
Parameters :
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 :
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 :
Returns :
void
|
| onViewCustomSet | ||||
onViewCustomSet(set)
|
||||
|
Method is triggered when the user clicks icon 'View' View a custom observation or feature set.
Parameters :
Returns :
void
|
| viewCustomSetInfo |
viewCustomSetInfo()
|
|
Method is triggered when the user clicks a link in the Metadata tab description
Returns :
void
|
| 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;
}