Skip to content

Commit

Permalink
feat: add guide-tag
Browse files Browse the repository at this point in the history
  • Loading branch information
杍鱼 committed Jan 17, 2018
1 parent a200e7b commit 6ce3674
Show file tree
Hide file tree
Showing 4 changed files with 279 additions and 1 deletion.
14 changes: 13 additions & 1 deletion src/graphic/g.js
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,13 @@ module.exports = {
ctx.arcTo(x, y + h, x, y, r);
ctx.arcTo(x, y, x + w, y, r);
},
radiusRectArray(x, y, w, h, r, assignRadius, ctx) {
ctx.moveTo(x + r, y);
ctx[assignRadius[0] ? 'arcTo' : 'lineTo'](x + w, y, x + w, y + h, r);
ctx[assignRadius[1] ? 'arcTo' : 'lineTo'](x + w, y + h, x, y + h, r);
ctx[assignRadius[2] ? 'arcTo' : 'lineTo'](x, y + h, x, y, r);
ctx[assignRadius[3] ? 'arcTo' : 'lineTo'](x, y, x + w, y, r);
},
drawRect(points, canvas, cfg) {
const ctx = before(canvas, cfg);
let minX = points[0].x;
Expand All @@ -127,7 +134,12 @@ module.exports = {
const height = maxY - minY;
if (cfg.radius) {
const radius = Math.min(cfg.radius, width / 2, height / 2);
this.radiusRect(x, y, width, height, radius, ctx);
// 指定边渲染渲染
if (cfg.assignRadius) {
this.radiusRectArray(x, y, width, height, radius, cfg.assignRadius, ctx);
} else {
this.radiusRect(x, y, width, height, radius, ctx);
}
} else {
ctx.rect(x, y, width, height);
}
Expand Down
22 changes: 22 additions & 0 deletions src/guide/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ Guide.Line = require('./line');
Guide.Arc = require('./arc');
Guide.Html = require('./html');
Guide.Rect = require('./rect');
Guide.Tag = require('./tag');

const GuideAssist = require('../chart/assist/guide');

Expand Down Expand Up @@ -117,7 +118,28 @@ Util.mix(GuideAssist.prototype, {
const guide = new Guide.Rect(config);
this.addGuide(guide);
return this;
},
/**
* 添加辅助html
* @chainable
* @param {Array} point 位置
* @param {String} text 文字内容
* @param {Object} cfg 配置项
* @return {Object} guideAssist 对象
*/
tag(point, text, cfg) {
const config = {
type: 'tag',
point,
text,
cfg: Util.mix({}, cfg)
};
Util.mix(config, this._getDefault());
const guide = new Guide.Tag(config);
this.addGuide(guide);
return this;
}

});

module.exports = Guide;
228 changes: 228 additions & 0 deletions src/guide/tag.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,228 @@
/**
* @fileOverview guide tag
* @author 杍鱼 <yancheng.cyc@antfin.com>
*/

const Guide = require('./guide');
const G = require('../graphic/g');
const Global = require('../global');
const Util = require('../util');

/**
* 文字提示Tag
* {string} direct 两个字符长度,支持八个方向朝向. 例如 tl 表示左上角tag
* direct第一个表示垂直方向 t - top, c - center, b - bottom
* direct第二个表示水平方向 l - left, c - center, r - right
* @class Guide.Tag
*/
class Tag extends Guide {
getDefaultCfg() {
return {
type: 'tag',
point: [],
top: true,
text: '',
defaultCfg: Global.guide.tag
};
}

// override paint
paint(coord, canvas) {
const self = this;
const { defaultCfg, text } = self;
const cfg = Util.mix({}, defaultCfg, self.cfg);
const point = self.parsePoint(coord, self.point);
const { padding } = cfg;
if (!cfg.font) {
const fontCfg = {};
Util.mix(fontCfg, defaultCfg, cfg);
cfg.font = [
fontCfg.fontStyle,
fontCfg.fontVariant,
fontCfg.fontWeight,
fontCfg.fontSize + 'px',
fontCfg.fontFamily
].join(' ');
}
const textSize = this.getTextSize(text, canvas, cfg.font);
const paddingArray = this.getPadding(padding);
const tagWidth = textSize.width + paddingArray[1] + paddingArray[3];
const tagHeight = textSize.height + paddingArray[0] + paddingArray[2];
// tag边界自适应计算
this.selfAdaption(point, tagWidth, tagHeight, canvas, cfg);
// 渲染tag
this.renderTag(point, text, tagWidth, tagHeight, canvas, cfg);
}

/**
* 渲染tag的主方法
* tag由三部分组成 1.rect矩形 2.三角标 3.文字内容
* @param {string} point - 标记点坐标.
* @param {string} text - 文字内容.
* @param {string} tagWidth - tag宽度,不包括三角标.
* @param {string} tagHeight - tag高度,不包括三角标..
* @param {string} canvas - 渲染的canvas.
* @param {string} cfg - canvas渲染上下文
*/
renderTag(point, text, tagWidth, tagHeight, canvas, cfg) {
const { direct, side, offsetX, offsetY } = cfg;
const x = point.x + offsetX;
const y = point.y + offsetY;

let rectPoints = [];
let sidePoints = [];
let textPosition = {};
switch (direct) {
case 'tl':
rectPoints = [{ x: x - tagWidth, y: y - tagHeight - side }, { x, y: y - side }];
sidePoints = [{ x, y: y - side }, { x: x - side, y: y - side }, { x, y }];
textPosition = { x: x - tagWidth + tagWidth / 2, y: y - tagHeight - side + tagHeight / 2 };
cfg.assignRadius = [ true, false, true, true ];
break;
case 'tc':
rectPoints = [{ x: x - tagWidth / 2, y: y - tagHeight - side }, { x: x + tagWidth / 2, y: y - side }];
sidePoints = [{ x, y }, { x: x + side, y: y - side }, { x: x - side, y: y - side }];
textPosition = { x: x - tagWidth / 2 + tagWidth / 2, y: y - tagHeight - side + tagHeight / 2 };
break;
case 'tr':
rectPoints = [{ x, y: y - tagHeight - side }, { x: x + tagWidth, y: y - side }];
sidePoints = [{ x, y }, { x, y: y - side }, { x: x + side, y: y - side }];
textPosition = { x: x + tagWidth / 2, y: y - tagHeight - side + tagHeight / 2 };
cfg.assignRadius = [ true, true, false, true ];
break;
case 'cl':
rectPoints = [{ x: x - side - tagWidth, y: y - tagHeight / 2 }, { x: x - side, y: y + tagHeight / 2 }];
sidePoints = [{ x, y }, { x: x - side, y: y + side }, { x: x - side, y: y - side }];
textPosition = { x: x - side - tagWidth + tagWidth / 2, y: y - tagHeight / 2 + tagHeight / 2 };
break;
case 'cr':
rectPoints = [{ x: x + side, y: y - tagHeight / 2 }, { x: x + side + tagWidth, y: y + tagHeight / 2 }];
sidePoints = [{ x, y }, { x: x + side, y: y + side }, { x: x + side, y: y - side }];
textPosition = { x: x + side + tagWidth / 2, y: y - tagHeight / 2 + tagHeight / 2 };
break;
case 'bl':
rectPoints = [{ x: x - tagWidth, y: y + side }, { x, y: y + side + tagHeight }];
sidePoints = [{ x, y }, { x, y: y + side }, { x: x - side, y: y + side }];
textPosition = { x: x - tagWidth + tagWidth / 2, y: y + side + tagHeight / 2 };
cfg.assignRadius = [ false, true, true, true ];
break;
case 'bc':
rectPoints = [{ x: x - tagWidth / 2, y: y + side }, { x: x + tagWidth / 2, y: y + side + tagHeight }];
sidePoints = [{ x, y }, { x: x - side, y: y + side }, { x: x + side, y: y + side }];
textPosition = { x: x - tagWidth / 2 + tagWidth / 2, y: y + side + tagHeight / 2 };
break;
case 'br':
rectPoints = [{ x, y: y + side }, { x: x + tagWidth, y: y + side + tagHeight }];
sidePoints = [{ x, y }, { x, y: y + side }, { x: x + side, y: y + side }];
textPosition = { x: x + tagWidth / 2, y: y + side + tagHeight / 2 };
cfg.assignRadius = [ true, true, true, false ];
break;
default:
break;
}
this.paintRect(rectPoints, canvas, cfg); // 矩形
this.paintSide(sidePoints, canvas, cfg); // 三角标
this.paintText(text, textPosition, canvas, cfg); // 文字内容
}

paintRect(points, canvas, cfg) {
G.drawRect(points, canvas, cfg);
}

paintText(text, position, canvas, cfg) {
cfg.textBaseline = 'middle';
cfg.textAlign = 'center';
const style = {
textBaseline: 'middle',
textAlign: 'center',
fill: cfg.color || '#ffffff'
};
G.drawText(text, position, canvas, Util.mix({}, cfg, style));
}

// 绘制三角形
paintSide(points, canvas, cfg) {
points.push(points[0]); // hack: 直接三个点三角形可能会有像素偏差
G.drawLines(points, canvas, cfg);
}

/**
* 自动计算如果tag超出画布边界改变方向
* 改变朝向再对对应的偏移对称变化
* @param {object} point - 标记点坐标.
* @param {string} tagWidth - tag宽度,不包括三角标.
* @param {string} tagHeight - tag高度,不包括三角标..
* @param {object} canvas - 渲染的canvas.
* @param {object} cfg - cfg上下文.
*/
selfAdaption(point, tagWidth, tagHeight, canvas, cfg) {
const { side, direct } = cfg;
const { clientWidth, clientHeight } = canvas; // 屏幕上canvas实际尺寸
const { x, y } = point;
let offsetX = cfg.offsetX;
let offsetY = cfg.offsetY;

let vertical = direct.charAt(0);
let horizontal = direct.charAt(1);
if (vertical === 't') {
if (y - side - tagHeight + offsetY < 0) {
vertical = 'b';
offsetY = -offsetY;
}
}
if (vertical === 'b') {
if (y + side + tagHeight + offsetY > clientHeight) {
vertical = 't';
offsetY = -offsetY;
}
}
if (horizontal === 'l') {
const diff = vertical === 'c' ? side : 0;
if (x - diff - tagWidth + offsetX < 0) {
horizontal = 'r';
offsetX = -offsetX;
}
}
if (horizontal === 'r') {
const diff = vertical === 'c' ? side : 0;
if (x + diff + tagWidth + offsetX > clientWidth) {
horizontal = 'l';
offsetX = -offsetX;
}
}
cfg.offsetX = offsetX;
cfg.offsetY = offsetY;
cfg.direct = vertical + horizontal;
}

// 获取文字宽高
getTextSize(text, canvas, font) {
const ctx = canvas.getContext('2d');
ctx.save();
ctx.font = font;
const width = parseInt(ctx.measureText(text).width);
const height = parseInt(ctx.measureText('M').width); // 经验估算: canvas没有文本高度的api, 不同字体下M的宽度约等于高度
ctx.restore();
return { width, height };
}

// 解析padding 支持类css的写法
getPadding(padding) {
let top;
let left;
let right;
let bottom;
if (Util.isNumber(padding)) {
top = bottom = padding;
left = right = padding;
} else if (Util.isArray(padding)) {
top = padding[0];
right = !Util.isNull(padding[1]) ? padding[1] : padding[0];
bottom = !Util.isNull(padding[2]) ? padding[2] : padding[0];
left = !Util.isNull(padding[3]) ? padding[3] : right;
}
return [ top, right, bottom, left ];
}
}

module.exports = Tag;
16 changes: 16 additions & 0 deletions src/theme.js
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,22 @@ const Theme = {
html: {
offset: [ 0, 0 ],
align: 'cc'
},
tag: {
direct: 'tl', // 默认的朝向
padding: [ 4, 6 ], // tag内边距.
radius: 2, // tag圆角
fill: '#1890FF', // tag颜色
stroke: null, // 无边框
offsetX: 0, // X轴偏移
offsetY: 0, // Y轴偏移
side: 4, // 三角标的边长
fontStyle: '', // 字体样式普通,斜体
fontVariant: '',
fontWeight: '',
fontSize: 14,
fontFamily: 'sans-serif',
color: '#FFFFFF'
}
}
};
Expand Down

0 comments on commit 6ce3674

Please sign in to comment.