a4ce294df5ce1775c150a421863e5914403ae364ff4c76f288448fbd30b894f928c022a1dbf5f6d91349b49cd1b8e1cd5421de309801b7f149343586ee9487 9.4 KB

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