project/node_modules/echarts/lib/util/layout.js

449 lines
15 KiB
JavaScript
Raw Normal View History

2024-07-14 15:48:34 +08:00
/*
* 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;
}