@@ -13,6 +13,9 @@ import { VDefaultsProvider } from '@/components/VDefaultsProvider'
13
13
import { useColor } from '@/composables/color'
14
14
import { makeDensityProps } from '@/composables/density'
15
15
16
+ // Directives
17
+ import vClickOutside from '@/directives/click-outside'
18
+
16
19
// Utilities
17
20
import { computed , shallowRef , toRef , watch } from 'vue'
18
21
import { formatTextTemplate } from './utils'
@@ -103,6 +106,8 @@ export const makeVPieProps = propsFactory({
103
106
export const VPie = genericComponent < VPieSlots > ( ) ( {
104
107
name : 'VPie' ,
105
108
109
+ directives : { vClickOutside } ,
110
+
106
111
props : makeVPieProps ( ) ,
107
112
108
113
setup ( props , { slots } ) {
@@ -153,7 +158,7 @@ export const VPie = genericComponent<VPieSlots>()({
153
158
const visibleItems = computed ( ( ) => {
154
159
// hidden items get (value: 0) to trigger disappearing animation
155
160
return arcs . value . map ( item => {
156
- return isActive ( item )
161
+ return isVisible ( item )
157
162
? item
158
163
: { ...item , value : 0 }
159
164
} )
@@ -185,12 +190,12 @@ export const VPie = genericComponent<VPieSlots>()({
185
190
return typeof paletteItem === 'object' ? paletteItem . pattern : undefined
186
191
}
187
192
188
- function isActive ( item : PieItem ) {
193
+ function isVisible ( item : PieItem ) {
189
194
return visibleItemsKeys . value . includes ( item . key )
190
195
}
191
196
192
197
function toggle ( item : PieItem ) {
193
- if ( isActive ( item ) ) {
198
+ if ( isVisible ( item ) ) {
194
199
visibleItemsKeys . value = visibleItemsKeys . value . filter ( x => x !== item . key )
195
200
} else {
196
201
visibleItemsKeys . value = [ ...visibleItemsKeys . value , item . key ]
@@ -199,29 +204,53 @@ export const VPie = genericComponent<VPieSlots>()({
199
204
200
205
const tooltipItem = shallowRef < PieItem | null > ( null )
201
206
const tooltipVisible = shallowRef ( false )
207
+ const tooltipTarget = shallowRef < [ x : number , y : number ] > ( [ 0 , 0 ] )
202
208
203
209
let mouseLeaveTimeout = null ! as ReturnType < typeof setTimeout >
204
210
205
- function onMouseenter ( item : PieItem ) {
206
- if ( ! props . tooltip ) return
211
+ function setItemActive ( item : PieItem , active : boolean ) {
212
+ arcs . value . forEach ( a => a . isActive = a . key === item . key && active )
207
213
208
- clearTimeout ( mouseLeaveTimeout )
209
- tooltipVisible . value = true
210
- tooltipItem . value = item
214
+ if ( props . tooltip ) {
215
+ setTooltip ( item , active )
216
+ }
211
217
}
212
218
213
- function onMouseleave ( ) {
214
- if ( ! props . tooltip ) return
215
-
219
+ function setTooltip ( item : PieItem , active : boolean ) {
216
220
clearTimeout ( mouseLeaveTimeout )
217
- mouseLeaveTimeout = setTimeout ( ( ) => {
218
- tooltipVisible . value = false
219
221
220
- // intentionally reusing timeout here
222
+ if ( active ) {
223
+ tooltipVisible . value = true
224
+ tooltipItem . value = item
225
+ } else {
221
226
mouseLeaveTimeout = setTimeout ( ( ) => {
222
- tooltipItem . value = null
223
- } , 500 )
224
- } , 100 )
227
+ tooltipVisible . value = false
228
+
229
+ // intentionally reusing timeout here
230
+ mouseLeaveTimeout = setTimeout ( ( ) => {
231
+ tooltipItem . value = null
232
+ } , 500 )
233
+ } , 100 )
234
+ }
235
+ }
236
+
237
+ let frame = - 1
238
+ function onSvgMousemove ( { clientX, clientY } : MouseEvent ) {
239
+ cancelAnimationFrame ( frame )
240
+ frame = requestAnimationFrame ( ( ) => {
241
+ tooltipTarget . value = [ clientX , clientY ]
242
+ } )
243
+ }
244
+
245
+ function onSvgTouchstart ( { touches } : TouchEvent ) {
246
+ if ( ! touches ) return
247
+ const { clientX, clientY } = touches [ 0 ]
248
+ tooltipTarget . value = [ clientX , clientY ]
249
+ }
250
+
251
+ function onSvgClickOutside ( ) {
252
+ arcs . value . forEach ( a => a . isActive = false )
253
+ tooltipVisible . value = false
225
254
}
226
255
227
256
return ( ) => {
@@ -247,6 +276,7 @@ export const VPie = genericComponent<VPieSlots>()({
247
276
subtitleFormat : typeof props . tooltip === 'object' ? props . tooltip . subtitleFormat : '[value]' ,
248
277
transition : typeof props . tooltip === 'object' ? props . tooltip . transition : defaultTooltipTransition ,
249
278
offset : typeof props . tooltip === 'object' ? props . tooltip . offset : 16 ,
279
+ target : tooltipTarget . value ,
250
280
}
251
281
252
282
const legendDefaults = {
@@ -311,17 +341,22 @@ export const VPie = genericComponent<VPieSlots>()({
311
341
< svg
312
342
xmlns = "http://www.w3.org/2000/svg"
313
343
viewBox = "0 0 100 100"
344
+ class = "v-pie__segments"
345
+ onMousemove = { onSvgMousemove }
346
+ onTouchstart = { onSvgTouchstart }
347
+ v-click-outside = { { handler : onSvgClickOutside } }
314
348
>
315
349
{ arcs . value . map ( ( item , index ) => (
316
350
< VPieSegment
317
351
{ ...segmentProps }
318
352
key = { item . key }
353
+ active = { item . isActive }
319
354
color = { item . color }
320
- value = { isActive ( item ) ? arcSize ( item . value ) : 0 }
355
+ value = { isVisible ( item ) ? arcSize ( item . value ) : 0 }
321
356
rotate = { arcOffset ( index ) }
322
357
pattern = { item . pattern }
323
- onMouseenter = { ( ) => onMouseenter ( item ) }
324
- onMouseleave = { ( ) => onMouseleave ( ) }
358
+ onUpdate : active = { val => setItemActive ( item , val ) }
359
+ onTouchend = { ( ) => setItemActive ( item , true ) }
325
360
/>
326
361
) ) }
327
362
</ svg >
@@ -343,7 +378,7 @@ export const VPie = genericComponent<VPieSlots>()({
343
378
{ legendConfig . value . visible && (
344
379
< VDefaultsProvider key = "legend" defaults = { legendDefaults } >
345
380
< div class = "v-pie__legend" >
346
- { slots . legend ?.( { isActive, toggle, items : arcs . value , total : total . value } ) ?? (
381
+ { slots . legend ?.( { isActive : isVisible , toggle, items : arcs . value , total : total . value } ) ?? (
347
382
< VChipGroup
348
383
column
349
384
multiple
0 commit comments