@@ -76,12 +76,12 @@ function makeDragBox(gd, plotinfo, x, y, w, h, ns, ew) {
76
76
// which are the x/y {ax._id: ax} hash objects and their values
77
77
// for linked axis relative to this subplot
78
78
var links ;
79
+ // similar to `links` but for matching axes
80
+ var matches ;
79
81
// set to ew/ns val when active, set to '' when inactive
80
82
var xActive , yActive ;
81
83
// are all axes in this subplot are fixed?
82
84
var allFixedRanges ;
83
- // is subplot constrained?
84
- var isSubplotConstrained ;
85
85
// do we need to edit x/y ranges?
86
86
var editX , editY ;
87
87
// graph-wide optimization flags
@@ -119,10 +119,10 @@ function makeDragBox(gd, plotinfo, x, y, w, h, ns, ew) {
119
119
yActive = isDirectionActive ( yaxes , ns ) ;
120
120
allFixedRanges = ! yActive && ! xActive ;
121
121
122
- links = calcLinks ( gd , xaHash , yaHash ) ;
123
- isSubplotConstrained = links . isSubplotConstrained ;
124
- editX = ew || isSubplotConstrained ;
125
- editY = ns || isSubplotConstrained ;
122
+ links = calcLinks ( gd , gd . _fullLayout . _axisConstraintGroups , xaHash , yaHash ) ;
123
+ matches = calcLinks ( gd , gd . _fullLayout . _axisMatchGroups , xaHash , yaHash ) ;
124
+ editX = ew || links . isSubplotConstrained || matches . isSubplotConstrained ;
125
+ editY = ns || links . isSubplotConstrained || matches . isSubplotConstrained ;
126
126
127
127
var fullLayout = gd . _fullLayout ;
128
128
hasScatterGl = fullLayout . _has ( 'scattergl' ) ;
@@ -337,22 +337,36 @@ function makeDragBox(gd, plotinfo, x, y, w, h, ns, ew) {
337
337
corners . attr ( 'd' , 'M0,0Z' ) ;
338
338
}
339
339
340
- if ( isSubplotConstrained ) {
340
+ if ( links . isSubplotConstrained ) {
341
341
if ( dx > MINZOOM || dy > MINZOOM ) {
342
342
zoomMode = 'xy' ;
343
343
if ( dx / pw > dy / ph ) {
344
344
dy = dx * ph / pw ;
345
345
if ( y0 > y1 ) box . t = y0 - dy ;
346
346
else box . b = y0 + dy ;
347
- }
348
- else {
347
+ } else {
349
348
dx = dy * pw / ph ;
350
349
if ( x0 > x1 ) box . l = x0 - dx ;
351
350
else box . r = x0 + dx ;
352
351
}
353
352
corners . attr ( 'd' , xyCorners ( box ) ) ;
353
+ } else {
354
+ noZoom ( ) ;
354
355
}
355
- else {
356
+ }
357
+ else if ( matches . isSubplotConstrained ) {
358
+ if ( dx > MINZOOM || dy > MINZOOM ) {
359
+ zoomMode = 'xy' ;
360
+
361
+ var r0 = Math . min ( box . l / pw , ( ph - box . b ) / ph ) ;
362
+ var r1 = Math . max ( box . r / pw , ( ph - box . t ) / ph ) ;
363
+
364
+ box . l = r0 * pw ;
365
+ box . r = r1 * pw ;
366
+ box . b = ( 1 - r0 ) * ph ;
367
+ box . t = ( 1 - r1 ) * ph ;
368
+ corners . attr ( 'd' , xyCorners ( box ) ) ;
369
+ } else {
356
370
noZoom ( ) ;
357
371
}
358
372
}
@@ -399,10 +413,10 @@ function makeDragBox(gd, plotinfo, x, y, w, h, ns, ew) {
399
413
400
414
// TODO: edit linked axes in zoomAxRanges and in dragTail
401
415
if ( zoomMode === 'xy' || zoomMode === 'x' ) {
402
- zoomAxRanges ( xaxes , box . l / pw , box . r / pw , updates , links . xaxes ) ;
416
+ zoomAxRanges ( xaxes , box . l / pw , box . r / pw , updates , links . xaxes , matches . xaxes ) ;
403
417
}
404
418
if ( zoomMode === 'xy' || zoomMode === 'y' ) {
405
- zoomAxRanges ( yaxes , ( ph - box . b ) / ph , ( ph - box . t ) / ph , updates , links . yaxes ) ;
419
+ zoomAxRanges ( yaxes , ( ph - box . b ) / ph , ( ph - box . t ) / ph , updates , links . yaxes , matches . yaxes ) ;
406
420
}
407
421
408
422
removeZoombox ( gd ) ;
@@ -468,6 +482,7 @@ function makeDragBox(gd, plotinfo, x, y, w, h, ns, ew) {
468
482
for ( i = 0 ; i < xaxes . length ; i ++ ) {
469
483
zoomWheelOneAxis ( xaxes [ i ] , xfrac , zoom ) ;
470
484
}
485
+ updateMatchedAxes ( matches . isSubplotConstrained ? yaxes : matches . xaxes , xaxes [ 0 ] . range ) ;
471
486
472
487
scrollViewBox [ 2 ] *= zoom ;
473
488
scrollViewBox [ 0 ] += scrollViewBox [ 2 ] * xfrac * ( 1 / zoom - 1 ) ;
@@ -478,6 +493,7 @@ function makeDragBox(gd, plotinfo, x, y, w, h, ns, ew) {
478
493
for ( i = 0 ; i < yaxes . length ; i ++ ) {
479
494
zoomWheelOneAxis ( yaxes [ i ] , yfrac , zoom ) ;
480
495
}
496
+ updateMatchedAxes ( matches . isSubplotConstrained ? xaxes : matches . yaxes , yaxes [ 0 ] . range ) ;
481
497
482
498
scrollViewBox [ 3 ] *= zoom ;
483
499
scrollViewBox [ 1 ] += scrollViewBox [ 3 ] * ( 1 - yfrac ) * ( 1 / zoom - 1 ) ;
@@ -513,9 +529,19 @@ function makeDragBox(gd, plotinfo, x, y, w, h, ns, ew) {
513
529
// prevent axis drawing from monkeying with margins until we're done
514
530
gd . _fullLayout . _replotting = true ;
515
531
532
+ var matchedByXaxes ;
533
+ var matchesByYaxes ;
534
+ if ( matches . isSubplotConstrained ) {
535
+ matchedByXaxes = yaxes ;
536
+ matchesByYaxes = xaxes ;
537
+ } else {
538
+ matchedByXaxes = matches . xaxes ;
539
+ matchesByYaxes = matches . yaxes ;
540
+ }
541
+
516
542
if ( xActive === 'ew' || yActive === 'ns' ) {
517
- if ( xActive ) dragAxList ( xaxes , dx ) ;
518
- if ( yActive ) dragAxList ( yaxes , dy ) ;
543
+ if ( xActive ) dragAxList ( xaxes , matchedByXaxes , dx ) ;
544
+ if ( yActive ) dragAxList ( yaxes , matchesByYaxes , dy ) ;
519
545
updateSubplots ( [ xActive ? - dx : 0 , yActive ? - dy : 0 , pw , ph ] ) ;
520
546
ticksAndAnnotations ( ) ;
521
547
return ;
@@ -546,7 +572,7 @@ function makeDragBox(gd, plotinfo, x, y, w, h, ns, ew) {
546
572
( movedAx . _rl [ end ] - movedAx . _rl [ otherEnd ] ) ;
547
573
}
548
574
549
- if ( isSubplotConstrained && xActive && yActive ) {
575
+ if ( links . isSubplotConstrained && xActive && yActive ) {
550
576
// dragging a corner of a constrained subplot:
551
577
// respect the fixed corner, but harmonize dx and dy
552
578
var dxySign = ( ( xActive === 'w' ) === ( yActive === 'n' ) ) ? 1 : - 1 ;
@@ -566,7 +592,7 @@ function makeDragBox(gd, plotinfo, x, y, w, h, ns, ew) {
566
592
var x0 = ( xActive === 'w' ) ? dx : 0 ;
567
593
var y0 = ( yActive === 'n' ) ? dy : 0 ;
568
594
569
- if ( isSubplotConstrained ) {
595
+ if ( links . isSubplotConstrained ) {
570
596
var i ;
571
597
if ( ! xActive && yActive . length === 1 ) {
572
598
// dragging one end of the y axis of a constrained subplot
@@ -588,6 +614,8 @@ function makeDragBox(gd, plotinfo, x, y, w, h, ns, ew) {
588
614
}
589
615
}
590
616
617
+ updateMatchedAxes ( matchedByXaxes , xaxes [ 0 ] . range ) ;
618
+ updateMatchedAxes ( matchesByYaxes , yaxes [ 0 ] . range ) ;
591
619
updateSubplots ( [ x0 , y0 , pw - dx , ph - dy ] ) ;
592
620
ticksAndAnnotations ( ) ;
593
621
}
@@ -607,10 +635,12 @@ function makeDragBox(gd, plotinfo, x, y, w, h, ns, ew) {
607
635
if ( editX ) {
608
636
pushActiveAxIds ( xaxes ) ;
609
637
pushActiveAxIds ( links . xaxes ) ;
638
+ pushActiveAxIds ( matches . xaxes ) ;
610
639
}
611
640
if ( editY ) {
612
641
pushActiveAxIds ( yaxes ) ;
613
642
pushActiveAxIds ( links . yaxes ) ;
643
+ pushActiveAxIds ( matches . yaxes ) ;
614
644
}
615
645
616
646
updates = { } ;
@@ -629,9 +659,14 @@ function makeDragBox(gd, plotinfo, x, y, w, h, ns, ew) {
629
659
if ( gd . _transitioningWithDuration ) return ;
630
660
631
661
var doubleClickConfig = gd . _context . doubleClick ;
632
- var axList = ( xActive ? xaxes : [ ] ) . concat ( yActive ? yaxes : [ ] ) ;
633
- var attrs = { } ;
634
662
663
+ var axList = [ ] ;
664
+ if ( xActive ) axList = axList . concat ( xaxes ) ;
665
+ if ( yActive ) axList = axList . concat ( yaxes ) ;
666
+ if ( matches . xaxes ) axList = axList . concat ( matches . xaxes ) ;
667
+ if ( matches . yaxes ) axList = axList . concat ( matches . yaxes ) ;
668
+
669
+ var attrs = { } ;
635
670
var ax , i , rangeInitial ;
636
671
637
672
// For reset+autosize mode:
@@ -668,10 +703,10 @@ function makeDragBox(gd, plotinfo, x, y, w, h, ns, ew) {
668
703
else if ( doubleClickConfig === 'reset' ) {
669
704
// when we're resetting, reset all linked axes too, so we get back
670
705
// to the fully-auto-with-constraints situation
671
- if ( xActive || isSubplotConstrained ) axList = axList . concat ( links . xaxes ) ;
672
- if ( yActive && ! isSubplotConstrained ) axList = axList . concat ( links . yaxes ) ;
706
+ if ( xActive || links . isSubplotConstrained ) axList = axList . concat ( links . xaxes ) ;
707
+ if ( yActive && ! links . isSubplotConstrained ) axList = axList . concat ( links . yaxes ) ;
673
708
674
- if ( isSubplotConstrained ) {
709
+ if ( links . isSubplotConstrained ) {
675
710
if ( ! xActive ) axList = axList . concat ( xaxes ) ;
676
711
else if ( ! yActive ) axList = axList . concat ( yaxes ) ;
677
712
}
@@ -713,10 +748,6 @@ function makeDragBox(gd, plotinfo, x, y, w, h, ns, ew) {
713
748
] , gd ) ;
714
749
}
715
750
716
- // x/y scaleFactor stash,
717
- // minimizes number of per-point DOM updates in updateSubplots below
718
- var xScaleFactorOld , yScaleFactorOld ;
719
-
720
751
// updateSubplots - find all plot viewboxes that should be
721
752
// affected by this drag, and update them. look for all plots
722
753
// sharing an affected axis (including the one being dragged),
@@ -768,6 +799,14 @@ function makeDragBox(gd, plotinfo, x, y, w, h, ns, ew) {
768
799
if ( editX2 ) {
769
800
xScaleFactor2 = xScaleFactor ;
770
801
clipDx = ew ? viewBox [ 0 ] : getShift ( xa , xScaleFactor2 ) ;
802
+ } else if ( matches . xaHash [ xa . _id ] ) {
803
+ xScaleFactor2 = xScaleFactor ;
804
+ clipDx = viewBox [ 0 ] * xa . _length / xa0 . _length ;
805
+ } else if ( matches . yaHash [ xa . _id ] ) {
806
+ xScaleFactor2 = yScaleFactor ;
807
+ clipDx = yActive === 'ns' ?
808
+ - viewBox [ 1 ] * xa . _length / ya0 . _length :
809
+ getShift ( xa , xScaleFactor2 , { n : 'top' , s : 'bottom' } [ yActive ] ) ;
771
810
} else {
772
811
xScaleFactor2 = getLinkedScaleFactor ( xa , xScaleFactor , yScaleFactor ) ;
773
812
clipDx = scaleAndGetShift ( xa , xScaleFactor2 ) ;
@@ -776,6 +815,14 @@ function makeDragBox(gd, plotinfo, x, y, w, h, ns, ew) {
776
815
if ( editY2 ) {
777
816
yScaleFactor2 = yScaleFactor ;
778
817
clipDy = ns ? viewBox [ 1 ] : getShift ( ya , yScaleFactor2 ) ;
818
+ } else if ( matches . yaHash [ ya . _id ] ) {
819
+ yScaleFactor2 = yScaleFactor ;
820
+ clipDy = viewBox [ 1 ] * ya . _length / ya0 . _length ;
821
+ } else if ( matches . xaHash [ ya . _id ] ) {
822
+ yScaleFactor2 = xScaleFactor ;
823
+ clipDy = xActive === 'ew' ?
824
+ - viewBox [ 0 ] * ya . _length / xa0 . _length :
825
+ getShift ( ya , yScaleFactor2 , { e : 'right' , w : 'left' } [ xActive ] ) ;
779
826
} else {
780
827
yScaleFactor2 = getLinkedScaleFactor ( ya , xScaleFactor , yScaleFactor ) ;
781
828
clipDy = scaleAndGetShift ( ya , yScaleFactor2 ) ;
@@ -809,16 +856,16 @@ function makeDragBox(gd, plotinfo, x, y, w, h, ns, ew) {
809
856
// the scale of the trace group.
810
857
// apply only when scale changes, as adjusting the scale of
811
858
// all the points can be expansive.
812
- if ( xScaleFactor2 !== xScaleFactorOld || yScaleFactor2 !== yScaleFactorOld ) {
859
+ if ( xScaleFactor2 !== sp . xScaleFactor || yScaleFactor2 !== sp . yScaleFactor ) {
813
860
Drawing . setPointGroupScale ( sp . zoomScalePts , xScaleFactor2 , yScaleFactor2 ) ;
814
861
Drawing . setTextPointsScale ( sp . zoomScaleTxt , xScaleFactor2 , yScaleFactor2 ) ;
815
862
}
816
863
817
864
Drawing . hideOutsideRangePoints ( sp . clipOnAxisFalseTraces , sp ) ;
818
865
819
866
// update x/y scaleFactor stash
820
- xScaleFactorOld = xScaleFactor2 ;
821
- yScaleFactorOld = yScaleFactor2 ;
867
+ sp . xScaleFactor = xScaleFactor2 ;
868
+ sp . yScaleFactor = yScaleFactor2 ;
822
869
}
823
870
}
824
871
}
@@ -832,7 +879,7 @@ function makeDragBox(gd, plotinfo, x, y, w, h, ns, ew) {
832
879
if ( editX && links . xaHash [ ax . _id ] ) {
833
880
return xScaleFactor ;
834
881
}
835
- if ( editY && ( isSubplotConstrained ? links . xaHash : links . yaHash ) [ ax . _id ] ) {
882
+ if ( editY && ( links . isSubplotConstrained ? links . xaHash : links . yaHash ) [ ax . _id ] ) {
836
883
return yScaleFactor ;
837
884
}
838
885
return 0 ;
@@ -847,8 +894,8 @@ function makeDragBox(gd, plotinfo, x, y, w, h, ns, ew) {
847
894
return 0 ;
848
895
}
849
896
850
- function getShift ( ax , scaleFactor ) {
851
- return ax . _length * ( 1 - scaleFactor ) * FROM_TL [ ax . constraintoward || 'middle' ] ;
897
+ function getShift ( ax , scaleFactor , from ) {
898
+ return ax . _length * ( 1 - scaleFactor ) * FROM_TL [ from || ax . constraintoward || 'middle' ] ;
852
899
}
853
900
854
901
return dragger ;
@@ -900,7 +947,7 @@ function getEndText(ax, end) {
900
947
}
901
948
}
902
949
903
- function zoomAxRanges ( axList , r0Fraction , r1Fraction , updates , linkedAxes ) {
950
+ function zoomAxRanges ( axList , r0Fraction , r1Fraction , updates , linkedAxes , matchedAxes ) {
904
951
var i ,
905
952
axi ,
906
953
axRangeLinear0 ,
@@ -922,14 +969,22 @@ function zoomAxRanges(axList, r0Fraction, r1Fraction, updates, linkedAxes) {
922
969
}
923
970
924
971
// zoom linked axes about their centers
925
- if ( linkedAxes && linkedAxes . length ) {
972
+ if ( linkedAxes . length ) {
926
973
var linkedR0Fraction = ( r0Fraction + ( 1 - r1Fraction ) ) / 2 ;
974
+ zoomAxRanges ( linkedAxes , linkedR0Fraction , 1 - linkedR0Fraction , updates , [ ] , [ ] ) ;
975
+ }
927
976
928
- zoomAxRanges ( linkedAxes , linkedR0Fraction , 1 - linkedR0Fraction , updates ) ;
977
+ // TODO is picking the first ax always ok in general?
978
+ // What if we're matching an overlaying axis?
979
+ var rng = axList [ 0 ] . range ;
980
+ for ( i = 0 ; i < matchedAxes . length ; i ++ ) {
981
+ axi = matchedAxes [ i ] ;
982
+ updates [ axi . _name + '.range[0]' ] = rng [ 0 ] ;
983
+ updates [ axi . _name + '.range[1]' ] = rng [ 1 ] ;
929
984
}
930
985
}
931
986
932
- function dragAxList ( axList , pix ) {
987
+ function dragAxList ( axList , matchedAxes , pix ) {
933
988
for ( var i = 0 ; i < axList . length ; i ++ ) {
934
989
var axi = axList [ i ] ;
935
990
if ( ! axi . fixedrange ) {
@@ -939,6 +994,16 @@ function dragAxList(axList, pix) {
939
994
] ;
940
995
}
941
996
}
997
+
998
+ updateMatchedAxes ( matchedAxes , axList [ 0 ] . range ) ;
999
+ }
1000
+
1001
+ function updateMatchedAxes ( matchedAxes , rng ) {
1002
+ // TODO is picking the first ax always ok in general?
1003
+ // What if we're matching an overlaying axis?
1004
+ for ( var i = 0 ; i < matchedAxes . length ; i ++ ) {
1005
+ matchedAxes [ i ] . range = rng . slice ( ) ;
1006
+ }
942
1007
}
943
1008
944
1009
// common transform for dragging one end of an axis
@@ -1052,15 +1117,14 @@ function xyCorners(box) {
1052
1117
'h' + clen + 'v3h-' + ( clen + 3 ) + 'Z' ;
1053
1118
}
1054
1119
1055
- function calcLinks ( gd , xaHash , yaHash ) {
1056
- var constraintGroups = gd . _fullLayout . _axisConstraintGroups ;
1120
+ function calcLinks ( gd , groups , xaHash , yaHash ) {
1057
1121
var isSubplotConstrained = false ;
1058
1122
var xLinks = { } ;
1059
1123
var yLinks = { } ;
1060
1124
var xID , yID , xLinkID , yLinkID ;
1061
1125
1062
- for ( var i = 0 ; i < constraintGroups . length ; i ++ ) {
1063
- var group = constraintGroups [ i ] ;
1126
+ for ( var i = 0 ; i < groups . length ; i ++ ) {
1127
+ var group = groups [ i ] ;
1064
1128
// check if any of the x axes we're dragging is in this constraint group
1065
1129
for ( xID in xaHash ) {
1066
1130
if ( group [ xID ] ) {
0 commit comments