【实现自己的可视化引擎01】认识Canvas
【实现自己的可视化框架引擎02】抽象图像元素
【实现自己的可视化引擎03】构建基础图元库
【实现自己的可视化引擎04】图像元素动画
【实现自己的可视化引擎05】交互与事件
【实现自己的可视化引擎06】折线图
【实现自己的可视化引擎07】柱状图
【实现自己的可视化引擎08】条形图
【实现自己的可视化引擎09】饼图
【实现自己的可视化引擎10】散点图
【实现自己的可视化引擎11】雷达图
【实现自己的可视化引擎12】K线图
【实现自己的可视化引擎13】仪表盘
【实现自己的可视化引擎14】地图
【实现自己的可视化引擎15】关系图
前言
条形图使用水平的柱子显示类别之间的数值比较。其中一个轴表示需要对比的分类维度,另一个轴代表相应的数值。效果图如图所示:
render() {
return (
<Bar
className="chart"
data={[{
label: '第一季度',
data: [{
key: 'a',
value: 80
}, {
key: 'b',
value: 100,
}, {
key: 'c',
value: 120
}]
}, {
label: '第二季度',
data: [{
key: 'a',
value: 100,
}, {
key: 'b',
value: 80,
}, {
key: 'c',
value: 100
}]
}, {
label: '第三季度',
data: [{
key: 'a',
value: 120,
}, {
key: 'b',
value: 30,
}, {
key: 'c',
value: 90
}]
}, {
label: '第四季度',
data: [{
key: 'a',
value: 40,
}, {
key: 'b',
value: 60,
}, {
key: 'c',
value: 100
}]
}]}
style={{
color: '#666666',
axisColor: '#666666',
fontColor: '#666666',
colors: {
a: '#4169E1',
b: '#43CD80',
c: '#FFC0CB',
},
xAxisAttr: 'height',
yAxisAttr: 'weight',
}}
/>)
}
条形图层
从效果图上可以看出,条形图由不同的长方形组合而成。其算法如下:
- 计算数据的最大值,并计算出单位数值的单位长度:xStep=width/max;
- 遍历数据依据数据值value计算长方形的长度width;
- 绘制长方形,并加入图层。 根据上述流程,编写代码:
make() {
this.childs.splice(0, this.childs.length);
if (this.data.length === 0) {
return;
}
let max = -9999999999999;
let min = 9999999999999;
// 计算所有数据的最大值与最小值
for (let i = 0; i < this.data.length; i++) {
for (let j = 0; j < this.data[i].data.length; j++) {
if (this.data[i].data[j].value > max) {
max = this.data[i].data[j].value;
}
if (this.data[i].data[j].value < min) {
min = this.data[i].data[j].value;
}
}
}
// 计算
let xStep = this.width / max;
let yStep = this.height / this.data.length;
// 遍历数据,绘制柱形(长方形)
for(let i = 0; i < this.data.length; i++) {
// 长方形高度
let rectHeight = yStep * 0.8 / this.data[i].data.length;
for(let j = 0; j < this.data[i].data.length; j++) {
// 长方形宽度
let rectWidth = this.data[i].data[j].value * xStep;
console.log(this.position.x);
const rect = new Rectangle(this.canvas, {
width: rectWidth,
height: rectHeight,
type: Rectangle.TYPE.FILL,
color: this.colors[this.data[i].data[j].key],
position: new Point(this.position.x + rectWidth / 2, this.position.y + (i + 0.1) * yStep + j * rectHeight),
});
console.log(rect);
this.addChild(rect);
}
let txt = new Text(this.canvas, {
text: this.data[i].label,
font: this.fontFamily,
size: this.fontSize,
color: this.fontColor,
});
txt.setPosition(new Point(0, this.position.y + (i + 0.5) * yStep - txt.height))
this.addChild(txt);
}
this.onMaked && this.onMaked(this, {
max,
xStep,
yStep
});
}
React 封装
React封装需要DOM的挂载完成,所以我们在生命周期componentDidMount函数中构建我们的图层。代码如下:
componentDidMount () {
const { data = [], style } = this.props;
this.canvas = new Canvas({
ele: this.ref.current,
canAction: false,
});
const xFontSize = Number(style.xFontSize || 20);
const yFontSize = Number(style.yFontSize || 20);
let width = 0;
for (let i = 0;i < data.length; i++) {
const txt = new Text(this.canvas, {
text: data[i].label,
size: xFontSize,
color: style.axisColor || '#999999',
font: style.fontFamily || 'PingFang SC',
});
if (width < txt.width) {
width = txt.width;
}
}
// 坐标轴标准配置
this.axisLayer = new AxisLayer(this.canvas, {
yAxisType: AxisLayer.AxisType.LABEL, // y轴为数值型
xAxisType: AxisLayer.AxisType.NUMBER, // x轴时间为字符型
xAxisGraduations: style.xAxis || 5, // 网格5列
yAxisGraduations: style.yAxis || 5, // 网格5行
xAxisPosition: AxisLayer.AxisPosition.BLOCK, // X轴坐标不计算
yAxisPosition: AxisLayer.AxisPosition.BLOCK, // Y轴坐标计算
yAxisLabels: [],
position: new Point(width + 10, 0),
xAxisRender: (value) => {
const enob = style.yEnob || 2;
return {
text: Number(value).toFixed(enob),
size: xFontSize,
color: style.axisColor || '#999999',
font: style.fontFamily || 'PingFang SC',
};
},
color: style.color,
});
// 条形图层
this.barLayer = new BarLayer(this.canvas, {
width: (this.canvas.width - yFontSize * 4 * this.canvas.ratio) * 0.9, // 预留20%的空白空间
height: (this.canvas.height - xFontSize * this.canvas.ratio) * 0.8, // 预留20%的空白空间
colors: style.colors,
fontColor: style.color,
fontSize: yFontSize,
fontFamily: style.fontFamily || 'PingFang SC',
position: new Point(
width + 10,
xFontSize * this.canvas.ratio * 0.9 + this.canvas.height * 0.1
)
}, data);
this.barLayer.onMaked = (layer, option) => {
const { max, xStep } = option;
// 计算坐标系坐标数值
const xAxisMax = max + (this.canvas.width - yFontSize * 4 * this.canvas.ratio) * 0.1 / xStep;
// 设置坐标轴的最大最小值
this.axisLayer.xAxisMin = 0;
this.axisLayer.xAxisMax = xAxisMax;
this.axisLayer.make();
}
this.canvas.addChild(this.axisLayer, this.barLayer);
this.barLayer.make();
this.canvas.paint();
}