| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523 | 
/** Licensed to the Apache Software Foundation (ASF) under one* or more contributor license agreements.  See the NOTICE file* distributed with this work for additional information* regarding copyright ownership.  The ASF licenses this file* to you under the Apache License, Version 2.0 (the* "License"); you may not use this file except in compliance* with the License.  You may obtain a copy of the License at**   http://www.apache.org/licenses/LICENSE-2.0** Unless required by applicable law or agreed to in writing,* software distributed under the License is distributed on an* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY* KIND, either express or implied.  See the License for the* specific language governing permissions and limitations* under the License.*/var zrUtil = require("zrender/lib/core/util");var BoundingRect = require("zrender/lib/core/BoundingRect");var _number = require("./number");var parsePercent = _number.parsePercent;var formatUtil = require("./format");/** Licensed to the Apache Software Foundation (ASF) under one* or more contributor license agreements.  See the NOTICE file* distributed with this work for additional information* regarding copyright ownership.  The ASF licenses this file* to you under the Apache License, Version 2.0 (the* "License"); you may not use this file except in compliance* with the License.  You may obtain a copy of the License at**   http://www.apache.org/licenses/LICENSE-2.0** Unless required by applicable law or agreed to in writing,* software distributed under the License is distributed on an* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY* KIND, either express or implied.  See the License for the* specific language governing permissions and limitations* under the License.*/// Layout helpers for each component positioningvar each = zrUtil.each;/** * @public */var LOCATION_PARAMS = ['left', 'right', 'top', 'bottom', 'width', 'height'];/** * @public */var HV_NAMES = [['width', 'left', 'right'], ['height', 'top', 'bottom']];function boxLayout(orient, group, gap, maxWidth, maxHeight) {  var x = 0;  var y = 0;  if (maxWidth == null) {    maxWidth = Infinity;  }  if (maxHeight == null) {    maxHeight = Infinity;  }  var currentLineMaxSize = 0;  group.eachChild(function (child, idx) {    var position = child.position;    var rect = child.getBoundingRect();    var nextChild = group.childAt(idx + 1);    var nextChildRect = nextChild && nextChild.getBoundingRect();    var nextX;    var nextY;    if (orient === 'horizontal') {      var moveX = rect.width + (nextChildRect ? -nextChildRect.x + rect.x : 0);      nextX = x + moveX; // Wrap when width exceeds maxWidth or meet a `newline` group      // FIXME compare before adding gap?      if (nextX > maxWidth || child.newline) {        x = 0;        nextX = moveX;        y += currentLineMaxSize + gap;        currentLineMaxSize = rect.height;      } else {        // FIXME: consider rect.y is not `0`?        currentLineMaxSize = Math.max(currentLineMaxSize, rect.height);      }    } else {      var moveY = rect.height + (nextChildRect ? -nextChildRect.y + rect.y : 0);      nextY = y + moveY; // Wrap when width exceeds maxHeight or meet a `newline` group      if (nextY > maxHeight || child.newline) {        x += currentLineMaxSize + gap;        y = 0;        nextY = moveY;        currentLineMaxSize = rect.width;      } else {        currentLineMaxSize = Math.max(currentLineMaxSize, rect.width);      }    }    if (child.newline) {      return;    }    position[0] = x;    position[1] = y;    orient === 'horizontal' ? x = nextX + gap : y = nextY + gap;  });}/** * VBox or HBox layouting * @param {string} orient * @param {module:zrender/container/Group} group * @param {number} gap * @param {number} [width=Infinity] * @param {number} [height=Infinity] */var box = boxLayout;/** * VBox layouting * @param {module:zrender/container/Group} group * @param {number} gap * @param {number} [width=Infinity] * @param {number} [height=Infinity] */var vbox = zrUtil.curry(boxLayout, 'vertical');/** * HBox layouting * @param {module:zrender/container/Group} group * @param {number} gap * @param {number} [width=Infinity] * @param {number} [height=Infinity] */var hbox = zrUtil.curry(boxLayout, 'horizontal');/** * If x or x2 is not specified or 'center' 'left' 'right', * the width would be as long as possible. * If y or y2 is not specified or 'middle' 'top' 'bottom', * the height would be as long as possible. * * @param {Object} positionInfo * @param {number|string} [positionInfo.x] * @param {number|string} [positionInfo.y] * @param {number|string} [positionInfo.x2] * @param {number|string} [positionInfo.y2] * @param {Object} containerRect {width, height} * @param {string|number} margin * @return {Object} {width, height} */function getAvailableSize(positionInfo, containerRect, margin) {  var containerWidth = containerRect.width;  var containerHeight = containerRect.height;  var x = parsePercent(positionInfo.x, containerWidth);  var y = parsePercent(positionInfo.y, containerHeight);  var x2 = parsePercent(positionInfo.x2, containerWidth);  var y2 = parsePercent(positionInfo.y2, containerHeight);  (isNaN(x) || isNaN(parseFloat(positionInfo.x))) && (x = 0);  (isNaN(x2) || isNaN(parseFloat(positionInfo.x2))) && (x2 = containerWidth);  (isNaN(y) || isNaN(parseFloat(positionInfo.y))) && (y = 0);  (isNaN(y2) || isNaN(parseFloat(positionInfo.y2))) && (y2 = containerHeight);  margin = formatUtil.normalizeCssArray(margin || 0);  return {    width: Math.max(x2 - x - margin[1] - margin[3], 0),    height: Math.max(y2 - y - margin[0] - margin[2], 0)  };}/** * Parse position info. * * @param {Object} positionInfo * @param {number|string} [positionInfo.left] * @param {number|string} [positionInfo.top] * @param {number|string} [positionInfo.right] * @param {number|string} [positionInfo.bottom] * @param {number|string} [positionInfo.width] * @param {number|string} [positionInfo.height] * @param {number|string} [positionInfo.aspect] Aspect is width / height * @param {Object} containerRect * @param {string|number} [margin] * * @return {module:zrender/core/BoundingRect} */function getLayoutRect(positionInfo, containerRect, margin) {  margin = formatUtil.normalizeCssArray(margin || 0);  var containerWidth = containerRect.width;  var containerHeight = containerRect.height;  var left = parsePercent(positionInfo.left, containerWidth);  var top = parsePercent(positionInfo.top, containerHeight);  var right = parsePercent(positionInfo.right, containerWidth);  var bottom = parsePercent(positionInfo.bottom, containerHeight);  var width = parsePercent(positionInfo.width, containerWidth);  var height = parsePercent(positionInfo.height, containerHeight);  var verticalMargin = margin[2] + margin[0];  var horizontalMargin = margin[1] + margin[3];  var aspect = positionInfo.aspect; // If width is not specified, calculate width from left and right  if (isNaN(width)) {    width = containerWidth - right - horizontalMargin - left;  }  if (isNaN(height)) {    height = containerHeight - bottom - verticalMargin - top;  }  if (aspect != null) {    // If width and height are not given    // 1. Graph should not exceeds the container    // 2. Aspect must be keeped    // 3. Graph should take the space as more as possible    // FIXME    // Margin is not considered, because there is no case that both    // using margin and aspect so far.    if (isNaN(width) && isNaN(height)) {      if (aspect > containerWidth / containerHeight) {        width = containerWidth * 0.8;      } else {        height = containerHeight * 0.8;      }    } // Calculate width or height with given aspect    if (isNaN(width)) {      width = aspect * height;    }    if (isNaN(height)) {      height = width / aspect;    }  } // If left is not specified, calculate left from right and width  if (isNaN(left)) {    left = containerWidth - right - width - horizontalMargin;  }  if (isNaN(top)) {    top = containerHeight - bottom - height - verticalMargin;  } // Align left and top  switch (positionInfo.left || positionInfo.right) {    case 'center':      left = containerWidth / 2 - width / 2 - margin[3];      break;    case 'right':      left = containerWidth - width - horizontalMargin;      break;  }  switch (positionInfo.top || positionInfo.bottom) {    case 'middle':    case 'center':      top = containerHeight / 2 - height / 2 - margin[0];      break;    case 'bottom':      top = containerHeight - height - verticalMargin;      break;  } // If something is wrong and left, top, width, height are calculated as NaN  left = left || 0;  top = top || 0;  if (isNaN(width)) {    // Width may be NaN if only one value is given except width    width = containerWidth - horizontalMargin - left - (right || 0);  }  if (isNaN(height)) {    // Height may be NaN if only one value is given except height    height = containerHeight - verticalMargin - top - (bottom || 0);  }  var rect = new BoundingRect(left + margin[3], top + margin[0], width, height);  rect.margin = margin;  return rect;}/** * Position a zr element in viewport *  Group position is specified by either *  {left, top}, {right, bottom} *  If all properties exists, right and bottom will be igonred. * * Logic: *     1. Scale (against origin point in parent coord) *     2. Rotate (against origin point in parent coord) *     3. Traslate (with el.position by this method) * So this method only fixes the last step 'Traslate', which does not affect * scaling and rotating. * * If be called repeatly with the same input el, the same result will be gotten. * * @param {module:zrender/Element} el Should have `getBoundingRect` method. * @param {Object} positionInfo * @param {number|string} [positionInfo.left] * @param {number|string} [positionInfo.top] * @param {number|string} [positionInfo.right] * @param {number|string} [positionInfo.bottom] * @param {number|string} [positionInfo.width] Only for opt.boundingModel: 'raw' * @param {number|string} [positionInfo.height] Only for opt.boundingModel: 'raw' * @param {Object} containerRect * @param {string|number} margin * @param {Object} [opt] * @param {Array.<number>} [opt.hv=[1,1]] Only horizontal or only vertical. * @param {Array.<number>} [opt.boundingMode='all'] *        Specify how to calculate boundingRect when locating. *        'all': Position the boundingRect that is transformed and uioned *               both itself and its descendants. *               This mode simplies confine the elements in the bounding *               of their container (e.g., using 'right: 0'). *        'raw': Position the boundingRect that is not transformed and only itself. *               This mode is useful when you want a element can overflow its *               container. (Consider a rotated circle needs to be located in a corner.) *               In this mode positionInfo.width/height can only be number. */function positionElement(el, positionInfo, containerRect, margin, opt) {  var h = !opt || !opt.hv || opt.hv[0];  var v = !opt || !opt.hv || opt.hv[1];  var boundingMode = opt && opt.boundingMode || 'all';  if (!h && !v) {    return;  }  var rect;  if (boundingMode === 'raw') {    rect = el.type === 'group' ? new BoundingRect(0, 0, +positionInfo.width || 0, +positionInfo.height || 0) : el.getBoundingRect();  } else {    rect = el.getBoundingRect();    if (el.needLocalTransform()) {      var transform = el.getLocalTransform(); // Notice: raw rect may be inner object of el,      // which should not be modified.      rect = rect.clone();      rect.applyTransform(transform);    }  } // The real width and height can not be specified but calculated by the given el.  positionInfo = getLayoutRect(zrUtil.defaults({    width: rect.width,    height: rect.height  }, positionInfo), containerRect, margin); // Because 'tranlate' is the last step in transform  // (see zrender/core/Transformable#getLocalTransform),  // we can just only modify el.position to get final result.  var elPos = el.position;  var dx = h ? positionInfo.x - rect.x : 0;  var dy = v ? positionInfo.y - rect.y : 0;  el.attr('position', boundingMode === 'raw' ? [dx, dy] : [elPos[0] + dx, elPos[1] + dy]);}/** * @param {Object} option Contains some of the properties in HV_NAMES. * @param {number} hvIdx 0: horizontal; 1: vertical. */function sizeCalculable(option, hvIdx) {  return option[HV_NAMES[hvIdx][0]] != null || option[HV_NAMES[hvIdx][1]] != null && option[HV_NAMES[hvIdx][2]] != null;}/** * Consider Case: * When defulat option has {left: 0, width: 100}, and we set {right: 0} * through setOption or media query, using normal zrUtil.merge will cause * {right: 0} does not take effect. * * @example * ComponentModel.extend({ *     init: function () { *         ... *         var inputPositionParams = layout.getLayoutParams(option); *         this.mergeOption(inputPositionParams); *     }, *     mergeOption: function (newOption) { *         newOption && zrUtil.merge(thisOption, newOption, true); *         layout.mergeLayoutParam(thisOption, newOption); *     } * }); * * @param {Object} targetOption * @param {Object} newOption * @param {Object|string} [opt] * @param {boolean|Array.<boolean>} [opt.ignoreSize=false] Used for the components *  that width (or height) should not be calculated by left and right (or top and bottom). */function mergeLayoutParam(targetOption, newOption, opt) {  !zrUtil.isObject(opt) && (opt = {});  var ignoreSize = opt.ignoreSize;  !zrUtil.isArray(ignoreSize) && (ignoreSize = [ignoreSize, ignoreSize]);  var hResult = merge(HV_NAMES[0], 0);  var vResult = merge(HV_NAMES[1], 1);  copy(HV_NAMES[0], targetOption, hResult);  copy(HV_NAMES[1], targetOption, vResult);  function merge(names, hvIdx) {    var newParams = {};    var newValueCount = 0;    var merged = {};    var mergedValueCount = 0;    var enoughParamNumber = 2;    each(names, function (name) {      merged[name] = targetOption[name];    });    each(names, function (name) {      // Consider case: newOption.width is null, which is      // set by user for removing width setting.      hasProp(newOption, name) && (newParams[name] = merged[name] = newOption[name]);      hasValue(newParams, name) && newValueCount++;      hasValue(merged, name) && mergedValueCount++;    });    if (ignoreSize[hvIdx]) {      // Only one of left/right is premitted to exist.      if (hasValue(newOption, names[1])) {        merged[names[2]] = null;      } else if (hasValue(newOption, names[2])) {        merged[names[1]] = null;      }      return merged;    } // Case: newOption: {width: ..., right: ...},    // or targetOption: {right: ...} and newOption: {width: ...},    // There is no conflict when merged only has params count    // little than enoughParamNumber.    if (mergedValueCount === enoughParamNumber || !newValueCount) {      return merged;    } // Case: newOption: {width: ..., right: ...},    // Than we can make sure user only want those two, and ignore    // all origin params in targetOption.    else if (newValueCount >= enoughParamNumber) {        return newParams;      } else {        // Chose another param from targetOption by priority.        for (var i = 0; i < names.length; i++) {          var name = names[i];          if (!hasProp(newParams, name) && hasProp(targetOption, name)) {            newParams[name] = targetOption[name];            break;          }        }        return newParams;      }  }  function hasProp(obj, name) {    return obj.hasOwnProperty(name);  }  function hasValue(obj, name) {    return obj[name] != null && obj[name] !== 'auto';  }  function copy(names, target, source) {    each(names, function (name) {      target[name] = source[name];    });  }}/** * Retrieve 'left', 'right', 'top', 'bottom', 'width', 'height' from object. * @param {Object} source * @return {Object} Result contains those props. */function getLayoutParams(source) {  return copyLayoutParams({}, source);}/** * Retrieve 'left', 'right', 'top', 'bottom', 'width', 'height' from object. * @param {Object} source * @return {Object} Result contains those props. */function copyLayoutParams(target, source) {  source && target && each(LOCATION_PARAMS, function (name) {    source.hasOwnProperty(name) && (target[name] = source[name]);  });  return target;}exports.LOCATION_PARAMS = LOCATION_PARAMS;exports.HV_NAMES = HV_NAMES;exports.box = box;exports.vbox = vbox;exports.hbox = hbox;exports.getAvailableSize = getAvailableSize;exports.getLayoutRect = getLayoutRect;exports.positionElement = positionElement;exports.sizeCalculable = sizeCalculable;exports.mergeLayoutParam = mergeLayoutParam;exports.getLayoutParams = getLayoutParams;exports.copyLayoutParams = copyLayoutParams;
 |