F2 是蚂蚁金服开源的一个专注于移动的可视化图表库,特点为专注移动,体验优雅,图表丰富,组件完备。但是因其目前只适配了H5,微信小程序和支付宝小程序,飞书小程序还未能很好的适配,故如果想在飞书小程序上使用需自行进行适配。
飞书小程序中Canvas API与原生的差异
F2 默认基于 Html5 Canvas 进行图表绘制,但是对于当前运行时环境来说,只要能够提供和 Html5 Canvas 上下文环境一样的上下文接口,一样也能使用 F2 进行图表绘制,我们先对照文档对接口进行整理。
下表为整理并测试后的接口对照,绿色表示已支持,黄色表示未支持(虽然飞书文档中描述为支持,但是实际并未实现)。
功能 | HTML5 | 飞书小程序 |
绘制一个填充的矩形 | fillRect(x, y, width, height) | |
绘制一个矩形的边框 | strokeRect(x, y, width, height) | |
清除指定矩形区域,让清除部分完全透明 | clearRect(x, y, width, height) | |
新建一条路径,生成之后,图形绘制命令被指向到路径上生成路径 | beginPath() | |
闭合路径之后图形绘制命令又重新指向到上下文中 | closePath() | |
通过线条来绘制图形轮廓 | stroke() | |
通过填充路径的内容区域生成实心的图形 | fill() | |
将笔触移动到指定的坐标x以及y上 | moveTo(x, y) | |
绘制一条从当前位置到指定x以及y位置的直线 | lineTo(x, y) | |
画一个以(x,y)为圆心的以radius为半径的圆弧(圆),从startAngle开始到endAngle结束,按照anticlockwise给定的方向(默认为顺时针)来生成 | arc(x, y, radius, startAngle, endAngle, anticlockwise) | |
根据给定的控制点和半径画一段圆弧,再以直线连接两个控制点 | arcTo(x1, y1, x2, y2, radius) | |
绘制二次贝塞尔曲线,cp1x,cp1y为一个控制点,x,y为结束点 | quadraticCurveTo(cp1x, cp1y, x, y) | |
绘制三次贝塞尔曲线,cp1x,cp1y为控制点一,cp2x,cp2y为控制点二,x,y为结束点 | bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y) | |
绘制一个左上角坐标为(x,y),宽高为width以及height的矩形 | rect(x, y, width, height) | |
设置图形的填充颜色 | fillStyle = color | |
设置图形轮廓的颜色 | strokeStyle = color | |
这个属性影响到 canvas 里所有图形的透明度,有效的值范围是 0.0 (完全透明)到 1.0(完全不透明),默认是 1.0 | globalAlpha = transparencyValue | |
设置线条宽度 | lineWidth = value | |
设置线条末端样式 | lineCap = type | |
设定线条与线条间接合处的样式 | lineJoin = type | |
限制当两条线相交时交接处最大长度;所谓交接处长度(斜接长度)是指线条交接处内角顶点到外角顶点的长度 | miterLimit = value | |
返回一个包含当前虚线样式,长度为非负偶数的数组 | getLineDash() | |
设置当前虚线样式 | setLineDash(segments) | |
设置虚线样式的起始偏移量 | lineDashOffset = value | |
我们可以用线性或者径向的渐变来填充或描边。我们用下面的方法新建一个 canvasGradient 对象,并且赋给图形的 fillStyle 或 strokeStyle 属性。 | createLinearGradient(x1, y1, x2, y2) createRadialGradient(x1, y1, r1, x2, y2, r2) gradient.addColorStop(position, color) | |
该方法接受两个参数。Image 可以是一个 Image 对象的引用,或者另一个 canvas 对象。Type 必须是下面的字符串值之一:repeat,repeat-x,repeat-y 和 no-repeat | createPattern(image, type) | |
shadowOffsetX 和 shadowOffsetY 用来设定阴影在 X 和 Y 轴的延伸距离,它们是不受变换矩阵所影响的。负值表示阴影会往上或左延伸,正值则表示会往下或右延伸,它们默认都为 0 | shadowOffsetX = float | |
shadowOffsetX 和 shadowOffsetY 用来设定阴影在 X 和 Y 轴的延伸距离,它们是不受变换矩阵所影响的。负值表示阴影会往上或左延伸,正值则表示会往下或右延伸,它们默认都为 0 | shadowOffsetY = float | |
shadowBlur 用于设定阴影的模糊程度,其数值并不跟像素数量挂钩,也不受变换矩阵的影响,默认为 0 | shadowBlur = float | |
shadowColor 是标准的 CSS 颜色值,用于设定阴影颜色效果,默认是全透明的黑色 | shadowColor = color | |
在指定的(x,y)位置填充指定的文本,绘制的最大宽度是可选的 | fillText(text, x, y [, maxWidth]) | |
在指定的(x,y)位置绘制文本边框,绘制的最大宽度是可选的 | strokeText(text, x, y [, maxWidth]) | |
当前我们用来绘制文本的样式. 这个字符串使用和 CSS font 属性相同的语法. 默认的字体是 10px sans-serif | font = value | |
文本对齐选项. 可选的值包括:start, end, left, right or center. 默认值是 start | textAlign = value | |
基线对齐选项. 可选的值包括:top, hanging, middle, alphabetic, ideographic, bottom。默认值是 alphabetic | textBaseline = value | |
文本方向。可能的值包括:ltr, rtl, inherit。默认值是 inherit | direction = value | |
将返回一个 TextMetrics对象的宽度、所在像素,这些体现文本特性的属性 | measureText() | |
其中 image 是 image 或者 canvas 对象,x 和 y 是其在目标 canvas 里的起始坐标 | drawImage(image, x, y) | |
这个方法多了2个参数:width 和 height,这两个参数用来控制 当向canvas画入时应该缩放的大小 | drawImage(image, x, y, width, height) | |
第一个参数和其它的是相同的,都是一个图像或者另一个 canvas 的引用。其它8个参数最好是参照右边的图解,前4个是定义图像源的切片位置和大小,后4个则是定义切片的目标显示位置和大小。 | drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight) | |
保存画布(canvas)的所有状态 | save() | |
save 和 restore 方法是用来保存和恢复 canvas 状态的,都没有参数。Canvas 的状态就是当前画面应用的所有样式和变形的一个快照 | restore() | |
translate 方法接受两个参数。x 是左右偏移量,y 是上下偏移量,如右图所示 | translate(x, y) | |
这个方法只接受一个参数:旋转的角度(angle),它是顺时针方向的,以弧度为单位的值 | rotate(angle) | |
scale 方法可以缩放画布的水平和垂直的单位。两个参数都是实数,可以为负数,x 为水平缩放因子,y 为垂直缩放因子,如果比1小,会比缩放图形, 如果比1大会放大图形。默认值为1, 为实际大小 | scale(x, y) | |
这个方法是将当前的变形矩阵乘上一个基于自身参数的矩阵 | transform(m11, m12, m21, m22, dx, dy) | |
这个方法会将当前的变形矩阵重置为单位矩阵,然后用相同的参数调用 transform 方法。如果任意一个参数是无限大,那么变形矩阵也必须被标记为无限大,否则会抛出异常。从根本上来说,该方法是取消了当前变形,然后设置为指定的变形,一步完成 | setTransform(m11, m12, m21, m22, dx, dy) | |
重置当前变形为单位矩阵,它和调用以下语句是一样的 ctx.setTransform(1, 0, 0, 1, 0, 0) | resetTransform() | |
将当前正在构建的路径转换为当前的裁剪路径 | clip() |
已支持属性或方法:
console.log('fillRect', context.fillRect);
console.log('strokeRect', context.strokeRect);
console.log('clearRect', context.clearRect);
console.log('beginPath', context.beginPath);
console.log('closePath', context.closePath);
console.log('stroke', context.stroke);
console.log('fill', context.fill);
console.log('moveTo', context.moveTo);
console.log('lineTo', context.lineTo);
console.log('arc', context.arc);
console.log('arcTo', context.arcTo);
console.log('quadraticCurveTo', context.quadraticCurveTo);
console.log('bezierCurveTo', context.bezierCurveTo);
console.log('rect', context.rect);
console.log('drawImage', context.drawImage);
console.log('save', context.save);
console.log('restore', context.restore);
console.log('translate', context.translate);
console.log('rotate', context.rotate);
console.log('scale', context.scale);
console.log('transform', context.transform);
console.log('setTransform', context.setTransform);
console.log('resetTransform', context.resetTransform);
console.log('clip', context.clip);
console.log('font', context.font);
未支持属性或方法:
console.log('fillStyle', context.fillStyle);
console.log('strokeStyle', context.strokeStyle);
console.log('globalAlpha', context.globalAlpha);
console.log('lineWidth', context.lineWidth);
console.log('lineCap', context.lineCap);
console.log('lineJoin', context.lineJoin);
console.log('miterLimit', context.miterLimit);
console.log('lineDashOffset', context.lineDashOffset);
console.log('createRadialGradient', context.createRadialGradient);
console.log('createPattern', context.createPattern);
console.log('shadowOffsetX', context.shadowOffsetX);
console.log('shadowOffsetY', context.shadowOffsetY);
console.log('shadowBlur', context.shadowBlur);
console.log('shadowColor', context.shadowColor);
console.log('fillText', context.fillText);
console.log('strokeText', context.strokeText);
console.log('textAlign', context.textAlign);
console.log('textBaseline', context.textBaseline);
console.log('direction', context.direction);
通过测试发现,以上为支持的接口飞书中其实已有实现,只是方法不一样:
console.log('setFillStyle', context.setFillStyle);
console.log('setStrokeStyle', context.setStrokeStyle);
console.log('setGlobalAlpha', context.setGlobalAlpha);
console.log('setLineWidth', context.setLineWidth);
console.log('setLineCap', context.setLineCap);
console.log('setLineJoin', context.setLineJoin);
console.log('setMiterLimit', context.setMiterLimit);
console.log('setTextAlign', context.setTextAlign);
console.log('setTextBaseline', context.setTextBaseline);
console.log('setFontSize', context.setFontSize);
console.log('setShadow', context.setShadow);
飞书小程序适配
对于Canvas属性上的差异,我们可以使用 Object.defineProperty(obj, prop, descriptor) 为相关的属性提供 setter 的方法,代码如下:
// libs\f2\context.js 飞书小程序的context适配
const CAPITALIZED_ATTRS_MAP = {
fillStyle: 'FillStyle',
fontSize: 'FontSize',
globalAlpha: 'GlobalAlpha',
opacity: 'GlobalAlpha',
lineCap: 'LineCap',
lineJoin: 'LineJoin',
lineWidth: 'LineWidth',
miterLimit: 'MiterLimit',
strokeStyle: 'StrokeStyle',
textAlign: 'TextAlign',
textBaseline: 'TextBaseline',
shadow: 'Shadow',
font: 'Font'
};
export default ctx => {
Object.keys(CAPITALIZED_ATTRS_MAP).map(key => {
Object.defineProperty(ctx, key, {
set(value) {
// 记录最新设置的值
ctx[`__${key}`] = value;
if (key === 'shadow' && ctx.setShadow && Array.isArray(value)) {
ctx.setShadow(value[0], value[1], value[2], value[3]);
return;
}
const name = 'set' + CAPITALIZED_ATTRS_MAP[key];
if (!ctx[name]) {
return;
}
ctx[name](value);
}
});
return key;
});
return ctx;
};
然后我们就可以使用了
<!-- pages/index/index.html -->
<view class="chart-wrapper">
<canvas canvas-id="chart" id="chart" class="chart"></canvas>
</view>
.chart-wrapper {
width: 638rpx;
height: 469rpx;
margin: 0 auto;
}
.chart {
width: 638rpx;
height: 469rpx;
}
// pages/index/index.js
// f2-all.js用的是这里的 node_modules\@antv\f2\build\f2-all.js
import F2 from '../../libs/f2/f2-all.js';
import F2Context from '../../libs/f2/context.js';
Page({
onLoad() {
this.createChart();
},
createChart() {
tt.createSelectorQuery()
.select(`#chart`) // id
.boundingClientRect((rect) => {
const data = [
{ genre: 'Sports', sold: 275 },
{ genre: 'Strategy', sold: 115 },
{ genre: 'Action', sold: 120 },
{ genre: 'Shooter', sold: 350 },
{ genre: 'Other', sold: 150 }
];
const { width, height } = rect;
const { pixelRatio } = tt.getSystemInfoSync();
const context = F2Context(tt.createCanvasContext(`chart`)); // canvas-id
const config = { context, width, height, pixelRatio };
this.chart = new F2.Chart(config);
this.chart.source(data);
this.chart.interval()
.position('genre*sold')
.color('genre');
this.chart.render();
}).exec()
}
});
这里会有报错,因为小程序里的颜色值可能不支持简写
我们把f2-all.js中对应的值补全,#FFF替换为#FFFFFF,#000替换为#000000,#999替换为#999999 适配后效果:
最后,为图表加上事件监听
<!-- pages/index/index.html -->
<view class="chart-wrapper">
<canvas canvas-id="chart" id="chart" class="chart" bindtouchstart="touchStart" bindtouchmove="touchMove"></canvas>
</view>
// pages/index/index.js
...
touchStart(e) {
if (this.chart) {
this.chart.get('el').dispatchEvent('touchstart', e);
}
},
touchMove(e) {
if (this.chart) {
this.chart.get('el').dispatchEvent('touchmove', e);
}
},
至此,F2在小程序也就可以完美使用了