dada-datav-vue3
Version:
dataV组件vue3版本
351 lines (260 loc) • 8.61 kB
JavaScript
import color from '@/utils/color'
import bezierCurve from '@/utils/bezier-curve'
import { deepClone } from '../plugin/util'
import allGraph from '../config/graphs'
import Graph from './graph.class'
/**
* @description Class of CRender
* @param {Object} canvas Canvas DOM
* @return {CRender} Instance of CRender
*/
export default class CRender {
constructor (canvas) {
if (!canvas) {
console.error('CRender Missing parameters!')
return
}
const ctx = canvas.getContext('2d')
const { clientWidth, clientHeight } = canvas
const area = [clientWidth, clientHeight]
canvas.setAttribute('width', clientWidth)
canvas.setAttribute('height', clientHeight)
/**
* @description Context of the canvas
* @type {Object}
* @example ctx = canvas.getContext('2d')
*/
this.ctx = ctx
/**
* @description Width and height of the canvas
* @type {Array}
* @example area = [300,100]
*/
this.area = area
/**
* @description Whether render is in animation rendering
* @type {Boolean}
* @example animationStatus = true|false
*/
this.animationStatus = false
/**
* @description Added graph
* @type {[Graph]}
* @example graphs = [Graph, Graph, ...]
*/
this.graphs = []
/**
* @description Color plugin
* @type {Object}
* @link https://github.com/jiaming743/color
*/
this.color = color
/**
* @description Bezier Curve plugin
* @type {Object}
* @link https://github.com/jiaming743/BezierCurve
*/
this.bezierCurve = bezierCurve
// bind event handler
canvas.addEventListener('mousedown', mouseDown.bind(this))
canvas.addEventListener('mousemove', mouseMove.bind(this))
canvas.addEventListener('mouseup', mouseUp.bind(this))
}
}
/**
* @description Clear canvas drawing area
* @return {Undefined} Void
*/
CRender.prototype.clearArea = function () {
const { area } = this
this.ctx.clearRect(0, 0, ...area)
}
/**
* @description Add graph to render
* @param {Object} config Graph configuration
* @return {Graph} Graph instance
*/
CRender.prototype.add = function (config = {}) {
const { name } = config
if (!name) {
console.error('add Missing parameters!')
return
}
const graphConfig = allGraph.get(name)
if (!graphConfig) {
console.warn('No corresponding graph configuration found!')
return
}
const graph = new Graph(graphConfig, config)
if (!graph.validator(graph)) return
graph.render = this
this.graphs.push(graph)
this.sortGraphsByIndex()
this.drawAllGraph()
return graph
}
/**
* @description Sort the graph by index
* @return {Undefined} Void
*/
CRender.prototype.sortGraphsByIndex = function () {
const { graphs } = this
graphs.sort((a, b) => {
if (a.index > b.index) return 1
if (a.index === b.index) return 0
if (a.index < b.index) return -1
})
}
/**
* @description Delete graph in render
* @param {Graph} graph The graph to be deleted
* @return {Undefined} Void
*/
CRender.prototype.delGraph = function (graph) {
if (typeof graph.delProcessor !== 'function') return
graph.delProcessor(this)
this.graphs = this.graphs.filter(graph => graph)
this.drawAllGraph()
}
/**
* @description Delete all graph in render
* @return {Undefined} Void
*/
CRender.prototype.delAllGraph = function () {
this.graphs.forEach(graph => graph.delProcessor(this))
this.graphs = this.graphs.filter(graph => graph)
this.drawAllGraph()
}
/**
* @description Draw all the graphs in the render
* @return {Undefined} Void
*/
CRender.prototype.drawAllGraph = function () {
this.clearArea()
this.graphs.filter(graph => graph && graph.visible).forEach(graph => graph.drawProcessor(this, graph))
}
/**
* @description Animate the graph whose animation queue is not empty
* and the animationPause is equal to false
* @return {Promise} Animation Promise
*/
CRender.prototype.launchAnimation = function () {
const { animationStatus } = this
if (animationStatus) return
this.animationStatus = true
return new Promise(resolve => {
animation.call(this, () => {
this.animationStatus = false
resolve()
}, Date.now())
})
}
/**
* @description Try to animate every graph
* @param {Function} callback Callback in animation end
* @param {Number} timeStamp Time stamp of animation start
* @return {Undefined} Void
*/
function animation (callback, timeStamp) {
const { graphs } = this
if (!animationAble(graphs)) {
callback()
return
}
graphs.forEach(graph => graph.turnNextAnimationFrame(timeStamp))
this.drawAllGraph()
requestAnimationFrame(animation.bind(this, callback, timeStamp))
}
/**
* @description Find if there are graph that can be animated
* @param {[Graph]} graphs
* @return {Boolean}
*/
function animationAble (graphs) {
return graphs.find(graph => !graph.animationPause && graph.animationFrameState.length)
}
/**
* @description Handler of CRender mousedown event
* @return {Undefined} Void
*/
function mouseDown (e) {
const { graphs } = this
const hoverGraph = graphs.find(graph => graph.status === 'hover')
if (!hoverGraph) return
hoverGraph.status = 'active'
}
/**
* @description Handler of CRender mousemove event
* @return {Undefined} Void
*/
function mouseMove (e) {
const { offsetX, offsetY } = e
const position = [offsetX, offsetY]
const { graphs } = this
const activeGraph = graphs.find(graph => (graph.status === 'active' || graph.status === 'drag'))
if (activeGraph) {
if (!activeGraph.drag) return
if (typeof activeGraph.move !== 'function') {
console.error('No move method is provided, cannot be dragged!')
return
}
activeGraph.moveProcessor(e)
activeGraph.status = 'drag'
return
}
const hoverGraph = graphs.find(graph => graph.status === 'hover')
const hoverAbleGraphs = graphs.filter(graph =>
(graph.hover && (typeof graph.hoverCheck === 'function' || graph.hoverRect)))
const hoveredGraph = hoverAbleGraphs.find(graph => graph.hoverCheckProcessor(position, graph))
if (hoveredGraph) {
document.body.style.cursor = hoveredGraph.style.hoverCursor
} else {
document.body.style.cursor = 'default'
}
let [hoverGraphMouseOuterIsFun, hoveredGraphMouseEnterIsFun] = [false, false]
if (hoverGraph) hoverGraphMouseOuterIsFun = typeof hoverGraph.mouseOuter === 'function'
if (hoveredGraph) hoveredGraphMouseEnterIsFun = typeof hoveredGraph.mouseEnter === 'function'
if (!hoveredGraph && !hoverGraph) return
if (!hoveredGraph && hoverGraph) {
if (hoverGraphMouseOuterIsFun) hoverGraph.mouseOuter(e, hoverGraph)
hoverGraph.status = 'static'
return
}
if (hoveredGraph && hoveredGraph === hoverGraph) return
if (hoveredGraph && !hoverGraph) {
if (hoveredGraphMouseEnterIsFun) hoveredGraph.mouseEnter(e, hoveredGraph)
hoveredGraph.status = 'hover'
return
}
if (hoveredGraph && hoverGraph && hoveredGraph !== hoverGraph) {
if (hoverGraphMouseOuterIsFun) hoverGraph.mouseOuter(e, hoverGraph)
hoverGraph.status = 'static'
if (hoveredGraphMouseEnterIsFun) hoveredGraph.mouseEnter(e, hoveredGraph)
hoveredGraph.status = 'hover'
}
}
/**
* @description Handler of CRender mouseup event
* @return {Undefined} Void
*/
function mouseUp (e) {
const { graphs } = this
const activeGraph = graphs.find(graph => graph.status === 'active')
const dragGraph = graphs.find(graph => graph.status === 'drag')
if (activeGraph && typeof activeGraph.click === 'function') activeGraph.click(e, activeGraph)
graphs.forEach(graph => graph && (graph.status = 'static'))
if (activeGraph) activeGraph.status = 'hover'
if (dragGraph) dragGraph.status = 'hover'
}
/**
* @description Clone Graph
* @param {Graph} graph The target to be cloned
* @return {Graph} Cloned graph
*/
CRender.prototype.clone = function (graph) {
const style = graph.style.getStyle()
let clonedGraph = { ...graph, style }
delete clonedGraph.render
clonedGraph = deepClone(clonedGraph, true)
return this.add(clonedGraph)
}