File

projects/web-mev/src/app/d3/components/limma/limma.component.ts

Description

Limma Component

Used for Limma analysis

Implements

OnInit AfterViewInit

Metadata

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

Index

Properties
Methods
Inputs

Constructor

constructor(analysesService: AnalysesService, dialog: MatDialog, metadataService: MetadataService)
Parameters :
Name Type Optional
analysesService AnalysesService No
dialog MatDialog No
metadataService MetadataService No

Inputs

outputs

Methods

createChart
createChart()

Function to generate D3 box plot

Returns : void
getBoxPlotStatistics
getBoxPlotStatistics(numbers: number[])

Help function to calculate basic statistics for Box Plot

Parameters :
Name Type Optional
numbers number[] No
Returns : { q1: any; median: any; q3: any; iqr: number; min: any; max: any; }
initializeFeatureResource
initializeFeatureResource()
Returns : void
loadFeaturesPage
loadFeaturesPage()

Function to load features by filters, pages and sorting settings specified by a user

Returns : void
ngAfterViewInit
ngAfterViewInit()
Returns : void
ngOnChanges
ngOnChanges()
Returns : void
ngOnInit
ngOnInit()
Returns : void
onCreateCustomFeatureSet
onCreateCustomFeatureSet()

Function that is triggered when the user clicks the "Create a custom sample" button

Returns : void
onResize
onResize(event)

Function is triggered when resizing the chart

Parameters :
Name Optional
event No
Returns : void
onSubmit
onSubmit()

Function is triggered when submitting the form with table filters

Returns : void
preprocessBoxPlotData
preprocessBoxPlotData()

Function to prepape the outputs data for D3 box plot visualization

Returns : void

Properties

allowedFilters
Type : object
Default value : { /*name: { defaultValue: '', hasOperator: false },*/ padj: { defaultValue: '', hasOperator: true, operatorDefaultValue: 'lte' }, log2FoldChange: { defaultValue: '', hasOperator: true, operatorDefaultValue: 'lte' } }
boxPlotData
boxPlotTypes
Type : object
Default value : { Experimental: { label: 'Treated/Experimental', yCat: 'experValues', yPoints: 'experPoints', color: '#f40357' }, Base: { label: 'Baseline/Control', yCat: 'baseValues', yPoints: 'basePoints', color: '#f4cc03' } }
boxWidth
Type : number
Default value : 20
containerId
Type : string
Default value : '#boxPlot'
dataSource
Type : FeaturesDataSource
defaultPageIndex
Type : number
Default value : 0
defaultPageSize
Type : number
Default value : 10
defaultSorting
Type : object
Default value : { field: 'log2FoldChange', direction: 'asc' }
delta
Type : number
Default value : 0.1
dgeResourceId
Public dialog
Type : MatDialog
displayedColumns
Type : []
Default value : [ 'name', 'overall_mean', 'log2FoldChange', 'pvalue', 'padj', 'lfcSE', 'stat' ]
filterForm
Default value : new FormGroup({})
imageName
Type : string
Default value : 'Limma/voom'
jitterWidth
Type : number
Default value : 10
margin
Type : object
Default value : { top: 50, right: 300, bottom: 100, left: 50 }
operators
Type : []
Default value : [ { id: 'eq', name: ' = ' }, { id: 'gte', name: ' >=' }, { id: 'gt', name: ' > ' }, { id: 'lte', name: ' <=' }, { id: 'lt', name: ' < ' }, { id: 'absgt', name: 'ABS(x) > ' }, { id: 'abslt', name: 'ABS(x) < ' } ]
outerHeight
Type : number
Default value : 700
paginator
Type : MatPaginator
Decorators :
@ViewChild(MatPaginator)
precision
Type : number
Default value : 2
sort
Type : MatSort
Decorators :
@ViewChild(MatSort)
svgElement
Type : ElementRef
Decorators :
@ViewChild('boxPlot')
tooltipOffsetX
Type : number
Default value : 10
xCat
Type : string
Default value : 'key'
xScale
yBaseCat
Default value : this.boxPlotTypes.Base.yCat
yBasePoints
Default value : this.boxPlotTypes.Base.yPoints
yExperCat
Default value : this.boxPlotTypes.Experimental.yCat
yExperPoints
Default value : this.boxPlotTypes.Experimental.yPoints
yScale
import {
  Component,
  OnInit,
  ChangeDetectionStrategy,
  ViewChild,
  AfterViewInit,
  Input,
  ElementRef
} from '@angular/core';
import { MatPaginator } from '@angular/material/paginator';
import { MatSort } from '@angular/material/sort';
import { AnalysesService } from '@app/features/analysis/services/analysis.service';
import { merge, BehaviorSubject, Observable } from 'rxjs';
import { tap, finalize } from 'rxjs/operators';
import { DataSource } from '@angular/cdk/table';
import * as d3 from 'd3';
import d3Tip from 'd3-tip';
import { FormGroup, FormControl } from '@angular/forms';
import { CustomSetType } from '@app/_models/metadata';
import { MatDialog } from '@angular/material/dialog';
import { AddSampleSetComponent } from '../dialogs/add-sample-set/add-sample-set.component';
import { MetadataService } from '@app/core/metadata/metadata.service';

/**
 * Limma Component
 *
 * Used for Limma analysis
 */
@Component({
  selector: 'mev-limma',
  templateUrl: './limma.component.html',
  styleUrls: ['./limma.component.scss'],
  changeDetection: ChangeDetectionStrategy.Default
})
export class LimmaComponent implements OnInit, AfterViewInit {
  @Input() outputs;
  dataSource: FeaturesDataSource; // datasource for MatTable
  boxPlotData; // data retrieved from the dgeResourceId resource, pre-processed for D3 box plot visualization
  dgeResourceId;

  @ViewChild(MatPaginator) paginator: MatPaginator;
  @ViewChild(MatSort) sort: MatSort;
  @ViewChild('boxPlot') svgElement: ElementRef;

  /* Table settings */
  displayedColumns = [
    'name',
    'overall_mean',
    'log2FoldChange',
    'pvalue',
    'padj',
    'lfcSE',
    'stat'
  ];
  operators = [
    { id: 'eq', name: ' = ' },
    { id: 'gte', name: ' >=' },
    { id: 'gt', name: ' > ' },
    { id: 'lte', name: ' <=' },
    { id: 'lt', name: ' < ' },
    { id: 'absgt', name: 'ABS(x) > ' },
    { id: 'abslt', name: 'ABS(x) < ' }
  ];

  defaultPageIndex = 0;
  defaultPageSize = 10;
  defaultSorting = { field: 'log2FoldChange', direction: 'asc' };

  /* Table filters */
  allowedFilters = {
    /*name: { defaultValue: '', hasOperator: false },*/
    padj: {
      defaultValue: '',
      hasOperator: true,
      operatorDefaultValue: 'lte'
    },
    log2FoldChange: {
      defaultValue: '',
      hasOperator: true,
      operatorDefaultValue: 'lte'
    }
  };

  filterForm = new FormGroup({});

  /* D3 Chart settings */
  containerId = '#boxPlot';
  imageName = 'Limma/voom'; // file name for downloaded SVG image
  margin = { top: 50, right: 300, bottom: 100, left: 50 };
  outerHeight = 700;
  precision = 2;
  delta = 0.1; // used for X and Y axis ranges (we add delta to avoid bug when both max and min are zeros)
  boxWidth = 20; // the width of rectangular box
  jitterWidth = 10;
  tooltipOffsetX = 10; // to position the tooltip on the right side of the triggering element

  boxPlotTypes = {
    Experimental: {
      label: 'Treated/Experimental',
      yCat: 'experValues',
      yPoints: 'experPoints',
      color: '#f40357'
    },
    Base: {
      label: 'Baseline/Control',
      yCat: 'baseValues',
      yPoints: 'basePoints',
      color: '#f4cc03'
    }
  };
  xCat = 'key'; // field name in data for X axis
  yExperCat = this.boxPlotTypes.Experimental.yCat; // field name in data for Y axis (used to build experimental box plots)
  yBaseCat = this.boxPlotTypes.Base.yCat; // field name in data for Y axis (used to build baseline box plots)
  yExperPoints = this.boxPlotTypes.Experimental.yPoints; // field name in data for Y axis (used to draw individual points for experimental samples)
  yBasePoints = this.boxPlotTypes.Base.yPoints; // field name in data for Y axis (used to draw individual points for baseline samples)
  xScale; // scale functions to transform data values into the the range
  yScale;

  constructor(
    private analysesService: AnalysesService,
    public dialog: MatDialog,
    private metadataService: MetadataService
  ) {
    this.dataSource = new FeaturesDataSource(this.analysesService);

    // adding form controls depending on the tables settings (the allowedFilters property)
    for (const key in this.allowedFilters) {
      if (this.allowedFilters.hasOwnProperty(key)) {
        // TSLint rule
        const defaultValue = this.allowedFilters[key].defaultValue;
        this.filterForm.addControl(key, new FormControl(defaultValue));
        if (this.allowedFilters[key].hasOperator) {
          const operatorDefaultValue = this.allowedFilters[key]
            .operatorDefaultValue;
          this.filterForm.addControl(
            key + '_operator',
            new FormControl(operatorDefaultValue)
          );
        }
      }
    }
  }

  ngOnInit() {
    this.initializeFeatureResource();
  }

  ngAfterViewInit() {
    this.sort.sortChange.subscribe(
      () => (this.paginator.pageIndex = this.defaultPageIndex)
    );
    this.dataSource.connect().subscribe(featureData => {
      this.boxPlotData = featureData;
      this.preprocessBoxPlotData();
      this.createChart();
    });
    merge(this.sort.sortChange, this.paginator.page)
      .pipe(
        tap(() => {
          this.loadFeaturesPage();
          this.preprocessBoxPlotData();
          this.createChart();
        })
      )
      .subscribe();
  }

  ngOnChanges(): void {
    this.initializeFeatureResource();
  }

  initializeFeatureResource(): void {
    this.dgeResourceId = this.outputs.dge_results;
    const sorting = {
      sortField: this.defaultSorting.field,
      sortDirection: this.defaultSorting.direction
    };
    this.dataSource.loadFeatures(
      this.dgeResourceId,
      {},
      sorting,
      this.defaultPageIndex,
      this.defaultPageSize
    );
  }

  /**
   * Function is triggered when submitting the form with table filters
   */
  onSubmit() {
    this.paginator.pageIndex = this.defaultPageIndex;
    this.loadFeaturesPage();
  }

  /**
   * Function is triggered when resizing the chart
   */
  onResize(event) {
    this.createChart();
  }

  /**
   * Help function to calculate basic statistics for Box Plot
   */
  getBoxPlotStatistics(numbers: number[]) {
    const q1 = d3.quantile(numbers, 0.25);
    const q3 = d3.quantile(numbers, 0.75);
    return {
      q1: q1,
      median: d3.quantile(numbers, 0.5),
      q3: q3,
      iqr: q3 - q1,
      min: d3.min(numbers), // q1 - 1.5 * interQuantileRange
      max: d3.max(numbers) // q3 + 1.5 * interQuantileRange
    };
  }

  /**
   * Function to prepape the outputs data for D3 box plot visualization
   */
  preprocessBoxPlotData() {
    const baseSamples = this.outputs.base_condition_samples.elements.map(
      elem => elem.id
    );
    const experSamples = this.outputs.experimental_condition_samples.elements.map(
      elem => elem.id
    );

    const countsFormatted = this.boxPlotData.map(elem => {
      const baseNumbers = baseSamples.reduce(
        (acc, cur) => [...acc, elem[cur]],
        []
      );
      const experNumbers = experSamples.reduce(
        (acc, cur) => [...acc, elem[cur]],
        []
      );
      const newElem = { key: elem.name };
      newElem[this.yExperCat] = this.getBoxPlotStatistics(experNumbers);
      newElem[this.yBaseCat] = this.getBoxPlotStatistics(baseNumbers);
      newElem[this.yExperPoints] = experNumbers;
      newElem[this.yBasePoints] = baseNumbers;
      return newElem;
    });
    this.boxPlotData = countsFormatted;

    // overwrite labels if there are custom names efimed by the user
    if (this.outputs.base_condition_name) {
      this.boxPlotTypes.Base.label = this.outputs.base_condition_name;
    }

    if (this.outputs.experimental_condition_name) {
      this.boxPlotTypes.Experimental.label = this.outputs.experimental_condition_name;
    }
  }

  /**
   * Function that is triggered when the user clicks the "Create a custom sample" button
   */
  onCreateCustomFeatureSet() {
    const features = this.dataSource.featuresSubject.value.map(elem => ({
      id: elem.name
    }));
    const dialogRef = this.dialog.open(AddSampleSetComponent, {
      data: { type: CustomSetType.FeatureSet }
    });

    dialogRef.afterClosed().subscribe(customSetData => {
      if (customSetData) {
        const customSet = {
          name: customSetData.name,
          type: CustomSetType.FeatureSet,
          elements: features,
          multiple: true
        };

        this.metadataService.addCustomSet(customSet);
      }
    });
  }

  /**
   * Function to generate D3 box plot
   */
  createChart(): void {
    const outerWidth = this.svgElement.nativeElement.offsetWidth;
    const outerHeight = this.outerHeight;
    const width = outerWidth - this.margin.left - this.margin.right;
    const height = outerHeight - this.margin.top - this.margin.bottom;

    const data = this.boxPlotData;

    d3.select(this.containerId)
      .selectAll('svg')
      .remove();

    const svg = d3
      .select(this.containerId)
      .append('svg')
      .attr('width', outerWidth)
      .attr('height', outerHeight)
      .append('g')
      .attr(
        'transform',
        'translate(' + this.margin.left + ',' + this.margin.top + ')'
      )
      .style('fill', 'none');

    // Tooltip
    const tooltipOffsetX = this.tooltipOffsetX;
    const tip = d3Tip()
      .attr('class', 'd3-tip')
      .offset([-10, 0])
      .html((event, d) => {
        // if it is a hover over an individual point, show the value
        if (d !== Object(d)) return 'Value: ' + d.toFixed(this.precision);

        // if it is a hover over a box plot, show table with basic statistic values
        const htmlTable =
          '<table><thead><th></th><th>' +
          this.boxPlotTypes.Experimental.label +
          '</th><th>' +
          this.boxPlotTypes.Base.label +
          '</th><thead>' +
          '<tr><td>Q1</td><td>' +
          d[this.yExperCat].q1.toFixed(this.precision) +
          '</td><td>' +
          d[this.yBaseCat].q1.toFixed(this.precision) +
          '</td></tr>' +
          '<tr><td>Q2</td><td>' +
          d[this.yExperCat].median.toFixed(this.precision) +
          '</td><td>' +
          d[this.yBaseCat].median.toFixed(this.precision) +
          '</td></tr>' +
          '<tr><td>Q3</td><td>' +
          d[this.yExperCat].q3.toFixed(this.precision) +
          '</td><td>' +
          d[this.yBaseCat].q3.toFixed(this.precision) +
          '</td></tr>' +
          '<tr><td>IQR</td><td>' +
          d[this.yExperCat].iqr.toFixed(this.precision) +
          '</td><td>' +
          d[this.yBaseCat].iqr.toFixed(this.precision) +
          '</td></tr>' +
          '<tr><td>MIN</td><td>' +
          d[this.yExperCat].min.toFixed(this.precision) +
          '</td><td>' +
          d[this.yBaseCat].min.toFixed(this.precision) +
          '</td></tr>' +
          '<tr><td>MAX</td><td>' +
          d[this.yExperCat].max.toFixed(this.precision) +
          '</td><td>' +
          d[this.yBaseCat].max.toFixed(this.precision) +
          '</td></tr>' +
          '</table>';
        return '<b>' + d[this.xCat] + '</b><br>' + htmlTable;
      });
    svg.call(tip);

    svg
      .append('rect')
      .attr('width', width)
      .attr('height', height)
      .style('fill', 'transparent');

    /* Setting up X-axis and Y-axis*/
    this.xScale = d3
      .scaleBand()
      .rangeRound([0, width])
      .domain(data.map(d => d.key))
      .paddingInner(1)
      .paddingOuter(0.5);

    this.yScale = d3.scaleLinear().rangeRound([height, 0]);

    const experMaxVal = d3.max(data, d => <number>d[this.yExperCat].max);
    const experMinVal = d3.min(data, d => <number>d[this.yExperCat].min);
    const baseMaxVal = d3.max(data, d => <number>d[this.yBaseCat].max);
    const baseMinVal = d3.min(data, d => <number>d[this.yBaseCat].min);
    const yMax = Math.max(baseMaxVal, experMaxVal);
    const yMin = Math.min(baseMinVal, experMinVal);
    const yRange = yMax - yMin + this.delta; // add delta to avoid bug when both max and min are zeros

    this.yScale.domain([
      yMin - yRange * this.delta,
      yMax + yRange * this.delta
    ]);

    svg
      .append('g')
      .attr('transform', 'translate(0,' + height + ')')
      .attr('class', 'x-axis')
      .call(d3.axisBottom(this.xScale))
      .selectAll('text')
      .style('text-anchor', 'end')
      .attr('dx', '-.8em')
      .attr('dy', '.15em')
      .attr('transform', 'rotate(-45)');

    svg.append('g').call(d3.axisLeft(this.yScale));

    // Box plots
    Object.keys(this.boxPlotTypes).forEach((key, i) => {
      const yCatProp = this.boxPlotTypes[key].yCat;
      const yPointsProp = this.boxPlotTypes[key].yPoints;
      const color = this.boxPlotTypes[key].color;

      // Main vertical line
      svg
        .selectAll('.vertLines')
        .data(data)
        .enter()
        .append('line')
        .attr(
          'x1',
          (d: any) =>
            this.xScale(d[this.xCat]) + (1.2 * i - 0.6) * this.boxWidth
        )
        .attr(
          'x2',
          (d: any) =>
            this.xScale(d[this.xCat]) + (1.2 * i - 0.6) * this.boxWidth
        )
        .attr('y1', (d: any) => this.yScale(d[yCatProp].min))
        .attr('y2', (d: any) => this.yScale(d[yCatProp].max))
        .attr('stroke', 'black')
        .style('width', 10);

      svg
        .selectAll('.boxes')
        .data(data)
        .enter()
        .append('rect')
        .attr(
          'x',
          d => this.xScale(d[this.xCat]) + (1.2 * i - 1.1) * this.boxWidth
        )
        .attr('y', d => this.yScale(d[yCatProp].q3))
        .attr(
          'height',
          d => this.yScale(d[yCatProp].q1) - this.yScale(d[yCatProp].q3)
        )
        .attr('width', this.boxWidth)
        .attr('stroke', 'black')
        .style('fill', color)
        .attr('pointer-events', 'all')
        .on('mouseover', function(mouseEvent: any, d) {
          tip.show(mouseEvent, d, this);
          tip.style('left', mouseEvent.x + tooltipOffsetX + 'px');
        })
        .on('mouseout', tip.hide);

      // Medians
      svg
        .selectAll('.medianLines')
        .data(data)
        .enter()
        .append('line')
        .attr(
          'x1',
          d => this.xScale(d[this.xCat]) + (1.2 * i - 1.1) * this.boxWidth
        )
        .attr(
          'x2',
          d => this.xScale(d[this.xCat]) + (1.2 * i - 0.1) * this.boxWidth
        )
        .attr('y1', d => this.yScale(d[yCatProp].median))
        .attr('y2', d => this.yScale(d[yCatProp].median))
        .attr('stroke', 'black')
        .style('width', 80);

      // Add individual points with jitter
      svg
        .selectAll('genePoints')
        .data(data)
        .enter()
        .each((d, ix, nodes) => {
          d3.select(nodes[ix])
            .selectAll('.individualPoints')
            .data(d[yPointsProp])
            .enter()
            .append('circle')
            .attr(
              'cx',
              this.xScale(data[ix][this.xCat]) +
                (1.2 * i - 0.6) * this.boxWidth -
                this.jitterWidth / 2 +
                Math.random() * this.jitterWidth
            )
            .attr('cy', d => this.yScale(d))
            .attr('r', 3)
            .style('fill', color)
            .attr('stroke', '#000000')
            .attr('pointer-events', 'all')
            .on('mouseover', function(mouseEvent: any, d) {
              tip.show(mouseEvent, d, this);
              tip.style('left', mouseEvent.x + tooltipOffsetX + 'px');
            })
            .on('mouseout', tip.hide);
        });

      // Legend
      const boxPlotColors = Object.keys(this.boxPlotTypes).map(key => ({
        label: this.boxPlotTypes[key].label,
        color: this.boxPlotTypes[key].color
      }));
      const legend = svg
        .selectAll('.legend')
        .data(boxPlotColors)
        .enter()
        .append('g')
        .classed('legend', true)
        .attr('transform', function(d, i) {
          return 'translate(0,' + i * 20 + ')';
        });

      legend
        .append('circle')
        .attr('r', 5)
        .attr('cx', width + 20)
        .attr('fill', d => d.color);

      legend
        .append('text')
        .attr('x', width + 30)
        .attr('dy', '.35em')
        .style('fill', '#000')
        .attr('class', 'legend-label')
        .text(d => d.label);
    });
  }

  /**
   * Function to load features by filters, pages and sorting settings specified by a user
   */
  loadFeaturesPage() {
    const formValues = this.filterForm.value; // i.e. {name: "asdfgh", pvalue: 3, pvalue_operator: "lte", log2FoldChange: 2, log2FoldChange_operator: "lte"}
    const paramFilter = {}; // has values {'log2FoldChange': '[absgt]:2'};
    for (const key in this.allowedFilters) {
      if (
        formValues.hasOwnProperty(key) &&
        formValues[key] !== '' &&
        formValues[key] !== null
      ) {
        if (formValues.hasOwnProperty(key + '_operator')) {
          paramFilter[key] =
            '[' + formValues[key + '_operator'] + ']:' + formValues[key];
        } else {
          paramFilter[key] = '[eq]:' + formValues[key];
        }
      }
    }

    const sorting = {
      sortField: this.sort.active,
      sortDirection: this.sort.direction
    };

    this.dataSource.loadFeatures(
      this.dgeResourceId,
      paramFilter,
      sorting,
      this.paginator.pageIndex,
      this.paginator.pageSize
    );
  }
}

export interface LimmaFeature {
  name: string;
  overall_mean: number;
  Control: number;
  Experimental: number;
  log2FoldChange: number;
  lfcSE: number;
  stat: number;
  pvalue: number;
  padj: number;
}

export class FeaturesDataSource implements DataSource<LimmaFeature> {
  public featuresSubject = new BehaviorSubject<LimmaFeature[]>([]);
  public featuresCount = 0;
  private loadingSubject = new BehaviorSubject<boolean>(false);
  public loading$ = this.loadingSubject.asObservable();

  constructor(private analysesService: AnalysesService) {}

  loadFeatures(
    resourceId: string,
    filterValues: object,
    sorting: object,
    pageIndex: number,
    pageSize: number
  ) {
    this.loadingSubject.next(true);

    this.analysesService
      .getResourceContent(
        resourceId,
        pageIndex + 1,
        pageSize,
        filterValues,
        sorting
      )
      .pipe(finalize(() => this.loadingSubject.next(false)))
      .subscribe(features => {
        this.featuresCount = features.count;
        const featuresFormatted = features.results.map(feature => {
          const newFeature = { name: feature.rowname, ...feature.values };
          return newFeature;
        });
        return this.featuresSubject.next(featuresFormatted);
      });
  }

  connect(): Observable<LimmaFeature[]> {
    return this.featuresSubject.asObservable();
  }

  disconnect(): void {
    this.featuresSubject.complete();
    this.loadingSubject.complete();
  }
}
<mat-card class="analysis-card">
  <mat-card-header>
    <div mat-card-avatar class="analysis-card__img"></div>
    <mat-card-title>Limma: {{ outputs?.job_name }}</mat-card-title>
  </mat-card-header>
  <mat-card-content class="analysis-card__main">
    <p class="analysis-card__instruction">
      Use the table pagination component at the bottom of the table to navigate
      between a set of table and chart results. <br>
      You can sort data in alphabetical and numerical order,
      or use filters to hide data you don't want to see.
    </p>
    <mat-divider [inset]="true"></mat-divider>
    <div class="analysis-card__content">
      <section class="filter-section">
        <form [formGroup]="filterForm" (ngSubmit)="onSubmit()">

          <!-- <div class="filter">
              <span class="label">
                  Name: 
              </span>
                <mat-form-field class="form-control">
                  <input matInput formControlName="name">
              </mat-form-field>
          </div> -->

          <div class="filter">
            <span class="label">
              P-value adjusted:
            </span>
            <mat-form-field class="form-control form-control__small" color="accent">
              <mat-select formControlName="padj_operator">
                <mat-option *ngFor="let operator of operators" [value]="operator.id">
                  {{ operator.name }}
                </mat-option>
              </mat-select>
            </mat-form-field>
            <mat-form-field class="form-control">
              <input matInput formControlName="padj" type=number step=any min=0>
            </mat-form-field>
          </div>

          <div class="filter">
            <span class="label">
              Log-Fold-Change:
            </span>
            <mat-form-field class="form-control form-control__small" color="accent">
              <mat-select formControlName="log2FoldChange_operator">
                <mat-option *ngFor="let operator of operators" [value]="operator.id">
                  {{ operator.name }}
                </mat-option>
              </mat-select>
            </mat-form-field>
            <mat-form-field class="form-control">
              <input matInput formControlName="log2FoldChange" type=number step=any>
            </mat-form-field>
          </div>

          <button type="submit" [disabled]="!filterForm.valid" mat-raised-button color="accent">Apply filters</button>
        </form>

      </section>

      <mev-spinner-overlay *ngIf="dataSource.loading$ | async"></mev-spinner-overlay>

      <div class="mat-elevation-z8">
        <mat-table class="deseq-table" [dataSource]="dataSource" matSort [matSortActive]="defaultSorting.field"
          [matSortDirection]="defaultSorting.direction" matSortDisableClear>

          <ng-container matColumnDef="name">
            <mat-header-cell *matHeaderCellDef>Name</mat-header-cell>
            <mat-cell *matCellDef="let row">{{row.name}}</mat-cell>
          </ng-container>

          <ng-container matColumnDef="overall_mean">
            <mat-header-cell *matHeaderCellDef>Overall Mean</mat-header-cell>
            <mat-cell class="description-cell" *matCellDef="let row">{{row.overall_mean}}</mat-cell>
          </ng-container>

          <ng-container matColumnDef="log2FoldChange">
            <mat-header-cell *matHeaderCellDef mat-sort-header>Log 2 Fold Change</mat-header-cell>
            <mat-cell class="description-cell" *matCellDef="let row">{{row.log2FoldChange}}</mat-cell>
          </ng-container>

          <ng-container matColumnDef="padj">
            <mat-header-cell *matHeaderCellDef mat-sort-header>P-value adjusted</mat-header-cell>
            <mat-cell class="description-cell" *matCellDef="let row">{{row.padj}}</mat-cell>
          </ng-container>

          <ng-container matColumnDef="pvalue">
            <mat-header-cell *matHeaderCellDef mat-sort-header>P-value</mat-header-cell>
            <mat-cell class="description-cell" *matCellDef="let row">{{row.pvalue}}</mat-cell>
          </ng-container>

          <ng-container matColumnDef="stat">
            <mat-header-cell *matHeaderCellDef>Stat</mat-header-cell>
            <mat-cell class="description-cell" *matCellDef="let row">{{row.stat}}</mat-cell>
          </ng-container>

          <ng-container matColumnDef="lfcSE">
            <mat-header-cell *matHeaderCellDef>Standard error value (lfcSE)</mat-header-cell>
            <mat-cell class="description-cell" *matCellDef="let row">{{row.lfcSE}}</mat-cell>
          </ng-container>

          <mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row>
          <mat-row *matRowDef="let row; columns: displayedColumns"></mat-row>

        </mat-table>


        <mat-paginator [length]="dataSource.featuresCount" [pageSize]="10" [pageSizeOptions]="[10, 25, 50]">
        </mat-paginator>
      </div>

      <div class="button-panel">
        <button mat-raised-button color="accent" (click)="onCreateCustomFeatureSet()">
          <mat-icon>add</mat-icon>
          Save as a feature set
        </button>
        <mev-download-button [containerId]="containerId" [imageName]="imageName"></mev-download-button>  
      </div>      
      <div #boxPlot id="boxPlot" class="chart" (window:resize)="onResize($event)"></div>

    </div>

  </mat-card-content>

</mat-card>

./limma.component.scss

.analysis-card__img {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' aria-hidden='true' focusable='false' width='1em' height='1em' style='-ms-transform: rotate(360deg); -webkit-transform: rotate(360deg); transform: rotate(360deg);' preserveAspectRatio='xMidYMid meet' viewBox='0 0 24 24'%3E%3Cpath d='M4 2h2v2c0 1.44.68 2.61 1.88 3.78c.86.83 2.01 1.63 3.21 2.42l-1.83 1.19C8.27 10.72 7.31 10 6.5 9.21C5.07 7.82 4 6.1 4 4V2m14 0h2v2c0 2.1-1.07 3.82-2.5 5.21c-1.41 1.38-3.21 2.52-4.96 3.63c-1.75 1.12-3.45 2.21-4.66 3.38C6.68 17.39 6 18.56 6 20v2H4v-2c0-2.1 1.07-3.82 2.5-5.21c1.41-1.38 3.21-2.52 4.96-3.63c1.75-1.12 3.45-2.21 4.66-3.38C17.32 6.61 18 5.44 18 4V2m-3.26 10.61c.99.67 1.95 1.39 2.76 2.18C18.93 16.18 20 17.9 20 20v2h-2v-2c0-1.44-.68-2.61-1.88-3.78c-.86-.83-2.01-1.63-3.21-2.42l1.83-1.19M7 3h10v1l-.06.5H7.06L7 4V3m.68 3h8.64c-.24.34-.52.69-.9 1.06l-.51.44H9.07l-.49-.44c-.38-.37-.66-.72-.9-1.06m1.41 10.5h5.84l.49.44c.38.37.66.72.9 1.06H7.68c.24-.34.52-.69.9-1.06l.51-.44m-2.03 3h9.88l.06.5v1H7v-1l.06-.5z' fill='%2337474f'/%3E%3C/svg%3E");
  background-size: 30px;
  background-position: top center;
  background-repeat: no-repeat;
}

.analysis-card__content {
  padding-top: 1rem;
}

.filter-section {
  font-size: 14px;
}

.filter {
  margin-right: 25px;
  display: inline;
}

.form-control {
  margin-right: 10px;
}

.form-control__small {
  width: 80px;
}

.button-panel {
  margin-top: 30px;
}

.chart {
  text-align: center;
}

::ng-deep {
  .x-axis text {
    font-size: 12px;
  }

  .legend-label {
    font-size: 12px;
  }

  .d3-tip {
    line-height: 1;
    font-weight: normal;
    padding: 12px;
    background: rgba(0, 0, 0, 0.8);
    color: #fff;
    border-radius: 2px;
    z-index: 1;
  }

  .d3-tip table th,
  td {
    padding: 5px 10px;
  }
}
Legend
Html element
Component
Html element with directive

result-matching ""

    No results matching ""