Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implementing matching axes #3506

Merged
merged 23 commits into from
Feb 18, 2019
Merged
Changes from 1 commit
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
6813865
mv handleConstraintDefaults into constraints.js file
etpinard Jan 24, 2019
6f2bb78
first cut at static `ax.matches` behavior
etpinard Jan 23, 2019
5ddbf2a
some linting & commenting in dragbox.js
etpinard Feb 1, 2019
c60424b
first cut at zoom/pan/scroll `ax.matches` behavior
etpinard Feb 1, 2019
13007e0
add doScroll wrapper for dragbox tests
etpinard Jan 31, 2019
e962e96
mv dragbox tests calling makePlot to own describe block
etpinard Jan 31, 2019
ca1de5d
DRY-up drag-start/assert/drag-end tests
etpinard Jan 31, 2019
3b314fe
add toBeWithinArray custom jasmine matcher
etpinard Feb 1, 2019
6621419
add mucho matching axes dragbox tests
etpinard Jan 29, 2019
9118505
align autobinning of histogram traces on matching axes
etpinard Jan 28, 2019
d9c2d4e
link ax._categories & ax._categoriesMap to same ref for matching axes
etpinard Jan 28, 2019
c290adf
add axis.matches boolean to splom dims
etpinard Feb 1, 2019
ef256db
generalize "update matched ax rng" logic
etpinard Feb 1, 2019
9f2fad1
disallow constraining AND matching range
etpinard Feb 1, 2019
8c09944
add "matches" + "scaleanchor" mock
etpinard Feb 1, 2019
c5f9e74
fix partial ax-range relayout calls for matching axes
etpinard Feb 1, 2019
d0581e2
fix typo (ax.setScale has no arg)
etpinard Feb 5, 2019
6b34ae3
use ax._matchGroup to improve matching axes relayout perf
etpinard Feb 5, 2019
99a9edb
allow fixedrange subplots to scaleanchor with `constrain:domain`
etpinard Feb 6, 2019
91431ec
drop *scaleanchor* constraints for axes under *matches* constraint
etpinard Feb 12, 2019
70b10ff
add safe-guard in dragbox.js
etpinard Feb 18, 2019
772efe5
add info about matching axis type in attr description
etpinard Feb 18, 2019
1713c83
fix typo
etpinard Feb 18, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
142 changes: 103 additions & 39 deletions src/plots/cartesian/dragbox.js
Original file line number Diff line number Diff line change
Expand Up @@ -76,12 +76,12 @@ function makeDragBox(gd, plotinfo, x, y, w, h, ns, ew) {
// which are the x/y {ax._id: ax} hash objects and their values
// for linked axis relative to this subplot
var links;
// similar to `links` but for matching axes
var matches;
// set to ew/ns val when active, set to '' when inactive
var xActive, yActive;
// are all axes in this subplot are fixed?
var allFixedRanges;
// is subplot constrained?
var isSubplotConstrained;
// do we need to edit x/y ranges?
var editX, editY;
// graph-wide optimization flags
Expand Down Expand Up @@ -119,10 +119,10 @@ function makeDragBox(gd, plotinfo, x, y, w, h, ns, ew) {
yActive = isDirectionActive(yaxes, ns);
allFixedRanges = !yActive && !xActive;

links = calcLinks(gd, xaHash, yaHash);
isSubplotConstrained = links.isSubplotConstrained;
editX = ew || isSubplotConstrained;
editY = ns || isSubplotConstrained;
links = calcLinks(gd, gd._fullLayout._axisConstraintGroups, xaHash, yaHash);
matches = calcLinks(gd, gd._fullLayout._axisMatchGroups, xaHash, yaHash);
editX = ew || links.isSubplotConstrained || matches.isSubplotConstrained;
editY = ns || links.isSubplotConstrained || matches.isSubplotConstrained;

var fullLayout = gd._fullLayout;
hasScatterGl = fullLayout._has('scattergl');
Expand Down Expand Up @@ -337,22 +337,36 @@ function makeDragBox(gd, plotinfo, x, y, w, h, ns, ew) {
corners.attr('d', 'M0,0Z');
}

if(isSubplotConstrained) {
if(links.isSubplotConstrained) {
if(dx > MINZOOM || dy > MINZOOM) {
zoomMode = 'xy';
if(dx / pw > dy / ph) {
dy = dx * ph / pw;
if(y0 > y1) box.t = y0 - dy;
else box.b = y0 + dy;
}
else {
} else {
dx = dy * pw / ph;
if(x0 > x1) box.l = x0 - dx;
else box.r = x0 + dx;
}
corners.attr('d', xyCorners(box));
} else {
noZoom();
}
else {
}
else if(matches.isSubplotConstrained) {
if(dx > MINZOOM || dy > MINZOOM) {
zoomMode = 'xy';

var r0 = Math.min(box.l / pw, (ph - box.b) / ph);
var r1 = Math.max(box.r / pw, (ph - box.t) / ph);

box.l = r0 * pw;
box.r = r1 * pw;
box.b = (1 - r0) * ph;
box.t = (1 - r1) * ph;
corners.attr('d', xyCorners(box));
} else {
noZoom();
}
}
Expand Down Expand Up @@ -399,10 +413,10 @@ function makeDragBox(gd, plotinfo, x, y, w, h, ns, ew) {

// TODO: edit linked axes in zoomAxRanges and in dragTail
if(zoomMode === 'xy' || zoomMode === 'x') {
zoomAxRanges(xaxes, box.l / pw, box.r / pw, updates, links.xaxes);
zoomAxRanges(xaxes, box.l / pw, box.r / pw, updates, links.xaxes, matches.xaxes);
}
if(zoomMode === 'xy' || zoomMode === 'y') {
zoomAxRanges(yaxes, (ph - box.b) / ph, (ph - box.t) / ph, updates, links.yaxes);
zoomAxRanges(yaxes, (ph - box.b) / ph, (ph - box.t) / ph, updates, links.yaxes, matches.yaxes);
}

removeZoombox(gd);
Expand Down Expand Up @@ -468,6 +482,7 @@ function makeDragBox(gd, plotinfo, x, y, w, h, ns, ew) {
for(i = 0; i < xaxes.length; i++) {
zoomWheelOneAxis(xaxes[i], xfrac, zoom);
}
updateMatchedAxes(matches.isSubplotConstrained ? yaxes : matches.xaxes, xaxes[0].range);
archmoj marked this conversation as resolved.
Show resolved Hide resolved

scrollViewBox[2] *= zoom;
scrollViewBox[0] += scrollViewBox[2] * xfrac * (1 / zoom - 1);
Expand All @@ -478,6 +493,7 @@ function makeDragBox(gd, plotinfo, x, y, w, h, ns, ew) {
for(i = 0; i < yaxes.length; i++) {
zoomWheelOneAxis(yaxes[i], yfrac, zoom);
}
updateMatchedAxes(matches.isSubplotConstrained ? xaxes : matches.yaxes, yaxes[0].range);

scrollViewBox[3] *= zoom;
scrollViewBox[1] += scrollViewBox[3] * (1 - yfrac) * (1 / zoom - 1);
Expand Down Expand Up @@ -513,9 +529,19 @@ function makeDragBox(gd, plotinfo, x, y, w, h, ns, ew) {
// prevent axis drawing from monkeying with margins until we're done
gd._fullLayout._replotting = true;

var matchedByXaxes;
var matchesByYaxes;
if(matches.isSubplotConstrained) {
matchedByXaxes = yaxes;
matchesByYaxes = xaxes;
} else {
matchedByXaxes = matches.xaxes;
matchesByYaxes = matches.yaxes;
}

if(xActive === 'ew' || yActive === 'ns') {
if(xActive) dragAxList(xaxes, dx);
if(yActive) dragAxList(yaxes, dy);
if(xActive) dragAxList(xaxes, matchedByXaxes, dx);
if(yActive) dragAxList(yaxes, matchesByYaxes, dy);
updateSubplots([xActive ? -dx : 0, yActive ? -dy : 0, pw, ph]);
ticksAndAnnotations();
return;
Expand Down Expand Up @@ -546,7 +572,7 @@ function makeDragBox(gd, plotinfo, x, y, w, h, ns, ew) {
(movedAx._rl[end] - movedAx._rl[otherEnd]);
}

if(isSubplotConstrained && xActive && yActive) {
if(links.isSubplotConstrained && xActive && yActive) {
// dragging a corner of a constrained subplot:
// respect the fixed corner, but harmonize dx and dy
var dxySign = ((xActive === 'w') === (yActive === 'n')) ? 1 : -1;
Expand All @@ -566,7 +592,7 @@ function makeDragBox(gd, plotinfo, x, y, w, h, ns, ew) {
var x0 = (xActive === 'w') ? dx : 0;
var y0 = (yActive === 'n') ? dy : 0;

if(isSubplotConstrained) {
if(links.isSubplotConstrained) {
var i;
if(!xActive && yActive.length === 1) {
// dragging one end of the y axis of a constrained subplot
Expand All @@ -588,6 +614,8 @@ function makeDragBox(gd, plotinfo, x, y, w, h, ns, ew) {
}
}

updateMatchedAxes(matchedByXaxes, xaxes[0].range);
updateMatchedAxes(matchesByYaxes, yaxes[0].range);
updateSubplots([x0, y0, pw - dx, ph - dy]);
ticksAndAnnotations();
}
Expand All @@ -607,10 +635,12 @@ function makeDragBox(gd, plotinfo, x, y, w, h, ns, ew) {
if(editX) {
pushActiveAxIds(xaxes);
pushActiveAxIds(links.xaxes);
pushActiveAxIds(matches.xaxes);
}
if(editY) {
pushActiveAxIds(yaxes);
pushActiveAxIds(links.yaxes);
pushActiveAxIds(matches.yaxes);
}

updates = {};
Expand All @@ -629,9 +659,14 @@ function makeDragBox(gd, plotinfo, x, y, w, h, ns, ew) {
if(gd._transitioningWithDuration) return;

var doubleClickConfig = gd._context.doubleClick;
var axList = (xActive ? xaxes : []).concat(yActive ? yaxes : []);
var attrs = {};

var axList = [];
if(xActive) axList = axList.concat(xaxes);
if(yActive) axList = axList.concat(yaxes);
if(matches.xaxes) axList = axList.concat(matches.xaxes);
if(matches.yaxes) axList = axList.concat(matches.yaxes);

var attrs = {};
var ax, i, rangeInitial;

// For reset+autosize mode:
Expand Down Expand Up @@ -668,10 +703,10 @@ function makeDragBox(gd, plotinfo, x, y, w, h, ns, ew) {
else if(doubleClickConfig === 'reset') {
// when we're resetting, reset all linked axes too, so we get back
// to the fully-auto-with-constraints situation
if(xActive || isSubplotConstrained) axList = axList.concat(links.xaxes);
if(yActive && !isSubplotConstrained) axList = axList.concat(links.yaxes);
if(xActive || links.isSubplotConstrained) axList = axList.concat(links.xaxes);
if(yActive && !links.isSubplotConstrained) axList = axList.concat(links.yaxes);

if(isSubplotConstrained) {
if(links.isSubplotConstrained) {
if(!xActive) axList = axList.concat(xaxes);
else if(!yActive) axList = axList.concat(yaxes);
}
Expand Down Expand Up @@ -713,10 +748,6 @@ function makeDragBox(gd, plotinfo, x, y, w, h, ns, ew) {
], gd);
}

// x/y scaleFactor stash,
// minimizes number of per-point DOM updates in updateSubplots below
var xScaleFactorOld, yScaleFactorOld;

// updateSubplots - find all plot viewboxes that should be
// affected by this drag, and update them. look for all plots
// sharing an affected axis (including the one being dragged),
Expand Down Expand Up @@ -768,6 +799,14 @@ function makeDragBox(gd, plotinfo, x, y, w, h, ns, ew) {
if(editX2) {
xScaleFactor2 = xScaleFactor;
clipDx = ew ? viewBox[0] : getShift(xa, xScaleFactor2);
} else if(matches.xaHash[xa._id]) {
xScaleFactor2 = xScaleFactor;
clipDx = viewBox[0] * xa._length / xa0._length;
} else if(matches.yaHash[xa._id]) {
xScaleFactor2 = yScaleFactor;
clipDx = yActive === 'ns' ?
-viewBox[1] * xa._length / ya0._length :
getShift(xa, xScaleFactor2, {n: 'top', s: 'bottom'}[yActive]);
} else {
xScaleFactor2 = getLinkedScaleFactor(xa, xScaleFactor, yScaleFactor);
clipDx = scaleAndGetShift(xa, xScaleFactor2);
Expand All @@ -776,6 +815,14 @@ function makeDragBox(gd, plotinfo, x, y, w, h, ns, ew) {
if(editY2) {
yScaleFactor2 = yScaleFactor;
clipDy = ns ? viewBox[1] : getShift(ya, yScaleFactor2);
} else if(matches.yaHash[ya._id]) {
yScaleFactor2 = yScaleFactor;
clipDy = viewBox[1] * ya._length / ya0._length;
} else if(matches.xaHash[ya._id]) {
yScaleFactor2 = xScaleFactor;
clipDy = xActive === 'ew' ?
-viewBox[0] * ya._length / xa0._length :
getShift(ya, yScaleFactor2, {e: 'right', w: 'left'}[xActive]);
} else {
yScaleFactor2 = getLinkedScaleFactor(ya, xScaleFactor, yScaleFactor);
clipDy = scaleAndGetShift(ya, yScaleFactor2);
Expand Down Expand Up @@ -809,16 +856,16 @@ function makeDragBox(gd, plotinfo, x, y, w, h, ns, ew) {
// the scale of the trace group.
// apply only when scale changes, as adjusting the scale of
// all the points can be expansive.
if(xScaleFactor2 !== xScaleFactorOld || yScaleFactor2 !== yScaleFactorOld) {
if(xScaleFactor2 !== sp.xScaleFactor || yScaleFactor2 !== sp.yScaleFactor) {
Drawing.setPointGroupScale(sp.zoomScalePts, xScaleFactor2, yScaleFactor2);
Drawing.setTextPointsScale(sp.zoomScaleTxt, xScaleFactor2, yScaleFactor2);
}

Drawing.hideOutsideRangePoints(sp.clipOnAxisFalseTraces, sp);

// update x/y scaleFactor stash
xScaleFactorOld = xScaleFactor2;
yScaleFactorOld = yScaleFactor2;
sp.xScaleFactor = xScaleFactor2;
sp.yScaleFactor = yScaleFactor2;
}
}
}
Expand All @@ -832,7 +879,7 @@ function makeDragBox(gd, plotinfo, x, y, w, h, ns, ew) {
if(editX && links.xaHash[ax._id]) {
return xScaleFactor;
}
if(editY && (isSubplotConstrained ? links.xaHash : links.yaHash)[ax._id]) {
if(editY && (links.isSubplotConstrained ? links.xaHash : links.yaHash)[ax._id]) {
return yScaleFactor;
}
return 0;
Expand All @@ -847,8 +894,8 @@ function makeDragBox(gd, plotinfo, x, y, w, h, ns, ew) {
return 0;
}

function getShift(ax, scaleFactor) {
return ax._length * (1 - scaleFactor) * FROM_TL[ax.constraintoward || 'middle'];
function getShift(ax, scaleFactor, from) {
return ax._length * (1 - scaleFactor) * FROM_TL[from || ax.constraintoward || 'middle'];
}

return dragger;
Expand Down Expand Up @@ -900,7 +947,7 @@ function getEndText(ax, end) {
}
}

function zoomAxRanges(axList, r0Fraction, r1Fraction, updates, linkedAxes) {
function zoomAxRanges(axList, r0Fraction, r1Fraction, updates, linkedAxes, matchedAxes) {
var i,
axi,
axRangeLinear0,
Expand All @@ -922,14 +969,22 @@ function zoomAxRanges(axList, r0Fraction, r1Fraction, updates, linkedAxes) {
}

// zoom linked axes about their centers
if(linkedAxes && linkedAxes.length) {
if(linkedAxes.length) {
archmoj marked this conversation as resolved.
Show resolved Hide resolved
var linkedR0Fraction = (r0Fraction + (1 - r1Fraction)) / 2;
zoomAxRanges(linkedAxes, linkedR0Fraction, 1 - linkedR0Fraction, updates, [], []);
}

zoomAxRanges(linkedAxes, linkedR0Fraction, 1 - linkedR0Fraction, updates);
// TODO is picking the first ax always ok in general?
// What if we're matching an overlaying axis?
var rng = axList[0].range;
for(i = 0; i < matchedAxes.length; i++) {
axi = matchedAxes[i];
updates[axi._name + '.range[0]'] = rng[0];
updates[axi._name + '.range[1]'] = rng[1];
}
}

function dragAxList(axList, pix) {
function dragAxList(axList, matchedAxes, pix) {
for(var i = 0; i < axList.length; i++) {
var axi = axList[i];
if(!axi.fixedrange) {
Expand All @@ -939,6 +994,16 @@ function dragAxList(axList, pix) {
];
}
}

updateMatchedAxes(matchedAxes, axList[0].range);
}

function updateMatchedAxes(matchedAxes, rng) {
// TODO is picking the first ax always ok in general?
// What if we're matching an overlaying axis?
for(var i = 0; i < matchedAxes.length; i++) {
matchedAxes[i].range = rng.slice();
}
}

// common transform for dragging one end of an axis
Expand Down Expand Up @@ -1052,15 +1117,14 @@ function xyCorners(box) {
'h' + clen + 'v3h-' + (clen + 3) + 'Z';
}

function calcLinks(gd, xaHash, yaHash) {
var constraintGroups = gd._fullLayout._axisConstraintGroups;
function calcLinks(gd, groups, xaHash, yaHash) {
var isSubplotConstrained = false;
var xLinks = {};
var yLinks = {};
var xID, yID, xLinkID, yLinkID;

for(var i = 0; i < constraintGroups.length; i++) {
var group = constraintGroups[i];
for(var i = 0; i < groups.length; i++) {
var group = groups[i];
// check if any of the x axes we're dragging is in this constraint group
for(xID in xaHash) {
if(group[xID]) {
Expand Down