父组件:
import React, { Component } from 'react';
import HistogramChart from './view/home'
class Chart extends Component {
constructor (props) {
super(props);
this.state = {
//x轴数据
chartList: {
xName: ['Mon44', 'Tue', 'Wed', 'Thu'], // 第一行
xCount: ['100', '200', '300', '40'], // 第二行 可以不传根据业务需求
yData: [120, 199, 150, 180] // y 数量
},
}
}
render () {
return (
<HistogramChart chartData={this.state.chartList} />
)
}
}
export default Chart;
子组件:
import React, { Component } from 'react';
class HistogramChart extends Component {
constructor (props) {
super(props);
this.state = {
uuid:new Date().getTime()+Math.ceil(Math.random()*1000000),
option: {
//x轴数据
xAxis: {
data: [], // 第一行
datas: [] // 第二行
},
//柱图数据
series: [{
data: [], // y 数量
//图形样式:柱图
type: 'bar'
}]
}
}
}
//创建图表函数,wrap:图表父元素id;data:图表数据
fnCharts (wrap, data) {
if (this.state.option.xAxis.data.length === 0) {
throw Error('this.state.option.xAxis.data length===0')
} else if (this.state.option.series[0].data.length === 0) {
throw Error('this.state.option.series[0].data length===0')
}
//2.获取canvasWrap元素
var eWrap = document.getElementById(wrap);
//2.获取canvasWrap元素宽度和高度,用于设置canvas画布大小
var nWrapW = eWrap.offsetWidth;
var nWrapH = eWrap.offsetHeight;
//3.1 创建canvas画布
var eCanvas = document.createElement('canvas');
//3.2 设置canvas画布的宽度和高度
eCanvas.width = nWrapW;
eCanvas.height = nWrapH;
//3.3 将canvas画布放入到canvasWrap元素中
eWrap.appendChild(eCanvas);
//3.4 创建绘图上下文环境(才能够在Canvas画布上绘制)
var oCtx = eCanvas.getContext('2d');
//4.设定坐标区域左上角和右下角
//起点设置为50.5,而不是整数,是为了让线条变清晰
var nZoneStartX = 50.5;
var nZoneStartY = 50.5;
var nZoneEndX = nWrapW - nZoneStartX;
var nZoneEndY = nWrapH - nZoneStartY;
//5.1 使用线条函数绘制x轴轴线
this.fnCreatLine(oCtx, nZoneStartX, nZoneEndY, nZoneEndX, nZoneEndY);
//计算x轴长度
var nLonX = nZoneEndX - nZoneStartX;
//获取x轴数据数组长度
var nDataLon = this.state.option.xAxis.data.length;
//根据x轴数据数组长度循环,在循环中绘制刻度线和刻度数值名称
for (let i = 0; i < nDataLon; i++) {
//计算出x轴刻度线起点在x轴上的值
let nScaleX = nZoneStartX + Math.floor(nLonX * (i / nDataLon));
//刻度线起点都在x轴上
let nScaleY = nZoneEndY;
//5.2 绘制刻度线,长度为10
this.fnCreatLine(oCtx, nScaleX, nScaleY, nScaleX, nScaleY + 10);
//从数据中获取刻度名称字符串
let sName = this.state.option.xAxis.data[i];
//计算出刻度名称起点
let nNameX = nZoneStartX + Math.floor(nLonX * (i / nDataLon)) + Math.floor(nLonX * (1 / nDataLon)) / 2;
let nNameY = nZoneEndY + 15;
//5.3 绘制刻度名称
this.fnCreatText(oCtx, sName, nNameX, nNameY, '#aaa', 'center');
}
//5.1 第二排名称
this.fnCreatLine(oCtx, nZoneStartX, nZoneEndY, nZoneEndX, nZoneEndY);
//根据x轴数据数组长度循环,在循环中绘制刻度线和刻度数值名称 长度大于0时进入
if (this.state.option.xAxis.datas.length > 0) {
for (let i = 0; i < nDataLon; i++) {
//计算出x轴刻度线起点在x轴上的值
let sName = this.state.option.xAxis.datas[i];
//计算出刻度名称起点
let nNameX = nZoneStartX + Math.floor(nLonX * (i / nDataLon)) + Math.floor(nLonX * (1 / nDataLon)) / 2;
let nNameY = nZoneEndY + 29;
//5.3 绘制刻度名称
this.fnCreatText(oCtx, sName, nNameX, nNameY, '#aaa', 'center');
}
}
//6.1 使用线条函数绘制y轴轴线
this.fnCreatLine(oCtx, nZoneStartX, nZoneEndY, nZoneStartX, nZoneStartY);
//绘制y轴刻度线前,需要有刻度最大值、最小值、刻度线段数和刻度线之间的间隔这些数据。
//刻度最大值先从数组中取最大值,等下再计算应该显示的最大值
var nMaxScal = Math.max.apply(null, this.state.option.series[0].data);
//刻度最小值在本实例中取0
var nMinScal = 0;
//刻度线段数在本实例中设置为4
var nSplit = 4;
//计算刻度间隔值
var nStep = (nMaxScal - nMinScal) / nSplit;
//这时候会发现刻度间隔值好像有点奇怪,因为一般图表的刻度间隔值都是5的倍数,
//比如:[0,0.5,1.0,1.5,2]或[0,50,100,150,200]。
//所以还需要进一步计算,看nStep是否是5的倍数,如果不是,则递增nIncrease,使其达到最接近的5的倍数。
//计算第一步,根据nStep算出倍数值应该是0.5或5或50或...
//在本实例中通过把nStep数值先转换为字符串再进行处理(也可以使用对数和指数去计算)。
var sTemp = '' + nStep; //把nStep转换为字符串
//声明一个需要递增的数,默认为1
var nIncrease = 1;
//声明一个变量用于解决小数相乘产生的精度bug
var nTempMultiple = 1;
//nIncrease取10的n次幂,通过以下判断计算
if (sTemp.indexOf('.') === -1) {
//如果nStep不包含小数点,nIncrease取10的sTemp.length-2次幂。
//比如nStep为19的话,nIncrease = 10的0次幂,递增数为1
//nStep为9的话,nIncrease = 10的-1次幂,递增数为0.1
//nStep为199的话,nIncrease = 10的1次幂,递增数为10
nIncrease = Math.pow(10, sTemp.length - 2);
} else {
//如果nStep包含小数点,nIncrease取10的sTemp整数位-2次幂。
nIncrease = Math.pow(10, sTemp.indexOf('.') - 2);
//这个变量用于解决小数相乘可能产生的精度bug,比如nIncrease是小数的情况
nTempMultiple = Math.pow(10, sTemp.indexOf('.'));
}
//倍数取整,便于递增,如165改成160,16.5改成16,1.65改成1.6,可通过下列公式实现
nStep = Math.ceil(nStep / nIncrease) * (nIncrease * nTempMultiple) / nTempMultiple;
//使用循环递增nIncrease修正刻度值
while (nStep % (nIncrease * 5) !== 0) {
nStep += nIncrease * 1;
}
//通过间隔值乘以线段数,修改刻度最大值
nMaxScal = nStep * nSplit;
//计算y轴长度,这里多减3是因为y轴顶端要留点距离
var nLonY = nZoneEndY - nZoneStartY - 3;
//绘制y轴刻度
for (let i = 0; i <= nSplit; i++) {
//刻度线起点都在y轴上
let nScaleX = nZoneStartX;
//计算出y轴刻度线起点在y轴上的值
let nScaleY = nZoneEndY - Math.floor(nLonY * (i / nSplit));
//6.2 绘制刻度线
this.fnCreatLine(oCtx, nScaleX, nScaleY, nScaleX - 10, nScaleY);
//6.3 绘制刻度值
this.fnCreatText(oCtx, '' + i * nStep, nScaleX - 20, nScaleY, '#333');
if (i !== 0) {
//6.4 非0位置,绘制x轴网格线
this.fnCreatLine(oCtx, nScaleX, nScaleY, nScaleX + nLonX, nScaleY, '#ccc');
}
}
//7.1 计算柱图宽度
let nBarWidth = 30
// Math.ceil(Math.floor(nLonX * (1 / nDataLon)) * .8);
//遍历x轴数据
for (let i = 0; i < nDataLon; i++) {
//7.2 计算柱图高度
let nBarHeight = nLonY / nMaxScal * this.state.option.series[0].data[i];
//7.3 计算柱图X起点
let nBarStartX = nZoneStartX + Math.floor(nLonX * (i / nDataLon))
+ (Math.floor(nLonX * (1 / nDataLon)) - nBarWidth) / 2;
//7.4 计算柱图Y起点
let nBarStartY = nZoneEndY - nBarHeight;
//7.5 绘制柱图
this.fnCreatRect(oCtx, nBarStartX, nBarStartY, nBarWidth, nBarHeight);
}
}
//绘制线条函数
fnCreatLine (oCtx, sX, sY, eX, eY, color = '#000') {
//开始绘制路径
oCtx.beginPath();
//设置路径颜色
oCtx.strokeStyle = color;
//设置路径起点和终点,绘制线条
oCtx.moveTo(sX, sY);
oCtx.lineTo(eX, eY);
//给路径添加颜色
oCtx.stroke();
}
//绘制文字
fnCreatText (oCtx, text, x, y, color = '#000', align = 'end', baseLine = 'middle') {
//设置文字颜色
oCtx.fillStyle = color;
//设置水平对齐方式
oCtx.textAlign = align;
//设置垂直对齐方式
oCtx.textBaseline = baseLine;
//绘制文字
oCtx.fillText(text, x, y);
}
//绘制矩形
fnCreatRect (oCtx, x, y, width, height, color = '#0093E0') {
//设置颜色
oCtx.fillStyle = color;
oCtx.fillRect(x, y, width, height);
}
// 页面初始化钩子函数
componentDidMount () {
let data = Object.assign({}, this.state.option, { xAxis: { data: this.props.chartData.xName, datas: this.props.chartData.xCount ? this.props.chartData.xCount : [] }, series: [{ data: this.props.chartData.yData, type: 'bar' }] })
this.setState({
option: data
}, () => {
this.fnCharts(this.state.uuid, this.state.option);
})
//调用图表函数,并传入元素id和option数据
}
render () {
return (
<div id={this.state.uuid} style={{ width: '40%', height: '400px' }}></div>
)
}
}
export default HistogramChart;
效果图: