Menu

QuickStart 04 - CreateRenderer (2021.02.24) - 图1

Create a pie chart container using createRenderer api

First let’s create a vue file named “CanvasApp”:

  1. <template>
  2. <piechart @click="handleClick" :data="state.data" :x="200" :y="200" :r="200"></piechart>
  3. </template>
  4. <script>
  5. import { reactive, ref } from 'vue'
  6. export default {
  7. setup() {
  8. const state = reactive({
  9. data: [
  10. {
  11. name: '大专',
  12. count: 200,
  13. color: 'brown'
  14. },
  15. {
  16. name: '本科',
  17. count: 300,
  18. color: 'yellow'
  19. },
  20. {
  21. name: '硕士',
  22. count: 100,
  23. color: 'pink'
  24. },
  25. {
  26. name: '博士',
  27. count: 50,
  28. color: 'skyblue'
  29. }
  30. ]
  31. })
  32. function handleClick() {
  33. state.data.push({
  34. name: '其他',
  35. count: 30,
  36. color: 'orange'
  37. })
  38. }
  39. return {
  40. state,
  41. handleClick
  42. }
  43. }
  44. }
  45. </script>
  46. <style>
  47. </style>

Then, in main.js, we init our custom renderer and bind container:

  1. import { createApp } from "vue"
  2. import App from "./App.vue"
  3. import "./index.css"
  4. createApp(App).mount("#app")
  5. import { createRenderer } from "vue"
  6. import CanvasApp from "./components/CanvasApp.vue"
  7. const draw = (el, noClear) => {
  8. if (!noClear) {
  9. ctx.clearRect(0, 0, canvas.width, canvas.height)
  10. }
  11. if (el.tag === "piechart") {
  12. let { data, r, x, y } = el
  13. let total = data.reduce((memo, current) => memo + current.count, 0)
  14. let start = 0,
  15. end = 0
  16. data.forEach((item) => {
  17. end += (item.count / total) * 360
  18. drawPieChart(start, end, item.color, x, y, r)
  19. drawPieChartText(item.name, (start + end) / 2, x, y, r)
  20. start = end
  21. })
  22. }
  23. el.childs && el.childs.forEach((child) => draw(child, true))
  24. }
  25. const d2a = (n) => {
  26. return (n * Math.PI) / 180
  27. }
  28. const drawPieChart = (start, end, color, cx, cy, r) => {
  29. let x = cx + Math.cos(d2a(start)) * r
  30. let y = cy + Math.sin(d2a(start)) * r
  31. ctx.beginPath()
  32. ctx.moveTo(cx, cy)
  33. ctx.lineTo(x, y)
  34. ctx.arc(cx, cy, r, d2a(start), d2a(end), false)
  35. ctx.fillStyle = color
  36. ctx.fill()
  37. ctx.stroke()
  38. ctx.closePath()
  39. }
  40. const drawPieChartText = (val, position, cx, cy, r) => {
  41. ctx.beginPath()
  42. let x = cx + (Math.cos(d2a(position)) * r) / 1.25 - 20
  43. let y = cy + (Math.sin(d2a(position)) * r) / 1.25 - 20
  44. ctx.fillStyle = "#000000"
  45. ctx.font = "20px 微软雅黑"
  46. ctx.fillText(val, x, y)
  47. ctx.closePath()
  48. }
  49. const nodeOps = {
  50. insert: (child, parent, anchor) => {
  51. // 我们重写了insert逻辑,因为在我们canvasApp中不存在实际dom插入操作
  52. // 这里面只需要将元素之间的父子关系保存一下即可
  53. child.parent = parent
  54. if (!parent.childs) {
  55. parent.childs = [child]
  56. } else {
  57. parent.childs.push(child)
  58. }
  59. // 只有canvas有nodeType,这里就是开始绘制内容到canvas
  60. if (parent.nodeType == 1) {
  61. draw(child)
  62. // 如果子元素上附加了事件,我们给canvas添加监听器
  63. if (child.onClick) {
  64. ctx.canvas.addEventListener("click", () => {
  65. child.onClick()
  66. setTimeout(() => {
  67. draw(child)
  68. }, 0)
  69. })
  70. }
  71. }
  72. },
  73. remove: (child) => {},
  74. createElement: (tag, isSVG, is) => {
  75. // 创建元素时由于没有需要创建的dom元素,只需返回当前元素数据对象
  76. return { tag }
  77. },
  78. createText: (text) => {},
  79. createComment: (text) => {},
  80. setText: (node, text) => {},
  81. setElementText: (el, text) => {},
  82. parentNode: (node) => {},
  83. nextSibling: (node) => {},
  84. querySelector: (selector) => {},
  85. setScopeId(el, id) {},
  86. cloneNode(el) {},
  87. insertStaticContent(content, parent, anchor, isSVG) {},
  88. patchProp(el, key, prevValue, nextValue) {
  89. el[key] = nextValue
  90. },
  91. }
  92. // 创建一个渲染器
  93. let renderer = createRenderer(nodeOps)
  94. // 保存画布和其上下文
  95. let ctx
  96. let canvas
  97. // 扩展mount,首先创建一个画布元素
  98. function createCanvasApp(App) {
  99. const app = renderer.createApp(App)
  100. const mount = app.mount
  101. app.mount = (selector) => {
  102. canvas = document.createElement("canvas")
  103. canvas.width = window.innerWidth
  104. canvas.height = window.innerHeight
  105. document.querySelector(selector).appendChild(canvas)
  106. ctx = canvas.getContext("2d")
  107. mount(canvas)
  108. }
  109. return app
  110. }
  111. createCanvasApp(CanvasApp).mount("#demo")