乐趣区

关于javascript:canvas-柱形图

canvas 柱形图

前言

在用 uni-app 做我的项目时,偶尔遇到一个中央须要柱形图,因为插件没找到适合的, 本人就写了一个,分享给大家,如果有写得不好的中央,请大家多多指教。

效果图

性能:通过 X 轴数组的长度,计算失去每段的宽度,从而能够实现图像宽度和地位的主动调配。通过 Y 轴数组最大值和最小值,计算出柱形在 Y 轴下面对应的坐标,从而实现图形比例调配高度。自定义宽高,未定义宽度时,自适应屏幕宽度。

绘制剖析

这个图标由 xy 轴、数据条形组成。

  • 轴线:应用 moveTo(x, y)lineTo(x, y)实现
  • 文字:应用 fillText(text, x, y) 实现
  • 长方形:应用 fillRect(x, y, width, height)实现

实现步骤

– 显示的数据

 itemsX: ['一月', '二月', '三月', '四月', '五月', '六月', '七月', '八月', '九月', '十月', '十一月', '十二月',],
 itemsY: [65, 35, 43, 77, 75, 48, 95, 80, 65, 35, 43, 77],

– 定义画布

 <canvas canvas-id="secondCanvas"
            :style="{height:`${height}px`,width:`${canvasWidth}px`}">
 </canvas>

– 计算 Y 轴上的刻度值数据

算出 Y 轴每段的长度,通过进行 for 循环, 将每段的刻度值顺次保留到 yScales 数组中

Y 轴每段长度 = (y 轴最大值 – y 轴最小值) / 总段数

getyScale () {let length = (this.maxNum - this.yAxisMinNum) / this.yNumber 
      for (let index = 0; index < this.yNumber; index++) {this.yScales.push(Math.ceil(length * (index + 1) + this.initialValue))
      }
    },

–x 轴和 y 轴的每段长度

计算出 xy 轴每段的长度, 再通过 for 循环, 每段的长度乘以 for 循环的索引值就能够失去每段在 Y 轴线和 Y 轴线上的坐标点

padding是给文字预留边距

XY 轴每段长度 =(总长度 – 两边的边距)/ 总的段数

this.xLength = Math.floor((this.canvasWidth - this.padding * 2) / this.itemsX.length)
this.yLength = Math.floor((this.height - this.padding * 2) / this.yNumber)

– 绘制刻度和刻度值

1. 计算在轴线上的坐标点
坐标点 = 每段长度 * 对应第几段的索引值

let newlength = length * (index + 1)

2. 绘制刻度

通过 context.moveTo()context.lineTo()绘制刻度

context.moveTo(this.padding + newlength, this.height - this.padding)
context.lineTo(this.padding + newlength, this.height - this.padding + 5) 

3. 绘制刻度值

刻度值通过 for 循环的在数组中失去对应的值, 同时因为点位会间接定到轴线上, 所应依据状况, 增加偏移量

 context.fillText(items[index], this.padding - 15, this.height - this.padding - newlength + 5);

– 绘制柱形图

1. 计算柱形图高度

总高度减去预留两边的预留边距 乘以 Y 轴数组的值除以总高度

通过 去除边距的总高度 Y 轴对应刻度的值除以最大值的百分比 计算出每个

柱形图的高度 =(总高度 – 两边边距)*((Y 轴对应的刻度值 – Y 轴最小值)/ (Y 轴最大值 – Y 轴最小值)

let yHeight = Math.ceil((this.height - (this.padding * 2)) * ((this.itemsY[index] - this.yAxisMinNum) / (this.maxNum - this.yAxisMinNum)))  

2. 计算fillRect y 轴坐标值

因为 canvas 起始坐标 (0,0) 是左上角, 所以要用总高度减去下方边距减去柱状图边距, 从而失去 y 轴的坐标

let y = this.height - this.padding -yHeight 

3. 计算fillRect x 轴坐标值和宽度

宽度是每段长度的一半, 所以为了保障图像在每段居中,x 轴的坐标值就须要在每段 4 分之 1 的地位开始, 因而在索引值前面须要加上一个 0.25

let xWidth = this.xLength / 2let x = this.padding + this.xLength * (index + 0.25)

4. 绘制柱形图

将计算的 z,y 坐标和对应的宽度长度填入 context.fillRect()

context.fillRect(x, y, this.xLength / 2, yHeight,)

残缺代码

属性名 类型 默认值 是否必须 阐明
itemsX Array [] x 轴显示数据
itemsY Array [] y 轴显示数据
maxNumy Number y 轴显示数据最大值 y 轴刻度值最大值, 小于 y 轴显示数据最大值时, 替换为 y 轴显示最大值
minNumy Number 0 y 轴刻度值最小值, 大于 y 轴显示数据最小值时, 替换为 y 轴显示最小值
width Number 屏幕宽度 图像宽度
height Number 500 图像高度
padding Number 30 图像边距
graphColor String ‘#000000’ 柱形图色彩
   data () {    return {      // 自定义的变量      itemsX: ['一月', '二月', '三月', '四月', '五月', '六月', '七月', '八月', '九月', '十月', '十一月', '十二月',],      itemsY: [65, 35, 43, 77, 75, 48, 95, 80, 65, 35, 43, 77],      maxNumy: 0,      minNumy: 0,      width: 768,      height: 500,      yNumber: 5,      padding: 30,      graphColor: '#72f6ff',              // 非自定义      yScales: [], // y 轴上刻度值数组      xLength: '', // x 轴每段长度      yLength:'', // y 轴每段长度      scaleIntervalMax: '',  // y 轴刻度值最大值      scaleIntervalMin:'',  // y 轴刻度值最小值}  },  computed: {yAxisMinNum () {// 判断最小值是否大于 itmsY 数组中的最小值, 大于则替换      return this.minNumy < this.scaleIntervalMin ? this.minNumy : this.scaleIntervalMin},    canvasWidth () { // 获取图像宽度      let windowWidth      uni.getSystemInfo({        success (res) {windowWidth = res.windowWidth}      })      return this.width || windowWidth // 判断是否自定义宽度,没有则自适应屏幕宽度    },  },  mounted () {                                        // 判断最大值是否小于 itmsY 数组中的最小值, 小于则替换    this.scaleIntervalMax = this.getMaxNum(this.itemsY) > this.maxNumy ? this.getMaxNum(this.itemsY) : this.maxNumy      this.scaleIntervalMin = this.getMinNum(this.itemsY)    this.getyScale()    this.getSingleLengths()    this.drawCanvas()},  methods: {getyScale () {// 取得 y 轴上刻度值数组      let length = (this.scaleIntervalMax - this.yAxisMinNum) / this.yNumber      for (let index = 0; index < this.yNumber; index++) {this.yScales.push(Math.ceil(length * (index + 1) + this.yAxisMinNum))      }    },    getMaxNum (items) {let num = items[0]      let cur      for (let i = 1; i < items.length; i++) {cur = items[i]        cur > num ? num = cur : null      }      return num    },    getMinNum (items) {let num = items[0]      let cur      for (let i = 1; i < items.length; i++) {cur = items[i]        cur < num ? num = cur : null      }      return num    },    getSingleLengths () { // 获取 xy 每段长度      this.xLength = Math.floor((this.canvasWidth - this.padding * 2) / this.itemsX.length)      this.yLength = Math.floor((this.height - this.padding * 2) / this.yNumber)    },    drawCanvas () { // 绘制      let context = uni.createCanvasContext('secondCanvas')      this.drawCoordinateAxis(context)      this.drawScale(this.xLength, this.itemsX.length, this.itemsX, context, 'x')      this.drawScale(this.yLength, this.yNumber, this.yScales, context, 'y')      this.drawGraph(context)    },    drawCoordinateAxis (context) {// 绘制轴线和初始地位文字      context.beginPath()      context.textAlign = 'center';      context.fillStyle = '#ffffff';      context.strokeStyle = '#ffffff';      context.font = "16px Arial"      context.lineWidth = 1;      // x 轴线      context.moveTo(this.padding, this.height - this.padding + 0.5)      context.lineTo(this.canvasWidth - this.padding, this.height - this.padding + 0.5)      context.stroke()      // Y 轴线      context.moveTo(this.padding, this.height - this.padding)      context.lineTo(this.padding, this.padding)      context.stroke()      // xy 初始地位文字      context.fillText(this.yAxisMinNum, this.padding - 15, this.height - this.padding);      context.draw()},    drawScale (length, totalLength, items, context, type) {// 绘制刻度和刻度值      context.beginPath()      context.globalAlpha = 1      context.textAlign = 'center';      context.fillStyle = '#ffffff';      context.strokeStyle = '#ffffff';      context.font = "16px Arial"      for (let index = 0; index < totalLength; index++) {let newlength = length * (index + 1)        // 绘制 x 轴刻度        if (type == 'x') {context.moveTo(this.padding + newlength, this.height - this.padding)          context.lineTo(this.padding + newlength, this.height - this.padding + 5)          context.stroke()          context.fillText(items[index], this.padding + newlength - length / 2, this.height - this.padding + 20);        }        // 绘制 y 轴刻度        if (type == 'y') {context.moveTo(this.padding, this.height - this.padding - newlength)          context.lineTo(this.padding - 5, this.height - this.padding - newlength)          context.stroke()          context.fillText(items[index], this.padding - 15, this.height - this.padding - newlength + 5);        }      }      // 连贯上个图像持续绘制      context.draw(true)    },    drawGraph (context) {// 绘制柱状图      context.beginPath()      context.textAlign = 'center'      for (let index = 0; index < this.itemsX.length; index++) {// 柱状图 x 轴坐标        let x = this.padding + this.xLength * (index + 0.25)        // 柱状图宽度        let xWidth = this.xLength / 2        // 柱状图高度        let yHeight = Math.ceil((this.height - (this.padding * 2)) * ((this.itemsY[index] - this.yAxisMinNum) / (this.scaleIntervalMax - this.yAxisMinNum)))        // 柱状图 y 轴坐标        let y = this.height - this.padding - yHeight        context.fillStyle = this.graphColor        context.fillRect(x, y, xWidth, yHeight,)        context.fillStyle = '#fff'        // 文字坐标        context.fillText(this.itemsY[index], this.padding + this.xLength * (index + 0.5), y - 5);      }      context.draw(true)    }  }
退出移动版