wilzbach/msa

View on GitHub
src/views/canvas/CanvasSelection.js

Summary

Maintainability
B
5 hrs
Test Coverage
import {find} from "lodash";

const CanvasSelection = (function(g,ctx) {
  this.g = g;
  this.ctx = ctx;
  return this;
}
);

_.extend(CanvasSelection.prototype, {

  // TODO: should I be moved to the selection manager?
  // returns an array with the currently selected residues
  // e.g. [0,3] = pos 0 and 3 are selected
  _getSelection: function(model) {
    const maxLen = model.get("seq").length;
    const selection = [];
    const sels = this.g.selcol.getSelForRow(model.get("id"));
    const rows = find(sels, function(el) { return el.get("type") === "row"; });
    if ((typeof rows !== "undefined" && rows !== null)) {
      // full match
      const end = maxLen - 1;
      for (let n = 0; n <= end; n++) {
        selection.push(n);
      }
    } else if (sels.length > 0) {
      for (let i = 0, sel; i < sels.length; i++) {
        sel = sels[i];
        const start = sel.get("xStart");
        const end = sel.get("xEnd");
        for (let n = start; n <= end; n++) {
          selection.push(n);
        }
      }
    }

    return selection;
  },

  // loops over all selection and calls the render method
  _appendSelection: function(data) {
    const seq = data.model.get("seq");
    const selection = this._getSelection(data.model);
    // get the status of the upper and lower row
    const getNextPrev= this._getPrevNextSelection(data.model);
    const mPrevSel = getNextPrev[0];
    const mNextSel = getNextPrev[1];

    const boxWidth = this.g.zoomer.get("columnWidth");
    const boxHeight = this.g.zoomer.get("rowHeight");

    // avoid unnecessary loops
    if (selection.length === 0) { return; }

    let hiddenOffset = 0;
    return (() => {
      const result = [];
      const end = seq.length - 1;
      for (let n = 0; n <= end; n++) {
        result.push((() => {
          if (data.hidden.indexOf(n) >= 0) {
            return hiddenOffset++;
          } else {
            const k = n - hiddenOffset;
            // only if its a new selection
            if (selection.indexOf(n) >= 0 && (k === 0 || selection.indexOf(n - 1) < 0 )) {
              return this._renderSelection({n:n,
                                           k:k,
                                           selection: selection,
                                           mPrevSel: mPrevSel,
                                           mNextSel:mNextSel,
                                           xZero: data.xZero,
                                           yZero: data.yZero,
                                           model: data.model});
            }
          }
        })());
      }
      return result;
    })();
  },

  // draws a single user selection
  _renderSelection: function(data) {

    let xZero = data.xZero;
    const yZero = data.yZero;
    const n = data.n;
    const k = data.k;
    const selection = data.selection;
    // and checks the prev and next row for selection  -> no borders in a selection
    const mPrevSel= data.mPrevSel;
    const mNextSel = data.mNextSel;

    // get the length of this selection
    let selectionLength = 0;
    const end = data.model.get("seq").length - 1;
    for (let i = n; i <= end; i++) {
      if (selection.indexOf(i) >= 0) {
        selectionLength++;
      } else {
        break;
      }
    }

    // TODO: ugly!
    const boxWidth = this.g.zoomer.get("columnWidth");
    const boxHeight = this.g.zoomer.get("rowHeight");
    const totalWidth = (boxWidth * selectionLength) + 1;

    const hidden = this.g.columns.get('hidden');

    this.ctx.beginPath();
    const beforeWidth = this.ctx.lineWidth;
    this.ctx.lineWidth = 3;
    const beforeStyle = this.ctx.strokeStyle;
    this.ctx.strokeStyle = "#FF0000";

    xZero += k * boxWidth;

    // split up the selection into single cells
    let xPart = 0;
    const end1 = selectionLength - 1;
    for (let i = 0; i <= end1; i++) {
      let xPos = n + i;
      if (hidden.indexOf(xPos) >= 0) {
        continue;
      }
      // upper line
      if (!((typeof mPrevSel !== "undefined" && mPrevSel !== null) && mPrevSel.indexOf(xPos) >= 0)) {
        this.ctx.moveTo(xZero + xPart, yZero);
        this.ctx.lineTo(xPart + boxWidth + xZero, yZero);
      }
      // lower line
      if (!((typeof mNextSel !== "undefined" && mNextSel !== null) && mNextSel.indexOf(xPos) >= 0)) {
        this.ctx.moveTo(xPart + xZero, boxHeight + yZero);
        this.ctx.lineTo(xPart + boxWidth + xZero, boxHeight + yZero);
      }

      xPart += boxWidth;
    }

    // left
    this.ctx.moveTo(xZero,yZero);
    this.ctx.lineTo(xZero, boxHeight + yZero);

    // right
    this.ctx.moveTo(xZero + totalWidth,yZero);
    this.ctx.lineTo(xZero + totalWidth, boxHeight + yZero);

    this.ctx.stroke();
    this.ctx.strokeStyle = beforeStyle;
    return this.ctx.lineWidth = beforeWidth;
  },

  // looks at the selection of the prev and next el
  // TODO: this is very naive, as there might be gaps above or below
  _getPrevNextSelection: function(model) {

    const modelPrev = model.collection.prev(model);
    const modelNext = model.collection.next(model);
    let mPrevSel, mNextSel;
    if ((typeof modelPrev !== "undefined" && modelPrev !== null)) { mPrevSel = this._getSelection(modelPrev); }
    if ((typeof modelNext !== "undefined" && modelNext !== null)) { mNextSel = this._getSelection(modelNext); }
    return [mPrevSel,mNextSel];
  }
});
export default CanvasSelection;