Revision 58d6304a8688ac75ce1144d67ee25fc6669e60ed authored by Alexander Veit on 27 May 2022, 19:52:17 UTC, committed by Alexander Veit on 27 May 2022, 19:52:17 UTC
2 parent s c4468de + 1fcadc2
Raw File
Chromosome2DAnnotations.js
import { color } from 'd3-color';

import PixiTrack from './PixiTrack';
import ChromosomeInfo from './ChromosomeInfo';

// Configs
import { GLOBALS } from './configs';

// Maximum delay in ms between mousedown and mouseup that is registered as a
// click. Note we need to use mousedown and mouseup as PIXI doesn't recognize
// click events with our current setup. Since most UIs treat long clicks as
// either something special or a cancelation we follow best practices and
// implement a threshold on the delay as well.
const MAX_CLICK_DELAY = 300;

class Chromosome2DAnnotations extends PixiTrack {
  constructor(context, options) {
    super(context, options);
    const { chromInfoPath, pubSub } = context;

    this.pubSub = pubSub;
    this.rects = {};

    ChromosomeInfo(
      chromInfoPath,
      (newChromInfo) => {
        this.chromInfo = newChromInfo;
        this.draw();
      },
      this.pubSub,
    );
  }

  draw() {
    if (!this.chromInfo) {
      return;
    }

    const minRectWidth = this.options.minRectWidth
      ? this.options.minRectWidth
      : 10;
    const minRectHeight = this.options.minRectWidth
      ? this.options.minRectHeight
      : 10;

    super.draw();
    const graphics = this.pMain;
    graphics.clear();

    // The time stamp is used to keep track which rectangles have been drawn per
    // draw call. Each rectangle previously drawn that is not visible anymore
    // (i.e., is not drawn in the current draw call) will be removed at the end
    // by checking against the time stamp.
    const timeStamp = performance.now();

    // Regions have to follow the following form:
    // chrom1, start1, end1, chrom2, start2, end2, color-fill, color-line, min-width, min-height
    // If `color-line` is not given, `color-fill` is used
    this.options.regions.forEach((region) => {
      const id = region.slice(0, 6).join('-');

      if (!this.rects[id]) {
        this.rects[id] = { graphics: new GLOBALS.PIXI.Graphics() };
        graphics.addChild(this.rects[id].graphics);
      }

      this.rects[id].timeStamp = timeStamp;

      const colorFill = color(region[6]);
      let colorLine = color(region[7]);

      if (!colorLine) {
        colorLine = colorFill;
      }

      const colorFillHex = GLOBALS.PIXI.utils.rgb2hex([
        colorFill.r / 255.0,
        colorFill.g / 255.0,
        colorFill.b / 255.0,
      ]);
      const colorLineHex = GLOBALS.PIXI.utils.rgb2hex([
        colorLine.r / 255.0,
        colorLine.g / 255.0,
        colorLine.b / 255.0,
      ]);

      graphics.lineStyle(1, colorLineHex, colorLine.opacity);
      graphics.beginFill(colorFillHex, colorFill.opacity);

      // console.log('region:', region);
      let startX = this._xScale(
        this.chromInfo.chrPositions[region[0]].pos + +region[1],
      );
      const endX = this._xScale(
        this.chromInfo.chrPositions[region[0]].pos + +region[2],
      );

      let startY = this._yScale(
        this.chromInfo.chrPositions[region[3]].pos + +region[4],
      );
      const endY = this._yScale(
        this.chromInfo.chrPositions[region[3]].pos + +region[5],
      );

      let width = endX - startX;
      let height = endY - startY;

      const _minRectWidth =
        typeof region[8] !== 'undefined' ? region[8] : minRectWidth;
      const _minRectHeight =
        typeof region[9] !== 'undefined' ? region[9] : minRectHeight;

      if (width < _minRectWidth) {
        // this region is too small to draw so center it on the location
        // where it would be drawn
        startX = (startX + endX) / 2 - _minRectWidth / 2;
        width = _minRectWidth;
      }

      if (height < _minRectHeight) {
        startY = (startY + endY) / 2 - _minRectHeight / 2;
        height = _minRectHeight;
      }

      graphics.drawRect(startX, startY, width, height);

      // Make annotation clickable
      this.rects[id].graphics.clear();
      this.rects[id].graphics.interactive = true;
      this.rects[id].graphics.buttonMode = true;
      this.rects[id].graphics.hitArea = new GLOBALS.PIXI.Rectangle(
        startX,
        startY,
        width,
        height,
      );

      this.rects[id].graphics.mousedown = () => {
        this.rects[id].mouseDownTime = performance.now();
      };

      this.rects[id].graphics.mouseup = (event) => {
        if (
          performance.now() - this.rects[id].mouseDownTime <
          MAX_CLICK_DELAY
        ) {
          this.pubSub.publish('app.click', {
            type: 'annotation',
            event,
            payload: region,
          });
        }
      };
    });
  }

  setPosition(newPosition) {
    super.setPosition(newPosition);

    this.pMain.position.y = this.position[1];
    this.pMain.position.x = this.position[0];
  }

  zoomed(newXScale, newYScale) {
    this.xScale(newXScale);
    this.yScale(newYScale);

    this.draw();
  }
}

export default Chromosome2DAnnotations;
back to top