@@ -11,6 +11,7 @@ var customMatchers = require('../assets/custom_matchers');
1111var createGraphDiv = require ( '../assets/create_graph_div' ) ;
1212var destroyGraphDiv = require ( '../assets/destroy_graph_div' ) ;
1313var failTest = require ( '../assets/fail_test' ) ;
14+ var drag = require ( '../assets/drag' ) ;
1415
1516
1617describe ( 'Test annotations' , function ( ) {
@@ -740,3 +741,233 @@ describe('annotation clicktoshow', function() {
740741 . then ( done ) ;
741742 } ) ;
742743} ) ;
744+
745+ describe ( 'annotation dragging' , function ( ) {
746+ var gd ;
747+
748+ function textDrag ( ) { return gd . querySelector ( '.annotation-text-g>g' ) ; }
749+ function arrowDrag ( ) { return gd . querySelector ( '.annotation-arrow-g>.anndrag' ) ; }
750+ function textBox ( ) { return gd . querySelector ( '.annotation-text-g' ) ; }
751+
752+ beforeAll ( function ( ) {
753+ jasmine . addMatchers ( customMatchers ) ;
754+ } ) ;
755+
756+ beforeEach ( function ( done ) {
757+ gd = createGraphDiv ( ) ;
758+
759+ // we've already tested autorange with relayout, so fix the geometry
760+ // completely so we know exactly what we're dealing with
761+ // plot area is 300x300, and covers data range 100x100
762+ Plotly . plot ( gd ,
763+ [ { x : [ 0 , 100 ] , y : [ 0 , 100 ] , mode : 'markers' } ] ,
764+ {
765+ xaxis : { range : [ 0 , 100 ] } ,
766+ yaxis : { range : [ 0 , 100 ] } ,
767+ width : 500 ,
768+ height : 500 ,
769+ margin : { l : 100 , r : 100 , t : 100 , b : 100 , pad : 0 }
770+ } ,
771+ { editable : true }
772+ )
773+ . then ( done ) ;
774+ } ) ;
775+
776+ afterEach ( destroyGraphDiv ) ;
777+
778+ function initAnnotation ( annotation ) {
779+ return Plotly . relayout ( gd , { annotations : [ annotation ] } )
780+ . then ( function ( ) {
781+ return Plots . previousPromises ( gd ) ;
782+ } ) ;
783+ }
784+
785+ function dragAndReplot ( node , dx , dy , edge ) {
786+ return drag ( node , dx , dy , edge ) . then ( function ( ) {
787+ return Plots . previousPromises ( gd ) ;
788+ } ) ;
789+ }
790+
791+ /*
792+ * run through a series of drags of the same annotation
793+ * findDragger: fn that returns the element to drag on
794+ * (either textDrag or ArrowDrag)
795+ * autoshiftX, autoshiftY: how much does the annotation anchor shift
796+ * moving between one region and the next. Zero except if autoanchor
797+ * is active, ie paper-referenced with no arrow
798+ * coordScale: how big is the full plot? paper-referenced has scale 1
799+ * and for the plot defined above, data-referenced has scale 100
800+ */
801+ function checkDragging ( findDragger , autoshiftX , autoshiftY , coordScale ) {
802+ var bboxInitial = textBox ( ) . getBoundingClientRect ( ) ;
803+ // first move it within the same auto-anchor zone
804+ return dragAndReplot ( findDragger ( ) , 30 , - 30 )
805+ . then ( function ( ) {
806+ var bbox = textBox ( ) . getBoundingClientRect ( ) ;
807+
808+ // I'm not sure why these calculations aren't exact - they end up
809+ // being off by a fraction of a pixel, or a full pixel sometimes
810+ // even though as far as I can see in practice the positioning is
811+ // exact. In any event, this precision is enough to ensure that
812+ // anchor: auto is being used.
813+ expect ( bbox . left ) . toBeWithin ( bboxInitial . left + 30 , 1 ) ;
814+ expect ( bbox . top ) . toBeWithin ( bboxInitial . top - 30 , 1 ) ;
815+
816+ var ann = gd . layout . annotations [ 0 ] ;
817+ expect ( ann . x ) . toBeWithin ( 0.1 * coordScale , 0.01 * coordScale ) ;
818+ expect ( ann . y ) . toBeWithin ( 0.1 * coordScale , 0.01 * coordScale ) ;
819+
820+ // now move it to the center
821+ // note that we explicitly offset by half the box size because the
822+ // auto-anchor will move to the center
823+ return dragAndReplot ( findDragger ( ) , 120 - autoshiftX , - 120 + autoshiftY ) ;
824+ } )
825+ . then ( function ( ) {
826+ var bbox = textBox ( ) . getBoundingClientRect ( ) ;
827+ expect ( bbox . left ) . toBeWithin ( bboxInitial . left + 150 - autoshiftX , 2 ) ;
828+ expect ( bbox . top ) . toBeWithin ( bboxInitial . top - 150 + autoshiftY , 2 ) ;
829+
830+ var ann = gd . layout . annotations [ 0 ] ;
831+ expect ( ann . x ) . toBeWithin ( 0.5 * coordScale , 0.01 * coordScale ) ;
832+ expect ( ann . y ) . toBeWithin ( 0.5 * coordScale , 0.01 * coordScale ) ;
833+
834+ // next move it near the upper right corner, where the auto-anchor
835+ // moves to the top right corner
836+ // we don't move it all the way to the corner, so the annotation will
837+ // still be entirely on the plot even with an arrow.
838+ return dragAndReplot ( findDragger ( ) , 90 - autoshiftX , - 90 + autoshiftY ) ;
839+ } )
840+ . then ( function ( ) {
841+ var bbox = textBox ( ) . getBoundingClientRect ( ) ;
842+ expect ( bbox . left ) . toBeWithin ( bboxInitial . left + 240 - 2 * autoshiftX , 2 ) ;
843+ expect ( bbox . top ) . toBeWithin ( bboxInitial . top - 240 + 2 * autoshiftY , 2 ) ;
844+
845+ var ann = gd . layout . annotations [ 0 ] ;
846+ expect ( ann . x ) . toBeWithin ( 0.8 * coordScale , 0.01 * coordScale ) ;
847+ expect ( ann . y ) . toBeWithin ( 0.8 * coordScale , 0.01 * coordScale ) ;
848+
849+ // finally move it back to 0, 0
850+ return dragAndReplot ( findDragger ( ) , - 240 + 2 * autoshiftX , 240 - 2 * autoshiftY ) ;
851+ } )
852+ . then ( function ( ) {
853+ var bbox = textBox ( ) . getBoundingClientRect ( ) ;
854+ expect ( bbox . left ) . toBeWithin ( bboxInitial . left , 2 ) ;
855+ expect ( bbox . top ) . toBeWithin ( bboxInitial . top , 2 ) ;
856+
857+ var ann = gd . layout . annotations [ 0 ] ;
858+ expect ( ann . x ) . toBeWithin ( 0 * coordScale , 0.01 * coordScale ) ;
859+ expect ( ann . y ) . toBeWithin ( 0 * coordScale , 0.01 * coordScale ) ;
860+ } ) ;
861+ }
862+
863+ // for annotations with arrows: check that dragging the text moves only
864+ // ax and ay (and the textbox itself)
865+ function checkTextDrag ( ) {
866+ var ann = gd . layout . annotations [ 0 ] ,
867+ x0 = ann . x ,
868+ y0 = ann . y ,
869+ ax0 = ann . ax ,
870+ ay0 = ann . ay ;
871+
872+ var bboxInitial = textBox ( ) . getBoundingClientRect ( ) ;
873+
874+ return dragAndReplot ( textDrag ( ) , 50 , - 50 )
875+ . then ( function ( ) {
876+ var bbox = textBox ( ) . getBoundingClientRect ( ) ;
877+ expect ( bbox . left ) . toBeWithin ( bboxInitial . left + 50 , 1 ) ;
878+ expect ( bbox . top ) . toBeWithin ( bboxInitial . top - 50 , 1 ) ;
879+
880+ ann = gd . layout . annotations [ 0 ] ;
881+
882+ expect ( ann . x ) . toBe ( x0 ) ;
883+ expect ( ann . y ) . toBe ( y0 ) ;
884+ expect ( ann . ax ) . toBeWithin ( ax0 + 50 , 1 ) ;
885+ expect ( ann . ay ) . toBeWithin ( ay0 - 50 , 1 ) ;
886+ } ) ;
887+ }
888+
889+ it ( 'respects anchor: auto when paper-referenced without arrow' , function ( done ) {
890+ initAnnotation ( {
891+ x : 0 ,
892+ y : 0 ,
893+ showarrow : false ,
894+ text : 'blah<br>blah blah' ,
895+ xref : 'paper' ,
896+ yref : 'paper'
897+ } )
898+ . then ( function ( ) {
899+ var bbox = textBox ( ) . getBoundingClientRect ( ) ;
900+
901+ return checkDragging ( textDrag , bbox . width / 2 , bbox . height / 2 , 1 ) ;
902+ } )
903+ . catch ( failTest )
904+ . then ( done ) ;
905+ } ) ;
906+
907+ it ( 'also works paper-referenced with explicit anchors and no arrow' , function ( done ) {
908+ initAnnotation ( {
909+ x : 0 ,
910+ y : 0 ,
911+ showarrow : false ,
912+ text : 'blah<br>blah blah' ,
913+ xref : 'paper' ,
914+ yref : 'paper' ,
915+ xanchor : 'left' ,
916+ yanchor : 'top'
917+ } )
918+ . then ( function ( ) {
919+ // with offsets 0, 0 because the anchor doesn't change now
920+ return checkDragging ( textDrag , 0 , 0 , 1 ) ;
921+ } )
922+ . catch ( failTest )
923+ . then ( done ) ;
924+ } ) ;
925+
926+ it ( 'works paper-referenced with arrows' , function ( done ) {
927+ initAnnotation ( {
928+ x : 0 ,
929+ y : 0 ,
930+ text : 'blah<br>blah blah' ,
931+ xref : 'paper' ,
932+ yref : 'paper' ,
933+ ax : 30 ,
934+ ay : 30
935+ } )
936+ . then ( function ( ) {
937+ return checkDragging ( arrowDrag , 0 , 0 , 1 ) ;
938+ } )
939+ . then ( checkTextDrag )
940+ . catch ( failTest )
941+ . then ( done ) ;
942+ } ) ;
943+
944+ it ( 'works data-referenced with no arrow' , function ( done ) {
945+ initAnnotation ( {
946+ x : 0 ,
947+ y : 0 ,
948+ showarrow : false ,
949+ text : 'blah<br>blah blah'
950+ } )
951+ . then ( function ( ) {
952+ return checkDragging ( textDrag , 0 , 0 , 100 ) ;
953+ } )
954+ . catch ( failTest )
955+ . then ( done ) ;
956+ } ) ;
957+
958+ it ( 'works data-referenced with arrow' , function ( done ) {
959+ initAnnotation ( {
960+ x : 0 ,
961+ y : 0 ,
962+ text : 'blah<br>blah blah' ,
963+ ax : 30 ,
964+ ay : - 30
965+ } )
966+ . then ( function ( ) {
967+ return checkDragging ( arrowDrag , 0 , 0 , 100 ) ;
968+ } )
969+ . then ( checkTextDrag )
970+ . catch ( failTest )
971+ . then ( done ) ;
972+ } ) ;
973+ } ) ;
0 commit comments