react 封装 基于canvas实现echarts柱状图效果 拿来即用

父组件:

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;

效果图:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值