<commit_before>'use strict';

var define = require('../define');
var is = require('../is');
var util = require('../util');
var fn, elesfn;

fn = elesfn = ({

  position: define.data({
    field: 'position',
    bindingEvent: 'position',
    allowBinding: true,
    allowSetting: true,
    settingEvent: 'position',
    settingTriggersEvent: true,
    triggerFnName: 'rtrigger',
    allowGetting: true,
    validKeys: ['x', 'y'],
    onSet: function( eles ){
      var updatedEles = eles.updateCompoundBounds();
      updatedEles.rtrigger('position');
    },
    canSet: function( ele ){
      return !ele.locked() && !ele.isParent();
    }
  }),

  // position but no notification to renderer
  silentPosition: define.data({
    field: 'position',
    bindingEvent: 'position',
    allowBinding: false,
    allowSetting: true,
    settingEvent: 'position',
    settingTriggersEvent: false,
    triggerFnName: 'trigger',
    allowGetting: true,
    validKeys: ['x', 'y'],
    onSet: function( eles ){
      eles.updateCompoundBounds();
    },
    canSet: function( ele ){
      return !ele.locked() && !ele.isParent();
    }
  }),

  positions: function( pos, silent ){
    if( is.plainObject(pos) ){
      this.position(pos);

    } else if( is.fn(pos) ){
      var fn = pos;

      for( var i = 0; i < this.length; i++ ){
        var ele = this[i];

        var pos = fn.apply(ele, [i, ele]);

        if( pos && !ele.locked() && !ele.isParent() ){
          var elePos = ele._private.position;
          elePos.x = pos.x;
          elePos.y = pos.y;
        }
      }

      var updatedEles = this.updateCompoundBounds();
      var toTrigger = updatedEles.length > 0 ? this.add( updatedEles ) : this;

      if( silent ){
        toTrigger.trigger('position');
      } else {
        toTrigger.rtrigger('position');
      }
    }

    return this; // chaining
  },

  silentPositions: function( pos ){
    return this.positions( pos, true );
  },

  // get/set the rendered (i.e. on screen) positon of the element
  renderedPosition: function( dim, val ){
    var ele = this[0];
    var cy = this.cy();
    var zoom = cy.zoom();
    var pan = cy.pan();
    var rpos = is.plainObject( dim ) ? dim : undefined;
    var setting = rpos !== undefined || ( val !== undefined && is.string(dim) );

    if( ele && ele.isNode() ){ // must have an element and must be a node to return position
      if( setting ){
        for( var i = 0; i < this.length; i++ ){
          var ele = this[i];

          if( val !== undefined ){ // set one dimension
            ele._private.position[dim] = ( val - pan[dim] )/zoom;
          } else if( rpos !== undefined ){ // set whole position
            ele._private.position = {
              x: ( rpos.x - pan.x ) /zoom,
              y: ( rpos.y - pan.y ) /zoom
            };
          }
        }

        this.rtrigger('position');
      } else { // getting
        var pos = ele._private.position;
        rpos = {
          x: pos.x * zoom + pan.x,
          y: pos.y * zoom + pan.y
        };

        if( dim === undefined ){ // then return the whole rendered position
          return rpos;
        } else { // then return the specified dimension
          return rpos[ dim ];
        }
      }
    } else if( !setting ){
      return undefined; // for empty collection case
    }

    return this; // chaining
  },

  // get/set the position relative to the parent
  relativePosition: function( dim, val ){
    var ele = this[0];
    var cy = this.cy();
    var ppos = is.plainObject( dim ) ? dim : undefined;
    var setting = ppos !== undefined || ( val !== undefined && is.string(dim) );
    var hasCompoundNodes = cy.hasCompoundNodes();

    if( ele && ele.isNode() ){ // must have an element and must be a node to return position
      if( setting ){
        for( var i = 0; i < this.length; i++ ){
          var ele = this[i];
          var parent = hasCompoundNodes ? ele.parent() : null;
          var hasParent = parent && parent.length > 0;
          var relativeToParent = hasParent;

          if( hasParent ){
            parent = parent[0];
          }

          var origin = relativeToParent ? parent._private.position : { x: 0, y: 0 };

          if( val !== undefined ){ // set one dimension
            ele._private.position[dim] = val + origin[dim];
          } else if( ppos !== undefined ){ // set whole position
            ele._private.position = {
              x: ppos.x + origin.x,
              y: ppos.y + origin.y,
            };
          }
        }

        this.rtrigger('position');

      } else { // getting
        var pos = ele._private.position;
        var parent = hasCompoundNodes ? ele.parent() : null;
        var hasParent = parent && parent.length > 0;
        var relativeToParent = hasParent;

        if( hasParent ){
          parent = parent[0];
        }

        var origin = relativeToParent ? parent._private.position : { x: 0, y: 0 };

        ppos = {
          x: pos.x - origin.x,
          y: pos.y - origin.y
        };

        if( dim === undefined ){ // then return the whole rendered position
          return ppos;
        } else { // then return the specified dimension
          return ppos[ dim ];
        }
      }
    } else if( !setting ){
      return undefined; // for empty collection case
    }

    return this; // chaining
  },

  renderedBoundingBox: function( options ){
    var bb = this.boundingBox( options );
    var cy = this.cy();
    var zoom = cy.zoom();
    var pan = cy.pan();

    var x1 = bb.x1 * zoom + pan.x;
    var x2 = bb.x2 * zoom + pan.x;
    var y1 = bb.y1 * zoom + pan.y;
    var y2 = bb.y2 * zoom + pan.y;

    return {
      x1: x1,
      x2: x2,
      y1: y1,
      y2: y2,
      w: x2 - x1,
      h: y2 - y1
    };
  },

  updateCompoundBounds: function(){
    var cy = this.cy();

    if( !cy.styleEnabled() || !cy.hasCompoundNodes() ){ return cy.collection(); } // save cycles for non compound graphs or when style disabled

    var updated = [];

    function update( parent ){
      var children = parent.children();
      var style = parent._private.style;
      var includeLabels = style['compound-sizing-wrt-labels'].value === 'include';
      var bb = children.boundingBox({ includeLabels: includeLabels, includeEdges: true });
      var padding = {
        top: style['padding-top'].pfValue,
        bottom: style['padding-bottom'].pfValue,
        left: style['padding-left'].pfValue,
        right: style['padding-right'].pfValue
      };
      var pos = parent._private.position;
      var didUpdate = false;

      if( style['width'].value === 'auto' ){
        parent._private.autoWidth = bb.w;
        pos.x = (bb.x1 + bb.x2 - padding.left + padding.right)/2;
        didUpdate = true;
      }

      if( style['height'].value === 'auto' ){
        parent._private.autoHeight = bb.h;
        pos.y = (bb.y1 + bb.y2 - padding.top + padding.bottom)/2;
        didUpdate = true;
      }

      if( didUpdate ){
        updated.push( parent );
      }
    }

    // go up, level by level
    var eles = this.parent();
    while( eles.nonempty() ){

      // update each parent node in this level
      for( var i = 0; i < eles.length; i++ ){
        var ele = eles[i];

        update( ele );
      }

      // next level
      eles = eles.parent();
    }

    // return changed
    return this.spawn( updated );
  },

  // get the bounding box of the elements (in raw model position)
  boundingBox: function( options ){
    var eles = this;
    var cy = eles._private.cy;
    var cy_p = cy._private;
    var styleEnabled = cy_p.styleEnabled;

    options = options || util.staticEmptyObject();

    var includeNodes = options.includeNodes === undefined ? true : options.includeNodes;
    var includeEdges = options.includeEdges === undefined ? true : options.includeEdges;
    var includeLabels = options.includeLabels === undefined ? true : options.includeLabels;

    // recalculate projections etc
    if( styleEnabled ){
      cy_p.renderer.recalculateRenderedStyle( this );
    }

    var x1 = Infinity;
    var x2 = -Infinity;
    var y1 = Infinity;
    var y2 = -Infinity;

    // find bounds of elements
    for( var i = 0; i < eles.length; i++ ){
      var ele = eles[i];
      var _p = ele._private;
      var style = _p.style;
      var display = styleEnabled ? _p.style['display'].value : 'element';
      var isNode = _p.group === 'nodes';
      var ex1, ex2, ey1, ey2, x, y;
      var includedEle = false;

      if( display === 'none' ){ continue; } // then ele doesn't take up space

      if( isNode && includeNodes ){
        includedEle = true;

        var pos = _p.position;
        x = pos.x;
        y = pos.y;
        var w = ele.outerWidth();
        var halfW = w/2;
        var h = ele.outerHeight();
        var halfH = h/2;

        // handle node dimensions
        /////////////////////////

        ex1 = x - halfW;
        ex2 = x + halfW;
        ey1 = y - halfH;
        ey2 = y + halfH;

        x1 = ex1 < x1 ? ex1 : x1;
        x2 = ex2 > x2 ? ex2 : x2;
        y1 = ey1 < y1 ? ey1 : y1;
        y2 = ey2 > y2 ? ey2 : y2;

      } else if( ele.isEdge() && includeEdges ){
        includedEle = true;

        var n1 = _p.source;
        var n1_p = n1._private;
        var n1pos = n1_p.position;

        var n2 = _p.target;
        var n2_p = n2._private;
        var n2pos = n2_p.position;


        // handle edge dimensions (rough box estimate)
        //////////////////////////////////////////////

        var rstyle = _p.rstyle || {};
        var w = 0;
        var wHalf = 0;

        if( styleEnabled ){
          w = style['width'].pfValue;
          wHalf = w/2;
        }

        ex1 = n1pos.x;
        ex2 = n2pos.x;
        ey1 = n1pos.y;
        ey2 = n2pos.y;

        if( ex1 > ex2 ){
          var temp = ex1;
          ex1 = ex2;
          ex2 = temp;
        }

        if( ey1 > ey2 ){
          var temp = ey1;
          ey1 = ey2;
          ey2 = temp;
        }

        // take into account edge width
        ex1 -= wHalf;
        ex2 += wHalf;
        ey1 -= wHalf;
        ey2 += wHalf;

        x1 = ex1 < x1 ? ex1 : x1;
        x2 = ex2 > x2 ? ex2 : x2;
        y1 = ey1 < y1 ? ey1 : y1;
        y2 = ey2 > y2 ? ey2 : y2;

        // handle points along edge (sanity check)
        //////////////////////////////////////////

        if( styleEnabled ){
          var pts = rstyle.bezierPts || rstyle.linePts || [];

          for( var j = 0; j < pts.length; j++ ){
            var pt = pts[j];

            ex1 = pt.x - wHalf;
            ex2 = pt.x + wHalf;
            ey1 = pt.y - wHalf;
            ey2 = pt.y + wHalf;

            x1 = ex1 < x1 ? ex1 : x1;
            x2 = ex2 > x2 ? ex2 : x2;
            y1 = ey1 < y1 ? ey1 : y1;
            y2 = ey2 > y2 ? ey2 : y2;
          }
        }

        // precise haystacks (sanity check)
        ///////////////////////////////////

        if( styleEnabled && style['curve-style'].strValue === 'haystack' ){
          var hpts = rstyle.haystackPts;

          ex1 = hpts[0].x;
          ey1 = hpts[0].y;
          ex2 = hpts[1].x;
          ey2 = hpts[1].y;

          if( ex1 > ex2 ){
            var temp = ex1;
            ex1 = ex2;
            ex2 = temp;
          }

          if( ey1 > ey2 ){
            var temp = ey1;
            ey1 = ey2;
            ey2 = temp;
          }

          x1 = ex1 < x1 ? ex1 : x1;
          x2 = ex2 > x2 ? ex2 : x2;
          y1 = ey1 < y1 ? ey1 : y1;
          y2 = ey2 > y2 ? ey2 : y2;
        }

      } // edges


      // handle label dimensions
      //////////////////////////

      if( styleEnabled ){

        var _p = ele._private;
        var style = _p.style;
        var rstyle = _p.rstyle;
        var label = style['label'].strValue;
        var fontSize = style['font-size'];
        var halign = style['text-halign'];
        var valign = style['text-valign'];
        var labelWidth = rstyle.labelWidth;
        var labelHeight = rstyle.labelHeight;
        var labelX = rstyle.labelX;
        var labelY = rstyle.labelY;
        var isEdge = ele.isEdge();
        var rotation = style['text-rotation'];

        if( includeLabels && label && fontSize && labelHeight != null && labelWidth != null && labelX != null && labelY != null && halign && valign ){
          var lh = labelHeight;
          var lw = labelWidth;
          var lx1, lx2, ly1, ly2;

          if( isEdge ){
            lx1 = labelX - lw/2;
            lx2 = labelX + lw/2;
            ly1 = labelY - lh/2;
            ly2 = labelY + lh/2;
          } else {
            switch( halign.value ){
              case 'left':
                lx1 = labelX - lw;
                lx2 = labelX;
                break;

              case 'center':
                lx1 = labelX - lw/2;
                lx2 = labelX + lw/2;
                break;

              case 'right':
                lx1 = labelX;
                lx2 = labelX + lw;
                break;
            }

            switch( valign.value ){
              case 'top':
                ly1 = labelY - lh;
                ly2 = labelY;
                break;

              case 'center':
                ly1 = labelY - lh/2;
                ly2 = labelY + lh/2;
                break;

              case 'bottom':
                ly1 = labelY;
                ly2 = labelY + lh;
                break;
            }
          }

          if( ( isEdge && rotation.strValue === 'autorotate' ) || ( rotation.pfValue != null && rotation.pfValue !== 0 ) ){
            var theta = _p.rscratch.labelAngle;
            var cos = Math.cos( theta );
            var sin = Math.sin( theta );

            var rotate = function( x, y ){
              x = x - labelX;
              y = y - labelY;

              return {
                x: x*cos - y*sin + labelX,
                y: x*sin + y*cos + labelY
              };
            };

            var px1y1 = rotate( lx1, ly1 );
            var px1y2 = rotate( lx1, ly2 );
            var px2y1 = rotate( lx2, ly1 );
            var px2y2 = rotate( lx2, ly2 );

            lx1 = Math.min( px1y1.x, px1y2.x, px2y1.x, px2y2.x );
            lx2 = Math.max( px1y1.x, px1y2.x, px2y1.x, px2y2.x );
            ly1 = Math.min( px1y1.y, px1y2.y, px2y1.y, px2y2.y );
            ly2 = Math.max( px1y1.y, px1y2.y, px2y1.y, px2y2.y );
          }

          x1 = lx1 < x1 ? lx1 : x1;
          x2 = lx2 > x2 ? lx2 : x2;
          y1 = ly1 < y1 ? ly1 : y1;
          y2 = ly2 > y2 ? ly2 : y2;
        }
      } // style enabled for labels
    } // for

    var noninf = function(x){
      if( x === Infinity || x === -Infinity ){
        return 0;
      }

      return x;
    };

    x1 = noninf(x1);
    x2 = noninf(x2);
    y1 = noninf(y1);
    y2 = noninf(y2);

    return {
      x1: x1,
      x2: x2,
      y1: y1,
      y2: y2,
      w: x2 - x1,
      h: y2 - y1
    };
  }
});

var defineDimFns = function( opts ){
  opts.uppercaseName = util.capitalize( opts.name );
  opts.autoName = 'auto' + opts.uppercaseName;
  opts.labelName = 'label' + opts.uppercaseName;
  opts.outerName = 'outer' + opts.uppercaseName;
  opts.uppercaseOuterName = util.capitalize( opts.outerName );<commit_msg>parameterise bounding box calc for labels for { normal, source, target } cases #382<commit_after>'use strict';

var define = require('../define');
var is = require('../is');
var util = require('../util');
var fn, elesfn;

fn = elesfn = ({

  position: define.data({
    field: 'position',
    bindingEvent: 'position',
    allowBinding: true,
    allowSetting: true,
    settingEvent: 'position',
    settingTriggersEvent: true,
    triggerFnName: 'rtrigger',
    allowGetting: true,
    validKeys: ['x', 'y'],
    onSet: function( eles ){
      var updatedEles = eles.updateCompoundBounds();
      updatedEles.rtrigger('position');
    },
    canSet: function( ele ){
      return !ele.locked() && !ele.isParent();
    }
  }),

  // position but no notification to renderer
  silentPosition: define.data({
    field: 'position',
    bindingEvent: 'position',
    allowBinding: false,
    allowSetting: true,
    settingEvent: 'position',
    settingTriggersEvent: false,
    triggerFnName: 'trigger',
    allowGetting: true,
    validKeys: ['x', 'y'],
    onSet: function( eles ){
      eles.updateCompoundBounds();
    },
    canSet: function( ele ){
      return !ele.locked() && !ele.isParent();
    }
  }),

  positions: function( pos, silent ){
    if( is.plainObject(pos) ){
      this.position(pos);

    } else if( is.fn(pos) ){
      var fn = pos;

      for( var i = 0; i < this.length; i++ ){
        var ele = this[i];

        var pos = fn.apply(ele, [i, ele]);

        if( pos && !ele.locked() && !ele.isParent() ){
          var elePos = ele._private.position;
          elePos.x = pos.x;
          elePos.y = pos.y;
        }
      }

      var updatedEles = this.updateCompoundBounds();
      var toTrigger = updatedEles.length > 0 ? this.add( updatedEles ) : this;

      if( silent ){
        toTrigger.trigger('position');
      } else {
        toTrigger.rtrigger('position');
      }
    }

    return this; // chaining
  },

  silentPositions: function( pos ){
    return this.positions( pos, true );
  },

  // get/set the rendered (i.e. on screen) positon of the element
  renderedPosition: function( dim, val ){
    var ele = this[0];
    var cy = this.cy();
    var zoom = cy.zoom();
    var pan = cy.pan();
    var rpos = is.plainObject( dim ) ? dim : undefined;
    var setting = rpos !== undefined || ( val !== undefined && is.string(dim) );

    if( ele && ele.isNode() ){ // must have an element and must be a node to return position
      if( setting ){
        for( var i = 0; i < this.length; i++ ){
          var ele = this[i];

          if( val !== undefined ){ // set one dimension
            ele._private.position[dim] = ( val - pan[dim] )/zoom;
          } else if( rpos !== undefined ){ // set whole position
            ele._private.position = {
              x: ( rpos.x - pan.x ) /zoom,
              y: ( rpos.y - pan.y ) /zoom
            };
          }
        }

        this.rtrigger('position');
      } else { // getting
        var pos = ele._private.position;
        rpos = {
          x: pos.x * zoom + pan.x,
          y: pos.y * zoom + pan.y
        };

        if( dim === undefined ){ // then return the whole rendered position
          return rpos;
        } else { // then return the specified dimension
          return rpos[ dim ];
        }
      }
    } else if( !setting ){
      return undefined; // for empty collection case
    }

    return this; // chaining
  },

  // get/set the position relative to the parent
  relativePosition: function( dim, val ){
    var ele = this[0];
    var cy = this.cy();
    var ppos = is.plainObject( dim ) ? dim : undefined;
    var setting = ppos !== undefined || ( val !== undefined && is.string(dim) );
    var hasCompoundNodes = cy.hasCompoundNodes();

    if( ele && ele.isNode() ){ // must have an element and must be a node to return position
      if( setting ){
        for( var i = 0; i < this.length; i++ ){
          var ele = this[i];
          var parent = hasCompoundNodes ? ele.parent() : null;
          var hasParent = parent && parent.length > 0;
          var relativeToParent = hasParent;

          if( hasParent ){
            parent = parent[0];
          }

          var origin = relativeToParent ? parent._private.position : { x: 0, y: 0 };

          if( val !== undefined ){ // set one dimension
            ele._private.position[dim] = val + origin[dim];
          } else if( ppos !== undefined ){ // set whole position
            ele._private.position = {
              x: ppos.x + origin.x,
              y: ppos.y + origin.y,
            };
          }
        }

        this.rtrigger('position');

      } else { // getting
        var pos = ele._private.position;
        var parent = hasCompoundNodes ? ele.parent() : null;
        var hasParent = parent && parent.length > 0;
        var relativeToParent = hasParent;

        if( hasParent ){
          parent = parent[0];
        }

        var origin = relativeToParent ? parent._private.position : { x: 0, y: 0 };

        ppos = {
          x: pos.x - origin.x,
          y: pos.y - origin.y
        };

        if( dim === undefined ){ // then return the whole rendered position
          return ppos;
        } else { // then return the specified dimension
          return ppos[ dim ];
        }
      }
    } else if( !setting ){
      return undefined; // for empty collection case
    }

    return this; // chaining
  },

  renderedBoundingBox: function( options ){
    var bb = this.boundingBox( options );
    var cy = this.cy();
    var zoom = cy.zoom();
    var pan = cy.pan();

    var x1 = bb.x1 * zoom + pan.x;
    var x2 = bb.x2 * zoom + pan.x;
    var y1 = bb.y1 * zoom + pan.y;
    var y2 = bb.y2 * zoom + pan.y;

    return {
      x1: x1,
      x2: x2,
      y1: y1,
      y2: y2,
      w: x2 - x1,
      h: y2 - y1
    };
  },

  updateCompoundBounds: function(){
    var cy = this.cy();

    if( !cy.styleEnabled() || !cy.hasCompoundNodes() ){ return cy.collection(); } // save cycles for non compound graphs or when style disabled

    var updated = [];

    function update( parent ){
      var children = parent.children();
      var style = parent._private.style;
      var includeLabels = style['compound-sizing-wrt-labels'].value === 'include';
      var bb = children.boundingBox({ includeLabels: includeLabels, includeEdges: true });
      var padding = {
        top: style['padding-top'].pfValue,
        bottom: style['padding-bottom'].pfValue,
        left: style['padding-left'].pfValue,
        right: style['padding-right'].pfValue
      };
      var pos = parent._private.position;
      var didUpdate = false;

      if( style['width'].value === 'auto' ){
        parent._private.autoWidth = bb.w;
        pos.x = (bb.x1 + bb.x2 - padding.left + padding.right)/2;
        didUpdate = true;
      }

      if( style['height'].value === 'auto' ){
        parent._private.autoHeight = bb.h;
        pos.y = (bb.y1 + bb.y2 - padding.top + padding.bottom)/2;
        didUpdate = true;
      }

      if( didUpdate ){
        updated.push( parent );
      }
    }

    // go up, level by level
    var eles = this.parent();
    while( eles.nonempty() ){

      // update each parent node in this level
      for( var i = 0; i < eles.length; i++ ){
        var ele = eles[i];

        update( ele );
      }

      // next level
      eles = eles.parent();
    }

    // return changed
    return this.spawn( updated );
  },

  // get the bounding box of the elements (in raw model position)
  boundingBox: function( options ){
    var eles = this;
    var cy = eles._private.cy;
    var cy_p = cy._private;
    var styleEnabled = cy_p.styleEnabled;

    options = options || util.staticEmptyObject();

    var includeNodes = options.includeNodes === undefined ? true : options.includeNodes;
    var includeEdges = options.includeEdges === undefined ? true : options.includeEdges;
    var includeLabels = options.includeLabels === undefined ? true : options.includeLabels;

    // recalculate projections etc
    if( styleEnabled ){
      cy_p.renderer.recalculateRenderedStyle( this );
    }

    var x1 = Infinity;
    var x2 = -Infinity;
    var y1 = Infinity;
    var y2 = -Infinity;

    // find bounds of elements
    for( var i = 0; i < eles.length; i++ ){
      var ele = eles[i];
      var _p = ele._private;
      var style = _p.style;
      var display = styleEnabled ? _p.style['display'].value : 'element';
      var isNode = _p.group === 'nodes';
      var ex1, ex2, ey1, ey2, x, y;
      var includedEle = false;

      if( display === 'none' ){ continue; } // then ele doesn't take up space

      if( isNode && includeNodes ){
        includedEle = true;

        var pos = _p.position;
        x = pos.x;
        y = pos.y;
        var w = ele.outerWidth();
        var halfW = w/2;
        var h = ele.outerHeight();
        var halfH = h/2;

        // handle node dimensions
        /////////////////////////

        ex1 = x - halfW;
        ex2 = x + halfW;
        ey1 = y - halfH;
        ey2 = y + halfH;

        x1 = ex1 < x1 ? ex1 : x1;
        x2 = ex2 > x2 ? ex2 : x2;
        y1 = ey1 < y1 ? ey1 : y1;
        y2 = ey2 > y2 ? ey2 : y2;

      } else if( ele.isEdge() && includeEdges ){
        includedEle = true;

        var n1 = _p.source;
        var n1_p = n1._private;
        var n1pos = n1_p.position;

        var n2 = _p.target;
        var n2_p = n2._private;
        var n2pos = n2_p.position;


        // handle edge dimensions (rough box estimate)
        //////////////////////////////////////////////

        var rstyle = _p.rstyle || {};
        var w = 0;
        var wHalf = 0;

        if( styleEnabled ){
          w = style['width'].pfValue;
          wHalf = w/2;
        }

        ex1 = n1pos.x;
        ex2 = n2pos.x;
        ey1 = n1pos.y;
        ey2 = n2pos.y;

        if( ex1 > ex2 ){
          var temp = ex1;
          ex1 = ex2;
          ex2 = temp;
        }

        if( ey1 > ey2 ){
          var temp = ey1;
          ey1 = ey2;
          ey2 = temp;
        }

        // take into account edge width
        ex1 -= wHalf;
        ex2 += wHalf;
        ey1 -= wHalf;
        ey2 += wHalf;

        x1 = ex1 < x1 ? ex1 : x1;
        x2 = ex2 > x2 ? ex2 : x2;
        y1 = ey1 < y1 ? ey1 : y1;
        y2 = ey2 > y2 ? ey2 : y2;

        // handle points along edge (sanity check)
        //////////////////////////////////////////

        if( styleEnabled ){
          var pts = rstyle.bezierPts || rstyle.linePts || [];

          for( var j = 0; j < pts.length; j++ ){
            var pt = pts[j];

            ex1 = pt.x - wHalf;
            ex2 = pt.x + wHalf;
            ey1 = pt.y - wHalf;
            ey2 = pt.y + wHalf;

            x1 = ex1 < x1 ? ex1 : x1;
            x2 = ex2 > x2 ? ex2 : x2;
            y1 = ey1 < y1 ? ey1 : y1;
            y2 = ey2 > y2 ? ey2 : y2;
          }
        }

        // precise haystacks (sanity check)
        ///////////////////////////////////

        if( styleEnabled && style['curve-style'].strValue === 'haystack' ){
          var hpts = rstyle.haystackPts;

          ex1 = hpts[0].x;
          ey1 = hpts[0].y;
          ex2 = hpts[1].x;
          ey2 = hpts[1].y;

          if( ex1 > ex2 ){
            var temp = ex1;
            ex1 = ex2;
            ex2 = temp;
          }

          if( ey1 > ey2 ){
            var temp = ey1;
            ey1 = ey2;
            ey2 = temp;
          }

          x1 = ex1 < x1 ? ex1 : x1;
          x2 = ex2 > x2 ? ex2 : x2;
          y1 = ey1 < y1 ? ey1 : y1;
          y2 = ey2 > y2 ? ey2 : y2;
        }

      } // edges


      // handle label dimensions
      //////////////////////////

      if( styleEnabled ){

        checkLabel( ele );

        if( ele.isEdge() ){
          checkLabel( ele, 'source' );
          checkLabel( ele, 'source' );
        }

      } // style enabled for labels
    } // for

    function prefixedProperty( obj, propName, prefix ){
      if( prefix ){
        propName = prefix + propName.toUpperCase(); // e.g. sourceLabelWidth
      }

      return obj[ propName ];
    }

    function checkLabel( ele, prefix ){
      var prefixDash;

      if( prefix ){
        prefixDash = prefix + '-';
      } else {
        prefixDash = '';
      }

      var _p = ele._private;
      var style = _p.style;
      var rstyle = _p.rstyle;
      var label = style[ prefixDash + 'label' ].strValue;
      var fontSize = style['font-size'];
      var halign = style['text-halign'];
      var valign = style['text-valign'];
      var labelWidth = prefixedProperty( rstyle, 'labelWidth', prefix );
      var labelHeight = prefixedProperty( rstyle, 'labelHeight', prefix );
      var labelX = prefixedProperty( rstyle, 'labelX', prefix );
      var labelY = prefixedProperty( rstyle, 'labelY', prefix );
      var isEdge = ele.isEdge();
      var rotation = style[ prefixDash + 'text-rotation' ];

      if( includeLabels && label && fontSize && labelHeight != null && labelWidth != null && labelX != null && labelY != null && halign && valign ){
        var lh = labelHeight;
        var lw = labelWidth;
        var lx1, lx2, ly1, ly2;

        if( isEdge ){
          lx1 = labelX - lw/2;
          lx2 = labelX + lw/2;
          ly1 = labelY - lh/2;
          ly2 = labelY + lh/2;
        } else {
          switch( halign.value ){
            case 'left':
              lx1 = labelX - lw;
              lx2 = labelX;
              break;

            case 'center':
              lx1 = labelX - lw/2;
              lx2 = labelX + lw/2;
              break;

            case 'right':
              lx1 = labelX;
              lx2 = labelX + lw;
              break;
          }

          switch( valign.value ){
            case 'top':
              ly1 = labelY - lh;
              ly2 = labelY;
              break;

            case 'center':
              ly1 = labelY - lh/2;
              ly2 = labelY + lh/2;
              break;

            case 'bottom':
              ly1 = labelY;
              ly2 = labelY + lh;
              break;
          }
        }

        if( ( isEdge && rotation.strValue === 'autorotate' ) || ( rotation.pfValue != null && rotation.pfValue !== 0 ) ){
          var theta = _p.rscratch.labelAngle;
          var cos = Math.cos( theta );
          var sin = Math.sin( theta );

          var rotate = function( x, y ){
            x = x - labelX;
            y = y - labelY;

            return {
              x: x*cos - y*sin + labelX,
              y: x*sin + y*cos + labelY
            };
          };

          var px1y1 = rotate( lx1, ly1 );
          var px1y2 = rotate( lx1, ly2 );
          var px2y1 = rotate( lx2, ly1 );
          var px2y2 = rotate( lx2, ly2 );

          lx1 = Math.min( px1y1.x, px1y2.x, px2y1.x, px2y2.x );
          lx2 = Math.max( px1y1.x, px1y2.x, px2y1.x, px2y2.x );
          ly1 = Math.min( px1y1.y, px1y2.y, px2y1.y, px2y2.y );
          ly2 = Math.max( px1y1.y, px1y2.y, px2y1.y, px2y2.y );
        }

        x1 = lx1 < x1 ? lx1 : x1;
        x2 = lx2 > x2 ? lx2 : x2;
        y1 = ly1 < y1 ? ly1 : y1;
        y2 = ly2 > y2 ? ly2 : y2;
      }
    }

    var noninf = function(x){
      if( x === Infinity || x === -Infinity ){
        return 0;
      }

      return x;
    };

    x1 = noninf(x1);
    x2 = noninf(x2);
    y1 = noninf(y1);
    y2 = noninf(y2);

    return {
      x1: x1,
      x2: x2,
      y1: y1,
      y2: y2,
      w: x2 - x1,
      h: y2 - y1
    };
  }
});

var defineDimFns = function( opts ){
  opts.uppercaseName = util.capitalize( opts.name );
  opts.autoName = 'auto' + opts.uppercaseName;
  opts.labelName = 'label' + opts.uppercaseName;
  opts.outerName = 'outer' + opts.uppercaseName;
  opts.uppercaseOuterName = util.capitalize( opts.outerName );
{'fraction_non_alphanumeric': 0.14247017091260883, 'fraction_numerical': 0.02073524669461464, 'mean_word_length': 1.5156972499391579, 'pattern_counts': {'":': 0, '<': 35, '<?xml version=': 0, '>': 37, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '789385', 'n_tokens_mistral': 10244, 'n_tokens_neox': 9913, 'n_words': 2739}