| Current Path : /home/cbequiperp/www/components/com_flexicontent/librairies/zrender/tool/ |
| Current File : /home/cbequiperp/www/components/com_flexicontent/librairies/zrender/tool/area.js |
/**
* zrender: 图形空间辅助类
*
* @author Kener (@Kener-林峰, kener.linfeng@gmail.com)
* pissang (https://www.github.com/pissang)
*
* isInside:是否在区域内部
* isOutside:是否在区域外部
* getTextWidth:测算单行文本宽度
*/
define(
function (require) {
'use strict';
var util = require('./util');
var curve = require('./curve');
var _ctx;
var _textWidthCache = {};
var _textHeightCache = {};
var _textWidthCacheCounter = 0;
var _textHeightCacheCounter = 0;
var TEXT_CACHE_MAX = 5000;
var PI2 = Math.PI * 2;
function normalizeRadian(angle) {
angle %= PI2;
if (angle < 0) {
angle += PI2;
}
return angle;
}
/**
* 包含判断
*
* @param {Object} shape : 图形
* @param {Object} area : 目标区域
* @param {number} x : 横坐标
* @param {number} y : 纵坐标
*/
function isInside(shape, area, x, y) {
if (!area || !shape) {
// 无参数或不支持类型
return false;
}
var zoneType = shape.type;
_ctx = _ctx || util.getContext();
// 未实现或不可用时(excanvas不支持)则数学运算,主要是line,polyline,ring
var _mathReturn = _mathMethod(shape, area, x, y);
if (typeof _mathReturn != 'undefined') {
return _mathReturn;
}
if (shape.buildPath && _ctx.isPointInPath) {
return _buildPathMethod(shape, _ctx, area, x, y);
}
// 上面的方法都行不通时
switch (zoneType) {
case 'ellipse': // Todo,不精确
return true;
// 旋轮曲线 不准确
case 'trochoid':
var _r = area.location == 'out'
? area.r1 + area.r2 + area.d
: area.r1 - area.r2 + area.d;
return isInsideCircle(area, x, y, _r);
// 玫瑰线 不准确
case 'rose' :
return isInsideCircle(area, x, y, area.maxr);
// 路径,椭圆,曲线等-----------------13
default:
return false; // Todo,暂不支持
}
}
/**
* 用数学方法判断,三个方法中最快,但是支持的shape少
*
* @param {Object} shape : 图形
* @param {Object} area :目标区域
* @param {number} x : 横坐标
* @param {number} y : 纵坐标
* @return {boolean=} true表示坐标处在图形中
*/
function _mathMethod(shape, area, x, y) {
var zoneType = shape.type;
// 在矩形内则部分图形需要进一步判断
switch (zoneType) {
// 贝塞尔曲线
case 'bezier-curve':
if (typeof(area.cpX2) === 'undefined') {
return isInsideQuadraticStroke(
area.xStart, area.yStart,
area.cpX1, area.cpY1,
area.xEnd, area.yEnd,
area.lineWidth, x, y
);
}
return isInsideCubicStroke(
area.xStart, area.yStart,
area.cpX1, area.cpY1,
area.cpX2, area.cpY2,
area.xEnd, area.yEnd,
area.lineWidth, x, y
);
// 线
case 'line':
return isInsideLine(
area.xStart, area.yStart,
area.xEnd, area.yEnd,
area.lineWidth, x, y
);
// 折线
case 'polyline':
return isInsidePolyline(
area.pointList, area.lineWidth, x, y
);
// 圆环
case 'ring':
return isInsideRing(
area.x, area.y, area.r0, area.r, x, y
);
// 圆形
case 'circle':
return isInsideCircle(
area.x, area.y, area.r, x, y
);
// 扇形
case 'sector':
var startAngle = area.startAngle * Math.PI / 180;
var endAngle = area.endAngle * Math.PI / 180;
if (!area.clockWise) {
startAngle = -startAngle;
endAngle = -endAngle;
}
return isInsideSector(
area.x, area.y, area.r0, area.r,
startAngle, endAngle,
!area.clockWise,
x, y
);
// 多边形
case 'path':
return area.pathArray && isInsidePath(
area.pathArray, Math.max(area.lineWidth, 5),
area.brushType, x, y
);
case 'polygon':
case 'star':
case 'isogon':
return isInsidePolygon(area.pointList, x, y);
// 文本
case 'text':
var rect = area.__rect || shape.getRect(area);
return isInsideRect(
rect.x, rect.y, rect.width, rect.height, x, y
);
// 矩形
case 'rectangle':
// 图片
case 'image':
return isInsideRect(
area.x, area.y, area.width, area.height, x, y
);
}
}
/**
* 通过buildPath方法来判断,三个方法中较快,但是不支持线条类型的shape,
* 而且excanvas不支持isPointInPath方法
*
* @param {Object} shape : shape
* @param {Object} context : 上下文
* @param {Object} area :目标区域
* @param {number} x : 横坐标
* @param {number} y : 纵坐标
* @return {boolean} true表示坐标处在图形中
*/
function _buildPathMethod(shape, context, area, x, y) {
// 图形类实现路径创建了则用类的path
context.beginPath();
shape.buildPath(context, area);
context.closePath();
return context.isPointInPath(x, y);
}
/**
* !isInside
*/
function isOutside(shape, area, x, y) {
return !isInside(shape, area, x, y);
}
/**
* 线段包含判断
* @param {number} x0
* @param {number} y0
* @param {number} x1
* @param {number} y1
* @param {number} lineWidth
* @param {number} x
* @param {number} y
* @return {boolean}
*/
function isInsideLine(x0, y0, x1, y1, lineWidth, x, y) {
if (lineWidth === 0) {
return false;
}
var _l = Math.max(lineWidth, 5);
var _a = 0;
var _b = x0;
// Quick reject
if (
(y > y0 + _l && y > y1 + _l)
|| (y < y0 - _l && y < y1 - _l)
|| (x > x0 + _l && x > x1 + _l)
|| (x < x0 - _l && x < x1 - _l)
) {
return false;
}
if (x0 !== x1) {
_a = (y0 - y1) / (x0 - x1);
_b = (x0 * y1 - x1 * y0) / (x0 - x1) ;
}
else {
return Math.abs(x - x0) <= _l / 2;
}
var tmp = _a * x - y + _b;
var _s = tmp * tmp / (_a * _a + 1);
return _s <= _l / 2 * _l / 2;
}
/**
* 三次贝塞尔曲线描边包含判断
* @param {number} x0
* @param {number} y0
* @param {number} x1
* @param {number} y1
* @param {number} x2
* @param {number} y2
* @param {number} x3
* @param {number} y3
* @param {number} lineWidth
* @param {number} x
* @param {number} y
* @return {boolean}
*/
function isInsideCubicStroke(
x0, y0, x1, y1, x2, y2, x3, y3,
lineWidth, x, y
) {
if (lineWidth === 0) {
return false;
}
var _l = Math.max(lineWidth, 5);
// Quick reject
if (
(y > y0 + _l && y > y1 + _l && y > y2 + _l && y > y3 + _l)
|| (y < y0 - _l && y < y1 - _l && y < y2 - _l && y < y3 - _l)
|| (x > x0 + _l && x > x1 + _l && x > x2 + _l && x > x3 + _l)
|| (x < x0 - _l && x < x1 - _l && x < x2 - _l && x < x3 - _l)
) {
return false;
}
var d = curve.cubicProjectPoint(
x0, y0, x1, y1, x2, y2, x3, y3,
x, y, null
);
return d <= _l / 2;
}
/**
* 二次贝塞尔曲线描边包含判断
* @param {number} x0
* @param {number} y0
* @param {number} x1
* @param {number} y1
* @param {number} x2
* @param {number} y2
* @param {number} lineWidth
* @param {number} x
* @param {number} y
* @return {boolean}
*/
function isInsideQuadraticStroke(
x0, y0, x1, y1, x2, y2,
lineWidth, x, y
) {
if (lineWidth === 0) {
return false;
}
var _l = Math.max(lineWidth, 5);
// Quick reject
if (
(y > y0 + _l && y > y1 + _l && y > y2 + _l)
|| (y < y0 - _l && y < y1 - _l && y < y2 - _l)
|| (x > x0 + _l && x > x1 + _l && x > x2 + _l)
|| (x < x0 - _l && x < x1 - _l && x < x2 - _l)
) {
return false;
}
var d = curve.quadraticProjectPoint(
x0, y0, x1, y1, x2, y2,
x, y, null
);
return d <= _l / 2;
}
/**
* 圆弧描边包含判断
* @param {number} cx
* @param {number} cy
* @param {number} r
* @param {number} startAngle
* @param {number} endAngle
* @param {boolean} anticlockwise
* @param {number} lineWidth
* @param {number} x
* @param {number} y
* @return {Boolean}
*/
function isInsideArcStroke(
cx, cy, r, startAngle, endAngle, anticlockwise,
lineWidth, x, y
) {
if (lineWidth === 0) {
return false;
}
var _l = Math.max(lineWidth, 5);
x -= cx;
y -= cy;
var d = Math.sqrt(x * x + y * y);
if ((d - _l > r) || (d + _l < r)) {
return false;
}
if (Math.abs(startAngle - endAngle) >= PI2) {
// Is a circle
return true;
}
if (anticlockwise) {
var tmp = startAngle;
startAngle = normalizeRadian(endAngle);
endAngle = normalizeRadian(tmp);
} else {
startAngle = normalizeRadian(startAngle);
endAngle = normalizeRadian(endAngle);
}
if (startAngle > endAngle) {
endAngle += PI2;
}
var angle = Math.atan2(y, x);
if (angle < 0) {
angle += PI2;
}
return (angle >= startAngle && angle <= endAngle)
|| (angle + PI2 >= startAngle && angle + PI2 <= endAngle);
}
function isInsidePolyline(points, lineWidth, x, y) {
var lineWidth = Math.max(lineWidth, 10);
for (var i = 0, l = points.length - 1; i < l; i++) {
var x0 = points[i][0];
var y0 = points[i][1];
var x1 = points[i + 1][0];
var y1 = points[i + 1][1];
if (isInsideLine(x0, y0, x1, y1, lineWidth, x, y)) {
return true;
}
}
return false;
}
function isInsideRing(cx, cy, r0, r, x, y) {
var d = (x - cx) * (x - cx) + (y - cy) * (y - cy);
return (d < r * r) && (d > r0 * r0);
}
/**
* 矩形包含判断
*/
function isInsideRect(x0, y0, width, height, x, y) {
return x >= x0 && x <= (x0 + width)
&& y >= y0 && y <= (y0 + height);
}
/**
* 圆形包含判断
*/
function isInsideCircle(x0, y0, r, x, y) {
return (x - x0) * (x - x0) + (y - y0) * (y - y0)
< r * r;
}
/**
* 扇形包含判断
*/
function isInsideSector(
cx, cy, r0, r, startAngle, endAngle, anticlockwise, x, y
) {
return isInsideArcStroke(
cx, cy, (r0 + r) / 2, startAngle, endAngle, anticlockwise,
r - r0, x, y
);
}
/**
* 多边形包含判断
* 与 canvas 一样采用 non-zero winding rule
*/
function isInsidePolygon(points, x, y) {
var N = points.length;
var w = 0;
for (var i = 0, j = N - 1; i < N; i++) {
var x0 = points[j][0];
var y0 = points[j][1];
var x1 = points[i][0];
var y1 = points[i][1];
w += windingLine(x0, y0, x1, y1, x, y);
j = i;
}
return w !== 0;
}
function windingLine(x0, y0, x1, y1, x, y) {
if ((y > y0 && y > y1) || (y < y0 && y < y1)) {
return 0;
}
if (y1 == y0) {
return 0;
}
var dir = y1 < y0 ? 1 : -1;
var t = (y - y0) / (y1 - y0);
var x_ = t * (x1 - x0) + x0;
return x_ > x ? dir : 0;
}
// 临时数组
var roots = [-1, -1, -1];
var extrema = [-1, -1];
function swapExtrema() {
var tmp = extrema[0];
extrema[0] = extrema[1];
extrema[1] = tmp;
}
function windingCubic(x0, y0, x1, y1, x2, y2, x3, y3, x, y) {
// Quick reject
if (
(y > y0 && y > y1 && y > y2 && y > y3)
|| (y < y0 && y < y1 && y < y2 && y < y3)
) {
return 0;
}
var nRoots = curve.cubicRootAt(y0, y1, y2, y3, y, roots);
if (nRoots === 0) {
return 0;
}
else {
var w = 0;
var nExtrema = -1;
var y0_, y1_;
for (var i = 0; i < nRoots; i++) {
var t = roots[i];
var x_ = curve.cubicAt(x0, x1, x2, x3, t);
if (x_ < x) { // Quick reject
continue;
}
if (nExtrema < 0) {
nExtrema = curve.cubicExtrema(y0, y1, y2, y3, extrema);
if (extrema[1] < extrema[0] && nExtrema > 1) {
swapExtrema();
}
y0_ = curve.cubicAt(y0, y1, y2, y3, extrema[0]);
if (nExtrema > 1) {
y1_ = curve.cubicAt(y0, y1, y2, y3, extrema[1]);
}
}
if (nExtrema == 2) {
// 分成三段单调函数
if (t < extrema[0]) {
w += y0_ < y0 ? 1 : -1;
}
else if (t < extrema[1]) {
w += y1_ < y0_ ? 1 : -1;
}
else {
w += y3 < y1_ ? 1 : -1;
}
}
else {
// 分成两段单调函数
if (t < extrema[0]) {
w += y0_ < y0 ? 1 : -1;
}
else {
w += y3 < y0_ ? 1 : -1;
}
}
}
return w;
}
}
function windingQuadratic(x0, y0, x1, y1, x2, y2, x, y) {
// Quick reject
if (
(y > y0 && y > y1 && y > y2)
|| (y < y0 && y < y1 && y < y2)
) {
return 0;
}
var nRoots = curve.quadraticRootAt(y0, y1, y2, y, roots);
if (nRoots === 0) {
return 0;
}
else {
var t = curve.quadraticExtremum(y0, y1, y2);
if (t >=0 && t <= 1) {
var w = 0;
var y_ = curve.quadraticAt(y0, y1, y2, t);
for (var i = 0; i < nRoots; i++) {
var x_ = curve.quadraticAt(x0, x1, x2, roots[i]);
if (x_ > x) {
continue;
}
if (roots[i] < t) {
w += y_ < y0 ? 1 : -1;
}
else {
w += y2 < y_ ? 1 : -1;
}
}
return w;
}
else {
var x_ = curve.quadraticAt(x0, x1, x2, roots[0]);
if (x_ > x) {
return 0;
}
return y2 < y0 ? 1 : -1;
}
}
}
// TODO
// Arc 旋转
function windingArc(
cx, cy, r, startAngle, endAngle, anticlockwise, x, y
) {
y -= cy;
if (y > r || y < -r) {
return 0;
}
var tmp = Math.sqrt(r * r - y * y);
roots[0] = -tmp;
roots[1] = tmp;
if (Math.abs(startAngle - endAngle) >= PI2) {
// Is a circle
startAngle = 0;
endAngle = PI2;
var dir = anticlockwise ? 1 : -1;
if (x >= roots[0] + cx && x <= roots[1] + cx) {
return dir;
} else {
return 0;
}
}
if (anticlockwise) {
var tmp = startAngle;
startAngle = normalizeRadian(endAngle);
endAngle = normalizeRadian(tmp);
} else {
startAngle = normalizeRadian(startAngle);
endAngle = normalizeRadian(endAngle);
}
if (startAngle > endAngle) {
endAngle += PI2;
}
var w = 0;
for (var i = 0; i < 2; i++) {
var x_ = roots[i];
if (x_ + cx > x) {
var angle = Math.atan2(y, x_);
var dir = anticlockwise ? 1 : -1;
if (angle < 0) {
angle = PI2 + angle;
}
if (
(angle >= startAngle && angle <= endAngle)
|| (angle + PI2 >= startAngle && angle + PI2 <= endAngle)
) {
if (angle > Math.PI / 2 && angle < Math.PI * 1.5) {
dir = -dir;
}
w += dir;
}
}
}
return w;
}
/**
* 路径包含判断
* 与 canvas 一样采用 non-zero winding rule
*/
function isInsidePath(pathArray, lineWidth, brushType, x, y) {
var w = 0;
var xi = 0;
var yi = 0;
var x0 = 0;
var y0 = 0;
var beginSubpath = true;
var firstCmd = true;
brushType = brushType || 'fill';
var hasStroke = brushType === 'stroke' || brushType === 'both';
var hasFill = brushType === 'fill' || brushType === 'both';
// var roots = [-1, -1, -1];
for (var i = 0; i < pathArray.length; i++) {
var seg = pathArray[i];
var p = seg.points;
// Begin a new subpath
if (beginSubpath || seg.command === 'M') {
if (i > 0) {
// Close previous subpath
if (hasFill) {
w += windingLine(xi, yi, x0, y0, x, y);
}
if (w !== 0) {
return true;
}
}
x0 = p[p.length - 2];
y0 = p[p.length - 1];
beginSubpath = false;
if (firstCmd && seg.command !== 'A') {
// 如果第一个命令不是M, 是lineTo, bezierCurveTo
// 等绘制命令的话,是会从该绘制的起点开始算的
// Arc 会在之后做单独处理所以这里忽略
firstCmd = false;
xi = x0;
yi = y0;
}
}
switch (seg.command) {
case 'M':
xi = p[0];
yi = p[1];
break;
case 'L':
if (hasStroke) {
if (isInsideLine(
xi, yi, p[0], p[1], lineWidth, x, y
)) {
return true;
}
}
if (hasFill) {
w += windingLine(xi, yi, p[0], p[1], x, y);
}
xi = p[0];
yi = p[1];
break;
case 'C':
if (hasStroke) {
if (isInsideCubicStroke(
xi, yi, p[0], p[1], p[2], p[3], p[4], p[5],
lineWidth, x, y
)) {
return true;
}
}
if (hasFill) {
w += windingCubic(
xi, yi, p[0], p[1], p[2], p[3], p[4], p[5], x, y
);
}
xi = p[4];
yi = p[5];
break;
case 'Q':
if (hasStroke) {
if (isInsideQuadraticStroke(
xi, yi, p[0], p[1], p[2], p[3],
lineWidth, x, y
)) {
return true;
}
}
if (hasFill) {
w += windingQuadratic(
xi, yi, p[0], p[1], p[2], p[3], x, y
);
}
xi = p[2];
yi = p[3];
break;
case 'A':
// TODO Arc 旋转
// TODO Arc 判断的开销比较大
var cx = p[0];
var cy = p[1];
var rx = p[2];
var ry = p[3];
var theta = p[4];
var dTheta = p[5];
var x1 = Math.cos(theta) * rx + cx;
var y1 = Math.sin(theta) * ry + cy;
// 不是直接使用 arc 命令
if (!firstCmd) {
w += windingLine(xi, yi, x1, y1);
} else {
firstCmd = false;
// 第一个命令起点还未定义
x0 = x1;
y0 = y1;
}
// zr 使用scale来模拟椭圆, 这里也对x做一定的缩放
var _x = (x - cx) * ry / rx + cx;
if (hasStroke) {
if (isInsideArcStroke(
cx, cy, ry, theta, theta + dTheta, 1 - p[7],
lineWidth, _x, y
)) {
return true;
}
}
if (hasFill) {
w += windingArc(
cx, cy, ry, theta, theta + dTheta, 1 - p[7],
_x, y
);
}
xi = Math.cos(theta + dTheta) * rx + cx;
yi = Math.sin(theta + dTheta) * ry + cy;
break;
case 'z':
if (hasStroke) {
if (isInsideLine(
xi, yi, x0, y0, lineWidth, x, y
)) {
return true;
}
}
beginSubpath = true;
break;
}
}
if (hasFill) {
w += windingLine(xi, yi, x0, y0, x, y);
}
return w !== 0;
}
/**
* 测算多行文本宽度
* @param {Object} text
* @param {Object} textFont
*/
function getTextWidth(text, textFont) {
var key = text + ':' + textFont;
if (_textWidthCache[key]) {
return _textWidthCache[key];
}
_ctx = _ctx || util.getContext();
_ctx.save();
if (textFont) {
_ctx.font = textFont;
}
text = (text + '').split('\n');
var width = 0;
for (var i = 0, l = text.length; i < l; i++) {
width = Math.max(
_ctx.measureText(text[i]).width,
width
);
}
_ctx.restore();
_textWidthCache[key] = width;
if (++_textWidthCacheCounter > TEXT_CACHE_MAX) {
// 内存释放
_textWidthCacheCounter = 0;
_textWidthCache = {};
}
return width;
}
/**
* 测算多行文本高度
* @param {Object} text
* @param {Object} textFont
*/
function getTextHeight(text, textFont) {
var key = text + ':' + textFont;
if (_textHeightCache[key]) {
return _textHeightCache[key];
}
_ctx = _ctx || util.getContext();
_ctx.save();
if (textFont) {
_ctx.font = textFont;
}
text = (text + '').split('\n');
// 比较粗暴
var height = (_ctx.measureText('国').width + 2) * text.length;
_ctx.restore();
_textHeightCache[key] = height;
if (++_textHeightCacheCounter > TEXT_CACHE_MAX) {
// 内存释放
_textHeightCacheCounter = 0;
_textHeightCache = {};
}
return height;
}
return {
isInside : isInside,
isOutside : isOutside,
getTextWidth : getTextWidth,
getTextHeight : getTextHeight,
isInsidePath: isInsidePath,
isInsidePolygon: isInsidePolygon,
isInsideSector: isInsideSector,
isInsideCircle: isInsideCircle,
isInsideLine: isInsideLine,
isInsideRect: isInsideRect,
isInsidePolyline: isInsidePolyline,
isInsideCubicStroke: isInsideCubicStroke,
isInsideQuadraticStroke: isInsideQuadraticStroke
};
}
);