Skip to content

Commit 2388e2b

Browse files
committed
Fix invisible legends with negative y
* Ensure that when the margins are expended to allocate the legend, there is enough space to clear the layout margins. Fixes plotly#384
1 parent 5ef69b3 commit 2388e2b

File tree

2 files changed

+64
-17
lines changed

2 files changed

+64
-17
lines changed

src/components/legend/draw.js

+35-17
Original file line numberDiff line numberDiff line change
@@ -184,27 +184,42 @@ module.exports = function draw(gd) {
184184
if(anchorUtils.isRightAnchor(opts)) {
185185
lx -= opts.width;
186186
}
187-
if(anchorUtils.isCenterAnchor(opts)) {
187+
else if(anchorUtils.isCenterAnchor(opts)) {
188188
lx -= opts.width / 2;
189189
}
190190

191191
if(anchorUtils.isBottomAnchor(opts)) {
192192
ly -= opts.height;
193193
}
194-
if(anchorUtils.isMiddleAnchor(opts)) {
194+
else if(anchorUtils.isMiddleAnchor(opts)) {
195195
ly -= opts.height / 2;
196196
}
197197

198+
lx = Math.round(lx);
199+
ly = Math.round(ly);
200+
201+
// Make sure the legend top is below the top margin
202+
if(ly < fullLayout.margin.t) ly = fullLayout.margin.t;
203+
204+
var scrollHeightMax = fullLayout.height - fullLayout.margin.b - ly;
205+
var scrollHeight = Math.min(scrollHeightMax, opts.height);
206+
207+
if(scrollHeight <= 2 * opts.borderwidth) {
208+
console.error('Legend.draw: insufficient space to draw legend');
209+
legend.remove();
210+
return;
211+
}
212+
198213
// Deal with scrolling
199-
var plotHeight = fullLayout.height - fullLayout.margin.b,
200-
scrollheight = Math.min(plotHeight - ly, opts.height),
201-
scrollPosition = scrollBox.attr('data-scroll') ? scrollBox.attr('data-scroll') : 0;
214+
var scrollPosition = scrollBox.attr('data-scroll') ?
215+
scrollBox.attr('data-scroll') :
216+
0;
202217

203218
scrollBox.attr('transform', 'translate(0, ' + scrollPosition + ')');
204219

205220
bg.attr({
206221
width: opts.width - 2 * opts.borderwidth,
207-
height: scrollheight - 2 * opts.borderwidth,
222+
height: scrollHeight - 2 * opts.borderwidth,
208223
x: opts.borderwidth,
209224
y: opts.borderwidth
210225
});
@@ -213,15 +228,15 @@ module.exports = function draw(gd) {
213228

214229
clipPath.select('rect').attr({
215230
width: opts.width,
216-
height: scrollheight,
231+
height: scrollHeight,
217232
x: 0,
218233
y: 0
219234
});
220235

221236
legend.call(Drawing.setClipUrl, clipId);
222237

223238
// If scrollbar should be shown.
224-
if(opts.height - scrollheight > 0 && !gd._context.staticPlot) {
239+
if(opts.height - scrollHeight > 0 && !gd._context.staticPlot) {
225240

226241
bg.attr({
227242
width: opts.width - 2 * opts.borderwidth + constants.scrollBarWidth
@@ -243,21 +258,21 @@ module.exports = function draw(gd) {
243258
scrollBox.attr('data-scroll',0);
244259
}
245260

246-
scrollHandler(0,scrollheight);
261+
scrollHandler(0,scrollHeight);
247262

248263
legend.on('wheel',null);
249264

250265
legend.on('wheel', function() {
251266
var e = d3.event;
252267
e.preventDefault();
253-
scrollHandler(e.deltaY / 20, scrollheight);
268+
scrollHandler(e.deltaY / 20, scrollHeight);
254269
});
255270

256271
scrollBar.on('.drag',null);
257272
scrollBox.on('.drag',null);
258273
var drag = d3.behavior.drag()
259274
.on('drag', function() {
260-
scrollHandler(d3.event.dy, scrollheight);
275+
scrollHandler(d3.event.dy, scrollHeight);
261276
});
262277

263278
scrollBar.call(drag);
@@ -266,12 +281,12 @@ module.exports = function draw(gd) {
266281
}
267282

268283

269-
function scrollHandler(delta, scrollheight) {
284+
function scrollHandler(delta, scrollHeight) {
270285

271-
var scrollBarTrack = scrollheight - constants.scrollBarHeight - 2 * constants.scrollBarMargin,
286+
var scrollBarTrack = scrollHeight - constants.scrollBarHeight - 2 * constants.scrollBarMargin,
272287
translateY = scrollBox.attr('data-scroll'),
273-
scrollBoxY = Lib.constrain(translateY - delta, scrollheight-opts.height, 0),
274-
scrollBarY = -scrollBoxY / (opts.height - scrollheight) * scrollBarTrack + constants.scrollBarMargin;
288+
scrollBoxY = Lib.constrain(translateY - delta, scrollHeight-opts.height, 0),
289+
scrollBarY = -scrollBoxY / (opts.height - scrollHeight) * scrollBarTrack + constants.scrollBarMargin;
275290

276291
scrollBox.attr('data-scroll', scrollBoxY);
277292
scrollBox.attr('transform', 'translate(0, ' + scrollBoxY + ')');
@@ -454,12 +469,15 @@ function repositionLegend(gd, traces) {
454469
opts.height = Math.ceil(opts.height);
455470

456471
// lastly check if the margin auto-expand has changed
457-
Plots.autoMargin(gd, 'legend', {
472+
// (using Plots.autoMargin2 to ensure the requested margins are padded with
473+
// the layout margins to ensure the legend doesn't exceed the plot area)
474+
Plots.autoMargin2(gd, 'legend', {
458475
x: opts.x,
459476
y: opts.y,
460477
l: opts.width * ({right: 1, center: 0.5}[xanchor] || 0),
461478
r: opts.width * ({left: 1, center: 0.5}[xanchor] || 0),
462479
b: opts.height * ({top: 1, middle: 0.5}[yanchor] || 0),
463-
t: opts.height * ({bottom: 1, middle: 0.5}[yanchor] || 0)
480+
t: opts.height * ({bottom: 1, middle: 0.5}[yanchor] || 0),
481+
pad: 0
464482
});
465483
}

src/plots/plots.js

+29
Original file line numberDiff line numberDiff line change
@@ -875,6 +875,35 @@ plots.autoMargin = function(gd, id, o) {
875875
}
876876
};
877877

878+
// Similar to plots.autoMargin, except that it pads the request with the layout
879+
// margins, so that an object can be drawn without reaching the layout margins.
880+
plots.autoMargin2 = function(gd, id, o) {
881+
var fullLayout = gd._fullLayout;
882+
883+
if(!fullLayout._pushmargin) fullLayout._pushmargin = {};
884+
885+
if(fullLayout.margin.autoexpand !== false) {
886+
if(!o) delete fullLayout._pushmargin[id];
887+
else {
888+
var pad = o.pad === undefined ? 12 : o.pad;
889+
890+
// if the item is too big, just give it enough automargin to
891+
// make sure you can still grab it and bring it back
892+
if(o.l+o.r > fullLayout.width*0.5) o.l = o.r = 0;
893+
if(o.b+o.t > fullLayout.height*0.5) o.b = o.t = 0;
894+
895+
fullLayout._pushmargin[id] = {
896+
l: {val: o.x, size: o.l + pad + fullLayout.margin.l},
897+
r: {val: o.x, size: o.r + pad + fullLayout.margin.r},
898+
b: {val: o.y, size: o.b + pad + fullLayout.margin.b},
899+
t: {val: o.y, size: o.t + pad + fullLayout.margin.t}
900+
};
901+
}
902+
903+
if(!gd._replotting) plots.doAutoMargin(gd);
904+
}
905+
};
906+
878907
plots.doAutoMargin = function(gd) {
879908
var fullLayout = gd._fullLayout;
880909
if(!fullLayout._size) fullLayout._size = {};

0 commit comments

Comments
 (0)