Skip to content
Snippets Groups Projects
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
explorer-page.component.ts 11.41 KiB
import {AfterViewInit, Component, ElementRef, OnInit, ViewChild, Output, EventEmitter, HostListener} from '@angular/core';
import {ActivatedRoute, Router} from '@angular/router';
import {Effect, Protein, ProteinNetwork} from '../protein-network';
import {HttpClient} from '@angular/common/http';
import {ApiService} from '../../api.service';
import {AnalysisService} from '../../analysis.service';

declare var vis: any;



@Component({
  selector: 'app-explorer-page',
  templateUrl: './explorer-page.component.html',
  styleUrls: ['./explorer-page.component.scss'],
})
export class ExplorerPageComponent implements OnInit, AfterViewInit {

  public showDetails = false;
  public currentProteinAc = '';
  public geneNames: Array<string> = [];
  public proteinNames: Array<string> = [];
  public proteinAcs: Array<string> = [];
  public watcher = 0;

  public viralProteinCheckboxes: Array<{ checked: boolean; data: Effect }> = [];

  public proteinData: ProteinNetwork;

  public filteredProteins = [];
  public proteins: any;
  public effects: any;
  public edges: any;

  private network: any;
  private nodeData: { nodes: any, edges: any } = {nodes: null, edges: null};

  private seed = 1;  // TODO: Remove this

  private dumpPositions = false;
  public physicsEnabled = false;

  public queryItems = [];
  public showAnalysisDialog = false;





  @ViewChild('network', {static: false}) networkEl: ElementRef;

  @HostListener('window:keydown', ['$event'])
  handleKeyboardEvent1(event: KeyboardEvent) {


    if (event.ctrlKey) {
        this.watcher = 1;
        // console.log(this.watcher);

    }
  }

  @HostListener('window:keyup', ['$event'])
  handleKeyboardEvent(event: KeyboardEvent) {


    if (event.ctrlKey) {
        this.watcher = 0;
        // console.log(this.watcher);
    }
  }

  constructor(private http: HttpClient,
              private route: ActivatedRoute,
              private router: Router,
              private api: ApiService,
              public analysis: AnalysisService) {
    this.geneNames.push('IFI16');
    this.proteinNames.push('Gamma-interface-inducible protein 16');
    this.proteinAcs.push('Q16666');

    this.route.queryParams.subscribe(async (params) => {
      this.dumpPositions = params.dumpPositions;
      this.physicsEnabled = !!this.dumpPositions;

      const protein = params.protein;
      if (!protein) {
        // In this case, the URL is just `/explorer`
        // Therefore, we do not show a modal
        this.showDetails = false;
        return;
      }

      // In this case, the URL is `/explorer/<protein>`

      if (this.currentProteinAc === protein) {
        // The protein group is the same as before, so we do not need to do anything
        // TODO Also highlight node when reloading the page/sharing the URL
        return;
      }

      // We have a new protein id, so we need to load it and show the modal...

      this.currentProteinAc = protein;

      // TODO: Perform call here for 'protein'...
      // this.zoomToNode(protein)
      this.showDetails = true;
    });

    this.analysis.subscribe((protein, selected) => {
      const nodeId = `pg_${protein.proteinAc}`;
      const node = this.nodeData.nodes.get(nodeId);
      const pos = this.network.getPositions([nodeId]);
      node.x = pos[nodeId].x;
      node.y = pos[nodeId].y;
      if (selected) {
        if (node) {
          node.color = '#c42eff';
          this.nodeData.nodes.update(node);
        }
      } else {
        if (node) {
          node.color = '#e2b600';
          this.nodeData.nodes.update(node);
        }
      }
    });
  }

  ngOnInit() {
  }


  async ngAfterViewInit() {
    if (!this.network) {
      await this.createNetwork();
    }
  }

  fillQueryItems() {
    this.queryItems = this.filteredProteins;
  }

  private async getNetwork() {
    const data: any = await this.api.getNetwork();
    this.proteins = data.proteins;
    this.effects = data.effects;
    this.edges = data.edges;
  }

  public reset(event) {
    const checked = event.target.checked;
    this.viralProteinCheckboxes.forEach(item => item.checked = checked);
    this.filterNodes();
  }

  private zoomToNode(id: string) {
    const coords = this.network.getPositions(id)[id];
    this.network.moveTo({
      position: {x: coords.x, y: coords.y},
      scale: 1.0,
      animation: true,
    });
  }

  public async openSummary(protein: Protein, zoom: boolean) {
    await this.router.navigate(['explorer'], {queryParams: {protein: protein.proteinAc}});
    if (zoom) {
      this.zoomToNode(`pg_${protein.proteinAc}`);
    }
  }

  public async closeSummary() {
    await this.router.navigate(['explorer']);
  }


  private async createNetwork() {
    await this.getNetwork();
    this.proteinData = new ProteinNetwork(this.proteins, this.effects, this.edges);
    if (!this.dumpPositions) {
      await this.proteinData.loadPositions(this.http);
    }
    this.proteinData.linkNodes();

    // Populate baits
    const effectNames = [];
    this.proteinData.effects.sort((a, b) => {
      return a.effectName.localeCompare(b.effectName);
    });
    this.proteinData.effects.forEach((effect) => {
      const effectName = effect.effectName;
      if (effectNames.indexOf(effectName) === -1) {
        effectNames.push(effectName);
        this.viralProteinCheckboxes.push({
          checked: false,
          data: effect,
        });
      }
    });

    const {nodes, edges} = this.mapDataToNodes(this.proteinData);
    this.nodeData.nodes = new vis.DataSet(nodes);
    this.nodeData.edges = new vis.DataSet(edges);

    const container = this.networkEl.nativeElement;
    const options = {
      layout: {
        improvedLayout: false,
      },
      edges: {
        smooth: false,
      },
      physics: {
        enabled: this.physicsEnabled,
        stabilization: {
          enabled: false,
        },
      },
    };

    this.network = new vis.Network(container, this.nodeData, options);
    this.network.on('click', (properties) => {
      const id: Array<string> = properties.nodes;
      // TODO use groupID
      if (id.length > 0) {
        if (id[0].startsWith('pg_')) {
          const protein = this.proteinData.getProtein(id[0].substr(3));
          this.openSummary(protein, false);
          // tslint:disable-next-line:no-console
          console.log(this.currentProteinAc);
          if (this.watcher === 1 ) {
            if (this.inSelection(protein.proteinAc) === true) {
              // tslint:disable-next-line:no-console
                console.log(this.removeFromSelection(protein.proteinAc));
            } else {
              // tslint:disable-next-line:no-console
                console.log(this.addToSelection(protein.proteinAc));
            // console.log(this.removeFromSelection(this.currentProteinAc));
              // tslint:disable-next-line:no-console
                console.log(this.analysis.getCount());
            }
          }
        } else {
          this.closeSummary();
        }
      }
    });

    if (this.dumpPositions) {
      this.network.on('stabilizationIterationsDone', () => {
        // tslint:disable-next-line:no-console
        console.log(JSON.stringify(this.network.getPositions()));
      });
      this.network.stabilize();
    }

    if (this.currentProteinAc) {
      this.zoomToNode(`pg_${this.currentProteinAc}`);
    }

    this.filteredProteins = this.proteins;
    this.fillQueryItems();

  }


  public async filterNodes() {
    const visibleIds = new Set<string>(this.nodeData.nodes.getIds());

    const removeIds = new Set<string>();
    const addNodes = new Map<string, Node>();

    const showAll = !this.viralProteinCheckboxes.find((eff) => eff.checked);
    const connectedProteinAcs = new Set<string>();

    this.viralProteinCheckboxes.forEach((cb) => {
      const effects = [];
      this.proteinData.effects.forEach((effect) => {
        if (effect.effectName === cb.data.effectName) {
          effects.push(effect);
        }
       });
      effects.forEach((effect) => {
        const nodeId = `eff_${effect.effectId}`;
        const found = visibleIds.has(nodeId);
        if ((cb.checked || showAll) && !found) {
          const node = this.mapEffectToNode(effect);
          // this.nodeData.nodes.add(node);
          addNodes.set(node.id, node);
        } else if ((!showAll && !cb.checked) && found) {
          // this.nodeData.nodes.remove(nodeId);
          removeIds.add(nodeId);
        }
        if (cb.checked || showAll) {
          effect.proteins.forEach((pg) => {
            connectedProteinAcs.add(pg.proteinAc);
          });
        }
     });
    });
    this.filteredProteins = [];
    for (const protein of this.proteinData.proteins) {
      const nodeId = `pg_${protein.proteinAc}`;
      const contains = connectedProteinAcs.has(protein.proteinAc);
      const found = visibleIds.has(nodeId);
      if (contains) {
        this.filteredProteins.push(protein);
      }
      if (contains && !found) {
        const node = this.mapProteinToNode(protein);
        // this.nodeData.nodes.add(node);
        addNodes.set(node.id, node);
      } else if (!contains && found) {
        // this.nodeData.nodes.remove(nodeId);
        removeIds.add(nodeId);
      }
    }

    this.nodeData.nodes.remove(Array.from(removeIds.values()));
    this.nodeData.nodes.add(Array.from(addNodes.values()));
    this.fillQueryItems();
 }


  public updatePhysicsEnabled() {
    this.network.setOptions({
      physics: {enabled: this.physicsEnabled},
    });
  }

  private mapProteinToNode(protein: Protein): any {
    let color = '#e2b600';
    if (this.analysis.inSelection(protein)) {
      color = '#c42eff';
    }
    return {
      id: `pg_${protein.proteinAc}`,
      label: `${protein.proteinAc}`,
      size: 10, font: '5px', color, shape: 'ellipse', shadow: false,
      x: protein.x,
      y: protein.y
    };
  }

  private mapEffectToNode(effect: Effect): any {
    return {
      id: `eff_${effect.effectId}`,
      label: `${effect.effectId}`,
      size: 10, color: '#118AB2', shape: 'box', shadow: true, font: {color: '#FFFFFF'},
      x: effect.x,
      y: effect.y
    };
  }

  private mapEdge(edge: any): any {
    return {from: `pg_${edge.proteinAc}`, to: `eff_${edge.effectId}`, color: {color: '#afafaf', highlight: '#854141'}};
  }

  private mapDataToNodes(data: ProteinNetwork): { nodes: any[], edges: any[] } {
    const nodes = [];
    const edges = [];

    for (const protein of data.proteins) {
      nodes.push(this.mapProteinToNode(protein));
    }

    for (const effect of data.effects) {
      nodes.push(this.mapEffectToNode(effect));
    }

    for (const edge of data.edges) {
      edges.push(this.mapEdge(edge));
    }

    return {
      nodes,
      edges,
    };
  }

  // TODO: Remove this:
  private random() {
    const x = Math.sin(this.seed++) * 10000;
    return x - Math.floor(x);
  }

  // Selection
  // TODO: Improve usage of group ids, revise this after models have been changed to just protein

  inSelection(proteinAc: string): boolean {
    if (!this.proteinData || !proteinAc) {
      return false;
    }
    const protein = this.proteinData.getProtein(proteinAc);
    return this.analysis.inSelection(protein);
  }

  addToSelection(proteinAc: string) {
    if (!this.proteinData || !proteinAc) {
      return false;
    }
    const protein = this.proteinData.getProtein(proteinAc);
    this.analysis.addProtein(protein);
  }

  removeFromSelection(proteinAc: string) {
    if (!this.proteinData || !proteinAc) {
      return false;
    }
    const protein = this.proteinData.getProtein(proteinAc);
    this.analysis.removeProtein(protein);
  }

}