Skip to content

Commit ef3ca48

Browse files
committed
allow constraints with same axis letter
1 parent dee136d commit ef3ca48

File tree

6 files changed

+87
-68
lines changed

6 files changed

+87
-68
lines changed

src/plots/cartesian/constraint_defaults.js

+9-7
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,12 @@ var Lib = require('../../lib');
1313
var id2name = require('./axis_ids').id2name;
1414

1515

16-
module.exports = function handleConstraintDefaults(containerIn, containerOut, coerce, counterAxes, layoutOut) {
16+
module.exports = function handleConstraintDefaults(containerIn, containerOut, coerce, allAxisIds, layoutOut) {
1717
var constraintGroups = layoutOut._axisConstraintGroups;
1818

1919
if(containerOut.fixedrange || !containerIn.scaleanchor) return;
2020

21-
var constraintOpts = getConstraintOpts(constraintGroups, containerOut._id, counterAxes, layoutOut);
21+
var constraintOpts = getConstraintOpts(constraintGroups, containerOut._id, allAxisIds, layoutOut);
2222

2323
var scaleanchor = Lib.coerce(containerIn, containerOut, {
2424
scaleanchor: {
@@ -39,26 +39,28 @@ module.exports = function handleConstraintDefaults(containerIn, containerOut, co
3939
updateConstraintGroups(constraintGroups, constraintOpts.thisGroup,
4040
containerOut._id, scaleanchor, scaleratio);
4141
}
42-
else if(counterAxes.indexOf(containerIn.scaleanchor) !== -1) {
42+
else if(allAxisIds.indexOf(containerIn.scaleanchor) !== -1) {
4343
Lib.warn('ignored ' + containerOut._name + '.scaleanchor: "' +
4444
containerIn.scaleanchor + '" to avoid either an infinite loop ' +
4545
'and possibly inconsistent scaleratios, or because the target' +
4646
'axis has fixed range.');
4747
}
4848
};
4949

50-
function getConstraintOpts(constraintGroups, thisID, counterAxes, layoutOut) {
50+
function getConstraintOpts(constraintGroups, thisID, allAxisIds, layoutOut) {
5151
// If this axis is already part of a constraint group, we can't
5252
// scaleanchor any other axis in that group, or we'd make a loop.
53-
// Filter counterAxes to enforce this, also matching axis types.
53+
// Filter allAxisIds to enforce this, also matching axis types.
5454

5555
var thisType = layoutOut[id2name(thisID)].type;
5656

5757
var i, j, idj, axj;
5858

5959
var linkableAxes = [];
60-
for(j = 0; j < counterAxes.length; j++) {
61-
idj = counterAxes[j];
60+
for(j = 0; j < allAxisIds.length; j++) {
61+
idj = allAxisIds[j];
62+
if(idj === thisID) continue;
63+
6264
axj = layoutOut[id2name(idj)];
6365
if(axj.type === thisType && !axj.fixedrange) linkableAxes.push(idj);
6466
}

src/plots/cartesian/dragbox.js

+60-52
Original file line numberDiff line numberDiff line change
@@ -659,80 +659,88 @@ module.exports = function dragBox(gd, plotinfo, x, y, w, h, ns, ew) {
659659
// affected by this drag, and update them. look for all plots
660660
// sharing an affected axis (including the one being dragged)
661661
function updateSubplots(viewBox) {
662-
var j;
663-
var plotinfos = fullLayout._plots,
664-
subplots = Object.keys(plotinfos);
662+
var plotinfos = fullLayout._plots;
663+
var subplots = Object.keys(plotinfos);
664+
var xScaleFactor = viewBox[2] / xa[0]._length;
665+
var yScaleFactor = viewBox[3] / ya[0]._length;
666+
var editX = ew || isSubplotConstrained;
667+
var editY = ns || isSubplotConstrained;
668+
669+
var i, xScaleFactor2, yScaleFactor2, clipDx, clipDy;
670+
671+
// Find the appropriate scaling for this axis, if it's linked to the
672+
// dragged axes by constraints. 0 is special, it means this axis shouldn't
673+
// ever be scaled (will be converted to 1 if the other axis is scaled)
674+
function getLinkedScaleFactor(ax) {
675+
if(ax.fixedrange) return 0;
676+
677+
if(editX && xaLinked.indexOf(ax) !== -1) {
678+
return xScaleFactor;
679+
}
680+
if(editY && (isSubplotConstrained ? xaLinked : yaLinked).indexOf(ax) !== -1) {
681+
return yScaleFactor;
682+
}
683+
return 0;
684+
}
685+
686+
function scaleAndGetShift(ax, scaleFactor) {
687+
if(scaleFactor) {
688+
ax.range = ax._r.slice();
689+
scaleZoom(ax, scaleFactor);
690+
return ax._length * (1 - scaleFactor) / 2;
691+
}
692+
return 0;
693+
}
665694

666-
for(var i = 0; i < subplots.length; i++) {
695+
for(i = 0; i < subplots.length; i++) {
667696

668697
var subplot = plotinfos[subplots[i]],
669698
xa2 = subplot.xaxis,
670699
ya2 = subplot.yaxis,
671-
editX = (viewBox[0] !== 0 || viewBox[2] !== pw) && !xa2.fixedrange,
672-
editY = (viewBox[1] !== 0 || viewBox[3] !== ph) && !ya2.fixedrange;
673-
674-
if(editX) {
675-
// TODO: now that we're doing recomputeAxisLists can this be turned into
676-
// just xa.indexOf(xa2) !== -1?
677-
var isInX = false;
678-
for(j = 0; j < xa.length; j++) {
679-
if(xa[j]._id === xa2._id) {
680-
isInX = true;
681-
break;
682-
}
683-
}
684-
editX = editX && isInX;
685-
}
700+
editX2 = editX && !xa2.fixedrange && (xa.indexOf(xa2) !== -1),
701+
editY2 = editY && !ya2.fixedrange && (ya.indexOf(ya2) !== -1);
686702

687-
if(editY) {
688-
var isInY = false;
689-
for(j = 0; j < ya.length; j++) {
690-
if(ya[j]._id === ya2._id) {
691-
isInY = true;
692-
break;
693-
}
694-
}
695-
editY = editY && isInY;
703+
if(editX2) {
704+
xScaleFactor2 = xScaleFactor;
705+
clipDx = viewBox[0];
706+
}
707+
else {
708+
xScaleFactor2 = getLinkedScaleFactor(xa2);
709+
clipDx = scaleAndGetShift(xa2, xScaleFactor2);
696710
}
697711

698-
var xScaleFactor = editX ? viewBox[2] / xa2._length : 1,
699-
yScaleFactor = editY ? viewBox[3] / ya2._length : 1;
700-
701-
var clipDx = editX ? viewBox[0] : 0,
702-
clipDy = editY ? viewBox[1] : 0;
703-
704-
// modify these if needed for linked axes
705-
if(editX && !editY && xaLinked.indexOf(ya2) !== -1) {
706-
yScaleFactor = xScaleFactor;
707-
clipDy = ya2._length * (1 - yScaleFactor) / 2;
708-
709-
// update range for the linked axis.
710-
ya2.range = ya2._r.slice();
711-
scaleZoom(ya2, yScaleFactor);
712+
if(editY2) {
713+
yScaleFactor2 = yScaleFactor;
714+
clipDy = viewBox[1];
712715
}
713-
if(editY && !editX && (isSubplotConstrained ? xaLinked : yaLinked).indexOf(xa2) !== -1) {
714-
xScaleFactor = yScaleFactor;
715-
clipDx = xa2._length * (1 - xScaleFactor) / 2;
716-
xa2.range = xa2._r.slice();
717-
scaleZoom(xa2, xScaleFactor);
716+
else {
717+
yScaleFactor2 = getLinkedScaleFactor(ya2);
718+
clipDy = scaleAndGetShift(ya2, yScaleFactor2);
718719
}
719720

720-
var plotDx = xa2._offset - clipDx / xScaleFactor,
721-
plotDy = ya2._offset - clipDy / yScaleFactor;
721+
// don't scale at all if neither axis is scalable here
722+
if(!xScaleFactor2 && !yScaleFactor2) continue;
723+
724+
// but if only one is, reset the other axis scaling
725+
if(!xScaleFactor2) xScaleFactor2 = 1;
726+
if(!yScaleFactor2) yScaleFactor2 = 1;
727+
728+
var plotDx = xa2._offset - clipDx / xScaleFactor2,
729+
plotDy = ya2._offset - clipDy / yScaleFactor2;
722730

723731
fullLayout._defs.selectAll('#' + subplot.clipId)
724732
.call(Drawing.setTranslate, clipDx, clipDy)
725-
.call(Drawing.setScale, xScaleFactor, yScaleFactor);
733+
.call(Drawing.setScale, xScaleFactor2, yScaleFactor2);
726734

727735
subplot.plot
728736
.call(Drawing.setTranslate, plotDx, plotDy)
729-
.call(Drawing.setScale, 1 / xScaleFactor, 1 / yScaleFactor)
737+
.call(Drawing.setScale, 1 / xScaleFactor2, 1 / yScaleFactor2)
730738

731739
// This is specifically directed at scatter traces, applying an inverse
732740
// scale to individual points to counteract the scale of the trace
733741
// as a whole:
734742
.select('.scatterlayer').selectAll('.points').selectAll('.point')
735-
.call(Drawing.setPointGroupScale, xScaleFactor, yScaleFactor);
743+
.call(Drawing.setPointGroupScale, xScaleFactor2, yScaleFactor2);
736744
}
737745
}
738746

src/plots/cartesian/layout_defaults.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,7 @@ module.exports = function supplyLayoutDefaults(layoutIn, layoutOut, fullData) {
231231
// sets of axes linked by `scaleanchor` along with the scaleratios compounded
232232
// together, populated in handleConstraintDefaults
233233
layoutOut._axisConstraintGroups = [];
234+
var allAxisIds = counterAxes.x.concat(counterAxes.y);
234235

235236
for(i = 0; i < axesList.length; i++) {
236237
axName = axesList[i];
@@ -239,6 +240,6 @@ module.exports = function supplyLayoutDefaults(layoutIn, layoutOut, fullData) {
239240
axLayoutIn = layoutIn[axName];
240241
axLayoutOut = layoutOut[axName];
241242

242-
handleConstraintDefaults(axLayoutIn, axLayoutOut, coerce, counterAxes[axLetter], layoutOut);
243+
handleConstraintDefaults(axLayoutIn, axLayoutOut, coerce, allAxisIds, layoutOut);
243244
}
244245
};
15.2 KB
Loading
+9-4
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,20 @@
11
{
22
"data":[
33
{"x": [0,1,1,0,0,1,1,2,2,3,3,2,2,3], "y": [0,0,1,1,3,3,2,2,3,3,1,1,0,0]},
4-
{"x": [0,1,2,3], "y": [1,2,4,8], "yaxis":"y2"}
4+
{"x": [0,1,2,3], "y": [1,2,4,8], "yaxis":"y2"},
5+
{"x": [1,10,100,10,1], "y": [0,1,2,3,4], "xaxis":"x2", "yaxis":"y3"},
6+
{"x": [1,100,30,80,1], "y": [1,1.5,2,2.5,3], "xaxis":"x2", "yaxis":"y4"}
57
],
68
"layout":{
7-
"width": 600,
8-
"height":600,
9+
"width": 800,
10+
"height":500,
911
"title": "fixed-ratio axes",
10-
"xaxis": {"nticks": 10, "title": "shared X axis"},
12+
"xaxis": {"nticks": 10, "domain": [0, 0.45], "title": "shared X axis"},
1113
"yaxis": {"scaleanchor": "x", "domain": [0, 0.45], "title": "1:1"},
1214
"yaxis2": {"scaleanchor": "x", "scaleratio": 0.2, "domain": [0.55,1], "title": "1:5"},
15+
"xaxis2": {"type": "log", "domain": [0.55, 1], "title": "unconstrained log X"},
16+
"yaxis3": {"domain": [0, 0.45], "anchor": "x2", "title": "Scale matches ->"},
17+
"yaxis4": {"scaleanchor": "y3", "domain": [0.55, 1], "anchor": "x2", "title": "Scale matches <-"},
1318
"showlegend": false
1419
}
1520
}

test/jasmine/tests/axes_test.js

+7-4
Original file line numberDiff line numberDiff line change
@@ -475,18 +475,21 @@ describe('Test axes', function() {
475475

476476
layoutIn = {
477477
xaxis: {scaleanchor: 'y', scaleratio: 2},
478-
yaxis: {scaleanchor: 'x', scaleratio: 3},
478+
yaxis: {scaleanchor: 'x', scaleratio: 3}, // dropped loop
479479

480480
xaxis2: {scaleanchor: 'y2', scaleratio: 5},
481481
yaxis2: {scaleanchor: 'x3', scaleratio: 7},
482482
xaxis3: {scaleanchor: 'y3', scaleratio: 9},
483-
yaxis3: {scaleanchor: 'x2', scaleratio: 11}
483+
yaxis3: {scaleanchor: 'x2', scaleratio: 11}, // dropped loop
484+
485+
xaxis4: {scaleanchor: 'x', scaleratio: 13}, // x<->x is OK now
486+
yaxis4: {scaleanchor: 'y', scaleratio: 17}, // y<->y is OK now
484487
};
485488

486489
supplyLayoutDefaults(layoutIn, layoutOut, fullData);
487490

488491
expect(layoutOut._axisConstraintGroups).toEqual([
489-
{x: 2, y: 1},
492+
{x: 2, y: 1, x4: 2 * 13, y4: 17},
490493
{x2: 5 * 7 * 9, y2: 7 * 9, y3: 1, x3: 9}
491494
]);
492495

@@ -507,7 +510,7 @@ describe('Test axes', function() {
507510
});
508511

509512
layoutIn = {
510-
xaxis: {scaleanchor: 'x2', scaleratio: 2}, // must be opposite letter
513+
xaxis: {scaleanchor: 'x', scaleratio: 2}, // can't link to itself
511514
yaxis: {scaleanchor: 'x4', scaleratio: 3}, // doesn't exist
512515
xaxis2: {scaleanchor: 'yaxis', scaleratio: 5} // must be an id, not a name
513516
};

0 commit comments

Comments
 (0)