93ca7d08857025c627e6cf52fc832b1ca8dba855e6f39d9e98abbfac257ca60ea373866feae56ccaa92b1b6164acf2855076f88277046f2ec986b75086df15 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350
  1. import color from '@jiaminghi/color'
  2. import bezierCurve from '@jiaminghi/bezier-curve'
  3. import { deepClone } from '../plugin/util'
  4. import allGraph from '../config/graphs'
  5. import Graph from './graph.class'
  6. /**
  7. * @description Class of CRender
  8. * @param {Object} canvas Canvas DOM
  9. * @return {CRender} Instance of CRender
  10. */
  11. export default class CRender {
  12. constructor (canvas) {
  13. if (!canvas) {
  14. console.error('CRender Missing parameters!')
  15. return
  16. }
  17. const ctx = canvas.getContext('2d')
  18. const { clientWidth, clientHeight } = canvas
  19. const area = [clientWidth, clientHeight]
  20. canvas.setAttribute('width', clientWidth)
  21. canvas.setAttribute('height', clientHeight)
  22. /**
  23. * @description Context of the canvas
  24. * @type {Object}
  25. * @example ctx = canvas.getContext('2d')
  26. */
  27. this.ctx = ctx
  28. /**
  29. * @description Width and height of the canvas
  30. * @type {Array}
  31. * @example area = [300,100]
  32. */
  33. this.area = area
  34. /**
  35. * @description Whether render is in animation rendering
  36. * @type {Boolean}
  37. * @example animationStatus = true|false
  38. */
  39. this.animationStatus = false
  40. /**
  41. * @description Added graph
  42. * @type {[Graph]}
  43. * @example graphs = [Graph, Graph, ...]
  44. */
  45. this.graphs = []
  46. /**
  47. * @description Color plugin
  48. * @type {Object}
  49. * @link https://github.com/jiaming743/color
  50. */
  51. this.color = color
  52. /**
  53. * @description Bezier Curve plugin
  54. * @type {Object}
  55. * @link https://github.com/jiaming743/BezierCurve
  56. */
  57. this.bezierCurve = bezierCurve
  58. // bind event handler
  59. canvas.addEventListener('mousedown', mouseDown.bind(this))
  60. canvas.addEventListener('mousemove', mouseMove.bind(this))
  61. canvas.addEventListener('mouseup', mouseUp.bind(this))
  62. }
  63. }
  64. /**
  65. * @description Clear canvas drawing area
  66. * @return {Undefined} Void
  67. */
  68. CRender.prototype.clearArea = function () {
  69. const { area } = this
  70. this.ctx.clearRect(0, 0, ...area)
  71. }
  72. /**
  73. * @description Add graph to render
  74. * @param {Object} config Graph configuration
  75. * @return {Graph} Graph instance
  76. */
  77. CRender.prototype.add = function (config = {}) {
  78. const { name } = config
  79. if (!name) {
  80. console.error('add Missing parameters!')
  81. return
  82. }
  83. const graphConfig = allGraph.get(name)
  84. if (!graphConfig) {
  85. console.warn('No corresponding graph configuration found!')
  86. return
  87. }
  88. const graph = new Graph(graphConfig, config)
  89. if (!graph.validator(graph)) return
  90. graph.render = this
  91. this.graphs.push(graph)
  92. this.sortGraphsByIndex()
  93. this.drawAllGraph()
  94. return graph
  95. }
  96. /**
  97. * @description Sort the graph by index
  98. * @return {Undefined} Void
  99. */
  100. CRender.prototype.sortGraphsByIndex = function () {
  101. const { graphs } = this
  102. graphs.sort((a, b) => {
  103. if (a.index > b.index) return 1
  104. if (a.index === b.index) return 0
  105. if (a.index < b.index) return -1
  106. })
  107. }
  108. /**
  109. * @description Delete graph in render
  110. * @param {Graph} graph The graph to be deleted
  111. * @return {Undefined} Void
  112. */
  113. CRender.prototype.delGraph = function (graph) {
  114. if (typeof graph.delProcessor !== 'function') return
  115. graph.delProcessor(this)
  116. this.graphs = this.graphs.filter(graph => graph)
  117. this.drawAllGraph()
  118. }
  119. /**
  120. * @description Delete all graph in render
  121. * @return {Undefined} Void
  122. */
  123. CRender.prototype.delAllGraph = function () {
  124. this.graphs.forEach(graph => graph.delProcessor(this))
  125. this.graphs = this.graphs.filter(graph => graph)
  126. this.drawAllGraph()
  127. }
  128. /**
  129. * @description Draw all the graphs in the render
  130. * @return {Undefined} Void
  131. */
  132. CRender.prototype.drawAllGraph = function () {
  133. this.clearArea()
  134. this.graphs.filter(graph => graph && graph.visible).forEach(graph => graph.drawProcessor(this, graph))
  135. }
  136. /**
  137. * @description Animate the graph whose animation queue is not empty
  138. * and the animationPause is equal to false
  139. * @return {Promise} Animation Promise
  140. */
  141. CRender.prototype.launchAnimation = function () {
  142. const { animationStatus } = this
  143. if (animationStatus) return
  144. this.animationStatus = true
  145. return new Promise(resolve => {
  146. animation.call(this, () => {
  147. this.animationStatus = false
  148. resolve()
  149. }, Date.now())
  150. })
  151. }
  152. /**
  153. * @description Try to animate every graph
  154. * @param {Function} callback Callback in animation end
  155. * @param {Number} timeStamp Time stamp of animation start
  156. * @return {Undefined} Void
  157. */
  158. function animation (callback, timeStamp) {
  159. const { graphs } = this
  160. if (!animationAble(graphs)) {
  161. callback()
  162. return
  163. }
  164. graphs.forEach(graph => graph.turnNextAnimationFrame(timeStamp))
  165. this.drawAllGraph()
  166. requestAnimationFrame(animation.bind(this, callback, timeStamp))
  167. }
  168. /**
  169. * @description Find if there are graph that can be animated
  170. * @param {[Graph]} graphs
  171. * @return {Boolean}
  172. */
  173. function animationAble (graphs) {
  174. return graphs.find(graph => !graph.animationPause && graph.animationFrameState.length)
  175. }
  176. /**
  177. * @description Handler of CRender mousedown event
  178. * @return {Undefined} Void
  179. */
  180. function mouseDown (e) {
  181. const { graphs } = this
  182. const hoverGraph = graphs.find(graph => graph.status === 'hover')
  183. if (!hoverGraph) return
  184. hoverGraph.status = 'active'
  185. }
  186. /**
  187. * @description Handler of CRender mousemove event
  188. * @return {Undefined} Void
  189. */
  190. function mouseMove (e) {
  191. const { offsetX, offsetY } = e
  192. const position = [offsetX, offsetY]
  193. const { graphs } = this
  194. const activeGraph = graphs.find(graph => (graph.status === 'active' || graph.status === 'drag'))
  195. if (activeGraph) {
  196. if (!activeGraph.drag) return
  197. if (typeof activeGraph.move !== 'function') {
  198. console.error('No move method is provided, cannot be dragged!')
  199. return
  200. }
  201. activeGraph.moveProcessor(e)
  202. activeGraph.status = 'drag'
  203. return
  204. }
  205. const hoverGraph = graphs.find(graph => graph.status === 'hover')
  206. const hoverAbleGraphs = graphs.filter(graph =>
  207. (graph.hover && (typeof graph.hoverCheck === 'function' || graph.hoverRect)))
  208. const hoveredGraph = hoverAbleGraphs.find(graph => graph.hoverCheckProcessor(position, graph))
  209. if (hoveredGraph) {
  210. document.body.style.cursor = hoveredGraph.style.hoverCursor
  211. } else {
  212. document.body.style.cursor = 'default'
  213. }
  214. let [hoverGraphMouseOuterIsFun, hoveredGraphMouseEnterIsFun] = [false, false]
  215. if (hoverGraph) hoverGraphMouseOuterIsFun = typeof hoverGraph.mouseOuter === 'function'
  216. if (hoveredGraph) hoveredGraphMouseEnterIsFun = typeof hoveredGraph.mouseEnter === 'function'
  217. if (!hoveredGraph && !hoverGraph) return
  218. if (!hoveredGraph && hoverGraph) {
  219. if (hoverGraphMouseOuterIsFun) hoverGraph.mouseOuter(e, hoverGraph)
  220. hoverGraph.status = 'static'
  221. return
  222. }
  223. if (hoveredGraph && hoveredGraph === hoverGraph) return
  224. if (hoveredGraph && !hoverGraph) {
  225. if (hoveredGraphMouseEnterIsFun) hoveredGraph.mouseEnter(e, hoveredGraph)
  226. hoveredGraph.status = 'hover'
  227. return
  228. }
  229. if (hoveredGraph && hoverGraph && hoveredGraph !== hoverGraph) {
  230. if (hoverGraphMouseOuterIsFun) hoverGraph.mouseOuter(e, hoverGraph)
  231. hoverGraph.status = 'static'
  232. if (hoveredGraphMouseEnterIsFun) hoveredGraph.mouseEnter(e, hoveredGraph)
  233. hoveredGraph.status = 'hover'
  234. }
  235. }
  236. /**
  237. * @description Handler of CRender mouseup event
  238. * @return {Undefined} Void
  239. */
  240. function mouseUp (e) {
  241. const { graphs } = this
  242. const activeGraph = graphs.find(graph => graph.status === 'active')
  243. const dragGraph = graphs.find(graph => graph.status === 'drag')
  244. if (activeGraph && typeof activeGraph.click === 'function') activeGraph.click(e, activeGraph)
  245. graphs.forEach(graph => graph && (graph.status = 'static'))
  246. if (activeGraph) activeGraph.status = 'hover'
  247. if (dragGraph) dragGraph.status = 'hover'
  248. }
  249. /**
  250. * @description Clone Graph
  251. * @param {Graph} graph The target to be cloned
  252. * @return {Graph} Cloned graph
  253. */
  254. CRender.prototype.clone = function (graph) {
  255. const style = graph.style.getStyle()
  256. let clonedGraph = { ...graph, style }
  257. delete clonedGraph.render
  258. clonedGraph = deepClone(clonedGraph, true)
  259. return this.add(clonedGraph)
  260. }