Skip to content

Commit b4d11e6

Browse files
committed
make axis constraints work with gl2d
1 parent ffd65fd commit b4d11e6

File tree

4 files changed

+259
-36
lines changed

4 files changed

+259
-36
lines changed

src/plot_api/plot_api.js

+12-12
Original file line numberDiff line numberDiff line change
@@ -2079,6 +2079,18 @@ function _relayout(gd, aobj) {
20792079
else if(proot.indexOf('geo') === 0) flags.doplot = true;
20802080
else if(proot.indexOf('ternary') === 0) flags.doplot = true;
20812081
else if(ai === 'paper_bgcolor') flags.doplot = true;
2082+
else if(proot === 'margin' ||
2083+
pp1 === 'autorange' ||
2084+
pp1 === 'rangemode' ||
2085+
pp1 === 'type' ||
2086+
pp1 === 'domain' ||
2087+
pp1 === 'fixedrange' ||
2088+
pp1 === 'scaleanchor' ||
2089+
pp1 === 'scaleratio' ||
2090+
ai.indexOf('calendar') !== -1 ||
2091+
ai.match(/^(bar|box|font)/)) {
2092+
flags.docalc = true;
2093+
}
20822094
else if(fullLayout._has('gl2d') &&
20832095
(ai.indexOf('axis') !== -1 || ai === 'plot_bgcolor')
20842096
) flags.doplot = true;
@@ -2102,18 +2114,6 @@ function _relayout(gd, aobj) {
21022114
else if(ai === 'margin.pad') {
21032115
flags.doticks = flags.dolayoutstyle = true;
21042116
}
2105-
else if(proot === 'margin' ||
2106-
pp1 === 'autorange' ||
2107-
pp1 === 'rangemode' ||
2108-
pp1 === 'type' ||
2109-
pp1 === 'domain' ||
2110-
pp1 === 'fixedrange' ||
2111-
pp1 === 'scaleanchor' ||
2112-
pp1 === 'scaleratio' ||
2113-
ai.indexOf('calendar') !== -1 ||
2114-
ai.match(/^(bar|box|font)/)) {
2115-
flags.docalc = true;
2116-
}
21172117
/*
21182118
* hovermode and dragmode don't need any redrawing, since they just
21192119
* affect reaction to user input, everything else, assume full replot.

src/plots/gl2d/camera.js

+113-8
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
var mouseChange = require('mouse-change');
1313
var mouseWheel = require('mouse-wheel');
14+
var cartesianConstants = require('../cartesian/constants');
1415

1516
module.exports = createCamera;
1617

@@ -22,8 +23,10 @@ function Camera2D(element, plot) {
2223
this.lastInputTime = Date.now();
2324
this.lastPos = [0, 0];
2425
this.boxEnabled = false;
26+
this.boxInited = false;
2527
this.boxStart = [0, 0];
2628
this.boxEnd = [0, 0];
29+
this.dragStart = [0, 0];
2730
}
2831

2932

@@ -37,13 +40,33 @@ function createCamera(scene) {
3740
scene.yaxis.autorange = false;
3841
}
3942

43+
function getSubplotConstraint() {
44+
// note: this assumes we only have one x and one y axis on this subplot
45+
// when this constraint is lifted this block won't make sense
46+
var constraints = scene.graphDiv._fullLayout._axisConstraintGroups;
47+
var xaId = scene.xaxis._id;
48+
var yaId = scene.yaxis._id;
49+
for(var i = 0; i < constraints.length; i++) {
50+
if(constraints[i][xaId] !== -1) {
51+
if(constraints[i][yaId] !== -1) return true;
52+
break;
53+
}
54+
}
55+
return false;
56+
}
57+
4058
result.mouseListener = mouseChange(element, function(buttons, x, y) {
4159
var dataBox = scene.calcDataBox(),
4260
viewBox = plot.viewBox;
4361

4462
var lastX = result.lastPos[0],
4563
lastY = result.lastPos[1];
4664

65+
var MINDRAG = cartesianConstants.MINDRAG * plot.pixelRatio;
66+
var MINZOOM = cartesianConstants.MINZOOM * plot.pixelRatio;
67+
68+
var dx, dy;
69+
4770
x *= plot.pixelRatio;
4871
y *= plot.pixelRatio;
4972

@@ -76,32 +99,114 @@ function createCamera(scene) {
7699
(viewBox[3] - viewBox[1]) * (dataBox[3] - dataBox[1]) +
77100
dataBox[1];
78101

79-
if(!result.boxEnabled) {
102+
if(!result.boxInited) {
80103
result.boxStart[0] = dataX;
81104
result.boxStart[1] = dataY;
105+
result.dragStart[0] = x;
106+
result.dragStart[1] = y;
82107
}
83108

84109
result.boxEnd[0] = dataX;
85110
result.boxEnd[1] = dataY;
86111

87-
result.boxEnabled = true;
112+
// we need to mark the box as initialized right away
113+
// so that we can tell the start and end pionts apart
114+
result.boxInited = true;
115+
116+
// but don't actually enable the box until the cursor moves
117+
if(!result.boxEnabled && (
118+
result.boxStart[0] !== result.boxEnd[0] ||
119+
result.boxStart[1] !== result.boxEnd[1])
120+
) {
121+
result.boxEnabled = true;
122+
}
123+
124+
// constrain aspect ratio if the axes require it
125+
var smallDx = Math.abs(result.dragStart[0] - x) < MINZOOM;
126+
var smallDy = Math.abs(result.dragStart[1] - y) < MINZOOM;
127+
if(getSubplotConstraint() && !(smallDx && smallDy)) {
128+
dx = result.boxEnd[0] - result.boxStart[0];
129+
dy = result.boxEnd[1] - result.boxStart[1];
130+
var dydx = (dataBox[3] - dataBox[1]) / (dataBox[2] - dataBox[0]);
131+
132+
if(Math.abs(dx * dydx) > Math.abs(dy)) {
133+
result.boxEnd[1] = result.boxStart[1] +
134+
Math.abs(dx) * dydx * (Math.sign(dy) || 1);
135+
136+
// gl-select-box clips to the plot area bounds,
137+
// which breaks the axis constraint, so don't allow
138+
// this box to go out of bounds
139+
if(result.boxEnd[1] < dataBox[1]) {
140+
result.boxEnd[1] = dataBox[1];
141+
result.boxEnd[0] = result.boxStart[0] +
142+
(dataBox[1] - result.boxStart[1]) / Math.abs(dydx);
143+
}
144+
else if(result.boxEnd[1] > dataBox[3]) {
145+
result.boxEnd[1] = dataBox[3];
146+
result.boxEnd[0] = result.boxStart[0] +
147+
(dataBox[3] - result.boxStart[1]) / Math.abs(dydx);
148+
}
149+
}
150+
else {
151+
result.boxEnd[0] = result.boxStart[0] +
152+
Math.abs(dy) / dydx * (Math.sign(dx) || 1);
153+
154+
if(result.boxEnd[0] < dataBox[0]) {
155+
result.boxEnd[0] = dataBox[0];
156+
result.boxEnd[1] = result.boxStart[1] +
157+
(dataBox[0] - result.boxStart[0]) * Math.abs(dydx);
158+
}
159+
else if(result.boxEnd[0] > dataBox[2]) {
160+
result.boxEnd[0] = dataBox[2];
161+
result.boxEnd[1] = result.boxStart[1] +
162+
(dataBox[2] - result.boxStart[0]) * Math.abs(dydx);
163+
}
164+
}
165+
}
166+
// otherwise clamp small changes to the origin so we get 1D zoom
167+
else {
168+
if(smallDx) result.boxEnd[0] = result.boxStart[0];
169+
if(smallDy) result.boxEnd[1] = result.boxStart[1];
170+
}
88171
}
89172
else if(result.boxEnabled) {
90-
updateRange(0, result.boxStart[0], result.boxEnd[0]);
91-
updateRange(1, result.boxStart[1], result.boxEnd[1]);
92-
unSetAutoRange();
173+
dx = result.boxStart[0] !== result.boxEnd[0];
174+
dy = result.boxStart[1] !== result.boxEnd[1];
175+
if(dx || dy) {
176+
if(dx) {
177+
updateRange(0, result.boxStart[0], result.boxEnd[0]);
178+
scene.xaxis.autorange = false;
179+
}
180+
if(dy) {
181+
updateRange(1, result.boxStart[1], result.boxEnd[1]);
182+
scene.yaxis.autorange = false;
183+
}
184+
scene.relayoutCallback();
185+
}
186+
else {
187+
scene.glplot.setDirty();
188+
}
93189
result.boxEnabled = false;
94-
scene.relayoutCallback();
190+
result.boxInited = false;
95191
}
96192
break;
97193

98194
case 'pan':
99195
result.boxEnabled = false;
196+
result.boxInited = false;
100197

101198
if(buttons) {
102-
var dx = (lastX - x) * (dataBox[2] - dataBox[0]) /
199+
if(!result.panning) {
200+
result.dragStart[0] = x;
201+
result.dragStart[1] = y;
202+
}
203+
204+
if(Math.abs(result.dragStart[0] - x) < MINDRAG) x = result.dragStart[0];
205+
if(Math.abs(result.dragStart[1] - y) < MINDRAG) y = result.dragStart[1];
206+
207+
dx = (lastX - x) * (dataBox[2] - dataBox[0]) /
103208
(plot.viewBox[2] - plot.viewBox[0]);
104-
var dy = (lastY - y) * (dataBox[3] - dataBox[1]) /
209+
dy = (lastY - y) * (dataBox[3] - dataBox[1]) /
105210
(plot.viewBox[3] - plot.viewBox[1]);
106211

107212
dataBox[0] += dx;

src/plots/gl2d/scene2d.js

+30-9
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ var createOptions = require('./convert');
2222
var createCamera = require('./camera');
2323
var convertHTMLToUnicode = require('../../lib/html2unicode');
2424
var showNoWebGlMsg = require('../../lib/show_no_webgl_msg');
25+
var enforceAxisConstraints = require('../../plots/cartesian/constraints');
2526

2627
var AXES = ['xaxis', 'yaxis'];
2728
var STATIC_CANVAS, STATIC_CONTEXT;
@@ -426,6 +427,13 @@ proto.plot = function(fullData, calcData, fullLayout) {
426427
ax.setScale();
427428
}
428429

430+
var mockLayout = {
431+
_axisConstraintGroups: this.graphDiv._fullLayout._axisConstraintGroups,
432+
xaxis: this.xaxis,
433+
yaxis: this.yaxis
434+
};
435+
enforceAxisConstraints({_fullLayout: mockLayout});
436+
429437
options.ticks = this.computeTickMarks();
430438

431439
options.dataBox = this.calcDataBox();
@@ -544,26 +552,36 @@ proto.draw = function() {
544552
var x = mouseListener.x * glplot.pixelRatio;
545553
var y = this.canvas.height - glplot.pixelRatio * mouseListener.y;
546554

555+
var result;
556+
547557
if(camera.boxEnabled && fullLayout.dragmode === 'zoom') {
548558
this.selectBox.enabled = true;
549559

550-
this.selectBox.selectBox = [
560+
var selectBox = this.selectBox.selectBox = [
551561
Math.min(camera.boxStart[0], camera.boxEnd[0]),
552562
Math.min(camera.boxStart[1], camera.boxEnd[1]),
553563
Math.max(camera.boxStart[0], camera.boxEnd[0]),
554564
Math.max(camera.boxStart[1], camera.boxEnd[1])
555565
];
556566

567+
// 1D zoom
568+
for(var i = 0; i < 2; i++) {
569+
if(camera.boxStart[i] === camera.boxEnd[i]) {
570+
selectBox[i] = glplot.dataBox[i];
571+
selectBox[i + 2] = glplot.dataBox[i + 2];
572+
}
573+
}
574+
557575
glplot.setDirty();
558576
}
559-
else {
577+
else if(!camera.panning) {
560578
this.selectBox.enabled = false;
561579

562580
var size = fullLayout._size,
563581
domainX = this.xaxis.domain,
564582
domainY = this.yaxis.domain;
565583

566-
var result = glplot.pick(
584+
result = glplot.pick(
567585
(x / glplot.pixelRatio) + size.l + domainX[0] * size.w,
568586
(y / glplot.pixelRatio) - (size.t + (1 - domainY[1]) * size.h)
569587
);
@@ -629,12 +647,15 @@ proto.draw = function() {
629647
});
630648
}
631649
}
632-
else if(!result && this.lastPickResult) {
633-
this.spikes.update({});
634-
this.lastPickResult = null;
635-
this.graphDiv.emit('plotly_unhover');
636-
Fx.loneUnhover(this.svgContainer);
637-
}
650+
}
651+
652+
// Remove hover effects if we're not over a point OR
653+
// if we're zooming or panning (in which case result is not set)
654+
if(!result && this.lastPickResult) {
655+
this.spikes.update({});
656+
this.lastPickResult = null;
657+
this.graphDiv.emit('plotly_unhover');
658+
Fx.loneUnhover(this.svgContainer);
638659
}
639660

640661
glplot.draw();

0 commit comments

Comments
 (0)