123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377 |
- import Style from './style.class'
- import transition from '../transition'
- import {
- deepClone,
- getRotatePointPos,
- getScalePointPos,
- getTranslatePointPos,
- checkPointIsInRect
- } from '../plugin/util'
- /**
- * @description Class Graph
- * @param {Object} graph Graph default configuration
- * @param {Object} config Graph config
- * @return {Graph} Instance of Graph
- */
- export default class Graph {
- constructor (graph, config) {
- config = deepClone(config, true)
-
- config = {animationFrame:24,...config};
-
- const defaultConfig = {
- /**
- * @description Weather to render graph
- * @type {Boolean}
- * @default visible = true
- */
- visible: true,
- /**
- * @description Whether to enable drag
- * @type {Boolean}
- * @default drag = false
- */
- drag: false,
- /**
- * @description Whether to enable hover
- * @type {Boolean}
- * @default hover = false
- */
- hover: false,
- /**
- * @description Graph rendering index
- * Give priority to index high graph in rendering
- * @type {Number}
- * @example index = 1
- */
- index: 1,
- /**
- * @description Animation delay time(ms)
- * @type {Number}
- * @default animationDelay = 0
- */
- animationDelay: 0,
- /**
- * @description Number of animation frames
- * @type {Number}
- * @default animationFrame = 30
- */
- animationFrame: 30,
- /**
- * @description Animation dynamic curve (Supported by transition)
- * @type {String}
- * @default animationCurve = 'linear'
- * @link https://github.com/jiaming743/Transition
- */
- animationCurve: 'linear',
- /**
- * @description Weather to pause graph animation
- * @type {Boolean}
- * @default animationPause = false
- */
- animationPause: false,
- /**
- * @description Rectangular hover detection zone
- * Use this method for hover detection first
- * @type {Null|Array}
- * @default hoverRect = null
- * @example hoverRect = [0, 0, 100, 100] // [Rect start x, y, Rect width, height]
- */
- hoverRect: null,
- /**
- * @description Mouse enter event handler
- * @type {Function|Null}
- * @default mouseEnter = null
- */
- mouseEnter: null,
- /**
- * @description Mouse outer event handler
- * @type {Function|Null}
- * @default mouseOuter = null
- */
- mouseOuter: null,
- /**
- * @description Mouse click event handler
- * @type {Function|Null}
- * @default click = null
- */
- click: null
- }
- const configAbleNot = {
- status: 'static',
- animationRoot: [],
- animationKeys: [],
- animationFrameState: [],
- cache: {}
- }
- if (!config.shape) config.shape = {}
- if (!config.style) config.style = {}
- const shape = Object.assign({}, graph.shape, config.shape)
- Object.assign(defaultConfig, config, configAbleNot)
- Object.assign(this, graph, defaultConfig)
- this.shape = shape
- this.style = new Style(config.style)
- this.addedProcessor()
- }
- }
- /**
- * @description Processor of added
- * @return {Undefined} Void
- */
- Graph.prototype.addedProcessor = function () {
- if (typeof this.setGraphCenter === 'function') this.setGraphCenter(null, this)
- // The life cycle 'added"
- if (typeof this.added === 'function') this.added(this)
- }
- /**
- * @description Processor of draw
- * @param {CRender} render Instance of CRender
- * @param {Graph} graph Instance of Graph
- * @return {Undefined} Void
- */
- Graph.prototype.drawProcessor = function (render, graph) {
- const { ctx } = render
- const { shape } = graph
- graph.style.initStyle(ctx,shape)
-
- if (typeof this.beforeDraw === 'function') this.beforeDraw(this, render)
- graph.draw(render, graph)
- // ctx.draw(true)
- if (typeof this.drawed === 'function') this.drawed(this, render)
- graph.style.restoreTransform(ctx)
- }
- /**
- * @description Processor of hover check
- * @param {Array} position Mouse Position
- * @param {Graph} graph Instance of Graph
- * @return {Boolean} Result of hover check
- */
- Graph.prototype.hoverCheckProcessor = function (position, { hoverRect, style, hoverCheck }) {
- const { graphCenter, rotate, scale, translate } = style
- if (graphCenter) {
- if (rotate) position = getRotatePointPos(-rotate, position, graphCenter)
- if (scale) position = getScalePointPos(scale.map(s => 1 / s), position, graphCenter)
- if (translate) position = getTranslatePointPos(translate.map(v => v * -1), position)
- }
- if (hoverRect) return checkPointIsInRect(position, ...hoverRect)
- return hoverCheck(position, this)
- }
- /**
- * @description Processor of move
- * @param {Event} e Mouse movement event
- * @return {Undefined} Void
- */
- Graph.prototype.moveProcessor = function (e) {
- this.move(e, this)
- if (typeof this.beforeMove === 'function') this.beforeMove(e, this)
- if (typeof this.setGraphCenter === 'function') this.setGraphCenter(e, this)
- if (typeof this.moved === 'function') this.moved(e, this)
- }
- /**
- * @description Update graph state
- * @param {String} attrName Updated attribute name
- * @param {Any} change Updated value
- * @return {Undefined} Void
- */
- Graph.prototype.attr = function (attrName, change = undefined) {
- if (!attrName || change === undefined) return false
- const isObject = typeof this[attrName] === 'object'
- if (isObject) change = deepClone(change, true)
- const { render } = this
- if (attrName === 'style') {
- this.style.update(change)
- } else if (isObject) {
- Object.assign(this[attrName], change)
- } else {
- this[attrName] = change
- }
- if (attrName === 'index') render.sortGraphsByIndex()
- render.drawAllGraph()
- }
- /**
- * @description Update graphics state (with animation)
- * Only shape and style attributes are supported
- * @param {String} attrName Updated attribute name
- * @param {Any} change Updated value
- * @param {Boolean} wait Whether to store the animation waiting
- * for the next animation request
- * @return {Promise} Animation Promise
- */
- Graph.prototype.animation = async function (attrName, change, wait = false) {
- if (attrName !== 'shape' && attrName !== 'style') {
- console.error('Only supported shape and style animation!')
- return
- }
- change = deepClone(change, true)
- if (attrName === 'style') this.style.colorProcessor(change)
- const changeRoot = this[attrName]
- const changeKeys = Object.keys(change)
- const beforeState = {}
- changeKeys.forEach(key => (beforeState[key] = changeRoot[key]))
- const { animationFrame, animationCurve, animationDelay } = this
- const animationFrameState = transition(animationCurve, beforeState, change, animationFrame, true)
- this.animationRoot.push(changeRoot)
- this.animationKeys.push(changeKeys)
- this.animationFrameState.push(animationFrameState)
- if (wait) return
- if (animationDelay > 0) await delay(animationDelay)
- const { render } = this
- return new Promise(async resolve => {
- await render.launchAnimation()
- resolve()
- })
- }
- /**
- * @description Extract the next frame of data from the animation queue
- * and update the graph state
- * @return {Undefined} Void
- */
- Graph.prototype.turnNextAnimationFrame = function (timeStamp) {
- const { animationDelay, animationRoot, animationKeys, animationFrameState, animationPause } = this
- if (animationPause) return
- if (Date.now() - timeStamp < animationDelay) return
- animationRoot.forEach((root, i) => {
- animationKeys[i].forEach(key => {
- root[key] = animationFrameState[i][0][key]
- })
- })
- animationFrameState.forEach((stateItem, i) => {
- stateItem.shift()
- const noFrame = stateItem.length === 0
- if (noFrame) animationRoot[i] = null
- if (noFrame) animationKeys[i] = null
- })
- this.animationFrameState = animationFrameState.filter(state => state.length)
- this.animationRoot = animationRoot.filter(root => root)
- this.animationKeys = animationKeys.filter(keys => keys)
- }
- /**
- * @description Skip to the last frame of animation
- * @return {Undefined} Void
- */
- Graph.prototype.animationEnd = function () {
- const { animationFrameState, animationKeys, animationRoot, render } = this
- animationRoot.forEach((root, i) => {
- const currentKeys = animationKeys[i]
- const lastState = animationFrameState[i].pop()
- currentKeys.forEach(key => (root[key] = lastState[key]))
- })
- this.animationFrameState = []
- this.animationKeys = []
- this.animationRoot = []
- return render.drawAllGraph()
- }
- /**
- * @description Pause animation behavior
- * @return {Undefined} Void
- */
- Graph.prototype.pauseAnimation = function () {
- this.attr('animationPause', true)
- }
- /**
- * @description Try animation behavior
- * @return {Undefined} Void
- */
- Graph.prototype.playAnimation = function () {
- const { render } = this
- this.attr('animationPause', false)
- return new Promise(async resolve => {
- await render.launchAnimation()
- resolve()
- })
- }
- /**
- * @description Processor of delete
- * @param {CRender} render Instance of CRender
- * @return {Undefined} Void
- */
- Graph.prototype.delProcessor = function (render) {
- const { graphs } = render
- const index = graphs.findIndex(graph => graph === this)
- if (index === -1) return
- if (typeof this.beforeDelete === 'function') this.beforeDelete(this)
- graphs.splice(index, 1, null)
- if (typeof this.deleted === 'function') this.deleted(this)
- }
- /**
- * @description Return a timed release Promise
- * @param {Number} time Release time
- * @return {Promise} A timed release Promise
- */
- function delay (time) {
- return new Promise(resolve => {
- setTimeout(resolve, time)
- })
- }
|