@@ -8,6 +8,11 @@ var supplyAllDefaults = require('../assets/supply_defaults');
88var createGraphDiv = require ( '../assets/create_graph_div' ) ;
99var destroyGraphDiv = require ( '../assets/destroy_graph_div' ) ;
1010var failTest = require ( '../assets/fail_test' ) ;
11+ var mouseEvent = require ( '../assets/mouse_event' ) ;
12+ var drag = require ( '../assets/drag' ) ;
13+
14+ var customAssertions = require ( '../assets/custom_assertions' ) ;
15+ var assertHoverLabelContent = customAssertions . assertHoverLabelContent ;
1116
1217describe ( 'Test splom trace defaults:' , function ( ) {
1318 var gd ;
@@ -545,3 +550,365 @@ describe('@gl Test splom interactions:', function() {
545550 . then ( done ) ;
546551 } ) ;
547552} ) ;
553+
554+ describe ( '@gl Test splom hover:' , function ( ) {
555+ var gd ;
556+
557+ afterEach ( function ( ) {
558+ Plotly . purge ( gd ) ;
559+ destroyGraphDiv ( ) ;
560+ } ) ;
561+
562+ function run ( s , done ) {
563+ gd = createGraphDiv ( ) ;
564+
565+ var fig = Lib . extendDeep ( { } ,
566+ s . mock || require ( '@mocks/splom_iris.json' )
567+ ) ;
568+
569+ if ( s . patch ) {
570+ fig = s . patch ( fig ) ;
571+ }
572+
573+ var pos = s . pos || [ 200 , 100 ] ;
574+
575+ return Plotly . plot ( gd , fig ) . then ( function ( ) {
576+ var to = setTimeout ( function ( ) {
577+ failTest ( 'no event data received' ) ;
578+ done ( ) ;
579+ } , 100 ) ;
580+
581+ gd . on ( 'plotly_hover' , function ( d ) {
582+ clearTimeout ( to ) ;
583+ assertHoverLabelContent ( s ) ;
584+
585+ var msg = ' - event data ' + s . desc ;
586+ var actual = d . points || [ ] ;
587+ var exp = s . evtPts ;
588+ expect ( actual . length ) . toBe ( exp . length , 'pt length' + msg ) ;
589+ for ( var i = 0 ; i < exp . length ; i ++ ) {
590+ for ( var k in exp [ i ] ) {
591+ var m = 'key ' + k + ' in pt ' + i + msg ;
592+ expect ( actual [ i ] [ k ] ) . toBe ( exp [ i ] [ k ] , m ) ;
593+ }
594+ }
595+
596+ // w/o this purge gets called before
597+ // hover throttle is complete
598+ setTimeout ( done , 0 ) ;
599+ } ) ;
600+
601+ mouseEvent ( 'mousemove' , pos [ 0 ] , pos [ 1 ] ) ;
602+ } )
603+ . catch ( failTest ) ;
604+ }
605+
606+ var specs = [ {
607+ desc : 'basic' ,
608+ nums : '7.7' ,
609+ name : 'Virginica' ,
610+ axis : '2.6' ,
611+ evtPts : [ { x : 2.6 , y : 7.7 , pointNumber : 18 , curveNumber : 2 } ]
612+ } , {
613+ desc : 'hovermode closest' ,
614+ patch : function ( fig ) {
615+ fig . layout . hovermode = 'closest' ;
616+ return fig ;
617+ } ,
618+ nums : '(2.6, 7.7)' ,
619+ name : 'Virginica' ,
620+ evtPts : [ { x : 2.6 , y : 7.7 , pointNumber : 18 , curveNumber : 2 } ]
621+ } , {
622+ desc : 'skipping over visible false dims' ,
623+ patch : function ( fig ) {
624+ fig . data [ 0 ] . dimensions [ 0 ] . visible = false ;
625+ return fig ;
626+ } ,
627+ nums : '7.7' ,
628+ name : 'Virginica' ,
629+ axis : '2.6' ,
630+ evtPts : [ { x : 2.6 , y : 7.7 , pointNumber : 18 , curveNumber : 2 } ]
631+ } , {
632+ desc : 'on log axes' ,
633+ mock : require ( '@mocks/splom_log.json' ) ,
634+ patch : function ( fig ) {
635+ fig . layout . margin = { t : 0 , l : 0 , b : 0 , r : 0 } ;
636+ fig . layout . width = 400 ;
637+ fig . layout . height = 400 ;
638+ return fig ;
639+ } ,
640+ pos : [ 20 , 380 ] ,
641+ nums : '100' ,
642+ axis : '10' ,
643+ evtPts : [ { x : 10 , y : 100 , pointNumber : 0 } ]
644+ } , {
645+ desc : 'on date axes' ,
646+ mock : require ( '@mocks/splom_dates.json' ) ,
647+ patch : function ( fig ) {
648+ fig . layout = {
649+ margin : { t : 0 , l : 0 , b : 0 , r : 0 } ,
650+ width : 400 ,
651+ height : 400
652+ } ;
653+ return fig ;
654+ } ,
655+ pos : [ 20 , 380 ] ,
656+ nums : 'Apr 2003' ,
657+ axis : 'Jan 2000' ,
658+ evtPts : [ { x : '2000-01-01' , y : '2003-04-21' , pointNumber : 0 } ]
659+ } ] ;
660+
661+ specs . forEach ( function ( s ) {
662+ it ( 'should generate correct hover labels ' + s . desc , function ( done ) {
663+ run ( s , done ) ;
664+ } ) ;
665+ } ) ;
666+ } ) ;
667+
668+ describe ( '@gl Test splom drag:' , function ( ) {
669+ var gd ;
670+
671+ beforeEach ( function ( ) {
672+ gd = createGraphDiv ( ) ;
673+ } ) ;
674+
675+ afterEach ( function ( ) {
676+ Plotly . purge ( gd ) ;
677+ destroyGraphDiv ( ) ;
678+ } ) ;
679+
680+ function _drag ( p0 , p1 ) {
681+ var node = d3 . select ( '.nsewdrag[data-subplot="xy"]' ) . node ( ) ;
682+ var dx = p1 [ 0 ] - p0 [ 0 ] ;
683+ var dy = p1 [ 1 ] - p0 [ 1 ] ;
684+ return drag ( node , dx , dy , null , p0 [ 0 ] , p0 [ 1 ] ) ;
685+ }
686+
687+ it ( 'should update scattermatrix ranges on pan' , function ( done ) {
688+ var fig = require ( '@mocks/splom_iris.json' ) ;
689+ fig . layout . dragmode = 'pan' ;
690+
691+ var xaxes = [ 'xaxis' , 'xaxis2' , 'xaxis3' ] ;
692+ var yaxes = [ 'yaxis' , 'yaxis2' , 'yaxis3' ] ;
693+
694+ function _assertRanges ( msg , xRanges , yRanges ) {
695+ xaxes . forEach ( function ( n , i ) {
696+ expect ( gd . _fullLayout [ n ] . range )
697+ . toBeCloseToArray ( xRanges [ i ] , 1 , n + ' range - ' + msg ) ;
698+ } ) ;
699+ yaxes . forEach ( function ( n , i ) {
700+ expect ( gd . _fullLayout [ n ] . range )
701+ . toBeCloseToArray ( yRanges [ i ] , 1 , n + ' range - ' + msg ) ;
702+ } ) ;
703+ }
704+
705+ Plotly . plot ( gd , fig )
706+ . then ( function ( ) {
707+ var scene = gd . calcdata [ 0 ] [ 0 ] . t . _scene ;
708+ spyOn ( scene . matrix , 'update' ) ;
709+ spyOn ( scene . matrix , 'draw' ) ;
710+
711+ _assertRanges ( 'before drag' , [
712+ [ 3.9 , 8.3 ] ,
713+ [ 1.7 , 4.7 ] ,
714+ [ 0.3 , 7.6 ]
715+ ] , [
716+ [ 3.8 , 8.4 ] ,
717+ [ 1.7 , 4.7 ] ,
718+ [ 0.3 , 7.6 ]
719+ ] ) ;
720+ } )
721+ . then ( function ( ) { return _drag ( [ 130 , 130 ] , [ 150 , 150 ] ) ; } )
722+ . then ( function ( ) {
723+ var scene = gd . calcdata [ 0 ] [ 0 ] . t . _scene ;
724+ // N.B. _drag triggers two updateSubplots call
725+ // - 1 update and 1 draw call per updateSubplot
726+ // - 2 update calls (1 for data, 1 for view opts)
727+ // during splom plot on mouseup
728+ // - 1 draw call during splom plot on mouseup
729+ expect ( scene . matrix . update ) . toHaveBeenCalledTimes ( 4 ) ;
730+ expect ( scene . matrix . draw ) . toHaveBeenCalledTimes ( 3 ) ;
731+
732+ _assertRanges ( 'after drag' , [
733+ [ 2.9 , 7.3 ] ,
734+ [ 1.7 , 4.7 ] ,
735+ [ 0.3 , 7.6 ]
736+ ] , [
737+ [ 5.1 , 9.6 ] ,
738+ [ 1.7 , 4.7 ] ,
739+ [ 0.3 , 7.6 ]
740+ ] ) ;
741+ } )
742+ . catch ( failTest )
743+ . then ( done ) ;
744+ } ) ;
745+ } ) ;
746+
747+ describe ( '@gl Test splom select:' , function ( ) {
748+ var gd ;
749+ var ptData ;
750+ var subplot ;
751+
752+ beforeEach ( function ( ) {
753+ gd = createGraphDiv ( ) ;
754+ } ) ;
755+
756+ afterEach ( function ( ) {
757+ Plotly . purge ( gd ) ;
758+ destroyGraphDiv ( ) ;
759+ } ) ;
760+
761+ function _select ( path , opts ) {
762+ return new Promise ( function ( resolve , reject ) {
763+ opts = opts || { } ;
764+ ptData = null ;
765+ subplot = null ;
766+
767+ var to = setTimeout ( function ( ) {
768+ reject ( 'fail: plotly_selected not emitter' ) ;
769+ } , 200 ) ;
770+
771+ gd . once ( 'plotly_selected' , function ( d ) {
772+ clearTimeout ( to ) ;
773+ ptData = ( d || { } ) . points ;
774+ subplot = Object . keys ( d . range || { } ) . join ( '' ) ;
775+ resolve ( ) ;
776+ } ) ;
777+
778+ Lib . clearThrottle ( ) ;
779+ mouseEvent ( 'mousemove' , path [ 0 ] [ 0 ] , path [ 0 ] [ 1 ] , opts ) ;
780+ mouseEvent ( 'mousedown' , path [ 0 ] [ 0 ] , path [ 0 ] [ 1 ] , opts ) ;
781+
782+ var len = path . length ;
783+ path . slice ( 1 , len ) . forEach ( function ( pt ) {
784+ Lib . clearThrottle ( ) ;
785+ mouseEvent ( 'mousemove' , pt [ 0 ] , pt [ 1 ] , opts ) ;
786+ } ) ;
787+
788+ mouseEvent ( 'mouseup' , path [ len - 1 ] [ 0 ] , path [ len - 1 ] [ 1 ] , opts ) ;
789+ } ) ;
790+ }
791+
792+ it ( 'should emit correct event data and draw selection outlines' , function ( done ) {
793+ var fig = require ( '@mocks/splom_0.json' ) ;
794+ fig . layout = {
795+ dragmode : 'select' ,
796+ width : 400 ,
797+ height : 400 ,
798+ margin : { l : 0 , t : 0 , r : 0 , b : 0 } ,
799+ grid : { xgap : 0 , ygap : 0 }
800+ } ;
801+
802+ function _assert ( _msg , ptExp , otherExp ) {
803+ var msg = ' - ' + _msg ;
804+
805+ expect ( ptData . length ) . toBe ( ptExp . length , 'pt length' + msg ) ;
806+ for ( var i = 0 ; i < ptExp . length ; i ++ ) {
807+ for ( var k in ptExp [ i ] ) {
808+ var m = 'key ' + k + ' in pt ' + i + msg ;
809+ expect ( ptData [ i ] [ k ] ) . toBe ( ptExp [ i ] [ k ] , m ) ;
810+ }
811+ }
812+
813+ expect ( subplot ) . toBe ( otherExp . subplot , 'subplot of selection' + msg ) ;
814+
815+ expect ( d3 . selectAll ( '.zoomlayer > .select-outline' ) . size ( ) )
816+ . toBe ( otherExp . selectionOutlineCnt , 'selection outline cnt' + msg ) ;
817+ }
818+
819+ Plotly . newPlot ( gd , fig )
820+ . then ( function ( ) { return _select ( [ [ 5 , 5 ] , [ 195 , 195 ] ] ) ; } )
821+ . then ( function ( ) {
822+ _assert ( 'first' , [
823+ { pointNumber : 0 , x : 1 , y : 1 } ,
824+ { pointNumber : 1 , x : 2 , y : 2 } ,
825+ { pointNumber : 2 , x : 3 , y : 3 }
826+ ] , {
827+ subplot : 'xy' ,
828+ selectionOutlineCnt : 2
829+ } ) ;
830+ } )
831+ . then ( function ( ) { return _select ( [ [ 50 , 50 ] , [ 100 , 100 ] ] ) ; } )
832+ . then ( function ( ) {
833+ _assert ( 'second' , [
834+ { pointNumber : 1 , x : 2 , y : 2 }
835+ ] , {
836+ subplot : 'xy' ,
837+ selectionOutlineCnt : 2
838+ } ) ;
839+ } )
840+ . then ( function ( ) { return _select ( [ [ 5 , 195 ] , [ 100 , 100 ] ] , { shiftKey : true } ) ; } )
841+ . then ( function ( ) {
842+ _assert ( 'multi-select' , [
843+ { pointNumber : 0 , x : 1 , y : 1 } ,
844+ { pointNumber : 1 , x : 2 , y : 2 }
845+ ] , {
846+ subplot : 'xy' ,
847+ // still '2' as the selection get merged
848+ selectionOutlineCnt : 2
849+ } ) ;
850+ } )
851+ . then ( function ( ) { return _select ( [ [ 205 , 205 ] , [ 395 , 395 ] ] ) ; } )
852+ . then ( function ( ) {
853+ _assert ( 'across other subplot' , [
854+ { pointNumber : 0 , x : 2 , y : 2 } ,
855+ { pointNumber : 1 , x : 5 , y : 5 } ,
856+ { pointNumber : 2 , x : 6 , y : 6 }
857+ ] , {
858+ subplot : 'x2y2' ,
859+ // outlines from previous subplot are cleared!
860+ selectionOutlineCnt : 2
861+ } ) ;
862+ } )
863+ . then ( function ( ) { return _select ( [ [ 50 , 50 ] , [ 100 , 100 ] ] ) ; } )
864+ . then ( function ( ) {
865+ _assert ( 'multi-select across other subplot (prohibited for now)' , [
866+ { pointNumber : 1 , x : 2 , y : 2 }
867+ ] , {
868+ subplot : 'xy' ,
869+ // outlines from previous subplot are cleared!
870+ selectionOutlineCnt : 2
871+ } ) ;
872+ } )
873+ . catch ( failTest )
874+ . then ( done ) ;
875+ } ) ;
876+
877+ it ( 'should redraw splom traces before scattergl trace (if any)' , function ( done ) {
878+ var fig = require ( '@mocks/splom_with-cartesian.json' ) ;
879+ fig . layout . dragmode = 'select' ;
880+ fig . layout . width = 400 ;
881+ fig . layout . height = 400 ;
882+ fig . layout . margin = { l : 0 , t : 0 , r : 0 , b : 0 } ;
883+ fig . layout . grid . xgap = 0 ;
884+ fig . layout . grid . ygap = 0 ;
885+
886+ var cnt = 0 ;
887+ var scatterGlCnt = 0 ;
888+ var splomCnt = 0 ;
889+
890+ Plotly . newPlot ( gd , fig ) . then ( function ( ) {
891+ // 'scattergl' trace module
892+ spyOn ( gd . _fullLayout . _modules [ 0 ] , 'style' ) . and . callFake ( function ( ) {
893+ cnt ++ ;
894+ scatterGlCnt = cnt ;
895+ } ) ;
896+ // 'splom' trace module
897+ spyOn ( gd . _fullLayout . _modules [ 1 ] , 'style' ) . and . callFake ( function ( ) {
898+ cnt ++ ;
899+ splomCnt = cnt ;
900+ } ) ;
901+ } )
902+ . then ( function ( ) { return _select ( [ [ 20 , 395 ] , [ 195 , 205 ] ] ) ; } )
903+ . then ( function ( ) {
904+ expect ( gd . _fullLayout . _modules [ 0 ] . style ) . toHaveBeenCalledTimes ( 1 ) ;
905+ expect ( gd . _fullLayout . _modules [ 1 ] . style ) . toHaveBeenCalledTimes ( 1 ) ;
906+
907+ expect ( cnt ) . toBe ( 2 ) ;
908+ expect ( splomCnt ) . toBe ( 1 , 'splom redraw before scattergl' ) ;
909+ expect ( scatterGlCnt ) . toBe ( 2 , 'scattergl redraw after splom' ) ;
910+ } )
911+ . catch ( failTest )
912+ . then ( done ) ;
913+ } ) ;
914+ } ) ;
0 commit comments