function getConnectedDistance(source, hints) {
  if (!hints) {
    hints = {};
  }

  // targets > sources by default
  function getDefaultWeight(connection) {
    return connection.source === source ? 1 : -1;
  }

  var defaultDistance = hints.defaultDistance || DEFAULT_DISTANCE,
      direction = hints.direction || 'e',
      filter = hints.filter,
      getWeight = hints.getWeight || getDefaultWeight,
      maxDistance = hints.maxDistance || DEFAULT_MAX_DISTANCE,
      reference = hints.reference || 'start';

  if (!filter) {
    filter = noneFilter;
  }

  function getDistance(a, b) {
    if (direction === 'n') {
      if (reference === 'start') {
        return asTRBL(a).top - asTRBL(b).bottom;
      } else if (reference === 'center') {
        return asTRBL(a).top - getMid(b).y;
      } else {
        return asTRBL(a).top - asTRBL(b).top;
      }
    } else if (direction === 'w') {
      if (reference === 'start') {
        return asTRBL(a).left - asTRBL(b).right;
      } else if (reference === 'center') {
        return asTRBL(a).left - getMid(b).x;
      } else {
        return asTRBL(a).left - asTRBL(b).left;
      }
    } else if (direction === 's') {
      if (reference === 'start') {
        return asTRBL(b).top - asTRBL(a).bottom;
      } else if (reference === 'center') {
        return getMid(b).y - asTRBL(a).bottom;
      } else {
        return asTRBL(b).bottom - asTRBL(a).bottom;
      }
    } else {
      if (reference === 'start') {
        return asTRBL(b).left - asTRBL(a).right;
      } else if (reference === 'center') {
        return getMid(b).x - asTRBL(a).right;
      } else {
        return asTRBL(b).right - asTRBL(a).right;
      }
    }
  }

  var sourcesDistances = source.incoming
    .filter(filter)
    .map(function(connection) {
      var weight = getWeight(connection);

      var distance = weight < 0
        ? getDistance(connection.source, source)
        : getDistance(source, connection.source);

      return {
        id: connection.source.id,
        distance: distance,
        weight: weight
      };
    });

  var targetsDistances = source.outgoing
    .filter(filter)
    .map(function(connection) {
      var weight = getWeight(connection);

      var distance = weight > 0
        ? getDistance(source, connection.target)
        : getDistance(connection.target, source);

      return {
        id: connection.target.id,
        distance: distance,
        weight: weight
      };
    });

  var distances = sourcesDistances.concat(targetsDistances).reduce(function(accumulator, currentValue) {
    accumulator[ currentValue.id + '__weight_' + currentValue.weight ] = currentValue;

    return accumulator;
  }, {});

  var distancesGrouped = reduce(distances, function(accumulator, currentValue) {
    var distance = currentValue.distance,
        weight = currentValue.weight;

    if (distance < 0 || distance > maxDistance) {
      return accumulator;
    }

    if (!accumulator[ String(distance) ]) {
      accumulator[ String(distance) ] = 0;
    }

    accumulator[ String(distance) ] += 1 * weight;

    if (!accumulator.distance || accumulator[ accumulator.distance ] < accumulator[ String(distance) ]) {
      accumulator.distance = distance;
    }

    return accumulator;
  }, {});

  return distancesGrouped.distance || defaultDistance;
}
Compute optimal distance between source and target based on existing connections to and from source.
Assumes left-to-right and top-to-down modeling.

@param {Shape} source
@param {Object} [hints]
@param {number} [hints.defaultDistance]
@param {string} [hints.direction]
@param {(connection: Connection) => boolean} [hints.filter]
@param {(connection: Connection) => number} [hints.getWeight]
@param {number} [hints.maxDistance]
@param {'start'|'center'|'end'} [hints.reference]

@return {number}