/**
* 图形相关函数
*
* @module util/graphic
*/
import * as zrUtil from 'zrender/src/core/util';
import * as pathTool from 'zrender/src/tool/path';
import * as colorTool from 'zrender/src/tool/color';
import * as matrix from 'zrender/src/core/matrix';
import * as vector from 'zrender/src/core/vector';
import Path from 'zrender/src/graphic/Path';
import Transformable from 'zrender/src/mixin/Transformable';
import ZImage from 'zrender/src/graphic/Image';
import Group from 'zrender/src/container/Group';
import Text from 'zrender/src/graphic/Text';
import Circle from 'zrender/src/graphic/shape/Circle';
import Sector from 'zrender/src/graphic/shape/Sector';
import Ring from 'zrender/src/graphic/shape/Ring';
import Polygon from 'zrender/src/graphic/shape/Polygon';
import Polyline from 'zrender/src/graphic/shape/Polyline';
import Rect from 'zrender/src/graphic/shape/Rect';
import Line from 'zrender/src/graphic/shape/Line';
import BezierCurve from 'zrender/src/graphic/shape/BezierCurve';
import Arc from 'zrender/src/graphic/shape/Arc';
// import CompoundPath from 'zrender/src/graphic/CompoundPath';
import LinearGradient from 'zrender/src/graphic/LinearGradient';
import RadialGradient from 'zrender/src/graphic/RadialGradient';
import BoundingRect from 'zrender/src/core/BoundingRect';
var round = Math.round;
var mathMax = Math.max;
var mathMin = Math.min;
/**
* Extend shape with parameters
*/
export function extendShape(opts) {
return Path.extend(opts);
};
/**
* Extend path
*/
export function extendPath(pathData, opts) {
return pathTool.extendFromString(pathData, opts);
};
export function PathString(pathData, opts) {
return pathTool.createFromString(pathData, opts);
};
/**
* Create a path element from path data string
* @param {string} pathData
* @param {Object} opts
* @param {module:zrender/core/BoundingRect} rect
* @param {string} [layout=cover] 'center' or 'cover'
*/
export function makePath(pathData, opts, rect, layout) {
var path = pathTool.createFromString(pathData, opts);
var boundingRect = path.getBoundingRect();
if (rect) {
var aspect = boundingRect.width / boundingRect.height;
if (layout === 'center') {
// Set rect to center, keep width / height ratio.
var width = rect.height * aspect;
var height;
if (width <= rect.width) {
height = rect.height;
}
else {
width = rect.width;
height = width / aspect;
}
var cx = rect.x + rect.width / 2;
var cy = rect.y + rect.height / 2;
rect.x = cx - width / 2;
rect.y = cy - height / 2;
rect.width = width;
rect.height = height;
}
resizePath(path, rect);
}
return path;
};
export var mergePath = pathTool.mergePath;
/**
* Resize a path to fit the rect
* @param {module:zrender/graphic/Path} path
* @param {Object} rect
*/
export function resizePath(path, rect) {
if (!path.applyTransform) {
return;
}
var pathRect = path.getBoundingRect();
var m = pathRect.calculateTransform(rect);
path.applyTransform(m);
};
/**
* Sub pixel optimize line for canvas
*
* @param {Object} param
* @param {Object} [param.shape]
* @param {number} [param.shape.x1]
* @param {number} [param.shape.y1]
* @param {number} [param.shape.x2]
* @param {number} [param.shape.y2]
* @param {Object} [param.style]
* @param {number} [param.style.lineWidth]
* @return {Object} Modified param
*/
export function subPixelOptimizeLine(param) {
var shape = param.shape;
var position = param.position || [0, 0];
var lineWidth = param.style.lineWidth;
if (round(shape.x1 * 2) === round(shape.x2 * 2)) {
shape.x1 = shape.x2 = subPixelOptimize(shape.x1, lineWidth, true, position[0]);
}
if (round(shape.y1 * 2) === round(shape.y2 * 2)) {
shape.y1 = shape.y2 = subPixelOptimize(shape.y1, lineWidth, true, position[1]);
}
return param;
};
/**
* Sub pixel optimize rect for canvas
*
* @param {Object} param
* @param {Object} [param.shape]
* @param {number} [param.shape.x]
* @param {number} [param.shape.y]
* @param {number} [param.shape.width]
* @param {number} [param.shape.height]
* @param {Object} [param.style]
* @param {number} [param.style.lineWidth]
* @return {Object} Modified param
*/
export function subPixelOptimizeRect(param) {
if (!param.style.stroke) return param;
var shape = param.shape;
var lineWidth = param.style.lineWidth;
var originX = shape.x;
var originY = shape.y;
var widthIsNegative = shape.width < 0;
var heightIsNegative = shape.height < 0;
var originWidth = Math.abs(shape.width);
var originHeight = Math.abs(shape.height);
shape.x = subPixelOptimize(shape.x, lineWidth, true);
shape.y = subPixelOptimize(shape.y, lineWidth, true);
shape.width = Math.max(
subPixelOptimize(originX + originWidth, lineWidth, false) - shape.x,
originWidth === 0 ? 0 : 1
);
widthIsNegative && (shape.width = -shape.width);
shape.height = Math.max(
subPixelOptimize(originY + originHeight, lineWidth, false) - shape.y,
originHeight === 0 ? 0 : 1
);
heightIsNegative && (shape.height = -shape.height);
return param;
};
/**
* Sub pixel optimize for canvas
*
* @param {number} xOrY Coordinate, such as x, y
* @param {number} lineWidth Should be nonnegative integer.
* @param {boolean=} positiveOrNegative Default false (negative).
* @return {number} Optimized position.
*/
export function subPixelOptimize(xOrY, lineWidth, positiveOrNegative, pos) {
// Assure that (xOrY + lineWidth / 2) is near integer edge,
// otherwise line will be fuzzy in canvas.
var doubledPosition = round(xOrY * 2);
return (doubledPosition + round(lineWidth) + (pos ? round(pos * 2) : 0)) % 2 === 0
? doubledPosition / 2
: (doubledPosition + (positiveOrNegative ? 1 : -1)) / 2;
};
function hasFillOrStroke(fillOrStroke) {
return fillOrStroke != null && fillOrStroke != 'none';
}
function liftColor(color) {
return typeof color === 'string' ? colorTool.lift(color, -0.1) : color;
}
/**
* @private
*/
function cacheElementStl(el) {
if (el.__hoverStlDirty) {
var stroke = el.style.stroke;
var fill = el.style.fill;
// Create hoverStyle on mouseover
var hoverStyle = el.__hoverStl;
hoverStyle.fill = hoverStyle.fill
|| (hasFillOrStroke(fill) ? liftColor(fill) : null);
hoverStyle.stroke = hoverStyle.stroke
|| (hasFillOrStroke(stroke) ? liftColor(stroke) : null);
var normalStyle = {};
for (var name in hoverStyle) {
if (hoverStyle.hasOwnProperty(name)) {
normalStyle[name] = el.style[name];
}
}
el.__normalStl = normalStyle;
el.__hoverStlDirty = false;
}
}
/**
* @private
*/
function doSingleEnterHover(el) {
if (el.__isHover) {
return;
}
cacheElementStl(el);
if (el.useHoverLayer) {
el.__zr && el.__zr.addHover(el, el.__hoverStl);
}
else {
el.setStyle(el.__hoverStl);
el.z2 += 1;
}
el.__isHover = true;
}
/**
* @inner
*/
function doSingleLeaveHover(el) {
if (!el.__isHover) {
return;
}
var normalStl = el.__normalStl;
if (el.useHoverLayer) {
el.__zr && el.__zr.removeHover(el);
}
else {
normalStl && el.setStyle(normalStl);
el.z2 -= 1;
}
el.__isHover = false;
}
/**
* @inner
*/
function doEnterHover(el) {
el.type === 'group'
? el.traverse(function (child) {
if (child.type !== 'group') {
doSingleEnterHover(child);
}
})
: doSingleEnterHover(el);
}
function doLeaveHover(el) {
el.type === 'group'
? el.traverse(function (child) {
if (child.type !== 'group') {
doSingleLeaveHover(child);
}
})
: doSingleLeaveHover(el);
}
/**
* @inner
*/
function setElementHoverStl(el, hoverStl) {
// If element has sepcified hoverStyle, then use it instead of given hoverStyle
// Often used when item group has a label element and it's hoverStyle is different
el.__hoverStl = el.hoverStyle || hoverStl || {};
el.__hoverStlDirty = true;
if (el.__isHover) {
cacheElementStl(el);
}
}
/**
* @inner
*/
function onElementMouseOver(e) {
if (this.__hoverSilentOnTouch && e.zrByTouch) {
return;
}
// Only if element is not in emphasis status
!this.__isEmphasis && doEnterHover(this);
}
/**
* @inner
*/
function onElementMouseOut(e) {
if (this.__hoverSilentOnTouch && e.zrByTouch) {
return;
}
// Only if element is not in emphasis status
!this.__isEmphasis && doLeaveHover(this);
}
/**
* @inner
*/
function enterEmphasis() {
this.__isEmphasis = true;
doEnterHover(this);
}
/**
* @inner
*/
function leaveEmphasis() {
this.__isEmphasis = false;
doLeaveHover(this);
}
/**
* Set hover style of element.
* This method can be called repeatly without side-effects.
* @param {module:zrender/Element} el
* @param {Object} [hoverStyle]
* @param {Object} [opt]
* @param {boolean} [opt.hoverSilentOnTouch=false]
* In touch device, mouseover event will be trigger on touchstart event
* (see module:zrender/dom/HandlerProxy). By this mechanism, we can
* conviniently use hoverStyle when tap on touch screen without additional
* code for compatibility.
* But if the chart/component has select feature, which usually also use
* hoverStyle, there might be conflict between 'select-highlight' and
* 'hover-highlight' especially when roam is enabled (see geo for example).
* In this case, hoverSilentOnTouch should be used to disable hover-highlight
* on touch device.
*/
export function setHoverStyle(el, hoverStyle, opt = { disableMouse: false }) {
el.__hoverSilentOnTouch = opt && opt.hoverSilentOnTouch;
el.type === 'group'
? el.traverse(function (child) {
if (child.type !== 'group') {
setElementHoverStl(child, hoverStyle);
}
})
: setElementHoverStl(el, hoverStyle);
if (!opt || !opt.disableMouse) {
// Duplicated function will be auto-ignored, see Eventful.js.
el.on('mouseover', onElementMouseOver)
.on('mouseout', onElementMouseOut);
}
// Emphasis, normal can be triggered manually
el.on('emphasis', enterEmphasis)
.on('normal', leaveEmphasis);
};
const FORMAT_TEXT_STYLE_LIST = [
'fill', 'stroke', 'shadowBlur', 'shadowColor', 'shadowOffsetX', 'shadowOffsetY',
'position', 'offset', 'lineWidth'
];
const FORMATTED_TEXT_STYLE_LIST = [
'textFill', 'textStroke', 'textShadowBlur', 'textShadowColor', 'textShadowOffsetX', 'textShadowOffsetY',
'textPosition', 'textOffset', 'textLineWidth'
];
export function formatTextStyle(text) {
if (!text) return;
let style = text.style ? text.style : text;
if (style.rich) {
zrUtil.each(style.rich, function (item) {
formatTextStyle(item)
})
}
zrUtil.each(FORMAT_TEXT_STYLE_LIST, function (item, index) {
if (typeof style[item] !== 'undefined') {
style[FORMATTED_TEXT_STYLE_LIST[index]] = style[item];
delete style[item];
}
});
return text;
}
var isObject = zrUtil.isObject;
var clone = zrUtil.clone;
var map = zrUtil.map;
function reverseStops(style, pos) {
if (isObject(style)) {
let stops = style.colorStops;
// 反向stops
let concatStops = pos && pos !== 1 ? map(clone(stops).reverse(), ({ offset, color }) => ({
color,
offset: 1 - offset * (1 - pos)
})) : [];
// 正向stops拼接反向stops
style.colorStops = map(stops, ({ offset, color }) => ({
color,
offset: pos ? pos * offset : (1 - offset)
})).concat(concatStops);
}
}
export function reverseStyleStops(style, pos) {
if (!style) return;
if ((style.hasOwnProperty('fill') && isObject(style.fill)) || (style.hasOwnProperty('stroke') && isObject(style.stroke))) {
let cloneStyle = clone(style);
reverseStops(cloneStyle.fill, pos);
reverseStops(cloneStyle.stroke, pos);
return cloneStyle;
} else {
return style;
}
}
export function enableDrag(shape, global, { onDragStart, onDragging, onDragEnd }) {
let isDragging = false;
let target;
function dragStart(e) {
if (!isDragging) {
isDragging = true;
target = e.target;
onDragStart && onDragStart(e);
}
}
function dragging(e) {
if (isDragging) {
e.target = target;
onDragging && onDragging(e)
}
}
function dragEnd(e) {
if (isDragging) {
e.target = target;
onDragEnd && onDragEnd(e);
isDragging = false;
target = undefined;
}
}
shape.on('mousedown', dragStart);
global.on('mousemove', dragging);
global.on('mouseup', dragEnd);
shape.disableDrag = function () {
shape.off('mousedown', dragStart);
global.off('mousedown', dragging);
global.off('mousedown', dragEnd);
}
}
export {
Path,
Group,
ZImage as Image,
Text,
Circle,
Sector,
Ring,
Polygon,
Polyline,
Rect,
Line,
BezierCurve,
Arc,
// CompoundPath,
LinearGradient,
RadialGradient,
BoundingRect,
colorTool
};