CategoriesChart.vue 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190
  1. <template>
  2. <div class='widgets__categories_chart' ref='container'>
  3. <div class='widgets__categories_chart__overlay' :class='{ "widgets__categories_chart__overlay--show" : loading }'>
  4. <loading-icon :dark='true'></loading-icon>
  5. </div>
  6. <div
  7. class='widgets__categories_chart__tooltip'
  8. :class='{ "widgets__categories_chart__tooltip--show": tooltipShow }'
  9. :style='{ "left": tooltipX, "top": tooltipY }'
  10. >
  11. </div>
  12. <div class='widgets__categories_chart__main'>
  13. <svg>
  14. <g ref='g'></g>
  15. </svg>
  16. <div class='widgets__categories_chart__main__legend'>
  17. <div
  18. v-for='(category, $index) in data'
  19. class='widgets__categories_chart__label'
  20. @mouseover='toggleLabelHover($index)'
  21. @mouseout='toggleLabelHover($index)'
  22. >
  23. <div class='widgets__categories_chart__label__square' :style="{ 'background-color': category.color }"></div>
  24. {{category.label}}
  25. </div>
  26. </div>
  27. </div>
  28. </div>
  29. </template>
  30. <script>
  31. import LoadingIcon from '../LoadingIcon'
  32. import AjaxErrorHandler from '../../assets/js/errorHandler'
  33. import * as d3 from 'd3'
  34. import throttle from 'lodash.throttle'
  35. export default {
  36. name: 'CategoriesChart',
  37. components: { LoadingIcon },
  38. data () {
  39. return {
  40. loading: true,
  41. padding: 20,
  42. tooltipX: 0,
  43. tooltipY: 0,
  44. tooltipShow: false,
  45. tooltipItem: 0,
  46. data: []
  47. }
  48. },
  49. methods: {
  50. updateFuncs () {
  51. if(!this.data.length) return
  52. let height = this.$refs.container.getBoundingClientRect().height
  53. let paddedHeight = (height - this.padding) / 2
  54. let translate = paddedHeight + this.padding / 2
  55. let pieSegments = d3.pie()(this.data.map(d => d.value))
  56. let arcGenerator = d3.arc()
  57. .innerRadius(paddedHeight - 40)
  58. .outerRadius(paddedHeight)
  59. .padAngle(Math.PI*2 * 2/360)
  60. let g = d3.select(this.$refs.g).attr('transform', `translate(${translate}, ${translate})`)
  61. let arcs = g.selectAll('path')
  62. .data(pieSegments)
  63. .enter()
  64. .append('path')
  65. .attr('d', arcGenerator)
  66. .attr('data-index', (d, i) => i)
  67. .attr('fill', (d, i) => this.data[i].color)
  68. let labels = g.selectAll('text')
  69. .data(pieSegments)
  70. .enter()
  71. .append('text')
  72. .text(d => d.value ? d.value : '')
  73. .attr('data-index', (d, i) => i)
  74. .attr('fill', '#fff')
  75. .attr('transform', d => {
  76. d.innerRadius = paddedHeight - 40
  77. d.outerRadius = paddedHeight
  78. let coords = arcGenerator.centroid(d)
  79. .map((val, i) => i ? val+5 : val-5)
  80. .join(',')
  81. return `translate(${coords})`
  82. })
  83. },
  84. toggleLabelHover (index) {
  85. let g = this.$refs.g
  86. let path = g.querySelector('path[data-index="' + index + '"]')
  87. let text = g.querySelector('text[data-index="' + index + '"]')
  88. let textTransform = text.getAttribute('transform')
  89. path.classList.toggle('widgets__categories_chart__main--large')
  90. if(textTransform.includes('scale')) {
  91. text.setAttribute('transform', textTransform.split(' ')[0])
  92. } else {
  93. text.setAttribute('transform', textTransform + ' scale(1.15)')
  94. }
  95. }
  96. },
  97. mounted () {
  98. window.addEventListener('resize', this.updateFuncs)
  99. this.axios
  100. .get('/api/v1/log/categories')
  101. .then(res => {
  102. this.data = res.data
  103. this.updateFuncs()
  104. this.loading = false
  105. })
  106. .catch(AjaxErrorHandler(this.$store))
  107. },
  108. destroyed () {
  109. window.removeEventListener('resize', this.updateFuncs)
  110. }
  111. }
  112. </script>
  113. <style lang='scss'>
  114. @import '../../assets/scss/variables.scss';
  115. .widgets__categories_chart {
  116. background-color: #fff;
  117. width: 100%;
  118. height: 100%;
  119. overflow: hidden;
  120. border-radius: 0.25rem 0.25rem 0 0;
  121. position: relative;
  122. @at-root #{&}__overlay {
  123. @include loading-overlay(#fff, 0.25rem 0.25rem 0 0);
  124. }
  125. @at-root #{&}__main {
  126. display: flex;
  127. flex-direction: row;
  128. height: 100%;
  129. svg {
  130. height: 100%;
  131. width: 11rem;
  132. path, text {
  133. transition: all 0.2s;
  134. }
  135. }
  136. @at-root #{&}--large {
  137. transform: scale(1.075);
  138. }
  139. @at-root #{&}__legend {
  140. padding: 10px 0;
  141. }
  142. }
  143. @at-root #{&}__label {
  144. position: relative;
  145. cursor: default;
  146. margin-left: 1rem;
  147. &:hover {
  148. text-decoration: underline;
  149. }
  150. @at-root #{&}__square {
  151. position: absolute;
  152. top: 0.375rem;
  153. left: -1.25rem;
  154. height: 0.75rem;
  155. width: 0.75rem;
  156. border-radius: 0.125rem;
  157. }
  158. }
  159. }
  160. </style>