/* * 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. */ /** * AUTO-GENERATED FILE. DO NOT MODIFY. */ /* * 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 positioning import * as zrUtil from 'zrender/lib/core/util.js'; import BoundingRect from 'zrender/lib/core/BoundingRect.js'; import { parsePercent } from './number.js'; import * as formatUtil from './format.js'; var each = zrUtil.each; /** * @public */ export var LOCATION_PARAMS = ['left', 'right', 'top', 'bottom', 'width', 'height']; /** * @public */ export 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 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; } child.x = x; child.y = y; child.markRedraw(); orient === 'horizontal' ? x = nextX + gap : y = nextY + gap; }); } /** * VBox or HBox layouting * @param {string} orient * @param {module:zrender/graphic/Group} group * @param {number} gap * @param {number} [width=Infinity] * @param {number} [height=Infinity] */ export var box = boxLayout; /** * VBox layouting * @param {module:zrender/graphic/Group} group * @param {number} gap * @param {number} [width=Infinity] * @param {number} [height=Infinity] */ export var vbox = zrUtil.curry(boxLayout, 'vertical'); /** * HBox layouting * @param {module:zrender/graphic/Group} group * @param {number} gap * @param {number} [width=Infinity] * @param {number} [height=Infinity] */ export 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. */ export function getAvailableSize(positionInfo, containerRect, margin) { var containerWidth = containerRect.width; var containerHeight = containerRect.height; var x = parsePercent(positionInfo.left, containerWidth); var y = parsePercent(positionInfo.top, containerHeight); var x2 = parsePercent(positionInfo.right, containerWidth); var y2 = parsePercent(positionInfo.bottom, containerHeight); (isNaN(x) || isNaN(parseFloat(positionInfo.left))) && (x = 0); (isNaN(x2) || isNaN(parseFloat(positionInfo.right))) && (x2 = containerWidth); (isNaN(y) || isNaN(parseFloat(positionInfo.top))) && (y = 0); (isNaN(y2) || isNaN(parseFloat(positionInfo.bottom))) && (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. */ export 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. Translate (with el.position by this method) * So this method only fixes the last step 'Translate', which does not affect * scaling and rotating. * * If be called repeatedly with the same input el, the same result will be gotten. * * Return true if the layout happened. * * @param el Should have `getBoundingRect` method. * @param positionInfo * @param positionInfo.left * @param positionInfo.top * @param positionInfo.right * @param positionInfo.bottom * @param positionInfo.width Only for opt.boundingModel: 'raw' * @param positionInfo.height Only for opt.boundingModel: 'raw' * @param containerRect * @param margin * @param opt * @param opt.hv Only horizontal or only vertical. Default to be [1, 1] * @param opt.boundingMode * 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. */ export function positionElement(el, positionInfo, containerRect, margin, opt, out) { var h = !opt || !opt.hv || opt.hv[0]; var v = !opt || !opt.hv || opt.hv[1]; var boundingMode = opt && opt.boundingMode || 'all'; out = out || el; out.x = el.x; out.y = el.y; if (!h && !v) { return false; } 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. var layoutRect = 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 dx = h ? layoutRect.x - rect.x : 0; var dy = v ? layoutRect.y - rect.y : 0; if (boundingMode === 'raw') { out.x = dx; out.y = dy; } else { out.x += dx; out.y += dy; } if (out === el) { el.markRedraw(); } return true; } /** * @param option Contains some of the properties in HV_NAMES. * @param hvIdx 0: horizontal; 1: vertical. */ export function sizeCalculable(option, hvIdx) { return option[HV_NAMES[hvIdx][0]] != null || option[HV_NAMES[hvIdx][1]] != null && option[HV_NAMES[hvIdx][2]] != null; } export function fetchLayoutMode(ins) { var layoutMode = ins.layoutMode || ins.constructor.layoutMode; return zrUtil.isObject(layoutMode) ? layoutMode : layoutMode ? { type: layoutMode } : null; } /** * Consider Case: * When default 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 () { * ... * let inputPositionParams = layout.getLayoutParams(option); * this.mergeOption(inputPositionParams); * }, * mergeOption: function (newOption) { * newOption && zrUtil.merge(thisOption, newOption, true); * layout.mergeLayoutParam(thisOption, newOption); * } * }); * * @param targetOption * @param newOption * @param opt */ export function mergeLayoutParam(targetOption, newOption, opt) { var ignoreSize = opt && 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_1 = names[i]; if (!hasProp(newParams, name_1) && hasProp(targetOption, name_1)) { newParams[name_1] = targetOption[name_1]; 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. */ export function getLayoutParams(source) { return copyLayoutParams({}, source); } /** * Retrieve 'left', 'right', 'top', 'bottom', 'width', 'height' from object. * @param {Object} source * @return {Object} Result contains those props. */ export function copyLayoutParams(target, source) { source && target && each(LOCATION_PARAMS, function (name) { source.hasOwnProperty(name) && (target[name] = source[name]); }); return target; }