myCharts 目录结构
myCharts 工程化配置
-
webpack.config.js
const path = require('path') const HtmlWebpackPlugin = require('html-webpack-plugin') module.exports = { mode: "development", entry: "./src/index.js", output: { filename: 'chart.js', path: path.resolve(__dirname, 'dist') }, devServer: { port: 5000 }, module: { rules: [ { test: /\.css/, use: ['style-loader', 'css-loader'] }, { test: /\.js/, exclude: /node_modules/, use: [ { loader: 'babel-loader', options: { presets: ['@babel/preset-env'] } } ] } ] }, plugins: [ new HtmlWebpackPlugin({ template: './public/index.html' }) ] }
-
public/index.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>可视化插件封装</title> <script> document.documentElement.style.fontSize = document.documentElement.clientWidth / 10 + 'px' window.addEventListener('resize', () => { document.documentElement.style.fontSize = document.documentElement.clientWidth / 10 + 'px' }) </script> </head> <body> <div class="box"></div> </body> </html>
myCharts初始化
-
src/index.js
import '../css/main.css' import MyCharts from './charts' new MyCharts({ select: '#box1', ratio: 1.5, type: 'cirque' })
-
src/charts.js
import utils from './utils' class MyCharts { constructor(defaultParam) { this.defaultParam = defaultParam this._canvasParDom = document.querySelector(this.defaultParam.select) this.containerWidth = this._canvasParDom.clientWidth this.containerHeight = this._canvasParDom.clientHeight this._canvas = document.createElement('canvas') // 设置默认配置 this.defaultConfig = { styles: { borderColor: "#6b9bb8", lineColor: '#9ec8da', pointColor: '#41627c' }, data: [], x: 40, padding: 20, fontSize: '16px', wd: this.containerWidth * this.defaultParam.ratio, ht: this.containerHeight * this.defaultParam.ratio, lineWidth: 2, hisColor: ['#7b8c7c', '#5c968a', '#576d93', '#a0d878', '#337d56', '#c1d0ae', '#93b469', '#bda29a'] } // 上下文绘制环境 this.ctx = this._canvas.getContext('2d') // 缩放画布大小 this._canvas.width = this.containerWidth * this.defaultParam.ratio this._canvas.height = this.containerHeight * this.defaultParam.ratio // 添加至div 当中 this._canvasParDom.appendChild(this._canvas) // 扩展或者覆盖配置 this.defaultParam = utils.extendsObj(this.defaultConfig, this.defaultParam) // 设置合适的画布宽度 this.defaultParam.wid = this._canvas.width - 20 this.init() } init() { switch (this.defaultParam.type) { case 'cirque': console.log('绘制圆环') break default: console.log('无此功能的绘制') } } } export default MyCharts
-
src/utils.js
let utils = { extendsObj: (obj1, obj2) => { for (let k in obj2) { obj1[k] = obj2[k] } return obj1 } } export default utils
myCharts动画函数实现
-
src/myAnimation.js
export default function myAnimation(param) { let current = 0 let looped const ctx = this.ctx const _canvas = this._canvas const callback = param.render const successCb = param.success; (function looping() { looped = requestAnimationFrame(looping) if (current < param.percent) { ctx.clearRect(0, 0, _canvas.width, _canvas.height) current = current + 4 > param.percent ? param.percent : current + 4 callback(current) } else { window.cancelAnimationFrame(looping) looped = null successCb && successCb() } })() }
进度园实现
-
src/charts.js
import Cirque from './cirque' import myAnimation from './myAnimation' // ... init() { switch (this.defaultParam.type) { case 'cirque': let circleConfig = { x: this.defaultParam.wd / 2, y: this.defaultParam.ht / 2, radius: 200, startAngle: 0, endAngle: 2 * Math.PI, arcWidth: 18, target: 90 } this.circleConfig = utils.extendsObj(this.defaultConfig, circleConfig) myAnimation.call(this, { percent: this.circleConfig.target, render: (current) => { Cirque.call(this, current / 100) } }) break default: console.log('无此功能的绘制') } }
-
src/cirque.js
let Cirque = function (percent) { const ctx = this.ctx const circleConfig = this.defaultParam // 绘制打底圆环 ctx.beginPath() ctx.lineWidth = circleConfig.arcWidth let grd = ctx.createRadialGradient(circleConfig.x, circleConfig.y, circleConfig.radius - 10, circleConfig.x, circleConfig.y, circleConfig.radius + 10) grd.addColorStop(0, "#e9eae9") grd.addColorStop("0.8", "#fefefe") grd.addColorStop("1", "#e9eae9") ctx.strokeStyle = grd ctx.arc(circleConfig.x, circleConfig.y, circleConfig.radius, circleConfig.startAngle, circleConfig.endAngle) ctx.stroke() ctx.closePath() // 绘制进度圆环 ctx.beginPath() ctx.lineWidth = circleConfig.arcWidth let linear = ctx.createLinearGradient(220, 220, 380, 200) linear.addColorStop(0, '#ffc26b') linear.addColorStop(0.5, '#ff9a5f') linear.addColorStop(1, '#ff8157') ctx.strokeStyle = linear ctx.arc(circleConfig.x, circleConfig.y, circleConfig.radius, circleConfig.startAngle, circleConfig.endAngle * percent) ctx.stroke() ctx.closePath() // 起点的圆形 ctx.beginPath() ctx.fillStyle = '#ff7854' ctx.arc(circleConfig.x + circleConfig.radius, circleConfig.y - 1, circleConfig.arcWidth / 2, circleConfig.startAngle, circleConfig.endAngle) ctx.fill() ctx.closePath() // 终点的圆形 ctx.beginPath() ctx.lineWidth = circleConfig.arcWidth - 10 ctx.fillStyle = '#fff' ctx.strokeStyle = '#ff7854' let tarX = circleConfig.x + circleConfig.radius * Math.cos(2 * Math.PI * percent) let tarY = circleConfig.y + circleConfig.radius * Math.sin(2 * Math.PI * percent) ctx.arc(tarX, tarY, circleConfig.arcWidth - 8, circleConfig.startAngle, circleConfig.endAngle) ctx.fill() ctx.stroke() ctx.closePath() } export default Cirque
折线图绘制
-
src/index.js
import '../css/main.css' import MyCharts from './charts' new MyCharts({ select: '#box2', ratio: 1.5, type: 'line', data: [{ xVal: '周一', yVal: 40 }, { xVal: '周二', yVal: 70 }, { xVal: '周三', yVal: 30 }, { xVal: '周四', yVal: 80 }, { xVal: '周五', yVal: 30 }, { xVal: '周六', yVal: 20 }, { xVal: '周日', yVal: 90 }] })
-
src/charts.js
import utils from './utils' import myAnimation from './myAnimation' import { drawAxis, drawPoint, drawBrokenLine, drawDashLine } from './broken' // ... init() { switch (this.defaultParam.type) { case 'line': myAnimation.call(this, { percent: 200, render: (current) => { // 绘制坐标系 drawAxis.call(this) // 绘制虚线 drawBrokenLine.call(this, current / 200) // 绘制Y轴虚线 drawDashLine.call(this, current / 200) // 绘制圆形 drawPoint.call(this, current / 200) } }) break; default: console.log('无此功能的绘制') } }
-
src/broken.js
export function drawAxis() { const defaultParam = this.defaultParam const ctx = this.ctx const pad = defaultParam.padding const bottomPad = 30 const wd = defaultParam.wd const ht = defaultParam.ht const data = defaultParam.data // 绘制坐标系 ctx.save() ctx.beginPath() ctx.lineWidth = 2 ctx.strokeStyle = defaultParam.styles.borderColor ctx.moveTo(pad, pad) ctx.lineTo(pad, ht - bottomPad) ctx.lineTo(wd - pad, ht - bottomPad) ctx.stroke() ctx.closePath() // 绘制文字刻度 for (let i = 0; i < data.length; i++) { ctx.beginPath() ctx.fillStyle = '#333' ctx.textAlign = 'center' ctx.font = defaultParam.fontSize + ' Microsoft YaHei' ctx.fillText(data[i].xVal, i * (defaultParam.wid / data.length - 1) + defaultParam.x, ht - 10) ctx.closePath() } ctx.restore() } export function drawPoint(speed) { const defaultParam = this.defaultParam const ctx = this.ctx const data = defaultParam.data const len = data.length const ht = defaultParam.ht // 计算 ctx.save() ctx.lineWidth = 2 for (let i = 0; i < len; i++) { let yVal = parseInt(data[i].yVal * speed) let tranY = ht - ht * yVal / defaultParam.maxPoint - 30 let tranX = i * (defaultParam.wid / len - 1) + defaultParam.x // 绘制图形 ctx.beginPath() ctx.shadowOffsetX = 0 ctx.shadowOffsetY = 0 ctx.shadowBlur = 3 ctx.shadowColor = defaultParam.styles.pointColor ctx.fillStyle = defaultParam.styles.pointColor ctx.strokeStyle = '#fff' ctx.arc(tranX, tranY, 6, 0, 2 * Math.PI, false) ctx.fill() ctx.stroke() ctx.closePath() // 绘制圆形对应的数值 ctx.beginPath() ctx.shadowBlur = 0 ctx.fillStyle = '#333' ctx.textAlign = 'center' ctx.font = defaultParam.fontSize + ' MicroSoft YaHei' ctx.fillText(yVal, tranX, tranY - 10) ctx.closePath() } ctx.restore() } export function drawBrokenLine(speed) { const defaultParam = this.defaultParam const ctx = this.ctx const bottomPad = 30 const data = defaultParam.data const ht = defaultParam.ht const maxPoint = defaultParam.maxPoint const len = data.length - 1 const stepDots = Math.floor(speed * len) // 绘制线条 ctx.save() ctx.beginPath() ctx.setLineDash([4, 4]) ctx.lineWidth = defaultParam.lineWidth ctx.strokeStyle = defaultParam.styles.lineColor for (let i = 0; i < len; i++) { // 起点 const yVal = data[i].yVal const axisY = ht - ht * (yVal / maxPoint) - bottomPad const averageNum = defaultParam.wid / data.length - 1 const axisX = i * averageNum + defaultParam.x // 终点 let axisEndX = (i + 1) * averageNum + defaultParam.x let axisEndY = ht - ht * (data[i + 1].yVal) / maxPoint - bottomPad if (i <= stepDots) { if (i === stepDots) { axisEndX = (axisEndX - axisX) * speed + axisX axisEndY = (axisEndY - axisY) * speed + axisY } ctx.moveTo(axisX, axisY) ctx.lineTo(axisEndX, axisEndY) } } ctx.stroke() ctx.closePath() ctx.restore() } export function drawDashLine(speed) { const defaultParam = this.defaultParam const ctx = this.ctx const bottomPad = 30 const data = defaultParam.data const ht = defaultParam.ht const maxPoint = defaultParam.maxPoint const len = data.length ctx.save() for (let i = 0; i < len; i++) { // 起始点 const averageNum = defaultParam.wid / data.length - 1 let axisX = i * averageNum + defaultParam.x let axisY = ht - ht * (data[i].yVal) / maxPoint * speed - bottomPad // 开始绘制 ctx.beginPath() ctx.lineWidth = 2 ctx.setLineDash([4, 4]) ctx.strokeStyle = '#d6d6d6' ctx.moveTo(axisX, ht - bottomPad) ctx.lineTo(axisX, axisY) ctx.stroke() ctx.closePath() } ctx.restore() }
直方图绘制
-
src/index.js
import '../css/main.css' import MyCharts from './charts' new MyCharts({ type: 'histogram', select: '#box3', ratio: 1.5, data: [{ xVal: 'vue', yVal: 80 }, { xVal: 'react', yVal: 70 }, { xVal: 'angular', yVal: 40 }, { xVal: 'webpack', yVal: 90 }, { xVal: '2222', yVal: 80 }, { xVal: 'typescript', yVal: 40 }, { xVal: 'ES6+', yVal: 100 }] })
-
src/charts.js
import utils from './utils' import Cirque from './cirque' import myAnimation from './myAnimation' import { drawHistogram } from './histogram' import { drawAxis, drawPoint, drawBrokenLine, drawDashLine } from './broken' // ... init() { switch (this.defaultParam.type) { case 'cirque': // ... break case 'line': // ... break; case 'histogram': myAnimation.call(this, { percent: 100, render: (current) => { // 绘制坐标系 drawAxis.call(this) // 绘制直方图 drawHistogram.call(this, current / 100) } }) break default: console.log('无此功能的绘制') } }
-
src/histogram.js
export function drawHistogram(speed) { const defaultParam = this.defaultParam const ctx = this.ctx const bottomPad = 30 const data = defaultParam.data const ht = defaultParam.ht const maxPoint = defaultParam.maxPoint const len = data.length let rectHeight = this._canvas.height - bottomPad for (let i = 0; i < len; i++) { let yVal = data[i].yVal * speed let axisY = ht - ht * (yVal / maxPoint) - bottomPad const averageNum = defaultParam.wid / data.length - 1 let axisX = i * averageNum + defaultParam.x ctx.save() ctx.beginPath() ctx.fillStyle = defaultParam.hisColor[i] ctx.fillRect(axisX - 15, axisY, 30, rectHeight - axisY) ctx.restore() } }