Skip to content

Commit

Permalink
Merge pull request #2445 from plotly/fix-centered-hover-info-elems-st…
Browse files Browse the repository at this point in the history
…acked

Fix stacking of centered hover info elements #865
  • Loading branch information
rmoestl authored Mar 6, 2018
2 parents 3bff26f + 90082d5 commit 161b602
Show file tree
Hide file tree
Showing 2 changed files with 123 additions and 47 deletions.
2 changes: 1 addition & 1 deletion src/components/fx/hover.js
Original file line number Diff line number Diff line change
Expand Up @@ -1135,7 +1135,7 @@ function alignHoverText(hoverLabels, rotateLabels) {

g.select('path').attr('d', d.anchor === 'middle' ?
// middle aligned: rect centered on data
('M-' + (d.bx / 2 + d.tx2width / 2) + ',-' + (d.by / 2) +
('M-' + (d.bx / 2 + d.tx2width / 2) + ',' + (offsetY - d.by / 2) +
'h' + d.bx + 'v' + d.by + 'h-' + d.bx + 'Z') :
// left or right aligned: side rect with arrow to data
('M0,0L' + (horzSign * HOVERARROWSIZE + offsetX) + ',' + (HOVERARROWSIZE + offsetY) +
Expand Down
168 changes: 122 additions & 46 deletions test/jasmine/tests/hover_label_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -1110,49 +1110,32 @@ describe('hover info', function() {

});

describe('centered', function() {
var trace1 = {
x: ['giraffes'],
y: [5],
name: 'LA Zoo',
type: 'bar',
text: ['Way too long hover info!']
};
var trace2 = {
x: ['giraffes'],
y: [5],
name: 'SF Zoo',
type: 'bar',
text: ['San Francisco']
};
var data = [trace1, trace2];
var layout = {width: 600, height: 300, barmode: 'stack'};

var gd;

beforeEach(function(done) {
gd = createGraphDiv();
Plotly.plot(gd, data, layout).then(done);
function hoverInfoNodes(traceName) {
var g = d3.selectAll('g.hoverlayer g.hovertext').filter(function() {
return !d3.select(this).select('[data-unformatted="' + traceName + '"]').empty();
});

function centeredHoverInfoNodes() {
var g = d3.selectAll('g.hoverlayer g.hovertext').filter(function() {
return !d3.select(this).select('[data-unformatted="LA Zoo"]').empty();
});
return {
primaryText: g.select('text:not([data-unformatted="' + traceName + '"])').node(),
primaryBox: g.select('path').node(),
secondaryText: g.select('[data-unformatted="' + traceName + '"]').node(),
secondaryBox: g.select('rect').node()
};
}

return {
primaryText: g.select('text:not([data-unformatted="LA Zoo"])').node(),
primaryBox: g.select('path').node(),
secondaryText: g.select('[data-unformatted="LA Zoo"]').node(),
secondaryBox: g.select('rect').node()
};
}
function ensureCentered(hoverInfoNodes) {
expect(hoverInfoNodes.primaryText.getAttribute('text-anchor')).toBe('middle');
expect(hoverInfoNodes.secondaryText.getAttribute('text-anchor')).toBe('middle');
return hoverInfoNodes;
}

function ensureCentered(hoverInfoNodes) {
expect(hoverInfoNodes.primaryText.getAttribute('text-anchor')).toBe('middle');
expect(hoverInfoNodes.secondaryText.getAttribute('text-anchor')).toBe('middle');
return hoverInfoNodes;
}
function assertLabelsInsideBoxes(nodes, msgPrefix) {
var msgPrefixFmt = msgPrefix ? '[' + msgPrefix + '] ' : '';

assertElemInside(nodes.primaryText, nodes.primaryBox,
msgPrefixFmt + 'Primary text inside box');
assertElemInside(nodes.secondaryText, nodes.secondaryBox,
msgPrefixFmt + 'Secondary text inside box');

function assertElemInside(elem, container, msg) {
var elemBB = elem.getBoundingClientRect();
Expand All @@ -1162,14 +1145,23 @@ describe('hover info', function() {
contBB.top < elemBB.top &&
contBB.bottom > elemBB.bottom).toBe(true, msg);
}
}

function assertSecondaryRightToPrimaryBox(nodes, msgPrefix) {
var msgPrefixFmt = msgPrefix ? '[' + msgPrefix + '] ' : '';

assertElemRightTo(nodes.secondaryBox, nodes.primaryBox,
msgPrefixFmt + 'Secondary box right to primary box');
assertElemTopsAligned(nodes.secondaryBox, nodes.primaryBox,
msgPrefixFmt + 'Top edges of primary and secondary boxes aligned');

function assertElemRightTo(elem, refElem, msg) {
var elemBB = elem.getBoundingClientRect();
var refElemBB = refElem.getBoundingClientRect();
expect(elemBB.left >= refElemBB.right).toBe(true, msg);
}

function assertTopsAligned(elem1, elem2, msg) {
function assertElemTopsAligned(elem1, elem2, msg) {
var elem1BB = elem1.getBoundingClientRect();
var elem2BB = elem2.getBoundingClientRect();

Expand All @@ -1178,21 +1170,105 @@ describe('hover info', function() {
var tolerance = 1.1;
expect(elem1BB.top - elem2BB.top).toBeWithin(0, tolerance, msg);
}
}

describe('centered', function() {
var trace1 = {
x: ['giraffes'],
y: [5],
name: 'LA Zoo',
type: 'bar',
text: ['Way too long hover info!']
};
var trace2 = {
x: ['giraffes'],
y: [5],
name: 'SF Zoo',
type: 'bar',
text: ['San Francisco']
};
var data = [trace1, trace2];
var layout = {width: 600, height: 300, barmode: 'stack'};

var gd;

beforeEach(function(done) {
gd = createGraphDiv();
Plotly.plot(gd, data, layout).then(done);
});

it('renders labels inside boxes', function() {
_hover(gd, 300, 150);

var nodes = ensureCentered(centeredHoverInfoNodes());
assertElemInside(nodes.primaryText, nodes.primaryBox, 'Primary text inside box');
assertElemInside(nodes.secondaryText, nodes.secondaryBox, 'Secondary text inside box');
var nodes = ensureCentered(hoverInfoNodes('LA Zoo'));
assertLabelsInsideBoxes(nodes);
});

it('renders secondary info box right to primary info box', function() {
_hover(gd, 300, 150);

var nodes = ensureCentered(centeredHoverInfoNodes());
assertElemRightTo(nodes.secondaryBox, nodes.primaryBox, 'Secondary box right to primary box');
assertTopsAligned(nodes.secondaryBox, nodes.primaryBox, 'Top edges of primary and secondary boxes aligned');
var nodes = ensureCentered(hoverInfoNodes('LA Zoo'));
assertSecondaryRightToPrimaryBox(nodes);
});
});

describe('centered', function() {
var trace1 = {
x: ['giraffes'],
y: [5],
name: 'LA Zoo',
type: 'bar',
text: ['Way too long hover info!']
};
var trace2 = {
x: ['giraffes'],
y: [5],
name: 'SF Zoo',
type: 'bar',
text: ['Way too looooong hover info!']
};
var trace3 = {
x: ['giraffes'],
y: [5],
name: 'NY Zoo',
type: 'bar',
text: ['New York']
};
var data = [trace1, trace2, trace3];
var layout = {width: 600, height: 300};

var gd;

beforeEach(function(done) {
gd = createGraphDiv();
Plotly.plot(gd, data, layout).then(done);
});

function calcLineOverlap(minA, maxA, minB, maxB) {
expect(minA).toBeLessThan(maxA);
expect(minB).toBeLessThan(maxB);

var overlap = Math.min(maxA, maxB) - Math.max(minA, minB);
return Math.max(0, overlap);
}

it('stacks nicely upon each other', function() {
_hover(gd, 300, 150);

var nodesLA = ensureCentered(hoverInfoNodes('LA Zoo'));
var nodesSF = ensureCentered(hoverInfoNodes('SF Zoo'));

// Ensure layout correct
assertLabelsInsideBoxes(nodesLA, 'LA Zoo');
assertLabelsInsideBoxes(nodesSF, 'SF Zoo');
assertSecondaryRightToPrimaryBox(nodesLA, 'LA Zoo');
assertSecondaryRightToPrimaryBox(nodesSF, 'SF Zoo');

// Ensure stacking, finally
var boxLABB = nodesLA.primaryBox.getBoundingClientRect();
var boxSFBB = nodesSF.primaryBox.getBoundingClientRect();
expect(calcLineOverlap(boxLABB.top, boxLABB.bottom, boxSFBB.top, boxSFBB.bottom))
.toBeWithin(0, 1); // Be robust against floating point arithmetic and subtle future layout changes
});
});
});
Expand Down

0 comments on commit 161b602

Please sign in to comment.