givanse/mvc-tree

View on GitHub
app/mixins/path-factory.js

Summary

Maintainability
C
1 day
Test Coverage
import Ember from 'ember';
import CoordinatesFactory from './coordinates-factory';

/* Meant to be used only by controllers/objects that have an svgenv property. */
export default Ember.Mixin.create(CoordinatesFactory, {

  // 1 is right, -1 is left
  _calcHDir: function(col1, col2) {
    if (col1 === col2) {
      return 1; // when 0, it defaults to the right i.e. 1
    } else {
      return col1 < col2 ? 1 : -1;
    }
  },

  _calcVDir: function(row1, row2) {
    if ( row1 === row2 ) {
      return -1;
    } else {
      // 1 is down, -1 is top 
      return row1 < row2 ? 1 : -1;
    }
  },

  _calcHDelta: function(col1, col2) {
    var delta = Math.abs(col1 - col2); 
    return delta === 0 ? 0 : delta - 1;
  },

  _calcVDelta: function(row1, row2) {
    if ( row1 === row2 ) {
      return 1;
    } else {
      var delta = Math.abs(row1 - row2);
      if (delta === 1) {
        return row1 < row2 ? 0 : 2; 
      } else {
        return delta - 1;
      }
    }
  },

  /*
    generate Horizontal Path Parent to Corner
  */
  _genHPathP2C(hDelta, vDelta) {
    var colW = this.get('svgenv.colW'),
        isEnoughSpace = hDelta > 0 ? true : vDelta > 1 ? true : false,
        length = isEnoughSpace ? (colW / 2) : 0;

    return length ? 'h' + length : '';
  },

  /*
    generate Horizontal Path Corner to Child
  */
  _genHPathC2Ch(hDelta, vDelta) {
    var colW = this.get('svgenv.colW'),
        isEnoughSpace = hDelta > 0 ? true : vDelta > 1 ? true : false,
        length = isEnoughSpace ? (colW / 2) : 0;

    length = hDelta === 0 ? length * -1 : length;

    return length ? 'h' + length : '';
  },

  /*
    generate Vertical Path Node to Corner
  */
  _genVPathN2C(hDelta, vDelta) {
    if ( hDelta < 2 && vDelta === 0 ) {
      return null;
    }
    var length = this.get('svgenv.rowH') / 2;
    if ( vDelta < 0 ) {
      return 'v-' + length;
    }
    return 'v' + length;
  },

  /*
    generate Vertical Path Corner to Node
  */
  _genVPathC2N(hDelta, vDelta) { // TODO: unit test
    if ( hDelta > 1 ) {
      return vDelta < 1 ? 'v-' + (this.get('svgenv.rowH') / 2) :
                             'v'  + (this.get('svgenv.rowH') / 2);
    } else if ( hDelta === 0 ) {
      if ( vDelta !== 0 ) {
        return vDelta < 1 ? 'v-' + (this.get('svgenv.rowH') / 2) :
                               'v'  + (this.get('svgenv.rowH') / 2);
      }
    } else if ( hDelta === 1 ) {
      if ( vDelta !== 0 ) {
        return vDelta < 1 ? 'v-' + (this.get('svgenv.rowH') / 2) :
                               'v'  + (this.get('svgenv.rowH') / 2);
      }
    }
    return null;
  },

  /*
    @param {number} col1 
    @param {number} row1 
    @param {number} col2 
    @param {number} row2 
   */
  _genPathC2C(col1, row1, col2, row2) {
    if ( col1 === col2 && row1 === row2) {
      return null;
    }

    var hDelta = this._calcHDelta(col1, col2), 
        vDelta = this._calcVDelta(row1, row2);

    if ( ! hDelta && ! vDelta) {
      return null;
    }

    var svgenv = this.get('svgenv'), 
        colW = svgenv.get('colW'),
        rowH = svgenv.get('rowH'),

        hDir = this._calcHDir(col1, col2), 
        vDir = this._calcVDir(row1, row2);

    var h = colW * hDelta * hDir,
        v = rowH * vDelta * vDir;

    h = h ? 'h' + h : '';
    v = v ? 'v' + v : '';

    return h && v ? h + ' ' + v : h + v;
  },

  _getHMultC2C: function(hDelta) {
    if ( hDelta <= 1 ) {
      return 0;
    } else {
      return hDelta - 1;
    }
  },

  _getVMultC2C: function(vDelta) {
    if ( vDelta === 0 || vDelta === 1 ) {
      return 0;
    }
    if ( vDelta === 2 ) {
      return 1;
    }
    if ( vDelta === -2 ) {
      return -1;
    }
    var isNegative = vDelta < 0;
    return isNegative ? vDelta + 1 : vDelta - 1;
  },

  /*
    Asumes a left to right direction.
  */
  _genPathC2CR: function(hDelta, vDelta) {
    var hMult = this._getHMultC2C(hDelta);
    var vMult = this._getVMultC2C(vDelta);

    if ( ! hMult && ! vMult ) {
      return null;
    }

    var h = hMult * this.get('svgenv.colW');
    var v = vMult * this.get('svgenv.rowH');
    h = h ? 'h' + h : '';
    v = v ? 'v' + v : '';
    return h && v ? h + ' ' + v : h + v;
  },

  _genChildArrow() {
    // TODO: maybe replace this with a <marker>
    var svgenv = this.get('svgenv'), 
        paddingT = svgenv.get('paddingT'),
        halfPT = paddingT / 2;

    return 'h-'+halfPT+' '+
           'l'+halfPT+' '+paddingT+' '+
           'l'+halfPT+' -'+paddingT+' '+
           'h-'+halfPT;
  },

  /*
    Adds two stems:
      1. first stem goes underneath the parent
      2. second stem goes above the child 
  */
  generatePathToChild: function(a, b) {
    var col1 = parseInt(a.get('col')), 
        row1 = parseInt(a.get('row')),
        col2 = parseInt(b.get('col')), 
        row2 = parseInt(b.get('row'));
 
    if ( col1 === col2 && row1 === row2) {
      return '';
    }

    var pathRoot = this.getBorderPos(a, {border: 'bottom'});

    var hDelta = Math.abs(col1 - col2),
        vDelta = Math.abs(row1 - row2);

    var pathP2C = this._genHPathP2C(hDelta, vDelta);
    var pathC2C = this._genPathC2C(col1, row1, col2, row2);
    var pathC2Ch = this._genHPathC2Ch(hDelta, vDelta);

    pathP2C = pathP2C ? pathP2C + ' ' : ''; 
    pathC2C = pathC2C ? pathC2C + ' ' : ''; 
    pathC2Ch = pathC2Ch ? pathC2Ch + ' ' : '';

    return 'M' + pathRoot.x + ' ' + pathRoot.y + ' ' +
           'v' + this.get('svgenv.paddingB') + ' ' +
           pathP2C + pathC2C + pathC2Ch +
           this._genChildArrow();
  },

  /*
    @method _orderNodes 
    @param {model:grid-node} a 
    @param {model:grid-node} b 
    @return {a: grid-node, b: grid-node} | null 
  */
  _orderNodes(a, b) {
    var aCol = a.get('col'),
        aRow = a.get('row'),
        bCol = b.get('col'),
        bRow = b.get('row');

    // nodes with the same position
    if ( aCol === bCol && aRow === bRow) { 
      return null;
    }

    // switch, we want left->right
    if ( ( aCol > bCol ) || ( aCol === bCol && aRow > bRow ) ) {
      var tmpNode = b;
      b = a;
      a = tmpNode;
    }

    return {a: a, b: b};
  },

  /*
    @method generateBindingPath 
    @param {model:grid-node} a 
    @param {model:grid-node} b 
    @return {String} SVG path 
  */
  generateBindingPath: function(a, b) {
    var orderedNodes = this._orderNodes(a, b);

    if ( ! orderedNodes ) {
      return null;
    }

    a = orderedNodes.a;
    b = orderedNodes.b;

    var hDelta = b.get('col') - a.get('col'),
        vDelta = b.get('row') - a.get('row');

    var paddingR = this.get('svgenv.paddingR');
    var pathN2C = this._genVPathN2C(hDelta, vDelta); 
    var pathC2C = this._genPathC2CR(hDelta, vDelta); 
    var pathC2N = this._genVPathC2N(hDelta, vDelta);
    var paddingL = this.get('svgenv.paddingL');

    var padRight = paddingR ? 'h' + paddingR + ' ' : '';
    pathN2C = pathN2C ? pathN2C + ' ' : ''; 
    pathC2C = pathC2C ? pathC2C + ' ' : ''; 
    pathC2N = pathC2N ? pathC2N : ''; 
    var padLeft = paddingL ? ' h' + paddingL : '';

    var path = padRight + pathN2C + pathC2C + pathC2N + padLeft;
    var pathRoot = this.getBorderPos(a, {border: 'right'});
    return path ? 'M' + pathRoot.x + ' ' + pathRoot.y + ' ' + path : null;
  }

});