上一章,我们已经介绍了图元基类Node,正如React构建组件库一样,我们也可以构建我们的基础图元库,以备我们后续开发使用。下面我们将简单介绍几个基本图元类。
直线
export default class Line extends Node {
constructor(canvas, style) {
super(canvas, style);
this.lineWidth = style.lineWidth || 3;
this.lineCap = style.lineCap || Line.LINE_CAP.BUTT;
this.lineDash = style.lineDash || [];
this.lineDashOffset = style.lineDashOffset || 0;
this.to = style.to || new Point(0, 0);
}
static LINE_CAP = {
BUTT: 'butt',
ROUND: 'round',
SQUARE: 'square'
}
setTo(to, y = 0) {
if (to instanceof Point) {
this.to = to;
} else {
this.to = new Point(to, y);
}
}
draw(painter) {
painter.beginPath();
painter.strokeStyle = this.color;
// 设置线框
painter.lineWidth = this.lineWidth;
// 设置线条末端样式
painter.lineCap = this.lineCap;
// 设置虚线样式
if (this.lineDash.length > 0) {
painter.setLineDash(this.lineDash);
painter.lineDashOffset = this.lineDashOffset;
}
// 设置渐变色
if (this.linearGradient.length > 0) {
const lingrad = painter.createLinearGradient(this.position.x, this.position.y, this.to.x, this.to.y);
for (let i = 0; i < this.linearGradient.length; i++) {
lingrad.addColorStop(this.linearGradient[i][0], this.linearGradient[i][1]);
}
painter.strokeStyle = lingrad;
}
painter.moveTo(this.position.x, this.position.y);
painter.translate(0, 2 * this.position.y);
painter.lineTo(this.to.x, - this.to.y);
painter.closePath();
painter.stroke();
}
}
下面我们给出React 下的使用例子
class AxisTest extends React.Component {
constructor(props) {
super(props);
this.myRef = React.createRef();
}
componentDidMount() {
this.canvas = new Canvas({
ele: this.myRef.current,
canAction: false
});
let line = new Line(this.canvas, {
color: '#89DE45',
lineDash: [12, 8],
linearGradient: [
[0, '#DEDEDE'],
[0.3, '#999999'],
[0.5, '#777777'],
[0.7, '#444444'],
[1, '#000000'],
],
lineWidth: 15,
position: new Point(0, 0),
to: new Point(this.canvas.width, this.canvas.height),
});
this.canvas.addChild(line);
this.canvas.paint();
}
render() {
return <div className="axias-chart" ref={this.myRef}/>
}
}
ReactDOM.render(
<AxisTest />,
document.getElementById('root')
)
长方形
export default class Rectangle extends Node {
static TYPE = {
STROKE: 1,
FILL: 2,
}
constructor(canvas, style) {
super(canvas, style);
this.type = style.type || Rectangle.TYPE.STROKE;
this.width = style.width || 0;
this.height = style.height || 0;
this.lineWidth = style.lineWidth || 3;
this.lineDash = style.lineDash || [];
}
setSize(width, height) {
this.width = width;
this.height = height || this.height;
}
draw(painter) {
if (this.type === Rectangle.TYPE.STROKE) {
painter.strokeStyle = this.color;
painter.lineWidth = this.lineWidth;
if (this.lineDash.length > 0) {
painter.setLineDash(this.lineDash);
}
} else {
painter.fillStyle = this.color;
}
// 设置渐变色
if (this.linearGradient.length > 0) {
const lingrad = painter.createLinearGradient(
this.position.x - this.width / 2,
this.position.y,
this.position.x + this.width / 2,
this.position.y,
);
for (let i = 0; i < this.linearGradient.length; i++) {
lingrad.addColorStop(this.linearGradient[i][0], this.linearGradient[i][1]);
}
if (this.type === Rectangle.TYPE.STROKE) {
painter.strokeStyle = lingrad;
} else {
painter.fillStyle = lingrad;
}
}
// 设置阴影
if (!!this.shadowOffsetX) {
painter.shadowOffsetX = this.shadowOffsetX;
}
if (!!this.shadowOffsetY) {
painter.shadowOffsetY = this.shadowOffsetY;
}
if (!!this.shadowBlur) {
painter.shadowBlur = this.shadowBlur;
}
if (!!this.shadowColor) {
painter.shadowColor = this.shadowColor;
}
if (this.type === Rectangle.TYPE.STROKE) {
painter.strokeRect(
this.position.x - this.width / 2,
this.position.y - this.height / 2,
this.width,
this.height
);
} else {
painter.fillRect(
this.position.x - this.width / 2,
this.position.y - this.height / 2,
this.width,
this.height
);
}
}
}
下面我们给出一个带边框的长方形的使用例子
let rectangle = new Rectangle(this.canvas, {
position: new Point(this.canvas.width / 2, this.canvas.height / 2),
width: 500,
height: 100,
type: Rectangle.TYPE.FILL,
linearGradient: [
[0, '#DEDEDE'],
[0.3, '#999999'],
[0.5, '#777777'],
[0.7, '#444444'],
[1, '#000000'],
],
});
let rectBorder = new Rectangle(this.canvas, {
position: new Point(this.canvas.width / 2, this.canvas.height / 2),
width: 500,
height: 100,
type: Rectangle.TYPE.STROKE,
color: '#2584BE',
lineDash: [20, 12],
lineWidth: 12,
});
this.canvas.addChild(rectangle, rectBorder);
this.canvas.paint();
圆形
export default class Circle extends Node {
static TYPE = {
STROKE: 1,
FILL: 2,
}
constructor(canvas, style) {
super(canvas, style);
this.type = style.type || Circle.TYPE.STROKE;
this.radius = style.radius || 30;
this.lineWidth = style.lineWidth || 3;
this.lineDash = style.lineDash || [];
}
draw(painter) {
if (this.type === Circle.TYPE.STROKE) {
painter.strokeStyle = this.color;
painter.lineWidth = this.lineWidth;
if (this.lineDash.length > 0) {
painter.setLineDash(this.lineDash);
}
} else {
painter.fillStyle = this.color;
}
// 设置渐变色
if (this.linearGradient.length > 0) {
const lingrad = painter.createRadialGradient(
this.position.x,
this.position.y,
0,
this.position.x,
this.position.y,
this.radius,
);
for (let i = 0; i < this.linearGradient.length; i++) {
lingrad.addColorStop(this.linearGradient[i][0], this.linearGradient[i][1]);
}
if (this.type === Circle.TYPE.STROKE) {
painter.strokeStyle = lingrad;
} else {
painter.fillStyle = lingrad;
}
}
// 设置阴影
if (!!this.shadowOffsetX) {
painter.shadowOffsetX = this.shadowOffsetX;
}
if (!!this.shadowOffsetY) {
painter.shadowOffsetY = this.shadowOffsetY;
}
if (!!this.shadowBlur) {
painter.shadowBlur = this.shadowBlur;
}
if (!!this.shadowColor) {
painter.shadowColor = this.shadowColor;
}
painter.beginPath();
painter.arc(
this.position.x / this.scaleX,
this.position.y / this.scaleY,
this.radius,
0,
2 * Math.PI
);
painter.closePath();
if (this.type === Circle.TYPE.STROKE) {
painter.stroke();
} else {
painter.fill();
}
}
}
下面我们给出一个带边框的圆形的使用例子
let circle = new Circle(this.canvas, {
radius: 100,
position: new Point(this.canvas.width / 2, 100),
type: Circle.TYPE.FILL,
linearGradient: [
[0, '#DEDEDE'],
[0.3, '#999999'],
[0.5, '#777777'],
[0.7, '#444444'],
[1, '#000000'],
],
});
let circleBorder = new Circle(this.canvas, {
radius: 100,
position: new Point(this.canvas.width / 2, 100),
type: Circle.TYPE.STROKE,
color: '#2584BE',
lineDash: [20, 12],
lineWidth: 12,
});
this.canvas.addChild(circle, circleBorder);
this.canvas.paint();
扇形
export default class Sector extends Node {
static TYPE = {
STROKE: 1,
FILL: 2,
}
constructor(canvas, style) {
super(canvas, style);
this.start = style.start || 0;
this.stop = style.stop || 2 * Math.PI;
this.radius = style.radius || 3;
this.type = style.type || Sector.TYPE.STROKE;
this.lineWidth = style.lineWidth || 3;
this.lineDash = style.lineDash || [];
}
setStart(_start) {
this.start = _start;
}
setStop(_stop) {
this.stop = _stop;
}
setRadius(radius) {
this.radius = radius;
}
draw(painter) {
if (this.type === Sector.TYPE.STROKE) {
painter.strokeStyle = this.color;
painter.lineWidth = this.lineWidth;
if (this.lineDash.length > 0) {
painter.setLineDash(this.lineDash);
}
} else {
painter.fillStyle = this.color;
}
// 设置渐变色
if (this.linearGradient.length > 0) {
const lingrad = painter.createRadialGradient(
this.position.x,
this.position.y,
0,
this.position.x,
this.position.y,
this.radius,
);
for (let i = 0; i < this.linearGradient.length; i++) {
lingrad.addColorStop(this.linearGradient[i][0], this.linearGradient[i][1]);
}
if (this.type === Sector.TYPE.STROKE) {
painter.strokeStyle = lingrad;
} else {
painter.fillStyle = lingrad;
}
}
// 设置阴影
if (!!this.shadowOffsetX) {
painter.shadowOffsetX = this.shadowOffsetX;
}
if (!!this.shadowOffsetY) {
painter.shadowOffsetY = this.shadowOffsetY;
}
if (!!this.shadowBlur) {
painter.shadowBlur = this.shadowBlur;
}
if (!!this.shadowColor) {
painter.shadowColor = this.shadowColor;
}
painter.beginPath();
painter.arc(
this.position.x / this.scaleX,
this.position.y / this.scaleY,
this.radius,
this.start,
this.stop
);
painter.lineTo(
this.position.x / this.scaleX,
this.position.y / this.scaleY
);
painter.closePath();
if (this.type === Sector.TYPE.STROKE) {
painter.stroke();
} else {
painter.fill();
}
}
}
下面是扇形的使用例子
let sector = new Sector(this.canvas, {
radius: 100,
position: new Point(this.canvas.width / 2, 100),
type: Sector.TYPE.FILL,
linearGradient: [
[0, '#DEDEDE'],
[0.3, '#999999'],
[0.5, '#777777'],
[0.7, '#444444'],
[1, '#000000'],
],
start: 0,
stop: Math.PI / 3
});
let sectorBorder = new Sector(this.canvas, {
radius: 100,
position: new Point(this.canvas.width / 2, 100),
type: Sector.TYPE.STROKE,
lineWidth: 10,
start: 0,
stop: Math.PI / 3
});
sector.rotation = 90;
this.canvas.addChild(sector, sectorBorder);
this.canvas.paint();
圆环
export default class Ring extends Node {
static TYPE = {
STROKE: 1,
FILL: 2,
}
constructor(canvas, style) {
super(canvas, style);
this.startAngle = style.startAngle || 0;
this.endAngle = style.endAngle || 360;
this.longRadius = style.longRadius || 10;
this.shortRadius = style.shortRadius || 5;
this.type = style.type || Ring.TYPE.STROKE;
this.lineWidth = style.lineWidth || 3;
this.lineDash = style.lineDash || [];
}
draw (painter) {
if (this.type === Ring.TYPE.STROKE) {
painter.strokeStyle = this.color;
painter.lineWidth = this.lineWidth;
if (this.lineDash.length > 0) {
painter.setLineDash(this.lineDash);
}
} else {
painter.fillStyle = this.color;
}
// 设置渐变色
if (this.linearGradient.length > 0) {
const lingrad = painter.createRadialGradient(
this.position.x,
this.position.y,
0,
this.position.x,
this.position.y,
this.radius,
);
for (let i = 0; i < this.linearGradient.length; i++) {
lingrad.addColorStop(this.linearGradient[i][0], this.linearGradient[i][1]);
}
if (this.type === Ring.TYPE.STROKE) {
painter.strokeStyle = lingrad;
} else {
painter.fillStyle = lingrad;
}
}
// 设置阴影
if (!!this.shadowOffsetX) {
painter.shadowOffsetX = this.shadowOffsetX;
}
if (!!this.shadowOffsetY) {
painter.shadowOffsetY = this.shadowOffsetY;
}
if (!!this.shadowBlur) {
painter.shadowBlur = this.shadowBlur;
}
if (!!this.shadowColor) {
painter.shadowColor = this.shadowColor;
}
painter.beginPath();
// 顺时针绘制长弧度
painter.arc(
this.position.x,
this.position.y,
this.longRadius,
this.startAngle / 180 * Math.PI,
this.endAngle / 180 * Math.PI
);
// 计算短弧线上的钟点
const sx = this.position.x + this.shortRadius * Math.cos(this.endAngle / 180 * Math.PI);
const sy = this.position.y + this.shortRadius * Math.sin(this.endAngle / 180 * Math.PI);
// 移动到短弧线上的起始点
painter.lineTo(sx, sy);
// 顺时针针绘制短弧线
painter.arc(
this.position.x,
this.position.y,
this.shortRadius,
this.endAngle / 180 * Math.PI,
this.startAngle / 180 * Math.PI,
true
);
// 计算长弧线的起始点
const lx = this.position.x + this.longRadius * Math.cos(this.startAngle / 180 * Math.PI);
const ly = this.position.y + this.longRadius * Math.sin( this.startAngle / 180 * Math.PI);
console.log(lx, ly);
painter.lineTo(lx, ly);
painter.closePath();
if (this.type === Ring.TYPE.STROKE) {
painter.stroke();
} else {
painter.fill();
}
}
}
多边形
多边形为多个坐标一次组合而成的闭合区域,其源代码如下:
export default class Polygon extends Node {
static TYPE = {
STROKE: 1,
FILL: 2,
}
constructor(canvas, style, points = []) {
super(canvas, style);
this.type = style.type || Polygon.TYPE.STROKE;
this.points = points || [];
this.lineWidth = style.lineWidth || 3;
this.lineDash = style.lineDash || [];
}
draw(painter) {
if (this.points.length === 0) {
return;
}
if (this.type === Polygon.TYPE.STROKE) {
painter.strokeStyle = this.color;
painter.lineWidth = this.lineWidth;
if (this.lineDash.length > 0) {
painter.setLineDash(this.lineDash);
}
} else {
painter.fillStyle = this.color;
}
// 设置阴影
if (!!this.shadowOffsetX) {
painter.shadowOffsetX = this.shadowOffsetX;
}
if (!!this.shadowOffsetY) {
painter.shadowOffsetY = this.shadowOffsetY;
}
if (!!this.shadowBlur) {
painter.shadowBlur = this.shadowBlur;
}
if (!!this.shadowColor) {
painter.shadowColor = this.shadowColor;
}
painter.beginPath();
painter.moveTo(this.points[0].x, -this.points[0].y);
// 遍历所有的点绘制多边形
for (let i = 1; i < this.points.length; i++) {
painter.lineTo(this.points[i].x, -this.points[i].y);
}
painter.closePath();
if (this.type === Polygon.TYPE.STROKE) {
painter.stroke();
} else {
painter.fill();
}
}
}
文本
export default class Text extends Node{
constructor(canvas, style) {
super(canvas, style);
this.text = style.text || '';
this.font = style.font;
this.size = style.size;
this.color = new Color(style.color || '#000000FF');
}
get width() {
// 通过measureText获取该文本的长度
this.canvas.painter.save();
this.canvas.painter.font = `${this.size}px ${this.font}`;
const width = this.canvas.painter.measureText(this.text).width;
this.canvas.painter.restore();
return width;
}
get height() {
return this.size;
}
draw(painter) {
painter.font = `${this.size}px ${this.font}`;
painter.fillStyle = this.color.getColor();
painter.fillText(this.text, this.position.x / this.scaleX, this.position.y / this.scaleY);
}
}
折线
折线是由多个坐标点组成的曲线,不同于Line由两个点组成。源代码如下:
export default class MultiLine extends Node {
static LINE_CAP = {
BUTT: 'butt',
ROUND: 'round',
SQUARE: 'square'
}
constructor(canvas, style, points = []) {
super(canvas, style)
this.points = points;
this.lineCap = style.lineCap || MultiLine.LINE_CAP.BUTT;
this.lineDash = style.lineDash || [];
this.lineDashOffset = style.lineDashOffset || 0;
}
draw(painter) {
if (this.points.length === 0) {
return;
}
painter.beginPath();
// 设置线框
painter.lineWidth = this.lineWidth;
painter.strokeStyle = this.color;
// 设置线条末端样式
painter.lineCap = this.lineCap;
// 设置虚线样式
if (this.lineDash.length > 0) {
painter.setLineDash(this.lineDash);
painter.lineDashOffset = this.lineDashOffset;
}
// 设置渐变色
if (this.linearGradient.length > 0) {
const lingrad = painter.createLinearGradient(this.position.x, this.position.y, this.to.x, this.to.y);
for (let i = 0; i < this.linearGradient.length; i++) {
lingrad.addColorStop(this.linearGradient[i][0], this.linearGradient[i][1]);
}
painter.strokeStyle = lingrad;
}
painter.moveTo(this.position.x, this.position.y);
// 遍历各点绘制线段
painter.translate(0, 2 * this.position.y);
for (let i = 0; i < this.points.length; i++) {
painter.lineTo(this.points[i].x, - this.points[i].y);
}
painter.stroke();
}
}
目录
【实现自己的可视化引擎01】认识Canvas
【实现自己的可视化框架引擎02】抽象图像元素
【实现自己的可视化引擎03】构建基础图元库
【实现自己的可视化引擎04】图像元素动画
【实现自己的可视化引擎05】交互与事件
【实现自己的可视化引擎06】折线图
【实现自己的可视化引擎07】柱状图
【实现自己的可视化引擎08】条形图
【实现自己的可视化引擎09】饼图
【实现自己的可视化引擎10】散点图
【实现自己的可视化引擎11】雷达图
【实现自己的可视化引擎12】K线图
【实现自己的可视化引擎13】仪表盘
【实现自己的可视化引擎14】地图
【实现自己的可视化引擎15】关系图