支持个性化配置的柱状图——canvas(再也不用和设计说你这个设计效果我做不到啦)

2,601 阅读4分钟
因为公司的业务需要,highcharts&echarts这些图表库的样式不符合设计的要求,所以在考虑了不用考虑ie8等浏览器的情况下,选择canvas封装了一个支持个性化配置的柱状图。希望有需要类似图的前端伙伴,可以有用。
因为个性化,需要各种自定义大小颜色的参数,所以我把参数分为了4类——基础参数、坐标轴、柱子数据、增强属性。
  • 先看个效果图

    柱状图.png

  • 接入方式

    • html中放置一个canvas
    <canvas id="canvas"></canvas>
    
    • 引入下面的function drawHistogram

    • 执行方法

    drawHistogram(chartsConfigObj);
    
  • 参数解释

    • 第一层参数解释
    /**
     * 第一层参数
     * @param {basicData} 必传,基础参数
     * @param {axisData} 必传,坐标轴参数
     * @param {columnData} 必传,柱子数据
     * @param {enhancementAttr} 非必传,一些增强属性
     */
    
    • 第二层参数
    let {
        basicData: {
          wrapEleId,
          width,
          height
        },
        axisData: {
          basic: {
            axislineWidth,
            axisColor,
            axisTextColor,
            axisTextFont,
          },
          axisX: {
            axisXStart,
            axisXWidth,
            axisXTextDistance
          },
          axisY: {
            axisYStart,
            axisYWidth,
            axisYIntervalNum,
            axisYTextDistance,
            axisYMaxNum
          }
        },
        columnData: {
          eachColumnWidth,
          spaceDistance,
          columnMaxHeight,
          columnColor,
          firstColumnStartX,
          firstColumnStartY
        },
        graphs
      } = chartsConfigObj;
      const {enhancementAttr} = chartsConfigObj;
      let {
        isColumnNeedRadius,
        columnGradientData,
        isRemoveAxisArrow,
        horizontalLineObj
      } = enhancementAttr || {}; 
      let {
        gradientStartColor,
        gradientEndColor
      } = columnGradientData || {};
      let {
        horizontalLineColor,
        horizontalLineDistance
      } = horizontalLineObj || {};
    /**
    * 具体的每个柱状图参数
    * @param {wrapEleId} canvas画布id
    * @param {width} 画布宽
    * @param {height} 画布高
    * @param {eachColumnWidth} 一个柱子的宽度
    * @param {spaceDistance} 柱子间的间距
    * @param {firstColumnStartX} 第一个柱子左下角的X轴坐标
    * @param {firstColumnStartY} 第一个柱子左下角的Y轴坐标
    * @param {columnMaxHeight} 柱子最大高度
    * @param {axislineWidth} 坐标轴的线的宽度
    * @param {axisXStart} 坐标轴X轴起点坐标
    * @param {axisYStart} 坐标轴Y轴起点坐标
    * @param {axisXWidth} 坐标轴X轴长度
    * @param {axisYWidth} 坐标轴Y轴长度
    * @param {axisXTextDistance} 坐标轴X轴文字距离X轴垂直方向的偏移量 
    * @param {axisYTextDistance} 坐标轴Y轴水平方向的偏移量
    * @param {axisYIntervalNum} 左边轴y轴间隔
    * @param {axisYMaxNum} 左边轴y轴标尺最大值
    * @param {axisColor} 坐标轴线颜色
    * @param {axisTextColor} 坐标轴文本颜色
    * @param {axisTextFont} 坐标轴文本fontSize、fontFamily
    * @param {columnColor} 柱状图颜色
    * @param {graphs} 图表数据-数组,[{x:,y:},...]
    * @param {columnGradientData} 柱子渐变色信息,不需要渐变就不传此参数
    * @param {gradientStartColor} 渐变开始色(靠近x轴)
    * @param {gradientEndColor} 渐变结束色
    * @param {isColumnNeedRadius} 柱子是否需要圆角
    * @param {gradientEndColor} 渐变结束色
    * @param {gradientEndColor} 渐变结束色
    * @param {gradientEndColor} 渐变结束色
    * @param {horizontalLineObj} 水平虚线背景参数,不需要则不传{color,space}
    */
    
  • 功能实现代码

export default function drawHistogram(chartsConfigObj)  {

    let {
      basicData: {
        wrapEleId,
        width,
        height
      },
      axisData: {
        basic: {
          axislineWidth,
          axisColor,
          axisTextColor,
          axisTextFont,
        },
        axisX: {
          axisXStart,
          axisXWidth,
          axisXTextDistance
        },
        axisY: {
          axisYStart,
          axisYWidth,
          axisYIntervalNum,
          axisYTextDistance,
          axisYMaxNum
        }
      },
      columnData: {
        eachColumnWidth,
        spaceDistance,
        columnMaxHeight,
        columnColor,
        firstColumnStartX,
        firstColumnStartY
      },
      graphs
    } = chartsConfigObj;
  
    const {enhancementAttr} = chartsConfigObj;
    
    let {
      isColumnNeedRadius,
      columnGradientData,
      isRemoveAxisArrow,
      horizontalLineObj
    } = enhancementAttr || {};
    
    let {
      gradientStartColor,
      gradientEndColor
    } = columnGradientData || {};
  
    let {
      horizontalLineColor,
      horizontalLineDistance
    } = horizontalLineObj || {};
  
    horizontalLineColor = horizontalLineColor ? horizontalLineColor : '#eee';
    horizontalLineDistance = horizontalLineDistance ? horizontalLineDistance: 5;

    isRemoveAxisArrow = isRemoveAxisArrow ? isRemoveAxisArrow : false;
  
    var canvas = document.getElementById(wrapEleId);
    var context = canvas.getContext("2d");
    canvas.width = width;
    canvas.height = height;
    context.beginPath();
  
    //画x轴
    context.beginPath();
    context.moveTo(axisXStart, axisYStart);
    context.lineTo(axisXStart + axisXWidth, axisYStart);
    context.strokeStyle = axisColor;
    context.lineWidth = axislineWidth;
    context.stroke();
    context.beginPath();

    if (!isRemoveAxisArrow) {
      context.moveTo(axisXStart + axisXWidth - 6, axisYStart-6);
      context.lineTo(axisXStart + axisXWidth, axisYStart);
      context.lineTo(axisXStart + axisXWidth - 6, axisYStart+6);
      context.strokeStyle = axisColor;
      context.lineWidth = axislineWidth;
      context.stroke();
    }
  
    //画y轴
    context.beginPath();
    var axisYEnd = height - (height - axisYStart + axisYWidth);
    context.moveTo(axisXStart, axisYStart);
    context.lineTo(axisXStart, axisYEnd);
    context.strokeStyle = axisColor;
    context.lineWidth = axislineWidth;
    context.stroke();
    context.beginPath();
    if (!isRemoveAxisArrow) {
      context.moveTo(axisXStart - 6, axisYEnd + 6);
      context.lineTo(axisXStart, axisYEnd);
      context.lineTo(axisXStart + 6, axisYEnd + 6);
      context.strokeStyle = axisColor;
      context.lineWidth = axislineWidth;
      context.stroke();
    }
  
    //画y轴上的数字
    var axisYNum = axisYMaxNum / axisYIntervalNum;
    var axisYLength = columnMaxHeight / axisYNum;
    for (var j = 0; j <= axisYNum; j++) {
      context.beginPath();
      context.moveTo(axisXStart, axisYStart);
      context.font = axisTextFont;
      context.textAlign = "center";
      context.fillStyle = axisTextColor;
      context.fillText(j*axisYIntervalNum,axisXStart - 15, axisYStart - j * axisYLength + axisYTextDistance);
      
      context.beginPath();
      context.moveTo(axisXStart, axisYStart - j * axisYLength);
      context.lineTo(axisXStart+5, axisYStart - j * axisYLength);
      context.strokeStyle = axisColor;
      context.lineWidth = axislineWidth;
      context.stroke();
  
      if (horizontalLineObj && j !== 0) {
        context.beginPath();
        context.moveTo(axisXStart, axisYStart - j * axisYLength);
        context.lineTo(axisXStart+axisXWidth, axisYStart - j * axisYLength);
        context.setLineDash([horizontalLineObj.space || 10]);
        context.strokeStyle = horizontalLineObj.color || '#eee';
        context.stroke();
      }
    }
    context.beginPath();
  
    //画柱状图
    var columnLength = graphs.length;
    for (var i = 0; i < columnLength; i++) {
      var colData = graphs[i];
      var colStratX = firstColumnStartX+(i*spaceDistance)+(i*eachColumnWidth);
      var colStratY = height - (height - firstColumnStartY + (colData.y/100) * columnMaxHeight);
  
      if(isColumnNeedRadius) {
        const r = eachColumnWidth / 2;
        const w = eachColumnWidth;
        const h = -((colData.y/100) * columnMaxHeight);
        const x = colStratX;
        const y = firstColumnStartY;
        context.beginPath();
        context.moveTo(x + r, y);
        context.arcTo(x + w, y, x + w, y + h, r);
        context.arcTo(x + w, y + h, x, y + h, r);
        context.arcTo(x, y + h, x, y, r);
        context.arcTo(x, y, x + w, y, r);
        context.closePath();
      } else {
        context.beginPath();
        context.moveTo(colStratX, firstColumnStartY);
        context.lineTo(colStratX, colStratY);
        context.lineTo(colStratX+eachColumnWidth, colStratY);
        context.lineTo(colStratX+eachColumnWidth, firstColumnStartY);
        context.closePath();
      }
      
  
      //需要渐变
      if (columnGradientData) {
        var grad  = context.createLinearGradient(0, firstColumnStartY, 0,colStratY);
        grad.addColorStop(0, gradientStartColor); // 绿
        grad.addColorStop(1, gradientEndColor);  // 紫
        context.fillStyle = grad;
      } else {
        context.fillStyle = columnColor;
      }
  
      context.fill();
      context.font = axisTextFont;
      context.textAlign="center";
      context.fillStyle = axisTextColor;
      context.fillText(colData.x, colStratX+eachColumnWidth/2,  firstColumnStartY + axisXTextDistance);
  
    }
    
  }
  • 例子数据
const chartsConfigObj = {
    basicData: {
      wrapEleId: 'canvas',
      width: 860,
      height: 400
    },
    axisData: {
      basic: {
        axislineWidth: 1,
        axisColor: '#999',
        axisTextColor: '#666',
        axisTextFont: '12px PingFangSC-Regular',
      },
      axisX: {
        axisXStart: 26,
        axisXWidth: 850,
      },
      axisY: {
        axisYStart: 365,
        axisYWidth: 190,
        axisYIntervalNum: 20,
        axisYTextDistance: 2,
        axisYMaxNum: 100
      }
    },
    columnData: {
      eachColumnWidth: 14,
      spaceDistance: 30,
      columnMaxHeight: 156,
      columnColor: '#f00',
      firstColumnStartX: 50,
      firstColumnStartY: 365
    },
    enhancementAttr: {
      isColumnNeedRadius: true,
      columnGradientData: {
        gradientStartColor: '#35E1A0',
        gradientEndColor: '#29CDC0'
      },
      horizontalLineObj: {
        horizontalLineColor: '#999',
        horizontalLineDistance: 5
      },
      isRemoveAxisArrow: true
    },
    graphs: [
      {
      'x': '2011',
      'y': '100'
      }, {
      'x': '2012',
      'y': '90'
      }, {
      'x': '2013',
      'y': '60'
      }, {
      'x': '2014',
      'y': '90'
      }, {
      'x': '2015',
      'y': '70'
      }, {
      'x': '2016',
      'y': '10'
      }, {
      'x': '2011',
      'y': '100'
      }, {
      'x': '2012',
      'y': '90'
      }, {
      'x': '2013',
      'y': '60'
      }, {
      'x': '2014',
      'y': '90'
      }, {
      'x': '2015',
      'y': '70'
      }, {
      'x': '2016',
      'y': '10'
      }, {
      'x': '2011',
      'y': '100'
      }, {
      'x': '2012',
      'y': '90'
      }, {
      'x': '2013',
      'y': '60'
      }, {
      'x': '2014',
      'y': '30'
      }, {
      'x': '2015',
      'y': '10'
      }, {
      'x': '2016',
      'y': '95'
      }
    ]
  };