@@ -152,7 +152,8 @@ function setupDragElement(gd, shapePath, shapeOptions, index, shapeLayer) {
152
152
element : sensoryElement . node ( ) ,
153
153
gd : gd ,
154
154
prepFn : startDrag ,
155
- doneFn : endDrag
155
+ doneFn : endDrag ,
156
+ clickFn : abortDrag
156
157
} ,
157
158
dragMode ;
158
159
@@ -295,18 +296,24 @@ function setupDragElement(gd, shapePath, shapeOptions, index, shapeLayer) {
295
296
296
297
// setup dragMode and the corresponding handler
297
298
updateDragMode ( evt ) ;
299
+ renderVisualCues ( shapeLayer , shapeOptions ) ;
298
300
deactivateClipPathTemporarily ( shapePath , shapeOptions , gd ) ;
299
301
dragOptions . moveFn = ( dragMode === 'move' ) ? moveShape : resizeShape ;
300
302
}
301
303
302
304
function endDrag ( ) {
303
305
setCursor ( shapePath ) ;
306
+ removeVisualCues ( shapeLayer ) ;
304
307
305
308
// Don't rely on clipPath being activated during re-layout
306
309
setClipPath ( shapePath , gd , shapeOptions ) ;
307
310
Registry . call ( 'relayout' , gd , update ) ;
308
311
}
309
312
313
+ function abortDrag ( ) {
314
+ removeVisualCues ( shapeLayer ) ;
315
+ }
316
+
310
317
function moveShape ( dx , dy ) {
311
318
if ( shapeOptions . type === 'path' ) {
312
319
var noOp = function ( coord ) { return coord ; } ,
@@ -347,6 +354,7 @@ function setupDragElement(gd, shapePath, shapeOptions, index, shapeLayer) {
347
354
}
348
355
349
356
shapePath . attr ( 'd' , getPathString ( gd , shapeOptions ) ) ;
357
+ renderVisualCues ( shapeLayer , shapeOptions ) ;
350
358
}
351
359
352
360
function resizeShape ( dx , dy ) {
@@ -411,6 +419,52 @@ function setupDragElement(gd, shapePath, shapeOptions, index, shapeLayer) {
411
419
}
412
420
413
421
shapePath . attr ( 'd' , getPathString ( gd , shapeOptions ) ) ;
422
+ renderVisualCues ( shapeLayer , shapeOptions ) ;
423
+ }
424
+
425
+ function renderVisualCues ( shapeLayer , shapeOptions ) {
426
+ if ( xPixelSized || yPixelSized ) {
427
+ renderAnchor ( ) ;
428
+ }
429
+
430
+ function renderAnchor ( ) {
431
+ var isNotPath = shapeOptions . type !== 'path' ;
432
+
433
+ // d3 join with dummy data to satisfy d3 data-binding
434
+ var visualCues = shapeLayer . selectAll ( '.visual-cue' ) . data ( [ 0 ] ) ;
435
+
436
+ // Enter
437
+ visualCues . enter ( )
438
+ . append ( 'path' )
439
+ . attr ( {
440
+ 'fill' : '#fff' ,
441
+ 'fill-rule' : 'evenodd' ,
442
+ 'stroke' : '#000' ,
443
+ 'stroke-width' : 1
444
+ } )
445
+ . classed ( 'visual-cue' , true ) ;
446
+
447
+ // Update
448
+ var anchorX = x2p ( xPixelSized ?
449
+ shapeOptions . xanchor :
450
+ isNotPath ?
451
+ shapeOptions . x0 :
452
+ helpers . extractPathCoords ( shapeOptions . path , constants . paramIsX ) [ 0 ] ) ;
453
+ var anchorY = y2p ( yPixelSized ?
454
+ shapeOptions . yanchor :
455
+ isNotPath ?
456
+ shapeOptions . y0 :
457
+ helpers . extractPathCoords ( shapeOptions . path , constants . paramIsY ) [ 0 ] ) ;
458
+
459
+ var crossHairPath = 'M' + ( anchorX - 1 ) + ',' + ( anchorY - 1 ) +
460
+ 'l-8,0 l0,-2 l8,0 l0,-8 l2,0 l0,8 l8,0 l0,2 l-8,0 l0,8 l-2,0 Z' ;
461
+
462
+ visualCues . attr ( 'd' , crossHairPath ) ;
463
+ }
464
+ }
465
+
466
+ function removeVisualCues ( shapeLayer ) {
467
+ shapeLayer . selectAll ( '.visual-cue' ) . remove ( ) ;
414
468
}
415
469
416
470
function deactivateClipPathTemporarily ( shapePath , shapeOptions , gd ) {
0 commit comments