| Current Path : /home/cbequiperp/www/components/com_flexicontent/librairies/zrender/shape/ |
| Current File : /home/cbequiperp/www/components/com_flexicontent/librairies/zrender/shape/Base.js |
/**
* shape基类
* @module zrender/shape/Base
* @author Kener (@Kener-林峰, kener.linfeng@gmail.com)
* errorrik (errorrik@gmail.com)
*/
/**
* @typedef {Object} IBaseShapeStyle
* @property {string} [brushType='fill']
* @property {string} [color='#000000'] 填充颜色
* @property {string} [strokeColor='#000000'] 描边颜色
* @property {string} [lineCape='butt'] 线帽样式,可以是 butt, round, square
* @property {number} [lineWidth=1] 描边宽度
* @property {number} [opacity=1] 绘制透明度
* @property {number} [shadowBlur=0] 阴影模糊度,大于0有效
* @property {string} [shadowColor='#000000'] 阴影颜色
* @property {number} [shadowOffsetX=0] 阴影横向偏移
* @property {number} [shadowOffsetY=0] 阴影纵向偏移
* @property {string} [text] 图形中的附加文本
* @property {string} [textColor='#000000'] 文本颜色
* @property {string} [textFont] 附加文本样式,eg:'bold 18px verdana'
* @property {string} [textPosition='end'] 附加文本位置, 可以是 inside, left, right, top, bottom
* @property {string} [textAlign] 默认根据textPosition自动设置,附加文本水平对齐。
* 可以是start, end, left, right, center
* @property {string} [textBaseline] 默认根据textPosition自动设置,附加文本垂直对齐。
* 可以是top, bottom, middle, alphabetic, hanging, ideographic
*/
/**
* @typedef {Object} module:zrender/shape/Base~IBoundingRect
* @property {number} x 左上角顶点x轴坐标
* @property {number} y 左上角顶点y轴坐标
* @property {number} width 包围盒矩形宽度
* @property {number} height 包围盒矩形高度
*/
define(
function(require) {
var vmlCanvasManager = window['G_vmlCanvasManager'];
var matrix = require('../tool/matrix');
var guid = require('../tool/guid');
var util = require('../tool/util');
var log = require('../tool/log');
var Transformable = require('../mixin/Transformable');
var Eventful = require('../mixin/Eventful');
function _fillText(ctx, text, x, y, textFont, textAlign, textBaseline) {
if (textFont) {
ctx.font = textFont;
}
ctx.textAlign = textAlign;
ctx.textBaseline = textBaseline;
var rect = _getTextRect(
text, x, y, textFont, textAlign, textBaseline
);
text = (text + '').split('\n');
var lineHeight = require('../tool/area').getTextHeight('国', textFont);
switch (textBaseline) {
case 'top':
y = rect.y;
break;
case 'bottom':
y = rect.y + lineHeight;
break;
default:
y = rect.y + lineHeight / 2;
}
for (var i = 0, l = text.length; i < l; i++) {
ctx.fillText(text[i], x, y);
y += lineHeight;
}
}
/**
* 返回矩形区域,用于局部刷新和文字定位
* @inner
* @param {string} text
* @param {number} x
* @param {number} y
* @param {string} textFont
* @param {string} textAlign
* @param {string} textBaseline
*/
function _getTextRect(text, x, y, textFont, textAlign, textBaseline) {
var area = require('../tool/area');
var width = area.getTextWidth(text, textFont);
var lineHeight = area.getTextHeight('国', textFont);
text = (text + '').split('\n');
switch (textAlign) {
case 'end':
case 'right':
x -= width;
break;
case 'center':
x -= (width / 2);
break;
}
switch (textBaseline) {
case 'top':
break;
case 'bottom':
y -= lineHeight * text.length;
break;
default:
y -= lineHeight * text.length / 2;
}
return {
x : x,
y : y,
width : width,
height : lineHeight * text.length
};
}
/**
* @alias module:zrender/shape/Base
* @constructor
* @extends module:zrender/mixin/Transformable
* @extends module:zrender/mixin/Eventful
* @param {Object} options 关于shape的配置项,可以是shape的自有属性,也可以是自定义的属性。
*/
var Base = function(options) {
options = options || {};
/**
* Shape id, 全局唯一
* @type {string}
*/
this.id = options.id || guid();
for (var key in options) {
this[key] = options[key];
}
/**
* 基础绘制样式
* @type {module:zrender/shape/Base~IBaseShapeStyle}
*/
this.style = this.style || {};
/**
* 高亮样式
* @type {module:zrender/shape/Base~IBaseShapeStyle}
*/
this.highlightStyle = this.highlightStyle || null;
/**
* 父节点
* @readonly
* @type {module:zrender/Group}
* @default null
*/
this.parent = null;
this.__dirty = true;
this.__clipShapes = [];
Transformable.call(this);
Eventful.call(this);
};
/**
* 图形是否可见,为true时不绘制图形,但是仍能触发鼠标事件
* @name module:zrender/shape/Base#invisible
* @type {boolean}
* @default false
*/
Base.prototype.invisible = false;
/**
* 图形是否忽略,为true时忽略图形的绘制以及事件触发
* @name module:zrender/shape/Base#ignore
* @type {boolean}
* @default false
*/
Base.prototype.ignore = false;
/**
* z层level,决定绘画在哪层canvas中
* @name module:zrender/shape/Base#zlevel
* @type {number}
* @default 0
*/
Base.prototype.zlevel = 0;
/**
* 是否可拖拽
* @name module:zrender/shape/Base#draggable
* @type {boolean}
* @default false
*/
Base.prototype.draggable = false;
/**
* 是否可点击
* @name module:zrender/shape/Base#clickable
* @type {boolean}
* @default false
*/
Base.prototype.clickable = false;
/**
* 是否可以hover
* @name module:zrender/shape/Base#hoverable
* @type {boolean}
* @default true
*/
Base.prototype.hoverable = true;
/**
* z值,跟zlevel一样影响shape绘制的前后顺序,z值大的shape会覆盖在z值小的上面,
* 但是并不会创建新的canvas,所以优先级低于zlevel,而且频繁改动的开销比zlevel小很多。
*
* @name module:zrender/shape/Base#z
* @type {number}
* @default 0
*/
Base.prototype.z = 0;
/**
* 绘制图形
*
* @param {CanvasRenderingContext2D} ctx
* @param {boolean} [isHighlight=false] 是否使用高亮属性
* @param {Function} [updateCallback]
* 需要异步加载资源的shape可以通过这个callback(e),
* 让painter更新视图,base.brush没用,需要的话重载brush
*/
Base.prototype.brush = function (ctx, isHighlight) {
var style = this.beforeBrush(ctx, isHighlight);
ctx.beginPath();
this.buildPath(ctx, style);
switch (style.brushType) {
/* jshint ignore:start */
case 'both':
ctx.fill();
case 'stroke':
style.lineWidth > 0 && ctx.stroke();
break;
/* jshint ignore:end */
default:
ctx.fill();
}
this.drawText(ctx, style, this.style);
this.afterBrush(ctx);
};
/**
* 具体绘制操作前的一些公共操作
* @param {CanvasRenderingContext2D} ctx
* @param {boolean} [isHighlight=false] 是否使用高亮属性
* @return {Object} 处理后的样式
*/
Base.prototype.beforeBrush = function (ctx, isHighlight) {
var style = this.style;
if (this.brushTypeOnly) {
style.brushType = this.brushTypeOnly;
}
if (isHighlight) {
// 根据style扩展默认高亮样式
style = this.getHighlightStyle(
style,
this.highlightStyle || {},
this.brushTypeOnly
);
}
if (this.brushTypeOnly == 'stroke') {
style.strokeColor = style.strokeColor || style.color;
}
ctx.save();
this.doClip(ctx);
this.setContext(ctx, style);
// 设置transform
this.setTransform(ctx);
return style;
};
/**
* 绘制后的处理
* @param {CanvasRenderingContext2D} ctx
*/
Base.prototype.afterBrush = function (ctx) {
ctx.restore();
};
var STYLE_CTX_MAP = [
[ 'color', 'fillStyle' ],
[ 'strokeColor', 'strokeStyle' ],
[ 'opacity', 'globalAlpha' ],
[ 'lineCap', 'lineCap' ],
[ 'lineJoin', 'lineJoin' ],
[ 'miterLimit', 'miterLimit' ],
[ 'lineWidth', 'lineWidth' ],
[ 'shadowBlur', 'shadowBlur' ],
[ 'shadowColor', 'shadowColor' ],
[ 'shadowOffsetX', 'shadowOffsetX' ],
[ 'shadowOffsetY', 'shadowOffsetY' ]
];
/**
* 设置 fillStyle, strokeStyle, shadow 等通用绘制样式
* @param {CanvasRenderingContext2D} ctx
* @param {module:zrender/shape/Base~IBaseShapeStyle} style
*/
Base.prototype.setContext = function (ctx, style) {
for (var i = 0, len = STYLE_CTX_MAP.length; i < len; i++) {
var styleProp = STYLE_CTX_MAP[i][0];
var styleValue = style[styleProp];
var ctxProp = STYLE_CTX_MAP[i][1];
if (typeof styleValue != 'undefined') {
ctx[ctxProp] = styleValue;
}
}
};
var clipShapeInvTransform = matrix.create();
Base.prototype.doClip = function (ctx) {
if (this.__clipShapes && !vmlCanvasManager) {
for (var i = 0; i < this.__clipShapes.length; i++) {
var clipShape = this.__clipShapes[i];
if (clipShape.needTransform) {
var m = clipShape.transform;
matrix.invert(clipShapeInvTransform, m);
ctx.transform(
m[0], m[1],
m[2], m[3],
m[4], m[5]
);
}
ctx.beginPath();
clipShape.buildPath(ctx, clipShape.style);
ctx.clip();
// Transform back
if (clipShape.needTransform) {
var m = clipShapeInvTransform;
ctx.transform(
m[0], m[1],
m[2], m[3],
m[4], m[5]
);
}
}
}
};
/**
* 根据默认样式扩展高亮样式
*
* @param {module:zrender/shape/Base~IBaseShapeStyle} style 默认样式
* @param {module:zrender/shape/Base~IBaseShapeStyle} highlightStyle 高亮样式
* @param {string} brushTypeOnly
*/
Base.prototype.getHighlightStyle = function (style, highlightStyle, brushTypeOnly) {
var newStyle = {};
for (var k in style) {
newStyle[k] = style[k];
}
var color = require('../tool/color');
var highlightColor = color.getHighlightColor();
// 根据highlightStyle扩展
if (style.brushType != 'stroke') {
// 带填充则用高亮色加粗边线
newStyle.strokeColor = highlightColor;
newStyle.lineWidth = (style.lineWidth || 1)
+ this.getHighlightZoom();
newStyle.brushType = 'both';
}
else {
if (brushTypeOnly != 'stroke') {
// 描边型的则用原色加工高亮
newStyle.strokeColor = highlightColor;
newStyle.lineWidth = (style.lineWidth || 1)
+ this.getHighlightZoom();
}
else {
// 线型的则用原色加工高亮
newStyle.strokeColor = highlightStyle.strokeColor
|| color.mix(
style.strokeColor,
color.toRGB(highlightColor)
);
}
}
// 可自定义覆盖默认值
for (var k in highlightStyle) {
if (typeof highlightStyle[k] != 'undefined') {
newStyle[k] = highlightStyle[k];
}
}
return newStyle;
};
// 高亮放大效果参数
// 当前统一设置为6,如有需要差异设置,通过this.type判断实例类型
Base.prototype.getHighlightZoom = function () {
return this.type != 'text' ? 6 : 2;
};
/**
* 移动位置
* @param {number} dx 横坐标变化
* @param {number} dy 纵坐标变化
*/
Base.prototype.drift = function (dx, dy) {
this.position[0] += dx;
this.position[1] += dy;
};
/**
* 变换鼠标位置到 shape 的局部坐标空间
* @method
* @param {number} x
* @param {number} y
* @return {Array.<number>}
*/
Base.prototype.getTansform = (function() {
var invTransform = [];
return function (x, y) {
var originPos = [ x, y ];
// 对鼠标的坐标也做相同的变换
if (this.needTransform && this.transform) {
matrix.invert(invTransform, this.transform);
matrix.mulVector(originPos, invTransform, [ x, y, 1 ]);
if (x == originPos[0] && y == originPos[1]) {
// 避免外部修改导致的needTransform不准确
this.updateNeedTransform();
}
}
return originPos;
};
})();
/**
* 构建绘制的Path
* @param {CanvasRenderingContext2D} ctx
* @param {module:zrender/shape/Base~IBaseShapeStyle} style
*/
Base.prototype.buildPath = function (ctx, style) {
log('buildPath not implemented in ' + this.type);
};
/**
* 计算返回包围盒矩形
* @param {module:zrender/shape/Base~IBaseShapeStyle} style
* @return {module:zrender/shape/Base~IBoundingRect}
*/
Base.prototype.getRect = function (style) {
log('getRect not implemented in ' + this.type);
};
/**
* 判断鼠标位置是否在图形内
* @param {number} x
* @param {number} y
* @return {boolean}
*/
Base.prototype.isCover = function (x, y) {
var originPos = this.getTansform(x, y);
x = originPos[0];
y = originPos[1];
// 快速预判并保留判断矩形
var rect = this.style.__rect;
if (!rect) {
rect = this.style.__rect = this.getRect(this.style);
}
if (x >= rect.x
&& x <= (rect.x + rect.width)
&& y >= rect.y
&& y <= (rect.y + rect.height)
) {
// 矩形内
return require('../tool/area').isInside(this, this.style, x, y);
}
return false;
};
/**
* 绘制附加文本
* @param {CanvasRenderingContext2D} ctx
* @param {module:zrender/shape/Base~IBaseShapeStyle} style 样式
* @param {module:zrender/shape/Base~IBaseShapeStyle} normalStyle 默认样式,用于定位文字显示
*/
Base.prototype.drawText = function (ctx, style, normalStyle) {
if (typeof(style.text) == 'undefined' || style.text === false) {
return;
}
// 字体颜色策略
var textColor = style.textColor || style.color || style.strokeColor;
ctx.fillStyle = textColor;
// 文本与图形间空白间隙
var dd = 10;
var al; // 文本水平对齐
var bl; // 文本垂直对齐
var tx; // 文本横坐标
var ty; // 文本纵坐标
var textPosition = style.textPosition // 用户定义
|| this.textPosition // shape默认
|| 'top'; // 全局默认
switch (textPosition) {
case 'inside':
case 'top':
case 'bottom':
case 'left':
case 'right':
if (this.getRect) {
var rect = (normalStyle || style).__rect
|| this.getRect(normalStyle || style);
switch (textPosition) {
case 'inside':
tx = rect.x + rect.width / 2;
ty = rect.y + rect.height / 2;
al = 'center';
bl = 'middle';
if (style.brushType != 'stroke'
&& textColor == style.color
) {
ctx.fillStyle = '#fff';
}
break;
case 'left':
tx = rect.x - dd;
ty = rect.y + rect.height / 2;
al = 'end';
bl = 'middle';
break;
case 'right':
tx = rect.x + rect.width + dd;
ty = rect.y + rect.height / 2;
al = 'start';
bl = 'middle';
break;
case 'top':
tx = rect.x + rect.width / 2;
ty = rect.y - dd;
al = 'center';
bl = 'bottom';
break;
case 'bottom':
tx = rect.x + rect.width / 2;
ty = rect.y + rect.height + dd;
al = 'center';
bl = 'top';
break;
}
}
break;
case 'start':
case 'end':
var pointList = style.pointList
|| [
[style.xStart || 0, style.yStart || 0],
[style.xEnd || 0, style.yEnd || 0]
];
var length = pointList.length;
if (length < 2) {
// 少于2个点就不画了~
return;
}
var xStart;
var xEnd;
var yStart;
var yEnd;
switch (textPosition) {
case 'start':
xStart = pointList[1][0];
xEnd = pointList[0][0];
yStart = pointList[1][1];
yEnd = pointList[0][1];
break;
case 'end':
xStart = pointList[length - 2][0];
xEnd = pointList[length - 1][0];
yStart = pointList[length - 2][1];
yEnd = pointList[length - 1][1];
break;
}
tx = xEnd;
ty = yEnd;
var angle = Math.atan((yStart - yEnd) / (xEnd - xStart)) / Math.PI * 180;
if ((xEnd - xStart) < 0) {
angle += 180;
}
else if ((yStart - yEnd) < 0) {
angle += 360;
}
dd = 5;
if (angle >= 30 && angle <= 150) {
al = 'center';
bl = 'bottom';
ty -= dd;
}
else if (angle > 150 && angle < 210) {
al = 'right';
bl = 'middle';
tx -= dd;
}
else if (angle >= 210 && angle <= 330) {
al = 'center';
bl = 'top';
ty += dd;
}
else {
al = 'left';
bl = 'middle';
tx += dd;
}
break;
case 'specific':
tx = style.textX || 0;
ty = style.textY || 0;
al = 'start';
bl = 'middle';
break;
}
if (tx != null && ty != null) {
_fillText(
ctx,
style.text,
tx, ty,
style.textFont,
style.textAlign || al,
style.textBaseline || bl
);
}
};
Base.prototype.modSelf = function() {
this.__dirty = true;
if (this.style) {
this.style.__rect = null;
}
if (this.highlightStyle) {
this.highlightStyle.__rect = null;
}
};
/**
* 图形是否会触发事件
* @return {boolean}
*/
// TODO, 通过 bind 绑定的事件
Base.prototype.isSilent = function () {
return !(
this.hoverable || this.draggable || this.clickable
|| this.onmousemove || this.onmouseover || this.onmouseout
|| this.onmousedown || this.onmouseup || this.onclick
|| this.ondragenter || this.ondragover || this.ondragleave
|| this.ondrop
);
};
util.merge(Base.prototype, Transformable.prototype, true);
util.merge(Base.prototype, Eventful.prototype, true);
return Base;
}
);