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

Legend Marker Vertical Alignment #3263

Merged
merged 7 commits into from
Nov 21, 2018
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
2 changes: 1 addition & 1 deletion src/components/legend/style.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ module.exports = function style(s, gd) {
if(gd._fullLayout.legend.valign === 'top') valignFactor = 1.0;
if(gd._fullLayout.legend.valign === 'bottom') valignFactor = -1.0;
var markerOffsetY = valignFactor * (0.5 * (d[0].lineHeight - d[0].height + 3));
Copy link
Contributor

@etpinard etpinard Nov 20, 2018

Choose a reason for hiding this comment

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

Nice use of lineHeight 🎉

if(markerOffsetY) layers.attr('transform', 'translate(0,' + markerOffsetY + ')');
Copy link
Contributor Author

@antoinerg antoinerg Nov 20, 2018

Choose a reason for hiding this comment

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

Fix bug when valign equals middle. Because markerOffsetY is zero when at middle position, I had to change this line.

Copy link
Contributor

@etpinard etpinard Nov 20, 2018

Choose a reason for hiding this comment

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

Hmm. But 'middle' is the default value isn't it? Why didn't our tests catch sooner?

Copy link
Contributor Author

@antoinerg antoinerg Nov 20, 2018

Choose a reason for hiding this comment

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

All the mocks were rendered properly because the transform attribute was not added. However, if there's already a transform attribute on the svg group because valign was top, then it needs to be removed on relayout if valign is switched back to middle.

Copy link
Contributor

Choose a reason for hiding this comment

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

However, if there's already a transform attribute on the svg group because valign was top, then it needs to be removed on relayout if valign is switched back to middle.

OK this makes sense. I don't understand how the transform gets removed and why this patch fixes the bug though. Boolean(NaN) is false so the old and this new line look equivalent to me.

Copy link
Contributor

Choose a reason for hiding this comment

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

Wouldn't something like

var valign = gd._fullLayout.valign;

if(valign === 'middle') {
  layers.attr('transform', null); // this here is a fun d3 trick to unset DOM attributes
} else {
  var factor = {top: 1, bottom: -1}[valign];
  var markerOffsetY = factor * (0.5 * (d[0].lineHeight - d[0].height + 3));
  layers.attr('transform', 'translate(0,' + markerOffsetY + ')');
}

be what we're looking for?

Copy link
Contributor Author

@antoinerg antoinerg Nov 20, 2018

Choose a reason for hiding this comment

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

Your solution would work except that we should check whether markerOffsetY is a number. It might not be when this function is visited and errors would be thrown in the console because of garbage translate(0, NaN). This is why I added if(!isNaN(markerOffsetY))!

Copy link
Contributor Author

@antoinerg antoinerg Nov 20, 2018

Choose a reason for hiding this comment

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

I'd have to check to make sure but I think it's called on first draw to figure out how big the legend will be. At this point, d[0].lineHeight d[0].height are not defined and we get a NaN. Although everything renders correctly in the end, we get the following in the console:
d3.js:663 Error: <g> attribute transform: Trailing garbage, "translate(0,NaN)".

Copy link
Contributor Author

@antoinerg antoinerg Nov 20, 2018

Choose a reason for hiding this comment

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

That is indeed what is happening which is why if(!isNaN(markerOffsetY)) is required.

Copy link
Contributor

Choose a reason for hiding this comment

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

Ok, in this case if we know that lineHeight/height are sometimes undefined, we should not even attempt to compute markerOffsetY. So something like:

var valign = gd._fullLayout.valign;
var lineHeight = d[0].lineHeight;
var height = d[0].height;

if(valign === 'middle' || !lineHeigth || !lheight) {
  layers.attr('transform', null); // this here is a fun d3 trick to unset DOM attributes
} else {
  var factor = {top: 1, bottom: -1}[valign];
  var markerOffsetY = factor * (0.5 * lineHeight - height + 3));
  layers.attr('transform', 'translate(0,' + markerOffsetY + ')');
}

would make that clearer.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

In commit c1b871d, I reused most of the above 😄 !

if(!isNaN(markerOffsetY)) layers.attr('transform', 'translate(0,' + markerOffsetY + ')');

var fill = layers
.selectAll('g.legendfill')
Expand Down
37 changes: 37 additions & 0 deletions test/jasmine/tests/legend_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -665,6 +665,43 @@ describe('legend relayout update', function() {
.catch(failTest)
.then(done);
});

describe('should update legend valign', function() {
var mock = require('@mocks/legend_valign_top.json');
var gd;

beforeEach(function() {
gd = createGraphDiv();
});
afterEach(destroyGraphDiv);

function markerOffsetY() {
var translateY = d3.select('.legend .traces .layers').attr('transform').match(/translate\(\d+,(-?\d+)\)/)[1];
Copy link
Contributor

Choose a reason for hiding this comment

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

Just noticed this. Using Drawing.getTranslate would be cleaner.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks! 🔍

Done in c1b871d!

return parseFloat(translateY);
}

it('it should translate markers', function(done) {
var mockCopy = Lib.extendDeep({}, mock);

var top, middle, bottom;
Plotly.plot(gd, mockCopy.data, mockCopy.layout)
.then(function() {
top = markerOffsetY();
return Plotly.relayout(gd, 'legend.valign', 'middle');
})
.then(function() {
middle = markerOffsetY();
expect(middle).toBeGreaterThan(top);
return Plotly.relayout(gd, 'legend.valign', 'bottom');
})
.then(function() {
bottom = markerOffsetY();
expect(bottom).toBeGreaterThan(middle);
})
.catch(failTest)
.then(done);
});
});
});

describe('legend orientation change:', function() {
Expand Down