var dataUtil = require("../utils/data_structure_util");
var classUtil = require("../utils/class_util");
var pathContain = require("../utils/contain/path");
var _constants = require("../utils/constants");
var mathMax = _constants.mathMax;
var mathAbs = _constants.mathAbs;
var mathSqrt = _constants.mathSqrt;
var Element = require("./Element");
var PathProxy = require("./PathProxy");
var Pattern = require("./Pattern");
function _typeof(obj) { "@babel/helpers - typeof"; if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); }
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }
function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }
function _possibleConstructorReturn(self, call) { if (call && (_typeof(call) === "object" || typeof call === "function")) { return call; } return _assertThisInitialized(self); }
function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; }
function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); }
function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); if (superClass) _setPrototypeOf(subClass, superClass); }
function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); }
/**
* @class qrenderer.graphic.Path
* @docauthor 大漠穷秋 <damoqiongqiu@126.com>
*/
var Path =
/*#__PURE__*/
function (_Element) {
_inherits(Path, _Element);
/**
* @method constructor Path
* @param {Object} options
*/
function Path(options) {
var _this;
_classCallCheck(this, Path);
_this = _possibleConstructorReturn(this, _getPrototypeOf(Path).call(this, options));
/**
* @property {String} type
*/
_this.type = 'path';
/**
* @property {PathProxy}
* @readonly
*/
_this.path = null;
/**
* @property {Number} strokeContainThreshold
*/
_this.strokeContainThreshold = 5;
/**
* @property {Number} segmentIgnoreThreshold
* This item default to be false. But in map series in echarts,
* in order to improve performance, it should be set to true,
* so the shorty segment won't draw.
*/
_this.segmentIgnoreThreshold = 0;
/**
* @property {Boolean} subPixelOptimize
* See `subPixelOptimize`.
*/
_this.subPixelOptimize = false;
/**
* @private
* @property __dirtyPath
*/
_this.__dirtyPath = true;
return _this;
}
/**
* @method render
*/
_createClass(Path, [{
key: "render",
value: function render() {
var ctx = this.ctx;
var prevEl = this.prevEl;
var path = this.path || new PathProxy(true);
var hasStroke = this.style.hasStroke();
var hasFill = this.style.hasFill();
var fill = this.style.fill;
var stroke = this.style.stroke;
var hasFillGradient = hasFill && !!fill.colorStops;
var hasStrokeGradient = hasStroke && !!stroke.colorStops;
var hasFillPattern = hasFill && !!fill.image;
var hasStrokePattern = hasStroke && !!stroke.image;
this.style.bind(ctx, this, prevEl);
this.applyTransform(ctx);
if (this.__dirty) {
var rect; // Update gradient because bounding rect may changed
if (hasFillGradient) {
rect = rect || this.getBoundingRect();
this._fillGradient = this.style.getGradient(ctx, fill, rect);
}
if (hasStrokeGradient) {
rect = rect || this.getBoundingRect();
this._strokeGradient = this.style.getGradient(ctx, stroke, rect);
}
} // Use the gradient or pattern
if (hasFillGradient) {
// PENDING If may have affect the state
ctx.fillStyle = this._fillGradient;
} else if (hasFillPattern) {
ctx.fillStyle = Pattern.prototype.getCanvasPattern.call(fill, ctx);
}
if (hasStrokeGradient) {
ctx.strokeStyle = this._strokeGradient;
} else if (hasStrokePattern) {
ctx.strokeStyle = Pattern.prototype.getCanvasPattern.call(stroke, ctx);
}
var lineDash = this.style.lineDash;
var lineDashOffset = this.style.lineDashOffset;
var ctxLineDash = !!ctx.setLineDash; // Update path sx, sy
var scale = this.getGlobalScale();
path.setScale(scale[0], scale[1], this.segmentIgnoreThreshold); // Proxy context
// Rebuild path in following 2 cases
// 1. Path is dirty
// 2. Path needs javascript implemented lineDash stroking.
// In this case, lineDash information will not be saved in PathProxy
if (this.__dirtyPath || lineDash && !ctxLineDash && hasStroke) {
path.beginPath(ctx); // Setting line dash before build path
if (lineDash && !ctxLineDash) {
path.setLineDash(lineDash);
path.setLineDashOffset(lineDashOffset);
}
this.buildPath(path, this.shape, false); // Clear path dirty flag
if (this.path) {
this.__dirtyPath = false;
}
} else {
// Replay path building
ctx.beginPath();
this.path.rebuildPath(ctx);
}
if (hasFill) {
if (this.style.fillOpacity != null) {
var originalGlobalAlpha = ctx.globalAlpha;
ctx.globalAlpha = this.style.fillOpacity * this.style.opacity;
path.fill(ctx);
ctx.globalAlpha = originalGlobalAlpha;
} else {
path.fill(ctx);
}
}
if (lineDash && ctxLineDash) {
ctx.setLineDash(lineDash);
ctx.lineDashOffset = lineDashOffset;
}
if (hasStroke) {
if (this.style.strokeOpacity != null) {
var _originalGlobalAlpha = ctx.globalAlpha;
ctx.globalAlpha = this.style.strokeOpacity * this.style.opacity;
path.stroke(ctx);
ctx.globalAlpha = _originalGlobalAlpha;
} else {
path.stroke(ctx);
}
}
if (lineDash && ctxLineDash) {
// PENDING
// Remove lineDash
ctx.setLineDash([]);
}
Element.prototype.render.call(this, ctx, prevEl);
}
/**
* @method buildPath
*
* Each subclass should provide its own implement for this method.
* When build path, some shape may decide if use moveTo to begin a new subpath or closePath, like in circle.
*
* 每个子类都需要为此方法提供自己的实现。
* 在构建路径时,某些形状需要根据情况决定使用 moveTo 来开始一段子路径,或者直接用 closePath 来封闭路径,比如圆形。
*
* @param {*} ctx
* @param {*} shapeCfg
* @param {*} inBundle
*/
}, {
key: "buildPath",
value: function buildPath(ctx, shapeCfg, inBundle) {}
/**
* @method createPathProxy
*/
}, {
key: "createPathProxy",
value: function createPathProxy() {
this.path = new PathProxy();
}
/**
* @method getBoundingRect
* Get bounding rect of this element, NOTE:
* this method will return the bounding rect without transforming(translate/scale/rotate/skew).
* However, direct modifications to the shape property will be reflected in the bouding-rect.
* For example, if we modify this.shape.width directly, then the new width property will be calculated.
*
*
* 获取当前元素的边界矩形,注意:
* 此方法返回的是没有经过 transform(translate/scale/rotate/skew) 处理的边界矩形,但是对 shape 属性直接进行的修改会反映在获取的边界矩形上。
* 例如,用代码直接对 this.shape.width 进行赋值,那么在计算边界矩形时就会用新的 width 属性进行计算。
*/
}, {
key: "getBoundingRect",
value: function getBoundingRect() {
var rect = this.__boundingRect;
var needsUpdateRect = !rect;
if (needsUpdateRect) {
var path = this.path;
if (!path) {
// Create path on demand.
path = this.path = new PathProxy();
}
if (this.__dirtyPath) {
path.beginPath();
this.buildPath(path, this.shape, false);
}
rect = path.getBoundingRect();
}
this.__boundingRect = rect;
if (this.style.hasStroke()) {
// Update rect with stroke lineWidth when
// 1. Element changes scale or lineWidth
// 2. Shape is changed
var rectWithStroke = this._boundingRectWithStroke || (this._boundingRectWithStroke = rect.clone());
if (this.__dirty || needsUpdateRect) {
rectWithStroke.copy(rect); // FIXME Must after composeParentTransform
var w = this.style.lineWidth; // PENDING, Min line width is needed when line is horizontal or vertical
var lineScale = this.style.strokeNoScale ? this.getLineScale() : 1; // Only add extra hover lineWidth when there are no fill
if (!this.style.hasFill()) {
w = mathMax(w, this.strokeContainThreshold || 4);
} // Consider line width
// Line scale can't be 0;
if (lineScale > 1e-10) {
rectWithStroke.width += w / lineScale;
rectWithStroke.height += w / lineScale;
rectWithStroke.x1 -= w / lineScale / 2;
rectWithStroke.y1 -= w / lineScale / 2;
rectWithStroke.x2 = rectWithStroke.x1 + rectWithStroke.width;
rectWithStroke.y2 = rectWithStroke.y1 + rectWithStroke.height;
}
} // Return rect with stroke
return rectWithStroke;
}
return rect;
}
/**
* @method containPoint
* @param {*} x
* @param {*} y
*/
}, {
key: "containPoint",
value: function containPoint(x, y) {
var localPos = this.globalToLocal(x, y);
var rect = this.getBoundingRect();
var style = this.style;
x = localPos[0];
y = localPos[1];
if (rect.containPoint(x, y)) {
var pathData = this.path.data;
if (style.hasStroke()) {
var lineWidth = style.lineWidth;
var lineScale = style.strokeNoScale ? this.getLineScale() : 1; // Line scale can't be 0;
if (lineScale > 1e-10) {
// Only add extra hover lineWidth when there are no fill
if (!style.hasFill()) {
lineWidth = mathMax(lineWidth, this.strokeContainThreshold);
}
if (pathContain.containStroke(pathData, lineWidth / lineScale, x, y)) {
return true;
}
}
}
if (style.hasFill()) {
return pathContain.containPoint(pathData, x, y);
}
}
return false;
}
/**
* @protected
* @method dirty
* @param {Boolean} dirtyPath
*/
}, {
key: "dirty",
value: function dirty(dirtyPath) {
if (dirtyPath == null) {
dirtyPath = true;
} // Only mark dirty, not mark clean
if (dirtyPath) {
this.__dirtyPath = dirtyPath;
}
Element.prototype.dirty.call(this); // Used as a clipping path
if (this.__clipTarget) {
this.__clipTarget.dirty();
}
}
/**
* @method _attrKV
* Overwrite _attrKV
* @param {*} key
* @param {Object} value
*/
}, {
key: "_attrKV",
value: function _attrKV(key, value) {
// FIXME
if (key === 'shape') {
this.__dirtyPath = true;
this.__boundingRect = null;
this.setShape(value);
} else {
Element.prototype._attrKV.call(this, key, value);
}
}
/**
* @method setShape
* @param {Object|String} key
* @param {Object} value
*/
}, {
key: "setShape",
value: function setShape(key, value) {
// Path from string may not have shape
if (!this.shape) {
return this;
}
if (dataUtil.isObject(key)) {
classUtil.copyOwnProperties(this.shape, key);
} else {
this.shape[key] = value;
}
this.dirty(true);
return this;
}
/**
* @method getLineScale
*/
}, {
key: "getLineScale",
value: function getLineScale() {
var m = this.transform; // Get the line scale.
// Determinant of `m` means how much the area is enlarged by the
// transformation. So its square root can be used as a scale factor
// for width.
return m && mathAbs(m[0] - 1) > 1e-10 && mathAbs(m[3] - 1) > 1e-10 ? mathSqrt(mathAbs(m[0] * m[3] - m[2] * m[1])) : 1;
}
}]);
return Path;
}(Element);
var _default = Path;
module.exports = _default;