graph.class.js 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377
  1. import Style from './style.class'
  2. import transition from '../transition'
  3. import {
  4. deepClone,
  5. getRotatePointPos,
  6. getScalePointPos,
  7. getTranslatePointPos,
  8. checkPointIsInRect
  9. } from '../plugin/util'
  10. /**
  11. * @description Class Graph
  12. * @param {Object} graph Graph default configuration
  13. * @param {Object} config Graph config
  14. * @return {Graph} Instance of Graph
  15. */
  16. export default class Graph {
  17. constructor (graph, config) {
  18. config = deepClone(config, true)
  19. config = {animationFrame:24,...config};
  20. const defaultConfig = {
  21. /**
  22. * @description Weather to render graph
  23. * @type {Boolean}
  24. * @default visible = true
  25. */
  26. visible: true,
  27. /**
  28. * @description Whether to enable drag
  29. * @type {Boolean}
  30. * @default drag = false
  31. */
  32. drag: false,
  33. /**
  34. * @description Whether to enable hover
  35. * @type {Boolean}
  36. * @default hover = false
  37. */
  38. hover: false,
  39. /**
  40. * @description Graph rendering index
  41. * Give priority to index high graph in rendering
  42. * @type {Number}
  43. * @example index = 1
  44. */
  45. index: 1,
  46. /**
  47. * @description Animation delay time(ms)
  48. * @type {Number}
  49. * @default animationDelay = 0
  50. */
  51. animationDelay: 0,
  52. /**
  53. * @description Number of animation frames
  54. * @type {Number}
  55. * @default animationFrame = 30
  56. */
  57. animationFrame: 30,
  58. /**
  59. * @description Animation dynamic curve (Supported by transition)
  60. * @type {String}
  61. * @default animationCurve = 'linear'
  62. * @link https://github.com/jiaming743/Transition
  63. */
  64. animationCurve: 'linear',
  65. /**
  66. * @description Weather to pause graph animation
  67. * @type {Boolean}
  68. * @default animationPause = false
  69. */
  70. animationPause: false,
  71. /**
  72. * @description Rectangular hover detection zone
  73. * Use this method for hover detection first
  74. * @type {Null|Array}
  75. * @default hoverRect = null
  76. * @example hoverRect = [0, 0, 100, 100] // [Rect start x, y, Rect width, height]
  77. */
  78. hoverRect: null,
  79. /**
  80. * @description Mouse enter event handler
  81. * @type {Function|Null}
  82. * @default mouseEnter = null
  83. */
  84. mouseEnter: null,
  85. /**
  86. * @description Mouse outer event handler
  87. * @type {Function|Null}
  88. * @default mouseOuter = null
  89. */
  90. mouseOuter: null,
  91. /**
  92. * @description Mouse click event handler
  93. * @type {Function|Null}
  94. * @default click = null
  95. */
  96. click: null
  97. }
  98. const configAbleNot = {
  99. status: 'static',
  100. animationRoot: [],
  101. animationKeys: [],
  102. animationFrameState: [],
  103. cache: {}
  104. }
  105. if (!config.shape) config.shape = {}
  106. if (!config.style) config.style = {}
  107. const shape = Object.assign({}, graph.shape, config.shape)
  108. Object.assign(defaultConfig, config, configAbleNot)
  109. Object.assign(this, graph, defaultConfig)
  110. this.shape = shape
  111. this.style = new Style(config.style)
  112. this.addedProcessor()
  113. }
  114. }
  115. /**
  116. * @description Processor of added
  117. * @return {Undefined} Void
  118. */
  119. Graph.prototype.addedProcessor = function () {
  120. if (typeof this.setGraphCenter === 'function') this.setGraphCenter(null, this)
  121. // The life cycle 'added"
  122. if (typeof this.added === 'function') this.added(this)
  123. }
  124. /**
  125. * @description Processor of draw
  126. * @param {CRender} render Instance of CRender
  127. * @param {Graph} graph Instance of Graph
  128. * @return {Undefined} Void
  129. */
  130. Graph.prototype.drawProcessor = function (render, graph) {
  131. const { ctx } = render
  132. const { shape } = graph
  133. graph.style.initStyle(ctx,shape)
  134. if (typeof this.beforeDraw === 'function') this.beforeDraw(this, render)
  135. graph.draw(render, graph)
  136. // ctx.draw(true)
  137. if (typeof this.drawed === 'function') this.drawed(this, render)
  138. graph.style.restoreTransform(ctx)
  139. }
  140. /**
  141. * @description Processor of hover check
  142. * @param {Array} position Mouse Position
  143. * @param {Graph} graph Instance of Graph
  144. * @return {Boolean} Result of hover check
  145. */
  146. Graph.prototype.hoverCheckProcessor = function (position, { hoverRect, style, hoverCheck }) {
  147. const { graphCenter, rotate, scale, translate } = style
  148. if (graphCenter) {
  149. if (rotate) position = getRotatePointPos(-rotate, position, graphCenter)
  150. if (scale) position = getScalePointPos(scale.map(s => 1 / s), position, graphCenter)
  151. if (translate) position = getTranslatePointPos(translate.map(v => v * -1), position)
  152. }
  153. if (hoverRect) return checkPointIsInRect(position, ...hoverRect)
  154. return hoverCheck(position, this)
  155. }
  156. /**
  157. * @description Processor of move
  158. * @param {Event} e Mouse movement event
  159. * @return {Undefined} Void
  160. */
  161. Graph.prototype.moveProcessor = function (e) {
  162. this.move(e, this)
  163. if (typeof this.beforeMove === 'function') this.beforeMove(e, this)
  164. if (typeof this.setGraphCenter === 'function') this.setGraphCenter(e, this)
  165. if (typeof this.moved === 'function') this.moved(e, this)
  166. }
  167. /**
  168. * @description Update graph state
  169. * @param {String} attrName Updated attribute name
  170. * @param {Any} change Updated value
  171. * @return {Undefined} Void
  172. */
  173. Graph.prototype.attr = function (attrName, change = undefined) {
  174. if (!attrName || change === undefined) return false
  175. const isObject = typeof this[attrName] === 'object'
  176. if (isObject) change = deepClone(change, true)
  177. const { render } = this
  178. if (attrName === 'style') {
  179. this.style.update(change)
  180. } else if (isObject) {
  181. Object.assign(this[attrName], change)
  182. } else {
  183. this[attrName] = change
  184. }
  185. if (attrName === 'index') render.sortGraphsByIndex()
  186. render.drawAllGraph()
  187. }
  188. /**
  189. * @description Update graphics state (with animation)
  190. * Only shape and style attributes are supported
  191. * @param {String} attrName Updated attribute name
  192. * @param {Any} change Updated value
  193. * @param {Boolean} wait Whether to store the animation waiting
  194. * for the next animation request
  195. * @return {Promise} Animation Promise
  196. */
  197. Graph.prototype.animation = async function (attrName, change, wait = false) {
  198. if (attrName !== 'shape' && attrName !== 'style') {
  199. console.error('Only supported shape and style animation!')
  200. return
  201. }
  202. change = deepClone(change, true)
  203. if (attrName === 'style') this.style.colorProcessor(change)
  204. const changeRoot = this[attrName]
  205. const changeKeys = Object.keys(change)
  206. const beforeState = {}
  207. changeKeys.forEach(key => (beforeState[key] = changeRoot[key]))
  208. const { animationFrame, animationCurve, animationDelay } = this
  209. const animationFrameState = transition(animationCurve, beforeState, change, animationFrame, true)
  210. this.animationRoot.push(changeRoot)
  211. this.animationKeys.push(changeKeys)
  212. this.animationFrameState.push(animationFrameState)
  213. if (wait) return
  214. if (animationDelay > 0) await delay(animationDelay)
  215. const { render } = this
  216. return new Promise(async resolve => {
  217. await render.launchAnimation()
  218. resolve()
  219. })
  220. }
  221. /**
  222. * @description Extract the next frame of data from the animation queue
  223. * and update the graph state
  224. * @return {Undefined} Void
  225. */
  226. Graph.prototype.turnNextAnimationFrame = function (timeStamp) {
  227. const { animationDelay, animationRoot, animationKeys, animationFrameState, animationPause } = this
  228. if (animationPause) return
  229. if (Date.now() - timeStamp < animationDelay) return
  230. animationRoot.forEach((root, i) => {
  231. animationKeys[i].forEach(key => {
  232. root[key] = animationFrameState[i][0][key]
  233. })
  234. })
  235. animationFrameState.forEach((stateItem, i) => {
  236. stateItem.shift()
  237. const noFrame = stateItem.length === 0
  238. if (noFrame) animationRoot[i] = null
  239. if (noFrame) animationKeys[i] = null
  240. })
  241. this.animationFrameState = animationFrameState.filter(state => state.length)
  242. this.animationRoot = animationRoot.filter(root => root)
  243. this.animationKeys = animationKeys.filter(keys => keys)
  244. }
  245. /**
  246. * @description Skip to the last frame of animation
  247. * @return {Undefined} Void
  248. */
  249. Graph.prototype.animationEnd = function () {
  250. const { animationFrameState, animationKeys, animationRoot, render } = this
  251. animationRoot.forEach((root, i) => {
  252. const currentKeys = animationKeys[i]
  253. const lastState = animationFrameState[i].pop()
  254. currentKeys.forEach(key => (root[key] = lastState[key]))
  255. })
  256. this.animationFrameState = []
  257. this.animationKeys = []
  258. this.animationRoot = []
  259. return render.drawAllGraph()
  260. }
  261. /**
  262. * @description Pause animation behavior
  263. * @return {Undefined} Void
  264. */
  265. Graph.prototype.pauseAnimation = function () {
  266. this.attr('animationPause', true)
  267. }
  268. /**
  269. * @description Try animation behavior
  270. * @return {Undefined} Void
  271. */
  272. Graph.prototype.playAnimation = function () {
  273. const { render } = this
  274. this.attr('animationPause', false)
  275. return new Promise(async resolve => {
  276. await render.launchAnimation()
  277. resolve()
  278. })
  279. }
  280. /**
  281. * @description Processor of delete
  282. * @param {CRender} render Instance of CRender
  283. * @return {Undefined} Void
  284. */
  285. Graph.prototype.delProcessor = function (render) {
  286. const { graphs } = render
  287. const index = graphs.findIndex(graph => graph === this)
  288. if (index === -1) return
  289. if (typeof this.beforeDelete === 'function') this.beforeDelete(this)
  290. graphs.splice(index, 1, null)
  291. if (typeof this.deleted === 'function') this.deleted(this)
  292. }
  293. /**
  294. * @description Return a timed release Promise
  295. * @param {Number} time Release time
  296. * @return {Promise} A timed release Promise
  297. */
  298. function delay (time) {
  299. return new Promise(resolve => {
  300. setTimeout(resolve, time)
  301. })
  302. }