@@ -17,6 +17,8 @@ var Axes = require('../../plots/cartesian/axes');
17
17
var Color = require ( '../color' ) ;
18
18
var Drawing = require ( '../drawing' ) ;
19
19
20
+ var dragElement = require ( '../dragelement' ) ;
21
+ var setCursor = require ( '../../lib/setcursor' ) ;
20
22
21
23
var shapes = module . exports = { } ;
22
24
@@ -299,15 +301,7 @@ function updateShape(gd, index, opt, value) {
299
301
var options = handleShapeDefaults ( optionsIn , gd . _fullLayout ) ;
300
302
gd . _fullLayout . shapes [ index ] = options ;
301
303
302
- var attrs = {
303
- 'data-index' : index ,
304
- 'fill-rule' : 'evenodd' ,
305
- d : shapePath ( gd , options )
306
- } ,
307
- clipAxes ;
308
-
309
- var lineColor = options . line . width ? options . line . color : 'rgba(0,0,0,0)' ;
310
-
304
+ var clipAxes ;
311
305
if ( options . layer !== 'below' ) {
312
306
clipAxes = ( options . xref + options . yref ) . replace ( / p a p e r / g, '' ) ;
313
307
drawShape ( gd . _fullLayout . _shapeUpperLayer ) ;
@@ -332,6 +326,14 @@ function updateShape(gd, index, opt, value) {
332
326
}
333
327
334
328
function drawShape ( shapeLayer ) {
329
+ var attrs = {
330
+ 'data-index' : index ,
331
+ 'fill-rule' : 'evenodd' ,
332
+ d : getPathString ( gd , options )
333
+ } ,
334
+ lineColor = options . line . width ?
335
+ options . line . color : 'rgba(0,0,0,0)' ;
336
+
335
337
var path = shapeLayer . append ( 'path' )
336
338
. attr ( attrs )
337
339
. style ( 'opacity' , options . opacity )
@@ -343,6 +345,160 @@ function updateShape(gd, index, opt, value) {
343
345
path . call ( Drawing . setClipUrl ,
344
346
'clip' + gd . _fullLayout . _uid + clipAxes ) ;
345
347
}
348
+
349
+ if ( gd . _context . editable ) setupDragElement ( gd , path , options , index ) ;
350
+ }
351
+ }
352
+
353
+ function setupDragElement ( gd , shapePath , shapeOptions , index ) {
354
+ var MINWIDTH = 10 ,
355
+ MINHEIGHT = 10 ;
356
+
357
+ var update ;
358
+ var x0 , y0 , x1 , y1 , astrX0 , astrY0 , astrX1 , astrY1 ;
359
+ var n0 , s0 , w0 , e0 , astrN , astrS , astrW , astrE , optN , optS , optW , optE ;
360
+ var pathIn , astrPath ;
361
+
362
+ var xa , ya , x2p , y2p , p2x , p2y ;
363
+
364
+ var dragOptions = {
365
+ setCursor : updateDragMode ,
366
+ element : shapePath . node ( ) ,
367
+ prepFn : startDrag ,
368
+ doneFn : endDrag
369
+ } ,
370
+ dragBBox = dragOptions . element . getBoundingClientRect ( ) ,
371
+ dragMode ;
372
+
373
+ dragElement . init ( dragOptions ) ;
374
+
375
+ function updateDragMode ( evt ) {
376
+ // choose 'move' or 'resize'
377
+ // based on initial position of cursor within the drag element
378
+ var w = dragBBox . right - dragBBox . left ,
379
+ h = dragBBox . bottom - dragBBox . top ,
380
+ x = evt . clientX - dragBBox . left ,
381
+ y = evt . clientY - dragBBox . top ,
382
+ cursor = ( w > MINWIDTH && h > MINHEIGHT && ! evt . shiftKey ) ?
383
+ dragElement . getCursor ( x / w , 1 - y / h ) :
384
+ 'move' ;
385
+
386
+ setCursor ( shapePath , cursor ) ;
387
+
388
+ // possible values 'move', 'sw', 'w', 'se', 'e', 'ne', 'n', 'nw' and 'w'
389
+ dragMode = cursor . split ( '-' ) [ 0 ] ;
390
+ }
391
+
392
+ function startDrag ( evt ) {
393
+ // setup conversion functions
394
+ xa = Axes . getFromId ( gd , shapeOptions . xref ) ;
395
+ ya = Axes . getFromId ( gd , shapeOptions . yref ) ;
396
+
397
+ x2p = getDataToPixel ( gd , xa ) ;
398
+ y2p = getDataToPixel ( gd , ya , true ) ;
399
+ p2x = getPixelToData ( gd , xa ) ;
400
+ p2y = getPixelToData ( gd , ya , true ) ;
401
+
402
+ // setup update strings and initial values
403
+ var astr = 'shapes[' + index + ']' ;
404
+ if ( shapeOptions . type === 'path' ) {
405
+ pathIn = shapeOptions . path ;
406
+ astrPath = astr + '.path' ;
407
+ }
408
+ else {
409
+ x0 = x2p ( shapeOptions . x0 ) ;
410
+ y0 = y2p ( shapeOptions . y0 ) ;
411
+ x1 = x2p ( shapeOptions . x1 ) ;
412
+ y1 = y2p ( shapeOptions . y1 ) ;
413
+
414
+ astrX0 = astr + '.x0' ;
415
+ astrY0 = astr + '.y0' ;
416
+ astrX1 = astr + '.x1' ;
417
+ astrY1 = astr + '.y1' ;
418
+ }
419
+
420
+ if ( x0 < x1 ) {
421
+ w0 = x0 ; astrW = astr + '.x0' ; optW = 'x0' ;
422
+ e0 = x1 ; astrE = astr + '.x1' ; optE = 'x1' ;
423
+ }
424
+ else {
425
+ w0 = x1 ; astrW = astr + '.x1' ; optW = 'x1' ;
426
+ e0 = x0 ; astrE = astr + '.x0' ; optE = 'x0' ;
427
+ }
428
+ if ( y0 < y1 ) {
429
+ n0 = y0 ; astrN = astr + '.y0' ; optN = 'y0' ;
430
+ s0 = y1 ; astrS = astr + '.y1' ; optS = 'y1' ;
431
+ }
432
+ else {
433
+ n0 = y1 ; astrN = astr + '.y1' ; optN = 'y1' ;
434
+ s0 = y0 ; astrS = astr + '.y0' ; optS = 'y0' ;
435
+ }
436
+
437
+ update = { } ;
438
+
439
+ // setup dragMode and the corresponding handler
440
+ updateDragMode ( evt ) ;
441
+ dragOptions . moveFn = ( dragMode === 'move' ) ? moveShape : resizeShape ;
442
+ }
443
+
444
+ function endDrag ( dragged ) {
445
+ setCursor ( shapePath ) ;
446
+ if ( dragged ) {
447
+ Plotly . relayout ( gd , update ) ;
448
+ }
449
+ }
450
+
451
+ function moveShape ( dx , dy ) {
452
+ if ( shapeOptions . type === 'path' ) {
453
+ var moveX = function moveX ( x ) { return p2x ( x2p ( x ) + dx ) ; } ;
454
+ if ( xa && xa . type === 'date' ) moveX = encodeDate ( moveX ) ;
455
+
456
+ var moveY = function moveY ( y ) { return p2y ( y2p ( y ) + dy ) ; } ;
457
+ if ( ya && ya . type === 'date' ) moveY = encodeDate ( moveY ) ;
458
+
459
+ shapeOptions . path = movePath ( pathIn , moveX , moveY ) ;
460
+ update [ astrPath ] = shapeOptions . path ;
461
+ }
462
+ else {
463
+ update [ astrX0 ] = shapeOptions . x0 = p2x ( x0 + dx ) ;
464
+ update [ astrY0 ] = shapeOptions . y0 = p2y ( y0 + dy ) ;
465
+ update [ astrX1 ] = shapeOptions . x1 = p2x ( x1 + dx ) ;
466
+ update [ astrY1 ] = shapeOptions . y1 = p2y ( y1 + dy ) ;
467
+ }
468
+
469
+ shapePath . attr ( 'd' , getPathString ( gd , shapeOptions ) ) ;
470
+ }
471
+
472
+ function resizeShape ( dx , dy ) {
473
+ if ( shapeOptions . type === 'path' ) {
474
+ // TODO: implement path resize
475
+ var moveX = function moveX ( x ) { return p2x ( x2p ( x ) + dx ) ; } ;
476
+ if ( xa && xa . type === 'date' ) moveX = encodeDate ( moveX ) ;
477
+
478
+ var moveY = function moveY ( y ) { return p2y ( y2p ( y ) + dy ) ; } ;
479
+ if ( ya && ya . type === 'date' ) moveY = encodeDate ( moveY ) ;
480
+
481
+ shapeOptions . path = movePath ( pathIn , moveX , moveY ) ;
482
+ update [ astrPath ] = shapeOptions . path ;
483
+ }
484
+ else {
485
+ var newN = ( ~ dragMode . indexOf ( 'n' ) ) ? n0 + dy : n0 ,
486
+ newS = ( ~ dragMode . indexOf ( 's' ) ) ? s0 + dy : s0 ,
487
+ newW = ( ~ dragMode . indexOf ( 'w' ) ) ? w0 + dx : w0 ,
488
+ newE = ( ~ dragMode . indexOf ( 'e' ) ) ? e0 + dx : e0 ;
489
+
490
+ if ( newS - newN > MINHEIGHT ) {
491
+ update [ astrN ] = shapeOptions [ optN ] = p2y ( newN ) ;
492
+ update [ astrS ] = shapeOptions [ optS ] = p2y ( newS ) ;
493
+ }
494
+
495
+ if ( newE - newW > MINWIDTH ) {
496
+ update [ astrW ] = shapeOptions [ optW ] = p2x ( newW ) ;
497
+ update [ astrE ] = shapeOptions [ optE ] = p2x ( newE ) ;
498
+ }
499
+ }
500
+
501
+ shapePath . attr ( 'd' , getPathString ( gd , shapeOptions ) ) ;
346
502
}
347
503
}
348
504
@@ -372,10 +528,58 @@ function isShapeInSubplot(gd, shape, plotinfo) {
372
528
}
373
529
374
530
function decodeDate ( convertToPx ) {
375
- return function ( v ) { return convertToPx ( v . replace ( '_' , ' ' ) ) ; } ;
531
+ return function ( v ) {
532
+ if ( v . replace ) v = v . replace ( '_' , ' ' ) ;
533
+ return convertToPx ( v ) ;
534
+ } ;
535
+ }
536
+
537
+ function encodeDate ( convertToDate ) {
538
+ return function ( v ) { return convertToDate ( v ) . replace ( ' ' , '_' ) ; } ;
539
+ }
540
+
541
+ function getDataToPixel ( gd , axis , isVertical ) {
542
+ var gs = gd . _fullLayout . _size ,
543
+ dataToPixel ;
544
+
545
+ if ( axis ) {
546
+ var d2l = dataToLinear ( axis ) ;
547
+
548
+ dataToPixel = function ( v ) {
549
+ return axis . _offset + axis . l2p ( d2l ( v , true ) ) ;
550
+ } ;
551
+
552
+ if ( axis . type === 'date' ) dataToPixel = decodeDate ( dataToPixel ) ;
553
+ }
554
+ else if ( isVertical ) {
555
+ dataToPixel = function ( v ) { return gs . t + gs . h * ( 1 - v ) ; } ;
556
+ }
557
+ else {
558
+ dataToPixel = function ( v ) { return gs . l + gs . w * v ; } ;
559
+ }
560
+
561
+ return dataToPixel ;
562
+ }
563
+
564
+ function getPixelToData ( gd , axis , isVertical ) {
565
+ var gs = gd . _fullLayout . _size ,
566
+ pixelToData ;
567
+
568
+ if ( axis ) {
569
+ var l2d = linearToData ( axis ) ;
570
+ pixelToData = function ( p ) { return l2d ( axis . p2l ( p - axis . _offset ) ) ; } ;
571
+ }
572
+ else if ( isVertical ) {
573
+ pixelToData = function ( p ) { return 1 - ( p - gs . t ) / gs . h ; } ;
574
+ }
575
+ else {
576
+ pixelToData = function ( p ) { return ( p - gs . l ) / gs . w ; } ;
577
+ }
578
+
579
+ return pixelToData ;
376
580
}
377
581
378
- function shapePath ( gd , options ) {
582
+ function getPathString ( gd , options ) {
379
583
var type = options . type ,
380
584
xa = Axes . getFromId ( gd , options . xref ) ,
381
585
ya = Axes . getFromId ( gd , options . yref ) ,
@@ -501,6 +705,29 @@ shapes.convertPath = function(pathIn, x2p, y2p) {
501
705
} ) ;
502
706
} ;
503
707
708
+ function movePath ( pathIn , moveX , moveY ) {
709
+ return pathIn . replace ( segmentRE , function ( segment ) {
710
+ var paramNumber = 0 ,
711
+ segmentType = segment . charAt ( 0 ) ,
712
+ xParams = paramIsX [ segmentType ] ,
713
+ yParams = paramIsY [ segmentType ] ,
714
+ nParams = numParams [ segmentType ] ;
715
+
716
+ var paramString = segment . substr ( 1 ) . replace ( paramRE , function ( param ) {
717
+ if ( paramNumber >= nParams ) return param ;
718
+
719
+ if ( xParams [ paramNumber ] ) param = moveX ( param ) ;
720
+ else if ( yParams [ paramNumber ] ) param = moveY ( param ) ;
721
+
722
+ paramNumber ++ ;
723
+
724
+ return param ;
725
+ } ) ;
726
+
727
+ return segmentType + paramString ;
728
+ } ) ;
729
+ }
730
+
504
731
shapes . calcAutorange = function ( gd ) {
505
732
var fullLayout = gd . _fullLayout ,
506
733
shapeList = fullLayout . shapes ,
0 commit comments