canvas实现睡眠波2.0,框架兼容,支持大部分PC端

// 使用方式 import { sleepWindow } from '@utils/sleepWave' // 获取dom然后绑定 const app = document.getElementById('app'); sleepWindow.init(app); // 在需要绘制的时候setOptions sleepWindow.setOption({ types: ['1', '2', '3', '4'], data: [ { label: 'test', value: 22 }, { label: '1', value: 12}, { label: '2', value: 1 }, ], })

1|0基本效果

2|0更新说明

  • 老版本的代码是用vue2写了tooltip,导致很多码友找我要兼容其他框架的demo,然后感觉自己这个demo明明是仿echarts风格写的,却完全不灵活,纯属是个半成品,所以回炉重造了一下。
  • 更新内容:
    • 写法更新,纯js,只依赖lodash,无多余累赘内容,应该能兼容大部分框架,但是由于有大量window相关操作,所以小程序和app应该暂时不兼容,uniapp应该支持。有问题、有bug都可以cue我,如果想要加入我一起维护,更是求之不得。
    • 支持自定义,包括:Y轴label、文字对齐、分类;X轴刻度、formatter自定义、最小值、最大值、splitNumber;线宽;块高、弧度、点击事件;颜色;动画;tooltip自定义;
    • 异常处理做得不是很完善,使用时注意边界值、异常值

3|0代码

// index.js import { SleepWave } from './sleepWave.js'; export const sleepWindow = new SleepWave(); window.$sleepWindow = sleepWindow;
// canvasEvent.js class Event { constructor () { this._listener = {} } /** * 监听 */ on (type, handler) { if (!this._listener[type]) { this._listener[type] = [] } this._listener[type].push(handler) } /** *触发 */ emit (type, event) { if (event == null || event.type == null) { return } const typeListeners = this._listener[type] if (!typeListeners) return for (let index = 0; index < typeListeners.length; index++) { const handler = typeListeners[index] handler(event) } } /** * 删除 */ remove (type, handler) { if (!handler) { this._listener[type] = [] return } if (this._listener[type]) { const listeners = this._listeners[type] for (let i = 0, len = listeners.length; i < len; i++) { if (listeners[i] === handler) { listeners.splice(i, 1) } } } } } export default Event
// sleepWave.js import { SleepWaveBlock } from './sleepBlock' import { SleepWaveLine } from './sleepLine' import { Tooltip } from './tooltip' import { throttle, isArray, isObject, uniq, isUndefined } from 'lodash' import { randomColor, getIdealTenTimesNum } from './tool/utils' export class SleepWave { $el = null canvas = null ctx = null tooltip = null eventList = ['click', 'mousemove'] children = [] // 存放子元素 // 图表显示相关主要数据 types = ['深睡', '浅睡', '眼动', '清醒'] colors = ['#FFB9E4', '#D3B4FF', '#9345FF', '#5701CD'] data = [] // 可配置基本属性 axisColor = '#EAEAEA' axisLabelColor = '#EAEAEA' axisWidthY = 50 // Y轴的宽度 axisHeightX = 20 // X轴的高度 paddingRight = 10 // 右侧留出的显示空间宽度 blockHeight = 0 // 方块的高度,不配置就默认取disY - 4 * borderRadius borderRadius = 12 // 方形弧度 lineWidth = 2 // 连线宽度 labelAlign = 'right' // Y轴label文字对齐 left or right showAxisLabelX = false // 是否显示X轴坐标刻度 maxX = 800 // X轴最大值 minX = 0 // X轴最小值 splitNumber = 5 // X轴刻度分割数 markHeightX = 5 // X轴刻度高度 animation = true // 是否开启动画,默认开启 drawerFrequency = 20 // 动画帧数,决定动画快慢,默认20帧 tooltipFormatter = null // 自定义tooltip xAxisFormatter = null // 自定义x轴label xAxisMarkVisible = true // 是否显示X轴刻度 yAxisLabelVisible = true // 是否显示Y轴type // 不可配置的属性 disX = 1 // 单位x的宽度 disY = 1 // 单位y的高度 // 初始化 init(dom) { this.$el = dom this.$el.classList.add('sleep-wave-canvas') document.styleSheets[0].insertRule('.sleep-wave-canvas { position: relative; overflow: auto }', 0); document.styleSheets[0].insertRule('.sleep-wave-canvas::-webkit-scrollbar { display:none }', 1); this.canvas = document.createElement('canvas') this.canvas.width = this.$el.clientWidth this.canvas.height = this.$el.clientHeight this.$el.appendChild(this.canvas) this.ctx = this.canvas.getContext('2d') } initTooltip() { this.tooltip = new Proxy(new Tooltip(this.$el, this.tooltipFormatter), { get: function (obj, prop) { return prop in obj ? obj[prop] : undefined }, set: function (obj, prop, value) { if (prop === 'visible') { if (value && !obj.visible) { obj.show() } if (!value && obj.visible) { obj.hide() } } obj[prop] = value if (prop === 'blockInfo') { obj.updateSlotDom() } return true } }) } // 设置option setOption(option) { // 清空整个画板 this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height) // 自定义属性 const { data, types, colors, tooltipFormatter, xAxisFormatter } = option this.tooltipFormatter = tooltipFormatter this.xAxisFormatter = xAxisFormatter this.setData(data, types, colors) this.initTooltip() this.borderRadius = option.borderRadius || this.borderRadius this.lineWidth = option.lineWidth || this.lineWidth this.splitNumber = option.splitNumber || this.splitNumber this.labelAlign = option.labelAlign || this.labelAlign this.markHeightX = option.markHeightX || this.markHeightX this.axisWidthY = option.axisWidthY || this.axisWidthY this.axisHeightX = option.axisHeightX || this.axisHeightX this.paddingRight = option.paddingRight || this.paddingRight this.blockHeight = option.blockHeight || this.blockHeight this.drawerFrequency = option.drawerFrequency || this.drawerFrequency this.animation = isUndefined(option.animation) ? this.animation : option.animation this.xAxisMarkVisible = isUndefined(option.xAxisMarkVisible) ? this.xAxisMarkVisible : option.xAxisMarkVisible this.yAxisLabelVisible = isUndefined(option.yAxisLabelVisible) ? this.yAxisLabelVisible : option.yAxisLabelVisible const dataMax = this.data.reduce((sum, item) => sum + item[0], 0) this.maxX = option.maxX || getIdealTenTimesNum(dataMax, this.splitNumber) // 根据maxX的字符长度增加paddingRight let maxXWidth = '' if (typeof this.xAxisFormatter === 'function') { maxXWidth = this.ctx.measureText(String(this.xAxisFormatter(this.maxX))).width } else { maxXWidth = this.ctx.measureText(String(this.maxX)).width } this.paddingRight += maxXWidth // 更新相关属性 this.updateProperties() // 开始绘制 this.draw() // 初始化事件 this.initEvent() } // 根据data类型取值 setData(data, types, colors) { if (types) { this.types = types } if (colors) { this.colors = colors } if (data.length <= 0) return // 如果数组元素的类型也是数组,则用第一位做时长,第二位做Y轴类型(0开始的整数) if (isArray(data[0])) { // 没传types则为index if (!types) { this.types = uniq(data.map(item => item[1])) } this.data = data.map(item => ([ item[0], this.types.indexOf(item[1]) ])).filter(item => this.types.indexOf(item[1]) > -1) } // 如果数组元素的类型是对象,则用 { label, value } 来做类型,并更新types else if (isObject(data[0])) { // 没传types则根据数据中包含的label来确定types if (!types) { this.types = uniq(data.map(item => item.label)) } this.data = data.map(item => ([ item.value, this.types.indexOf(item.label), ])).filter(item => item[1] > -1) } else { throw new TypeError('data is not valid in setOption') } if (this.colors.length < this.types.length) { for (let i = this.colors.length; i < this.types.length; i ++) { this.colors[i] = randomColor() } } } // 更新相关属性 updateProperties() { this.disX = (this.canvas.width - this.axisWidthY - this.paddingRight) / (this.maxX - this.minX) this.disY = (this.canvas.height - this.axisHeightX) / this.types.length this.blockHeight = this.blockHeight || (this.disY - 4 * this.borderRadius) // borderRadius最大为块高度的一半也就是绘图区高度的1/8 this.borderRadius = Math.min(this.disY / 4, this.borderRadius) } // canvas事件 initEvent() { this.eventList.forEach(eventName => { // 高频率事件需要防抖 this.canvas.addEventListener(eventName, throttle(this.handleEvent, 40)) }) } handleEvent = (event) => { this.children .filter(shape => shape.isEventInRegion(event.x, event.y)) .forEach(shape => shape.emit(event.type, event)) } // 绘制 draw() { // 绘制坐标 this.drawAxis() // 绘制Y轴刻度 this.drawAxisYLabel() // 绘制X轴刻度和值 this.drawAxisXMark() // 绘制波形 this.drawWave() } // 绘制波形 drawWave() { this.data.reduce((pre, item) => { // 先画方形自己 // 方框四个点位置 const startX = pre ? pre[0] : this.axisWidthY const endX = startX + item[0] * this.disX const startY = (this.types.length - item[1] - 0.5) * this.disY - this.borderRadius - this.blockHeight / 2 const endY = startY + this.blockHeight + 2 * this.borderRadius // 表示宽度是否够画直线,如果不够直接连着贝塞尔 const hasStraight = (endX - startX) > 2 * this.borderRadius // 够不够画直线都要计算那个点的位置,X轴差值 const nowRadius = hasStraight ? this.borderRadius : (endX - startX) / 2 const { colors, types } = this // block块对象 const block = new SleepWaveBlock(this, { startX, startY, endX, endY, nowRadius, value: item[0], title: types[item[1]], color: colors[item[1]], }) block.draw() this.children.push(block) // 再连线 if (pre && pre[1] !== item[1]) { // 一样高得情况不画线 const line = new SleepWaveLine(this, { pre, startX, startY, endY, nowType: item[1], nowRadius, }) line.draw() } return [endX, item[1], nowRadius] // 下一个pre为当前点的endx和item[1],注意不是x不是item[0], 这里的第三个是上一次绘图的弧度 }, null) } // 绘制坐标 drawAxis() { this.ctx.beginPath() this.ctx.lineWidth = 0.5 this.ctx.strokeStyle = this.axisColor this.ctx.moveTo(this.axisWidthY - 2, 0) this.ctx.lineTo(this.axisWidthY - 2, this.canvas.height - this.axisHeightX) this.ctx.lineTo(this.canvas.width - this.paddingRight, this.canvas.height - this.axisHeightX) this.ctx.stroke() } // 绘制Y轴type drawAxisYLabel() { this.ctx.font = '14px PingFang SC' this.ctx.fillStyle = this.axisLabelColor if (!this.yAxisLabelVisible) { return } const typesLen = this.types.length this.types.forEach((label, index) => { const textWidth = this.ctx.measureText(label).width // 问题过长还要省略显示 if (textWidth > this.axisWidthY - 12) { let temp = '' const ellipseWidth = this.ctx.measureText('...').width for (let i = 0; i < label.length; i ++) { if (this.ctx.measureText(label.slice(0, i + 1)).width + ellipseWidth <= this.axisWidthY - 12) { temp = label.slice(0, i + 1) } } const labelX = this.labelAlign === 'left' ? 4 : this.axisWidthY - this.ctx.measureText(temp + '...').width - 8 this.ctx.fillText(temp + '...', labelX, this.disY * (typesLen - index - 0.5)) } else { const labelX = this.labelAlign === 'left' ? 4 : this.axisWidthY - textWidth - 8 this.ctx.fillText(label, labelX, this.disY * (typesLen - index - 0.5)) } }) } // 绘制X轴刻度和值 drawAxisXMark() { const markStartX = this.axisWidthY - 2, markY = this.canvas.height - this.axisHeightX - 1 const markDis = (this.canvas.width - markStartX - this.paddingRight) / this.splitNumber const markValDis = (this.maxX - this.minX) / this.splitNumber this.ctx.font = '12px PingFang SC' this.ctx.fillStyle = this.axisLabelColor // 最小刻度值先画 const minText = String(this.minX) const minTextWidth = this.ctx.measureText(minText).width this.ctx.fillText(minText, markStartX - minTextWidth / 2, markY + 14) for (let i = 1; i <= this.splitNumber; i ++) { // 是否绘制刻度 if (this.xAxisMarkVisible) { this.ctx.beginPath() this.ctx.lineWidth = 0.5 this.ctx.strokeStyle = this.axisColor this.ctx.moveTo(markStartX + i * markDis - 1, markY) this.ctx.lineTo(markStartX + i * markDis - 1, markY - this.markHeightX) this.ctx.stroke() } this.ctx.font = '12px PingFang SC' this.ctx.fillStyle = this.axisLabelColor let markText = '' if (typeof this.xAxisFormatter === 'function') { markText = String(this.xAxisFormatter(this.minX + i * markValDis)) } else { markText = String(this.minX + i * markValDis) } const markTextWidth = this.ctx.measureText(markText).width this.ctx.fillText(markText, markStartX + i * markDis - markTextWidth / 2 - 1, markY + 14) } } }
// sleepBlock.js import Event from './canvasEvent' export class SleepWaveBlock extends Event { _parent = null point = { x: 0, y: 0 } mainWidth = 0 // 画布宽度 mainHeight = 0 // 画布高度 position = '' // tooltip是否固定定位,left || right || top || bottom,没有则是跟随鼠标移动 // 动画属性 drawerFrequency = 20 // 20帧动画 drawerIndex = 0 drawerSeg = 0 constructor (canvas, opts) { super() this._parent = canvas this.ctx = canvas.ctx this.mainWidth = this._parent.canvas.width this.mainHeight = this._parent.canvas.height // 基本属性直接由构造器传进来 Object.assign(this, opts) this.on('click', this.handleClick) this.on('mousemove', this.handleMouseMove) } commonHandler = () => { document.body.style.cursor = 'pointer' this._parent.tooltip.visible = true } // 点击事件自定义 handleClick = (e) => { this.commonHandler() } // 移动事件 handleMouseMove = (e) => { this.commonHandler() setTimeout(() => { // tooltip刚显示时还获取不到$el,下一帧再处理数据 this.setPosX() this.setPosY() this._parent.tooltip.blockInfo = { color: this.color, title: this.title, value: this.value, } }) } // 确认x位置 setPosX() { const { tooltip, axisWidthY } = this._parent const boxWidth = this._parent.tooltip.$el.clientWidth if (this.position === 'left') { tooltip.position.x = axisWidthY } else if (this.position === 'right') { tooltip.position.x = this.mainWidth - boxWidth - 12 } else { tooltip.position.x = this.point.x + boxWidth + 32 >= this.mainWidth ? this.mainWidth - boxWidth - 12 : this.point.x + 20 } } // 确认y位置 setPosY() { const { tooltip, axisHeightX } = this._parent const boxHeight = this._parent.tooltip.$el.clientHeight if (this.position === 'top') { tooltip.position.y = 8 } else if (this.position === 'bottom') { tooltip.position.y = this.mainHeight - axisHeightX - boxHeight } else { tooltip.position.y = this.point.y - boxHeight - 12 < 0 ? 8 : this.point.y - boxHeight - 8 } } draw () { const { startX, endX, nowRadius } = this const { drawerFrequency, animation } = this._parent this.drawerIndex = animation ? 0 : drawerFrequency this.drawerSeg = (endX - startX - 2 * nowRadius) / drawerFrequency / 2 this.drawOneTime() } drawOneTime() { const { drawerFrequency, borderRadius } = this._parent if (this.drawerIndex > drawerFrequency) { return } const { startY, endY, nowRadius } = this // 表示宽度是否够画直线,如果不够直接连着贝塞尔 const dis = (drawerFrequency - this.drawerIndex) * this.drawerSeg const startX = this.startX + dis const endX = this.endX - dis const hasStraight = (endX - startX) > 2 * borderRadius this.ctx.beginPath() this.ctx.lineWidth = this._parent.lineWidth this.ctx.strokeStyle = this.color // left border this.ctx.moveTo(startX, startY + borderRadius) this.ctx.lineTo(startX, endY - borderRadius) this.ctx.quadraticCurveTo(startX, endY, startX + nowRadius, endY) // bottom border if (hasStraight) { // 是否需要画直线 this.ctx.lineTo(endX - borderRadius, endY) } // right border this.ctx.quadraticCurveTo(endX, endY, endX, endY - borderRadius) this.ctx.lineTo(endX, startY + borderRadius) this.ctx.quadraticCurveTo(endX, startY, endX - nowRadius, startY) // top border if (hasStraight) { // 是否需要画直线 this.ctx.lineTo(startX + borderRadius, startY) } this.ctx.quadraticCurveTo(startX, startY, startX, startY + borderRadius) this.ctx.stroke() this.ctx.fillStyle = this.color this.ctx.fill() this.drawerIndex += 1 requestAnimationFrame(() => this.drawOneTime()) } // 事件触发的位置是不是当前block位置 isEventInRegion (clientX, clientY) { this.point = this.getEventPosition(clientX, clientY) // 计算基于canvas坐标系的坐标值 const { x, y } = this.point const width = this.endX - this.startX const height = this.endY - this.startY if (this.startX < x && x < this.startX + width && this.startY < y && y < this.startY + height) { return true } document.body.style.cursor = 'default' this._parent.tooltip.visible = false return false } getEventPosition (clientX, clientY) { const box = this._parent.canvas.getBoundingClientRect() return { x: clientX - box.left, y: clientY - box.top } } }
// sleepLine.js export class SleepWaveLine { _parent = null // 动画属性 drawerIndex = 0 drawerSeg = 0 // 绘制直线时Y轴每次绘制长度 linearGradient = '' // 渐变色 lineX = 0 // 连线X位置 lineStartY = 0 // 最终的Y位置 lineEndY = 0 // 最终的Y位置 dir = true // 0: 一样高(这种情况理论不应该存在),true: 从下到上,false:从上到下 preCurveSegX = 0 // 与上一个block连接的三角,绘制时的X轴方向每次绘制长度 curveSegX = 0 // 当前block绘制三角时X轴方向每次绘制长度 curveSegY = 0 // 绘制三角时Y轴方向每次绘制长度 idealCurveRadius = 0 // 理想的圆弧 constructor (canvas, opts) { this._parent = canvas this.ctx = canvas.ctx Object.assign(this, opts) } draw () { const { startX, startY, endY, pre, nowType, nowRadius } = this const { disY, types, blockHeight, animation, drawerFrequency, borderRadius, colors } = this._parent this.drawerIndex = animation ? 0 : drawerFrequency this.lineX = startX // 连线X位置 // 需要判断是从下到上还是从上到下,注意canvas Y轴是上小下大,这里的上下是视觉的上下 this.dir = pre[1] < nowType // 0: 一样高(这种情况理论不应该存在),true:从下到上,false:从上到下 // start到end是从左到右 this.lineStartY = this.dir ? ((types.length - pre[1] - 0.5) * disY - borderRadius - blockHeight / 2) : ((types.length - pre[1] - 0.5) * disY + borderRadius + blockHeight / 2) // 连线起点Y this.lineEndY = this.dir ? endY : startY// 连线终点Y this.linearGradient = this.ctx.createLinearGradient(this.lineX, this.lineStartY, this.lineX, this.lineEndY) // 线的渐变色 this.linearGradient.addColorStop(0, colors[pre[1]]) this.linearGradient.addColorStop(1, colors[nowType]) this.drawerSeg = Math.abs(this.lineEndY - this.lineStartY) / drawerFrequency / 2 this.preCurveSegX = pre[2] / drawerFrequency this.curveSegX = nowRadius / drawerFrequency this.curveSegY = borderRadius / drawerFrequency this.idealCurveRadius = (disY - blockHeight) / 2 // 先画直线 this.drawLineOneTime() } // 绘制直线 drawLineOneTime() { const { drawerFrequency, borderRadius, animation } = this._parent if (this.drawerIndex > drawerFrequency) { // 直线画完了,重置drawerIndex开始画曲线 this.drawerIndex = animation ? 0 : drawerFrequency this.drawCurveLine() return } const dis = this.drawerSeg * this.drawerIndex const startY = (this.lineStartY + this.lineEndY) / 2 + (this.dir ? dis : -dis) const endY = (this.lineStartY + this.lineEndY) / 2 + ((this.dir ? -dis : dis)) this.ctx.beginPath() this.ctx.lineWidth = this.lineWidth this.ctx.strokeStyle = this.linearGradient this.ctx.moveTo(this.lineX, startY) this.ctx.lineTo(this.lineX, endY) this.ctx.stroke() this.drawerIndex += 1 requestAnimationFrame(() => this.drawLineOneTime()) } // 绘制曲线 drawCurveLine() { const { drawerFrequency, animation, borderRadius, lineWidth } = this._parent if (this.drawerIndex > drawerFrequency) { return } const { lineX, pre, dir, nowRadius, lineStartY, lineEndY, preCurveSegX, curveSegX, curveSegY, linearGradient, idealCurveRadius } = this // start左三角 const curveStartLineX = lineX + lineWidth / 2 this.ctx.beginPath() this.ctx.lineWidth = lineWidth / 2 this.ctx.strokeStyle = linearGradient this.ctx.moveTo(curveStartLineX, lineStartY + (dir ? -idealCurveRadius : idealCurveRadius)) this.ctx.quadraticCurveTo( curveStartLineX, lineStartY + (dir ? -lineWidth / 2 : lineWidth / 2), curveStartLineX - preCurveSegX * this.drawerIndex, lineStartY + (dir ? -lineWidth / 2 : lineWidth / 2) ) this.ctx.quadraticCurveTo( curveStartLineX, lineStartY + (dir ? curveSegY * this.drawerIndex / 2 : -(curveSegY * this.drawerIndex) / 2), curveStartLineX, lineStartY + (dir ? curveSegY * this.drawerIndex : -(curveSegY * this.drawerIndex)) ) this.ctx.closePath() this.ctx.fillStyle = linearGradient this.ctx.fill() // end右三角 const curveEndLineX = lineX - lineWidth / 2 this.ctx.beginPath() this.ctx.lineWidth = lineWidth / 2 this.ctx.strokeStyle = linearGradient this.ctx.moveTo(curveEndLineX, lineEndY + (dir ? idealCurveRadius : -idealCurveRadius)) this.ctx.quadraticCurveTo( curveEndLineX, lineEndY + (dir ? lineWidth / 2 : -lineWidth / 2), curveEndLineX + curveSegX * this.drawerIndex, lineEndY + (dir ? lineWidth / 2 : -lineWidth / 2) ) this.ctx.quadraticCurveTo( curveEndLineX, lineEndY + (dir ? -curveSegY * this.drawerIndex / 2 : curveSegY * this.drawerIndex / 2), curveEndLineX, lineEndY + (dir ? -curveSegY * this.drawerIndex : curveSegY * this.drawerIndex) ) this.ctx.closePath() this.ctx.fillStyle = linearGradient this.ctx.fill() this.drawerIndex += 1 requestAnimationFrame(() => this.drawCurveLine()) } }
// tooltip.js import { isNumber, isObject } from 'lodash' export class Tooltip { labels = [] values = [] parentDom = null visible = false blockInfo = {} // 插槽dom slotDom = null // tooltip最外层dom及其位置 $el = null position = new Proxy({ x: 0, y: 0 }, { get: function (obj, prop) { return prop in obj ? obj[prop] : undefined }, set: (obj, prop, value) => { if (prop !== 'x' && prop !== 'y') { throw new TypeError(`${prop} is not valid property`) return false } if (!isNumber(value)) { throw new TypeError(`${value} is not a number`) return false } obj[prop] = value this.$el.style.cssText = `display: ${this.visible ? 'block' : 'none'}; position: absolute; left: ${obj.x}px; top: ${obj.y}px;` return true }, }) constructor(parent, formatter) { this.$el = document.createElement('div') // 允许自定义tooltip if (typeof formatter === 'function') { this.updateSlotDom = () => { this.$el.innerHTML = formatter(this.blockInfo) } } else { this.slotDom = this.defaultTooltip() this.updateSlotDom() this.$el.appendChild(this.slotDom) } this.parentDom = parent this.parentDom.appendChild(this.$el) this.$el.style.cssText = 'display: none;' } defaultTooltip() { const dom = document.createElement('div') dom.style.cssText = ` background-color: white; padding: 10px 8px; border-radius: 6px; box-shadow: 3px 3px 3px 3px rgba(0, 0, 0, 0.15); ` return dom } // 刷新slotDom updateSlotDom() { const { color, title, value } = this.blockInfo this.slotDom.style.border = `2px solid ${color || 'black'}` this.slotDom.innerHTML = ` <div style="font-size: 14px; white-space: nowrap;"> ${title || '--'}: <span style="font-weight: bold; color: ${color};">${value || '--'}</span> </div> ` } show() { this.$el.style.cssText = this.$el.style.cssText.replace('display: none', 'display: block') } hide () { this.$el.style.cssText = this.$el.style.cssText.replace('display: block', 'display: none') } }
// tool/utils.js export function randomColor (type = 'hex') { const chars = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'] switch(type) { // #000000型 case 'hex': const temp1 = new Array(6).fill(0) return '#' + temp1.map(item => { const randomIndex = Math.floor(Math.random() * 16) return chars[randomIndex] }).join('') // #00000000型 case 'hexo': const temp2 = new Array(8).fill(0) return '#' + temp2.map(item => { const randomIndex = Math.floor(Math.random() * 16) return chars[randomIndex] }).join('') // rgb(255,255,255)型 case 'rgb': const temp3 = new Array(3).fill(0) return 'rgb(' + temp3.map(item => { return Math.floor(Math.random() * 255) }).join(',') + ')' // rgba(255,255,255,1)型 case 'rgba': const temp4 = new Array(3).fill(0) const randomOpacity = Math.random().toFixed(2) const rgb = temp4.map(item => { return Math.floor(Math.random() * 255) }).join(',') return `rgba(${rgb},${randomOpacity})` } } export function getIdealTenTimesNum (max, split) { const str = String(max) // 幂次计算 const e = Number(str[0]) < 8 ? str.length - 1 : str.length const inc = Math.pow(10, e) // 取10的e次幂 let ideal = inc while (ideal % split !== 0 || ideal < max) { // 如果ideal不能整除split,则继续增加ideal ideal += inc } return ideal }
// webpack.config.js const path = require('path'); module.exports = { mode: 'development', // 入口,是一个对象 entry: { sleepWave: './src/index.js' }, // 输出 output: { filename: 'index.js', path: path.resolve(__dirname, './sleepWave'), library: { name: 'sleepWave', type: 'umd', }, }, module: { rules: [ { test: /\.js$/, exclude: /(node_moduless)/, // 排除掉node_module目录 use: { loader: 'babel-loader', options: { presets: ['@babel/preset-env'] } } }, { test: /.css$/, use: [ 'style-loader', // 将css-loader转换后的结果放到style标签里面 'css-loader' // 先执行css-loader,而且是从后往前执行,所以需要放到下面 ] } ] }, }
// package.json { "name": "sleep-wave", "version": "1.0.0", "description": "#### 介绍 睡眠波", "main": "sleepWave/index.js", "scripts": { "dev": "webpack-dev-server --config webpack.config.js", "build": "webpack --config webpack.config.js" }, "author": "Mizuki", "license": "ISC", "dependencies": { "lodash": "^4.17.21" }, "devDependencies": { "@babel/core": "^7.15.5", "@babel/plugin-transform-runtime": "^7.15.0", "@babel/polyfill": "^7.12.1", "@babel/preset-env": "^7.15.6", "babel-core": "^6.26.3", "babel-eslint": "^10.1.0", "babel-loader": "^8.2.2", "eslint": "^8.16.0", "eslint-plugin-vue": "^9.0.1", "webpack": "^5.52.1", "webpack-cli": "^4.8.0", "webpack-dev-server": "^4.2.1", "webpack-obfuscator": "^3.5.1" } }
// 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>Document</title> <script src="./sleepWave/index.js"></script> <style> html{ background-color: #222; } #app{ width: 100%; height: 500px; } </style> </head> <body> <div id="app"></div> <script> const app = document.getElementById('app'); $sleepWindow.init(app); $sleepWindow.setOption({ types: ['1', '2', '3', '4', 'xixixixixixixixixixi', 'test'], data: [ { label: 'test', value: 22 }, { label: '1', value: 12}, { label: '2', value: 1 }, { label: '3', value: 2}, { label: 'xixixixixixixixixixi', value: 5 }, { label: 'shs', value: 3 }, { label: '2', value: 12}, { label: '2', value: 22}, { label: 'shasds', value: 3 }, ], borderRadius: 16, blockHeight: 2, // animation: false, // axisWidthY: 10, // yAxisLabelVisible: false, // xAxisMarkVisible: false, // xAxisFormatter: (val) => { // return val * 100000000000000 // } // tooltipFormatter: (blockInfo) => { // return ` // <div style="background-color: white;"> // ${blockInfo.title} // ${blockInfo.value} // ${blockInfo.color} // </div> // ` // }, // maxX: 100, // splitNumber: 6, }) </script> </body> </html>

__EOF__

本文作者Mizuki
本文链接https://www.cnblogs.com/mizuki-vone/p/18376398.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   Mizuki-Vone  阅读(270)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· 清华大学推出第四讲使用 DeepSeek + DeepResearch 让科研像聊天一样简单!
· 实操Deepseek接入个人知识库
· CSnakes vs Python.NET:高效嵌入与灵活互通的跨语言方案对比
· Plotly.NET 一个为 .NET 打造的强大开源交互式图表库
点击右上角即可分享
微信分享提示