Skip to content
  • Sponsor plotly/plotly.js

  • Notifications You must be signed in to change notification settings
  • Fork 1.9k

[WIP] Fix issue #384 (invisible legends when y is negative) #410

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

Closed
wants to merge 4 commits into from
Closed
Changes from all commits
Commits
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
67 changes: 36 additions & 31 deletions src/components/legend/draw.js
Original file line number Diff line number Diff line change
@@ -184,27 +184,42 @@ module.exports = function draw(gd) {
if(anchorUtils.isRightAnchor(opts)) {
lx -= opts.width;
}
if(anchorUtils.isCenterAnchor(opts)) {
else if(anchorUtils.isCenterAnchor(opts)) {
lx -= opts.width / 2;
}

if(anchorUtils.isBottomAnchor(opts)) {
ly -= opts.height;
}
if(anchorUtils.isMiddleAnchor(opts)) {
else if(anchorUtils.isMiddleAnchor(opts)) {
ly -= opts.height / 2;
}

//lx = Math.round(lx);
//ly = Math.round(ly);

// Make sure the legend top is below the top margin
if(ly < fullLayout.margin.t) ly = fullLayout.margin.t;

var scrollHeightMax = fullLayout.height - fullLayout.margin.b - ly;
var scrollHeight = Math.min(scrollHeightMax, opts.height);

if(scrollHeight <= 2 * opts.borderwidth) {
console.error('Legend.draw: insufficient space to draw legend');
legend.remove();
return;
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On second thoughts, it'd be more user-friendly to provide a safe default here. For example:

  • if opts.height >= fullLayout.height, then ly = fullLayout.margin.t; scrollHeight = fullLayout.height;
  • else locate the legend the bottom.


// Deal with scrolling
var plotHeight = fullLayout.height - fullLayout.margin.b,
scrollheight = Math.min(plotHeight - ly, opts.height),
scrollPosition = scrollBox.attr('data-scroll') ? scrollBox.attr('data-scroll') : 0;
var scrollPosition = scrollBox.attr('data-scroll') ?
scrollBox.attr('data-scroll') :
0;

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

bg.attr({
width: opts.width - 2 * opts.borderwidth,
height: scrollheight - 2 * opts.borderwidth,
height: scrollHeight - 2 * opts.borderwidth,
x: opts.borderwidth,
y: opts.borderwidth
});
@@ -213,21 +228,21 @@ module.exports = function draw(gd) {

clipPath.select('rect').attr({
width: opts.width,
height: scrollheight,
height: scrollHeight,
x: 0,
y: 0
});

legend.call(Drawing.setClipUrl, clipId);

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

bg.attr({
width: opts.width - 2 * opts.borderwidth + constants.scrollBarWidth
});

clipPath.attr({
clipPath.select('rect').attr({
width: opts.width + constants.scrollBarWidth
});

@@ -243,21 +258,21 @@ module.exports = function draw(gd) {
scrollBox.attr('data-scroll',0);
}

scrollHandler(0,scrollheight);
scrollHandler(0,scrollHeight);

legend.on('wheel',null);

legend.on('wheel', function() {
var e = d3.event;
e.preventDefault();
scrollHandler(e.deltaY / 20, scrollheight);
scrollHandler(e.deltaY / 20, scrollHeight);
});

scrollBar.on('.drag',null);
scrollBox.on('.drag',null);
var drag = d3.behavior.drag()
.on('drag', function() {
scrollHandler(d3.event.dy, scrollheight);
scrollHandler(d3.event.dy, scrollHeight);
});

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


function scrollHandler(delta, scrollheight) {
function scrollHandler(delta, scrollHeight) {

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

scrollBox.attr('data-scroll', scrollBoxY);
scrollBox.attr('transform', 'translate(0, ' + scrollBoxY + ')');
@@ -368,7 +383,6 @@ function drawTexts(context, gd, d, i, traces) {

function repositionLegend(gd, traces) {
var fullLayout = gd._fullLayout,
gs = fullLayout._size,
opts = fullLayout.legend,
borderwidth = opts.borderwidth;

@@ -432,42 +446,33 @@ function repositionLegend(gd, traces) {
.attr('width', (gd._context.editable ? 0 : opts.width) + 40);

// now position the legend. for both x,y the positions are recorded as
// fractions of the plot area (left, bottom = 0,0). Outside the plot
// area is allowed but position will be clipped to the page.
// values <1/3 align the low side at that fraction, 1/3-2/3 align the
// center at that fraction, >2/3 align the right at that fraction

var lx = gs.l + gs.w * opts.x,
ly = gs.t + gs.h * (1-opts.y);
// fractions of the plot area (left, bottom = 0,0).

var xanchor = 'left';
if(anchorUtils.isRightAnchor(opts)) {
lx -= opts.width;
xanchor = 'right';
}
if(anchorUtils.isCenterAnchor(opts)) {
lx -= opts.width / 2;
xanchor = 'center';
}

var yanchor = 'top';
if(anchorUtils.isBottomAnchor(opts)) {
ly -= opts.height;
yanchor = 'bottom';
}
if(anchorUtils.isMiddleAnchor(opts)) {
ly -= opts.height / 2;
yanchor = 'middle';
}

// make sure we're only getting full pixels
opts.width = Math.ceil(opts.width);
opts.height = Math.ceil(opts.height);
lx = Math.round(lx);
ly = Math.round(ly);

// lastly check if the margin auto-expand has changed
Plots.autoMargin(gd, 'legend', {
// (using Plots.autoMarginVertical to ensure the requested margins are
// padded with the layout vertical margins to ensure the legend doesn't
// exceed the plot area)
Plots.autoMarginVertical(gd, 'legend', {
x: opts.x,
y: opts.y,
l: opts.width * ({right: 1, center: 0.5}[xanchor] || 0),
30 changes: 30 additions & 0 deletions src/plots/plots.js
Original file line number Diff line number Diff line change
@@ -875,6 +875,36 @@ plots.autoMargin = function(gd, id, o) {
}
};

// Similar to plots.autoMargin, except that it pads the request with the layout
// margins, so that an object can be drawn without reaching the layout vertical
// margins.
plots.autoMarginVertical = function(gd, id, o) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why do we need this?

var fullLayout = gd._fullLayout;

if(!fullLayout._pushmargin) fullLayout._pushmargin = {};

if(fullLayout.margin.autoexpand !== false) {
if(!o) delete fullLayout._pushmargin[id];
else {
var pad = o.pad === undefined ? 12 : o.pad;

// if the item is too big, just give it enough automargin to
// make sure you can still grab it and bring it back
if(o.l+o.r > fullLayout.width*0.5) o.l = o.r = 0;
if(o.b+o.t > fullLayout.height*0.5) o.b = o.t = 0;

fullLayout._pushmargin[id] = {
l: {val: o.x, size: o.l + pad},
r: {val: o.x, size: o.r + pad},
b: {val: o.y, size: o.b + fullLayout.margin.b},
t: {val: o.y, size: o.t + fullLayout.margin.t}
};
}

if(!gd._replotting) plots.doAutoMargin(gd);
}
};

plots.doAutoMargin = function(gd) {
var fullLayout = gd._fullLayout;
if(!fullLayout._size) fullLayout._size = {};