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

因为公司的业务需要,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'
      }
    ]
  };
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,839评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,543评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,116评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,371评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,384评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,111评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,416评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,053评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,558评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,007评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,117评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,756评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,324评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,315评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,539评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,578评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,877评论 2 345